Compare commits

..

14 Commits

Author SHA1 Message Date
Andrew Crow
2f8aac9b4c MSA-913: App to turn off switches that can monitor power usage when the device that is plugged in is not in use. 2016-03-01 15:46:11 -06:00
Dylan Bijnagte
4da9730319 Merge pull request #550 from Bijnagte/device-translations
adding korean translations for DTHs
2016-03-01 13:57:05 -06:00
Donald C. Kirker
25db4f5235 Merge pull request #571 from SmartThingsCommunity/DVCSMP-1440
Remove 3axis tile definition from 1st gen multi.
2016-03-01 10:58:09 -08:00
Donald Kirker
eae2a9ca08 Remove 3axis tile definition from 1st gen multi. 2016-03-01 10:53:42 -08:00
Yaima
2dd2d7cba4 Merge pull request #569 from Yaima/master
Ecobee multiple sensors fix
2016-02-29 14:50:45 -08:00
Yaima Valdivia
f5708bca8b Copyright - Ecobee Sensor 2016-02-29 14:49:50 -08:00
Yaima Valdivia
a9da6d130a Changed copyright information for Ecobee Sensor
https://smartthings.atlassian.net/browse/DVCSMP-1511
2016-02-29 14:49:02 -08:00
Yaima Valdivia
3a2c6f86be Merge branch 'master' of github.com:SmartThingsCommunity/SmartThingsPublic
# By Lars Finander
# Via Lars Finander
* 'master' of github.com:SmartThingsCommunity/SmartThingsPublic:
  DVCSMP-1516 Hue (Connect) throws 15k groovy.lang.MissingPropertyException per day
2016-02-29 14:39:36 -08:00
Yaima Valdivia
71d2b89a37 Ecobee multiple sensors fix
https://smartthings.atlassian.net/browse/DVCSMP-1511
2016-02-29 14:15:56 -08:00
Lars Finander
b131ba1507 Merge pull request #568 from larsfinander/hueConnectFix
DVCSMP-1516 Hue (Connect) throws 15k groovy.lang.MissingPropertyExcep…
2016-02-29 13:48:07 -08:00
Lars Finander
f04a9e3f7a DVCSMP-1516 Hue (Connect) throws 15k groovy.lang.MissingPropertyException per day
-Only parse messages with JSON if they are from Hue bridge
2016-02-29 13:41:58 -08:00
bflorian
6a905e4380 Merge pull request #567 from bflorian/DVCSMP-1531-hue-command-types
DVCSMP-1531 switch hue command methods to void
2016-02-29 14:37:16 -05:00
bflorian
442f16680d DVCSMP-1531 switch hue command methods to void 2016-02-29 14:11:51 -05:00
dylanbijnagte
d86dcfd82f adding korean translations for DTHs 2016-02-24 14:34:52 -06:00
13 changed files with 229 additions and 424 deletions

View File

@@ -1,225 +0,0 @@
/**
* Iris Care Pendant
*
* Copyright 2015 Mitch Pond
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
metadata {
definition (name: "Iris Care Pendant", namespace: "mitchpond", author: "Mitch Pond") {
capability "Sensor"
capability "Battery"
capability "Configuration"
capability "Button"
command "enrollResponse"
fingerprint endpointId: "01", inClusters: "0000,0001,0003,0020,0500", outClusters: "0003,0019", manufacturer: "CentraLite", model: "3455-L"
}
preferences {}
tiles(scale: 2) {
standardTile("button", "device.button", decoration: "flat", width: 2, height:2) {
state "default", icon: "st.unknown.zwave.remote-controller", backgroundColor: "#ffffff"
state "pushed", backgroundColor: "#79b821"
}
valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
state "battery", label:'${currentValue}% battery', unit:""
}
main (["battery"])
details(["button","battery"])
}
}
def parse(String description) {
//log.debug "description: $description"
def results = []
if (description?.startsWith('catchall:')) {
results = parseCatchAllMessage(description)
}
else if (description?.startsWith('read attr -')) {
results = parseReportAttributeMessage(description)
}
else if (description?.startsWith('zone status')) {
results = parseIasMessage(description)
}
if (description?.startsWith('enroll request')) {
List cmds = enrollResponse()
log.debug "enroll response: ${cmds}"
results = cmds?.collect { new physicalgraph.device.HubAction(it) }
}
return results
}
private Map parseCatchAllMessage(String description) {
Map resultMap = [:]
def cluster = zigbee.parse(description)
if (shouldProcessMessage(cluster)) {
switch(cluster.clusterId) {
case 0x0001:
resultMap = getBatteryResult(cluster.data.last())
break
}
}
return resultMap
}
private boolean shouldProcessMessage(cluster) {
// 0x0B is default response indicating message got through
// 0x07 is bind message
boolean ignoredMessage = cluster.profileId != 0x0104 ||
cluster.command == 0x0B ||
cluster.command == 0x07 ||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
return !ignoredMessage
}
private parseReportAttributeMessage(String description) {
Map descMap = (description - "read attr - ").split(",").inject([:]) { map, param ->
def nameAndValue = param.split(":")
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
}
def results = []
if (descMap.cluster == "0001" && descMap.attrId == "0020") {
log.debug "Received battery level report"
results = createEvent(getBatteryResult(Integer.parseInt(descMap.value, 16)))
}
return results
}
private parseIasMessage(String description) {
List parsedMsg = description.split(' ')
String msgCode = parsedMsg[2]
int status = Integer.decode(msgCode)
def linkText = getLinkText(device)
def results = []
//log.debug(description)
if (status & 0b00000010) {results << createEvent(getButtonResult('pushed'))}
else if (~status & 0b00000010) results << createEvent(getButtonResult('released'))
if (status & 0b00000100) {
//tampered
}
else if (~status & 0b00000100) {
//not tampered
}
if (status & 0b00001000) {
}
else if (~status & 0b00001000) {
//log.debug "${linkText} battery OK"
}
//log.debug results
return results
}
private Map getBatteryResult(rawValue) {
log.debug 'Battery'
def linkText = getLinkText(device)
def result = [
name: 'battery'
]
def volts = rawValue / 10
def descriptionText
if (volts > 3.5) {
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
}
else {
def minVolts = 2.1
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
result.value = Math.min(100, (int) pct * 100)
result.descriptionText = "${linkText} battery was ${result.value}%"
}
return result
}
private Map getButtonResult(value) {
//log.debug 'Button Status'
def linkText = getLinkText(device)
def descriptionText = "${linkText} was ${value == 'pushed' ? 'pushed' : 'released'}"
return [
name: 'button',
value: value,
data: [buttonNumber: 1],
descriptionText: descriptionText,
displayed: true,
isStateChange: true
]
}
def configure() {
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
log.debug "Configuring Reporting, IAS CIE, and Bindings."
def configCmds = [
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 100",
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 1 0x20 0x20 3600 21600 {01}", //checkin time 6 hrs
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
"st rattr 0x${device.deviceNetworkId} 1 1 0x20"
]
return configCmds
}
def enrollResponse() {
log.debug "Sending enroll response"
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
[
//Resending the CIE in case the enroll request is sent before CIE is written
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
//Enroll Response
"raw 0x500 {01 23 00 00 00}",
"send 0x${device.deviceNetworkId} 1 1", "delay 200"
]
}
private getEndpointId() {
new BigInteger(device.endpointId, 16).toString()
}
private hex(value) {
new BigInteger(Math.round(value).toString()).toString(16)
}
private String swapEndianHex(String hex) {
reverseArray(hex.decodeHex()).encodeHex()
}
private byte[] reverseArray(byte[] array) {
int i = 0;
int j = array.length - 1;
byte tmp;
while (j > i) {
tmp = array[j];
array[j] = array[i];
array[i] = tmp;
j--;
i++;
}
return array
}

View File

@@ -1,7 +1,5 @@
/**
* Ecobee Sensor
*
* Copyright 2015 Juan Risso
* Copyright 2015 SmartThings
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
@@ -12,6 +10,9 @@
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
* Ecobee Sensor
*
* Author: SmartThings
*/
metadata {
definition (name: "Ecobee Sensor", namespace: "smartthings", author: "SmartThings") {
@@ -26,7 +27,16 @@ metadata {
valueTile("temperature", "device.temperature", width: 2, height: 2) {
state("temperature", label:'${currentValue}°', unit:"F",
backgroundColors:[
[value: 31, color: "#153591"],
// Celsius
[value: 0, color: "#153591"],
[value: 7, color: "#1e9cbb"],
[value: 15, color: "#90d2a7"],
[value: 23, color: "#44b621"],
[value: 28, color: "#f1d801"],
[value: 35, color: "#d04e00"],
[value: 37, color: "#bc2323"],
// Fahrenheit
[value: 40, color: "#153591"],
[value: 44, color: "#1e9cbb"],
[value: 59, color: "#90d2a7"],
[value: 74, color: "#44b621"],

View File

@@ -16,8 +16,8 @@ metadata {
capability "Sensor"
command "setAdjustedColor"
command "reset"
command "refresh"
command "reset"
command "refresh"
}
simulator {
@@ -68,17 +68,17 @@ def parse(description) {
}
// handle commands
def on() {
void on() {
log.trace parent.on(this)
sendEvent(name: "switch", value: "on")
}
def off() {
void off() {
log.trace parent.off(this)
sendEvent(name: "switch", value: "off")
}
def nextLevel() {
void nextLevel() {
def level = device.latestValue("level") as Integer ?: 0
if (level <= 100) {
level = Math.min(25 * (Math.round(level / 25) + 1), 100) as Integer
@@ -89,25 +89,25 @@ def nextLevel() {
setLevel(level)
}
def setLevel(percent) {
void setLevel(percent) {
log.debug "Executing 'setLevel'"
parent.setLevel(this, percent)
sendEvent(name: "level", value: percent)
}
def setSaturation(percent) {
void setSaturation(percent) {
log.debug "Executing 'setSaturation'"
parent.setSaturation(this, percent)
sendEvent(name: "saturation", value: percent)
}
def setHue(percent) {
void setHue(percent) {
log.debug "Executing 'setHue'"
parent.setHue(this, percent)
sendEvent(name: "hue", value: percent)
}
def setColor(value) {
void setColor(value) {
log.debug "setColor: ${value}, $this"
parent.setColor(this, value)
if (value.hue) { sendEvent(name: "hue", value: value.hue)}
@@ -117,25 +117,25 @@ def setColor(value) {
if (value.switch) { sendEvent(name: "switch", value: value.switch)}
}
def reset() {
void reset() {
log.debug "Executing 'reset'"
def value = [level:100, hex:"#90C638", saturation:56, hue:23]
setAdjustedColor(value)
def value = [level:100, hex:"#90C638", saturation:56, hue:23]
setAdjustedColor(value)
parent.poll()
}
def setAdjustedColor(value) {
void setAdjustedColor(value) {
if (value) {
log.trace "setAdjustedColor: ${value}"
def adjusted = value + [:]
adjusted.hue = adjustOutgoingHue(value.hue)
// Needed because color picker always sends 100
adjusted.level = null
setColor(adjusted)
}
log.trace "setAdjustedColor: ${value}"
def adjusted = value + [:]
adjusted.hue = adjustOutgoingHue(value.hue)
// Needed because color picker always sends 100
adjusted.level = null
setColor(adjusted)
}
}
def refresh() {
void refresh() {
log.debug "Executing 'refresh'"
parent.manualRefresh()
}

View File

@@ -12,48 +12,48 @@ metadata {
capability "Switch"
capability "Refresh"
capability "Sensor"
command "refresh"
command "refresh"
}
simulator {
// TODO: define status and reply messages here
}
tiles(scale: 2) {
multiAttributeTile(name:"rich-control", type: "lighting", canChangeIcon: true){
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
}
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
attributeState "level", action:"switch level.setLevel", range:"(0..100)"
}
tileAttribute ("device.level", key: "SECONDARY_CONTROL") {
attributeState "level", label: 'Level ${currentValue}%'
tiles(scale: 2) {
multiAttributeTile(name:"rich-control", type: "lighting", canChangeIcon: true){
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
}
}
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
attributeState "level", action:"switch level.setLevel", range:"(0..100)"
}
tileAttribute ("device.level", key: "SECONDARY_CONTROL") {
attributeState "level", label: 'Level ${currentValue}%'
}
}
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
state "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
state "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
state "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
state "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
}
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false, range:"(0..100)") {
state "level", action:"switch level.setLevel"
}
standardTile("refresh", "device.switch", inactiveLabel: false, height: 2, width: 2, decoration: "flat") {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
}
main(["switch"])
details(["rich-control", "refresh"])
}
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false, range:"(0..100)") {
state "level", action:"switch level.setLevel"
}
standardTile("refresh", "device.switch", inactiveLabel: false, height: 2, width: 2, decoration: "flat") {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
main(["switch"])
details(["rich-control", "refresh"])
}
}
// parse events into attributes
@@ -74,23 +74,23 @@ def parse(description) {
}
// handle commands
def on() {
void on() {
parent.on(this)
sendEvent(name: "switch", value: "on")
}
def off() {
void off() {
parent.off(this)
sendEvent(name: "switch", value: "off")
}
def setLevel(percent) {
void setLevel(percent) {
log.debug "Executing 'setLevel'"
parent.setLevel(this, percent)
sendEvent(name: "level", value: percent)
}
def refresh() {
void refresh() {
log.debug "Executing 'refresh'"
parent.manualRefresh()
}

View File

@@ -0,0 +1,31 @@
#==============================================================================
# Copyright 2016 SmartThings
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy
# of the License at:
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#==============================================================================
# Purpose: Mobile Presence i18n Translation File
#
# Filename: mobile-presence.src/i18n/messages.properties
#
# Change History:
# 1. 20160205 TW Initial release with informal Korean translation.
#==============================================================================
# Korean (ko)
# Device Preferences
'''Give your device a name'''.ko=기기 이름 바꾸기
'''Set Device Image'''.ko=디바이스 이미지 설정
# Events / Notifications
'''{{ linkText }} has left'''.ko={{ linkText }}님이 나갔습니다
'''{{ linkText }} has arrived'''.ko={{ linkText }}님이 도착했습니다
'''present'''.ko=집안
'''not present'''.ko=부재중

View File

@@ -0,0 +1,14 @@
# Generated on Wed Feb 24 14:28:26 CST 2016 by dylan
'''Adjust temperature by this many degrees'''.ko=몇 도씩 온도를 조절하십시오
'''Degrees'''.ko=온도
'''Temperature Offset'''.ko=온도 직접 설정
'''This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter '-5'. If 3 degrees too cold, enter '+3'.'''.ko=기준 온도를 원하는대로 몇 도 올리거나 내려서 설정할 수 있습니다.
'''battery'''.ko=배터리
'''dry'''.ko=건조
'''wet'''.ko=누수
'''{{ device.displayName }} battery has too much power: (> 3.5) volts.'''.ko={{ device.displayName }} 배터리 전력이 너무 높습니다(3.5볼트 초과).
'''{{ device.displayName }} battery was {{ value }}'''.ko={{ device.displayName }} 배터리가 {{ value }}였습니다
'''{{ device.displayName }} is {{ value | translate }}'''.ko={{ device.displayName }}이(가) {{ value | translate }}입니다
'''{{ device.displayName }} was {{ value }}°C'''.ko={{ device.displayName }}이(가) {{ value }}°C였습니다
'''{{ device.displayName }} was {{ value }}°F'''.ko={{ device.displayName }}이(가) {{ value }}°F였습니다

View File

@@ -0,0 +1,13 @@
# Generated on Wed Feb 24 14:28:26 CST 2016 by dylan
'''Adjust temperature by this many degrees'''.ko=몇 도씩 온도를 조절하십시오
'''Degrees'''.ko=온도
'''Temperature Offset'''.ko=온도 직접 설정
'''This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter '-5'. If 3 degrees too cold, enter '+3'.'''.ko=기준 온도를 원하는대로 몇 도 올리거나 내려서 설정할 수 있습니다.
'''battery'''.ko=배터리
'''{{ device.displayName }} battery has too much power: (> 3.5) volts.'''.ko={{ device.displayName }} 배터리 전력이 너무 높습니다(3.5볼트 초과).
'''{{ device.displayName }} battery was {{ value }}'''.ko={{ device.displayName }} 배터리가 {{ value }}였습니다
'''{{ device.displayName }} detected motion'''.ko={{ device.displayName }}가 움직임을 감지하였습니다.
'''{{ device.displayName }} motion has stopped'''.ko={{ device.displayName }} 동작이 중단되었습니다
'''{{ device.displayName }} was {{ value }}°C'''.ko={{ device.displayName }}이(가) {{ value }}°C였습니다
'''{{ device.displayName }} was {{ value }}°F'''.ko={{ device.displayName }}이(가) {{ value }}°F였습니다

View File

@@ -0,0 +1,20 @@
# Generated on Wed Feb 24 14:28:26 CST 2016 by dylan
'''Adjust temperature by this many degrees'''.ko=몇 도씩 온도를 조절하십시오
'''Degrees'''.ko=온도
'''Do you want to use this sensor on a garage door?'''.ko=차고 문의 센서 사용 설정하기
'''No'''.ko=아니요
'''Tap to set'''.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=기준 온도를 원하는대로 몇 도 올리거나 내려서 설정할 수 있습니다.
'''Updating device to garage sensor'''.ko=기기-차고 센서 업데이트 중
'''Updating device to open/close sensor'''.ko=기기-열림/닫힘 센서 업데이트 중
'''Yes'''.ko=
'''{{ device.displayName }} status was closed'''.ko={{ device.displayName }}은(는) 닫힌 상태입니다
'''{{ device.displayName }} status was opened'''.ko={{ device.displayName }}은(는) 열린 상태입니다
'''{{ device.displayName }} was active'''.ko={{ device.displayName }}이(가) 활성화되었습니다
'''{{ device.displayName }} was closed'''.ko={{ device.displayName }}이(가) 닫혔습니다
'''{{ device.displayName }} was inactive'''.ko={{ device.displayName }}이(가) 비활성화되었습니다
'''{{ device.displayName }} was opened'''.ko={{ device.displayName }}이(가) 열렸습니다
'''{{ device.displayName }} was {{ value }}°C'''.ko={{ device.displayName }}이(가) {{ value }}°C였습니다
'''{{ device.displayName }} was {{ value }}°F'''.ko={{ device.displayName }}이(가) {{ value }}°F였습니다

View File

@@ -72,15 +72,12 @@ metadata {
]
)
}
valueTile("3axis", "device.threeAxis", decoration: "flat", wordWrap: false, width: 2, height: 2) {
state("threeAxis", label:'${currentValue}', unit:"", backgroundColor:"#ffffff")
}
valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
state "battery", label:'${currentValue}% battery', unit:""
}
main(["contact", "acceleration", "temperature"])
details(["contact", "acceleration", "temperature", "3axis", "battery"])
details(["contact", "acceleration", "temperature", "battery"])
}
}

View File

@@ -0,0 +1,65 @@
/**
* Controlled Power Off
*
* Copyright 2016 Andrew Crow
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
definition(
name: "Controlled Power Off",
namespace: "acrow311",
author: "Andrew Crow",
description: "Application used to power off devices that should be allowed to finish their cycle before being shut down such as air conditions and tankless water heaters. Application will monitor electric usage and delay shutdown until usage has returned to inactive state.",
category: "My Apps",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
oauth: true)
preferences {
section("Shutdown when device not active") {
input(name: "meter", type: "capability.powerMeter", title: "When This Power Meter...", required: true, multiple: false, description: null)
input(name: "threshold", type: "number", title: "Reports Below...", required: true, description: "In watts, enter integer value")
}
}
def installed() {
log.debug "Installed with settings: ${settings}"
initialize()
}
def updated() {
log.debug "Updated with settings: ${settings}"
initialize()
}
def initialize() {
unsubscribe()
subscribe(meter, "power", meterHandler)
}
def meterHandler(evt) {
def meterValue = evt.value as double
def thresholdValue = threshold as int
def switchState = meter.currentValue("switch") == "on" // Get current switch status (on = true)
if (switchState) { // If the switch is already off, do nothing
if (meterValue < thresholdValue) { // If the power consumption is low enough, turn off switch
log.info "${meter} reported energy ${meterValue} below ${threshold}. Turning off switch."
sendNotificationEvent("${meter} not running, shutting down.")
meter.off()
} else { // Power consumption too high - device connected to switch in use - do not shutdown.
sendNotificationEvent("Power consumption too high to shut off ${meter}. Attempting again in a moment.")
log.info "${meter} reported energy ${meterValue} above ${threshold}. Leaving switch on."
}
}
}

View File

@@ -1,129 +0,0 @@
/**
* 3400-X Keypad Manager
*
* Copyright 2015 Mitch Pond
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
definition(
name: "3400-X Keypad Manager",
namespace: "mitchpond",
author: "Mitch Pond",
description: "Service manager for Centralite 3400-X security keypad. Keeps keypad state in sync with Smart Home Monitor",
category: "My Apps",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
singleInstance: false)
preferences {
page(name: "setupPage")
}
def setupPage() {
dynamicPage(name: "setupPage",title: "3400-X Keypad Manager", install: true, uninstall: true) {
section("Settings") {
input(name: "keypad", title: "Keypad", type: "capability.lockCodes", multiple: false, required: true)
input(name: "pin" , title: "PIN code", type: "number", range: "0000..9999", required: true)
paragraph "PIN should be four digits. Shorter PINs will be padded with leading zeroes. (42 becomes 0042)"
label(title: "Assign a name", required: false)
}
def routines = location.helloHome?.getPhrases()*.label
routines?.sort()
section("Routines", hideable: true, hidden: true) {
input(name: "armRoutine", title: "Arm/Away routine", type: "enum", options: routines, required: false)
input(name: "disarmRoutine", title: "Disarm routine", type: "enum", options: routines, required: false)
input(name: "stayRoutine", title: "Arm/Stay routine", type: "enum", options: routines, required: false)
//input(name: "nightRoutine", title: "Arm/Night routine", type: "enum", options: routines, required: false)
}
}
}
def installed() {
log.debug "Installed with settings: ${settings}"
initialize()
}
def updated() {
log.debug "Updated with settings: ${settings}"
unsubscribe()
initialize()
}
def initialize() {
// TODO: subscribe to attributes, devices, locations, etc.
subscribe(location,"alarmSystemStatus",alarmStatusHandler)
subscribe(keypad,"codeEntered",codeEntryHandler)
//initialize keypad to correct state
def event = [name:"alarmSystemStatus", value: location.currentState("alarmSystemStatus").value,
displayed: true, description: "System Status is ${shmState}"]
alarmStatusHandler(event)
}
//Returns the PIN padded with zeroes to 4 digits
private String getPIN(){
return settings.pin.value.toString().padLeft(4,'0')
}
// TODO: implement event handlers
def alarmStatusHandler(event) {
log.debug "Keypad manager caught alarm status change: "+event.value
if (event.value == "off") keypad?.setDisarmed()
else if (event.value == "away") keypad?.setArmedAway()
else if (event.value == "stay") keypad?.setArmedStay()
}
private sendSHMEvent(String shmState){
def event = [name:"alarmSystemStatus", value: shmState,
displayed: true, description: "System Status is ${shmState}"]
sendLocationEvent(event)
}
private execRoutine(armMode) {
if (armMode == 'away') location.helloHome?.execute(settings.armRoutine)
else if (armMode == 'stay') location.helloHome?.execute(settings.stayRoutine)
else if (armMode == 'off') location.helloHome?.execute(settings.disarmRoutine)
}
def codeEntryHandler(evt){
//do stuff
log.debug "Caught code entry event! ${evt.value.value}"
def codeEntered = evt.value as String
def correctCode = getPIN()
def data = evt.data as String
def armMode = ''
if (data == '0') armMode = 'off'
else if (data == '3') armMode = 'away'
else if (data == '1') armMode = 'stay'
else if (data == '2') armMode = 'stay' //Currently no separate night mode for SHM, set to 'stay'
else {
log.error "${app.label}: Unexpected arm mode sent by keypad!: "+data
return []
}
if (codeEntered == correctCode) {
log.debug "Correct PIN entered. Change SHM state to ${armMode}"
keypad.acknowledgeArmRequest(data)
sendSHMEvent(armMode)
execRoutine(armMode)
}
else {
log.debug "Invalid PIN"
//Could also call acknowledgeArmRequest() with a parameter of 4 to report invalid code. Opportunity to simplify code?
keypad.sendInvalidKeycodeResponse()
}
}

View File

@@ -235,6 +235,7 @@ def connectionStatus(message, redirectUrl = null) {
def getEcobeeThermostats() {
log.debug "getting device list"
atomicState.remoteSensors = []
def requestBody = '{"selection":{"selectionType":"registered","selectionMatch":"","includeRuntime":true,"includeSensors":true}}'
@@ -251,7 +252,7 @@ def getEcobeeThermostats() {
if (resp.status == 200) {
resp.data.thermostatList.each { stat ->
atomicState.remoteSensors = stat.remoteSensors
atomicState.remoteSensors = atomicState.remoteSensors == null ? stat.remoteSensors : atomicState.remoteSensors << stat.remoteSensors
def dni = [app.id, stat.identifier].join('.')
stats[dni] = getThermostatDisplayName(stat)
}
@@ -273,11 +274,14 @@ def getEcobeeThermostats() {
Map sensorsDiscovered() {
def map = [:]
atomicState.remoteSensors.each {
if (it.type != "thermostat") {
def value = "${it?.name}"
def key = "ecobee_sensor-"+ it?.id + "-" + it?.code
map["${key}"] = value
log.info "list ${atomicState.remoteSensors}"
atomicState.remoteSensors.each { sensors ->
sensors.each {
if (it.type != "thermostat") {
def value = "${it?.name}"
def key = "ecobee_sensor-"+ it?.id + "-" + it?.code
map["${key}"] = value
}
}
}
atomicState.sensors = map

View File

@@ -455,7 +455,7 @@ def locationHandler(evt) {
log.error "/description.xml returned a bridge that didn't exist"
}
}
} else if(headerString?.contains("json")) {
} else if(headerString?.contains("json") && isValidSource(parsedEvent.mac)) {
log.trace "description.xml response (application/json)"
def body = new groovy.json.JsonSlurper().parseText(parsedEvent.body)
if (body.success != null) {
@@ -494,6 +494,11 @@ def doDeviceSync(){
discoverBridges()
}
def isValidSource(macAddress) {
def vbridges = getVerifiedHueBridges()
return (vbridges?.find {"${it.value.mac}" == macAddress}) != null
}
/////////////////////////////////////
//CHILD DEVICE METHODS
/////////////////////////////////////