mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-04-12 14:23:07 +01:00
Compare commits
12 Commits
MSA-1956-3
...
MSA-1970-1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b2baf1244 | ||
|
|
a99e050c6b | ||
|
|
5bf7caca0d | ||
|
|
3eddd68532 | ||
|
|
8d79379bba | ||
|
|
dd4da29bcd | ||
|
|
a3e9f1d2c1 | ||
|
|
8bfc3f0c1c | ||
|
|
b6136bf1d5 | ||
|
|
68f5cda945 | ||
|
|
da42ee63fb | ||
|
|
5b7a7097b8 |
@@ -0,0 +1,735 @@
|
|||||||
|
/**
|
||||||
|
|
||||||
|
* Fibaro Wall Plug ZW5
|
||||||
|
|
||||||
|
* Requires: Fibaro Double Switch 2 Child Device
|
||||||
|
|
||||||
|
*
|
||||||
|
|
||||||
|
* Copyright 2017 Artur Draga
|
||||||
|
|
||||||
|
*
|
||||||
|
|
||||||
|
* 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: "Fibaro Wall Plug ZW5", namespace: "ClassicGOD", author: "Artur Draga") {
|
||||||
|
|
||||||
|
capability "Switch"
|
||||||
|
|
||||||
|
capability "Energy Meter"
|
||||||
|
|
||||||
|
capability "Power Meter"
|
||||||
|
|
||||||
|
capability "Configuration"
|
||||||
|
|
||||||
|
capability "Health Check"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
command "reset"
|
||||||
|
|
||||||
|
command "refresh"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
fingerprint deviceId: "0x1001", inClusters:"0x5E,0x22,0x59,0x56,0x7A,0x32,0x71,0x73,0x98,0x31,0x85,0x70,0x72,0x5A,0x8E,0x25,0x86"
|
||||||
|
|
||||||
|
fingerprint deviceId: "0x1001", inClusters:"0x5E,0x22,0x59,0x56,0x7A,0x32,0x71,0x73,0x31,0x85,0x70,0x72,0x5A,0x8E,0x25,0x86"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
tiles (scale: 2) {
|
||||||
|
|
||||||
|
multiAttributeTile(name:"switch", type: "lighting", width: 3, height: 4, canChangeIcon: true){
|
||||||
|
|
||||||
|
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||||
|
|
||||||
|
attributeState "off", label: 'Off', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff", nextState:"turningOn"
|
||||||
|
|
||||||
|
attributeState "on", label: 'On', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#00a0dc", nextState:"turningOff"
|
||||||
|
|
||||||
|
attributeState "turningOn", label:'Turning On', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#00a0dc", nextState:"turningOff"
|
||||||
|
|
||||||
|
attributeState "turningOff", label:'Turning Off', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
tileAttribute("device.combinedMeter", key:"SECONDARY_CONTROL") {
|
||||||
|
|
||||||
|
attributeState("combinedMeter", label:'${currentValue}')
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
valueTile("power", "device.power", decoration: "flat", width: 2, height: 2) {
|
||||||
|
|
||||||
|
state "power", label:'${currentValue}\nW', action:"refresh"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
valueTile("energy", "device.energy", decoration: "flat", width: 2, height: 2) {
|
||||||
|
|
||||||
|
state "energy", label:'${currentValue}\nkWh', action:"refresh"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
valueTile("reset", "device.energy", decoration: "flat", width: 2, height: 2) {
|
||||||
|
|
||||||
|
state "reset", label:'reset\nkWh', action:"reset"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
preferences {
|
||||||
|
|
||||||
|
input ( name: "logging", title: "Logging", type: "boolean", required: false )
|
||||||
|
|
||||||
|
parameterMap().each {
|
||||||
|
|
||||||
|
input (
|
||||||
|
|
||||||
|
name: it.key,
|
||||||
|
|
||||||
|
title: "${it.num}. ${it.title}",
|
||||||
|
|
||||||
|
description: it.descr,
|
||||||
|
|
||||||
|
type: it.type,
|
||||||
|
|
||||||
|
options: it.options,
|
||||||
|
|
||||||
|
range: (it.min != null && it.max != null) ? "${it.min}..${it.max}" : null,
|
||||||
|
|
||||||
|
defaultValue: it.def,
|
||||||
|
|
||||||
|
required: false
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//UI and tile functions
|
||||||
|
|
||||||
|
def on() {
|
||||||
|
|
||||||
|
encap(zwave.basicV1.basicSet(value: 255))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def off() {
|
||||||
|
|
||||||
|
encap(zwave.basicV1.basicSet(value: 0))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def reset() {
|
||||||
|
|
||||||
|
def cmds = []
|
||||||
|
|
||||||
|
cmds << zwave.meterV3.meterReset()
|
||||||
|
|
||||||
|
cmds << zwave.meterV3.meterGet(scale: 0)
|
||||||
|
|
||||||
|
cmds << zwave.meterV3.meterGet(scale: 2)
|
||||||
|
|
||||||
|
encapSequence(cmds,1000)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def refresh() {
|
||||||
|
|
||||||
|
def cmds = []
|
||||||
|
|
||||||
|
cmds << zwave.meterV3.meterGet(scale: 0)
|
||||||
|
|
||||||
|
cmds << zwave.meterV3.meterGet(scale: 2)
|
||||||
|
|
||||||
|
cmds << zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 4)
|
||||||
|
|
||||||
|
encapSequence(cmds,1000)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//Configuration and synchronization
|
||||||
|
|
||||||
|
def updated() {
|
||||||
|
|
||||||
|
if ( state.lastUpdated && (now() - state.lastUpdated) < 500 ) return
|
||||||
|
|
||||||
|
def cmds = []
|
||||||
|
|
||||||
|
logging("${device.displayName} - Executing updated()","info")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def Integer cmdCount = 0
|
||||||
|
|
||||||
|
parameterMap().each {
|
||||||
|
|
||||||
|
if(settings."$it.key" != null) {
|
||||||
|
|
||||||
|
if (state."$it.key" == null) { state."$it.key" = [value: null, state: "synced"] }
|
||||||
|
|
||||||
|
if (state."$it.key".value != settings."$it.key" as Integer || state."$it.key".state == "notSynced") {
|
||||||
|
|
||||||
|
state."$it.key".value = settings."$it.key" as Integer
|
||||||
|
|
||||||
|
state."$it.key".state = "notSynced"
|
||||||
|
|
||||||
|
cmds << zwave.configurationV2.configurationSet(configurationValue: intToParam(state."$it.key".value, it.size), parameterNumber: it.num, size: it.size)
|
||||||
|
|
||||||
|
cmds << zwave.configurationV2.configurationGet(parameterNumber: it.num)
|
||||||
|
|
||||||
|
cmdCount = cmdCount + 1
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if ( cmdCount > 0 ) {
|
||||||
|
|
||||||
|
logging("${device.displayName} - sending config.", "info")
|
||||||
|
|
||||||
|
sendEvent(name: "combinedMeter", value: "SYNC IN PROGRESS.", displayed: false)
|
||||||
|
|
||||||
|
runIn((5+cmdCount*2), syncCheck)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
state.lastUpdated = now()
|
||||||
|
|
||||||
|
if (cmds) { response(encapSequence(cmds,1000)) }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def syncCheck() {
|
||||||
|
|
||||||
|
logging("${device.displayName} - Executing syncCheck()","info")
|
||||||
|
|
||||||
|
def Integer count = 0
|
||||||
|
|
||||||
|
if (device.currentValue("combinedMeter")?.contains("SYNC") && device.currentValue("combinedMeter") != "SYNC OK.") {
|
||||||
|
|
||||||
|
parameterMap().each {
|
||||||
|
|
||||||
|
if (state."$it.key".state == "notSynced" ) {
|
||||||
|
|
||||||
|
count = count + 1
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count == 0) {
|
||||||
|
|
||||||
|
logging("${device.displayName} - Sync Complete","info")
|
||||||
|
|
||||||
|
sendEvent(name: "combinedMeter", value: "SYNC OK.", displayed: false)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
logging("${device.displayName} Sync Incomplete","info")
|
||||||
|
|
||||||
|
if (device.currentValue("combinedMeter") != "SYNC FAILED!") {
|
||||||
|
|
||||||
|
sendEvent(name: "combinedMeter", value: "SYNC INCOMPLETE.", displayed: false)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//event handlers related to configuration and sync
|
||||||
|
|
||||||
|
def zwaveEvent(physicalgraph.zwave.commands.configurationv2.ConfigurationReport cmd) {
|
||||||
|
|
||||||
|
def paramKey = parameterMap().find( {it.num == cmd.parameterNumber } ).key
|
||||||
|
|
||||||
|
logging("${device.displayName} - Parameter ${paramKey} value is ${cmd.scaledConfigurationValue} expected " + state."$paramKey".value, "info")
|
||||||
|
|
||||||
|
if (state."$paramKey".value == cmd.scaledConfigurationValue) {
|
||||||
|
|
||||||
|
state."$paramKey".state = "synced"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def zwaveEvent(physicalgraph.zwave.commands.applicationstatusv1.ApplicationRejectedRequest cmd) {
|
||||||
|
|
||||||
|
logging("${device.displayName} - rejected request!","warn")
|
||||||
|
|
||||||
|
if (device.currentValue("combinedMeter") == "SYNC IN PROGRESS.") {
|
||||||
|
|
||||||
|
sendEvent(name: "combinedMeter", value: "SYNC FAILED!", displayed: false)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//event handlers
|
||||||
|
|
||||||
|
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) {
|
||||||
|
|
||||||
|
//ignore
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def zwaveEvent(physicalgraph.zwave.commands.switchbinaryv1.SwitchBinaryReport cmd) {
|
||||||
|
|
||||||
|
logging("${device.displayName} - SwitchBinaryReport received, value: ${cmd.value}","info")
|
||||||
|
|
||||||
|
sendEvent([name: "switch", value: (cmd.value == 0 ) ? "off": "on"])
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv5.SensorMultilevelReport cmd) {
|
||||||
|
|
||||||
|
logging("${device.displayName} - SensorMultilevelReport received, value: ${cmd.scaledSensorValue} scale: ${cmd.scale}","info")
|
||||||
|
|
||||||
|
if (cmd.sensorType == 4) {
|
||||||
|
|
||||||
|
sendEvent([name: "power", value: cmd.scaledSensorValue, unit: "W"])
|
||||||
|
|
||||||
|
updateCombinedMeter()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def zwaveEvent(physicalgraph.zwave.commands.meterv3.MeterReport cmd) {
|
||||||
|
|
||||||
|
logging("${device.displayName} - MeterReport received, value: ${cmd.scaledMeterValue} scale: ${cmd.scale}","info")
|
||||||
|
|
||||||
|
switch (cmd.scale) {
|
||||||
|
|
||||||
|
case 0:
|
||||||
|
|
||||||
|
sendEvent([name: "energy", value: cmd.scaledMeterValue, unit: "kWh"])
|
||||||
|
|
||||||
|
break
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
|
||||||
|
sendEvent([name: "power", value: cmd.scaledMeterValue, unit: "W"])
|
||||||
|
|
||||||
|
break
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCombinedMeter()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//other
|
||||||
|
|
||||||
|
private updateCombinedMeter() {
|
||||||
|
|
||||||
|
if (!device.currentValue("combinedMeter")?.contains("SYNC") || device.currentValue("combinedMeter") == "SYNC OK." || device.currentValue("combinedMeter") == null ) {
|
||||||
|
|
||||||
|
sendEvent([name: "combinedMeter", value: "${device.currentValue("power")} W / ${device.currentValue("energy")} kWh", displayed: false])
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
####################
|
||||||
|
|
||||||
|
## Z-Wave Toolkit ##
|
||||||
|
|
||||||
|
####################
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
def parse(String description) {
|
||||||
|
|
||||||
|
def result = []
|
||||||
|
|
||||||
|
logging("${device.displayName} - Parsing: ${description}")
|
||||||
|
|
||||||
|
if (description.startsWith("Err 106")) {
|
||||||
|
|
||||||
|
result = createEvent(
|
||||||
|
|
||||||
|
descriptionText: "Failed to complete the network security key exchange. If you are unable to receive data from it, you must remove it from your network and add it again.",
|
||||||
|
|
||||||
|
eventType: "ALERT",
|
||||||
|
|
||||||
|
name: "secureInclusion",
|
||||||
|
|
||||||
|
value: "failed",
|
||||||
|
|
||||||
|
displayed: true,
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
} else if (description == "updated") {
|
||||||
|
|
||||||
|
return null
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
def cmd = zwave.parse(description, cmdVersions())
|
||||||
|
|
||||||
|
if (cmd) {
|
||||||
|
|
||||||
|
logging("${device.displayName} - Parsed: ${cmd}")
|
||||||
|
|
||||||
|
zwaveEvent(cmd)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
|
||||||
|
|
||||||
|
def encapsulatedCommand = cmd.encapsulatedCommand(cmdVersions())
|
||||||
|
|
||||||
|
if (encapsulatedCommand) {
|
||||||
|
|
||||||
|
logging("${device.displayName} - Parsed SecurityMessageEncapsulation into: ${encapsulatedCommand}")
|
||||||
|
|
||||||
|
zwaveEvent(encapsulatedCommand)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
log.warn "Unable to extract secure cmd from $cmd"
|
||||||
|
|
||||||
|
createEvent(descriptionText: cmd.toString())
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def zwaveEvent(physicalgraph.zwave.commands.crc16encapv1.Crc16Encap cmd) {
|
||||||
|
|
||||||
|
def version = cmdVersions()[cmd.commandClass as Integer]
|
||||||
|
|
||||||
|
def ccObj = version ? zwave.commandClass(cmd.commandClass, version) : zwave.commandClass(cmd.commandClass)
|
||||||
|
|
||||||
|
def encapsulatedCommand = ccObj?.command(cmd.command)?.parse(cmd.data)
|
||||||
|
|
||||||
|
if (!encapsulatedCommand) {
|
||||||
|
|
||||||
|
log.warn "Could not extract crc16 command from $cmd"
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
logging("${device.displayName} - Parsed Crc16Encap into: ${encapsulatedCommand}")
|
||||||
|
|
||||||
|
zwaveEvent(encapsulatedCommand)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def zwaveEvent(physicalgraph.zwave.commands.multichannelv3.MultiChannelCmdEncap cmd) {
|
||||||
|
|
||||||
|
def encapsulatedCommand = cmd.encapsulatedCommand(cmdVersions())
|
||||||
|
|
||||||
|
if (encapsulatedCommand) {
|
||||||
|
|
||||||
|
logging("${device.displayName} - Parsed MultiChannelCmdEncap ${encapsulatedCommand}")
|
||||||
|
|
||||||
|
zwaveEvent(encapsulatedCommand, cmd.sourceEndPoint as Integer)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private logging(text, type = "debug") {
|
||||||
|
|
||||||
|
if (settings.logging == "true") {
|
||||||
|
|
||||||
|
log."$type" text
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private secEncap(physicalgraph.zwave.Command cmd) {
|
||||||
|
|
||||||
|
logging("${device.displayName} - encapsulating command using Secure Encapsulation, command: $cmd","info")
|
||||||
|
|
||||||
|
zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private crcEncap(physicalgraph.zwave.Command cmd) {
|
||||||
|
|
||||||
|
logging("${device.displayName} - encapsulating command using CRC16 Encapsulation, command: $cmd","info")
|
||||||
|
|
||||||
|
zwave.crc16EncapV1.crc16Encap().encapsulate(cmd).format() // doesn't work righ now because SmartThings...
|
||||||
|
|
||||||
|
//"5601${cmd.format()}0000"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private multiEncap(physicalgraph.zwave.Command cmd, Integer ep) {
|
||||||
|
|
||||||
|
logging("${device.displayName} - encapsulating command using Multi Channel Encapsulation, ep: $ep command: $cmd","info")
|
||||||
|
|
||||||
|
zwave.multiChannelV3.multiChannelCmdEncap(destinationEndPoint:ep).encapsulate(cmd)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private encap(physicalgraph.zwave.Command cmd) {
|
||||||
|
|
||||||
|
if (zwaveInfo.zw.contains("s")) {
|
||||||
|
|
||||||
|
secEncap(cmd)
|
||||||
|
|
||||||
|
} else if (zwaveInfo.cc.contains("56")){
|
||||||
|
|
||||||
|
crcEncap(cmd)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
logging("${device.displayName} - no encapsulation supported for command: $cmd","info")
|
||||||
|
|
||||||
|
cmd.format()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private encap(physicalgraph.zwave.Command cmd, Integer ep) {
|
||||||
|
|
||||||
|
encap(multiEncap(cmd, ep))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private encap(List encapList) {
|
||||||
|
|
||||||
|
encap(encapList[0], encapList[1])
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private encap(Map encapMap) {
|
||||||
|
|
||||||
|
encap(encapMap.cmd, encapMap.ep)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private encapSequence(cmds, Integer delay=250) {
|
||||||
|
|
||||||
|
delayBetween(cmds.collect{ encap(it) }, delay)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private encapSequence(cmds, Integer delay, Integer ep) {
|
||||||
|
|
||||||
|
delayBetween(cmds.collect{ encap(it, ep) }, delay)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private List intToParam(Long value, Integer size = 1) {
|
||||||
|
|
||||||
|
def result = []
|
||||||
|
|
||||||
|
size.times {
|
||||||
|
|
||||||
|
result = result.plus(0, (value & 0xFF) as Short)
|
||||||
|
|
||||||
|
value = (value >> 8)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
##########################
|
||||||
|
|
||||||
|
## Device Configuration ##
|
||||||
|
|
||||||
|
##########################
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
private Map cmdVersions() {
|
||||||
|
|
||||||
|
//[0x5E: 2, 0x59: 1, 0x80: 1, 0x56: 1, 0x7A: 3, 0x73: 1, 0x98: 1, 0x22: 1, 0x85: 2, 0x5B: 1, 0x70: 2, 0x8E: 2, 0x86: 2, 0x84: 2, 0x75: 2, 0x72: 2] //Fibaro KeyFob
|
||||||
|
|
||||||
|
//[0x5E: 2, 0x86: 1, 0x72: 2, 0x59: 1, 0x80: 1, 0x73: 1, 0x56: 1, 0x22: 1, 0x31: 5, 0x98: 1, 0x7A: 3, 0x20: 1, 0x5A: 1, 0x85: 2, 0x84: 2, 0x71: 3, 0x8E: 2, 0x70: 2, 0x30: 1, 0x9C: 1] //Fibaro Motion Sensor ZW5
|
||||||
|
|
||||||
|
//[0x5E: 2, 0x86: 1, 0x72: 1, 0x59: 1, 0x73: 1, 0x22: 1, 0x56: 1, 0x32: 3, 0x71: 1, 0x98: 1, 0x7A: 1, 0x25: 1, 0x5A: 1, 0x85: 2, 0x70: 2, 0x8E: 2, 0x60: 3, 0x75: 1, 0x5B: 1] //Fibaro Double Switch 2
|
||||||
|
|
||||||
|
[0x5E: 2, 0x22: 1, 0x59: 1, 0x56: 1, 0x7A: 1, 0x32: 3, 0x71: 1, 0x73: 1, 0x98: 1, 0x31: 5, 0x85: 2, 0x70: 2, 0x72: 2, 0x5A: 1, 0x8E: 2, 0x25: 1, 0x86: 2] //Fibaro Wall Plug ZW5
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private parameterMap() {[
|
||||||
|
|
||||||
|
[key: "alwaysActive", num: 1, size: 1, type: "enum", options: [0: "0 - inactive", 1: "1 - activated"], def: "0", title: "Always on function", descr: null],
|
||||||
|
|
||||||
|
[key: "restoreState", num: 2, size: 1, type: "enum", options: [0: "0 - power off after power failure", 1: "1 - restore state"], def: "1", title: "Restore state after power failure", descr: null],
|
||||||
|
|
||||||
|
[key: "overloadSafety", num: 3, size: 2, type: "number", def: 0, min: 0, max: 30000 , title: "Overload safety switch", descr: null],
|
||||||
|
|
||||||
|
[key: "immediatePowerReports", num: 10, size: 1, type: "number", def: 80, min: 1, max: 100, title: "Immediate power reports", descr: null],
|
||||||
|
|
||||||
|
[key: "standardPowerReports", num: 11, size: 1, type: "number", def: 15, min: 1, max: 100, title: "Standard power reports", descr: null],
|
||||||
|
|
||||||
|
[key: "powerReportFrequency", num: 12, size: 2, type: "number", def: 30, min: 5, max: 600, title: "Power reporting interval", descr: null],
|
||||||
|
|
||||||
|
[key: "energyReport", num: 13, size: 2, type: "number", def: 10, min: 0, max: 500, title: "Energy reports", descr: null],
|
||||||
|
|
||||||
|
[key: "periodicReports", num: 14, size: 2, type: "number", def: 3600, min: 0, max: 32400, title: "Periodic power and energy reports", descr: null],
|
||||||
|
|
||||||
|
[key: "deviceEnergyConsumed", num: 15, size: 1, type: "enum", options: [0: "0 - don't measure", 1: "1 - measure"], def: "0", title: "Energy consumed by the device itself", descr: null],
|
||||||
|
|
||||||
|
[key: "powerLoad", num: 40, size: 2, type: "number", def: 25000, min: 1000, max: 30000, title: "Power load which makes the LED ring flash violet", descr: null],
|
||||||
|
|
||||||
|
[key: "ringColorOn", num: 41, size: 1, type: "enum", options: [
|
||||||
|
|
||||||
|
0: "0 - Off",
|
||||||
|
|
||||||
|
1: "1 - Load based - continuous",
|
||||||
|
|
||||||
|
2: "2 - Load based - steps",
|
||||||
|
|
||||||
|
3: "3 - White",
|
||||||
|
|
||||||
|
4: "4 - Red",
|
||||||
|
|
||||||
|
5: "5 - Green",
|
||||||
|
|
||||||
|
6: "6 - Blue",
|
||||||
|
|
||||||
|
7: "7 - Yellow",
|
||||||
|
|
||||||
|
8: "8 - Cyan",
|
||||||
|
|
||||||
|
9: "9 - Magenta"
|
||||||
|
|
||||||
|
], def: "1", title: "Ring LED color when on", descr: null],
|
||||||
|
|
||||||
|
[key: "ringColorOff", num: 42, size: 1, type: "enum", options: [
|
||||||
|
|
||||||
|
0: "0 - Off",
|
||||||
|
|
||||||
|
1: "1 - Last measured power",
|
||||||
|
|
||||||
|
3: "3 - White",
|
||||||
|
|
||||||
|
4: "4 - Red",
|
||||||
|
|
||||||
|
5: "5 - Green",
|
||||||
|
|
||||||
|
6: "6 - Blue",
|
||||||
|
|
||||||
|
7: "7 - Yellow",
|
||||||
|
|
||||||
|
8: "8 - Cyan",
|
||||||
|
|
||||||
|
9: "9 - Magenta"
|
||||||
|
|
||||||
|
], def: "0", title: "Ring LED color when off", descr: null]
|
||||||
|
|
||||||
|
]}
|
||||||
@@ -38,9 +38,9 @@ metadata {
|
|||||||
tiles (scale: 2){
|
tiles (scale: 2){
|
||||||
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
||||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL", icon: "st.Lighting.light18") {
|
tileAttribute ("device.switch", key: "PRIMARY_CONTROL", icon: "st.Lighting.light18") {
|
||||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", nextState:"turningOff"
|
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||||
attributeState "turningOn", label:'${name}', icon:"st.switches.light.on", backgroundColor:"#79b821"
|
attributeState "turningOn", label:'${name}', icon:"st.switches.light.on", backgroundColor:"#00A0DC"
|
||||||
attributeState "turningOff", label:'${name}', icon:"st.switches.light.off", backgroundColor:"#ffffff"
|
attributeState "turningOff", label:'${name}', icon:"st.switches.light.off", backgroundColor:"#ffffff"
|
||||||
}
|
}
|
||||||
tileAttribute ("device.color", key: "COLOR_CONTROL") {
|
tileAttribute ("device.color", key: "COLOR_CONTROL") {
|
||||||
@@ -52,20 +52,20 @@ metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
standardTile("motion", "device.motion", width: 2, height: 2, canChangeIcon: true, canChangeBackground: true) {
|
standardTile("motion", "device.motion", width: 2, height: 2, canChangeIcon: true, canChangeBackground: true) {
|
||||||
state "active", label:'motion', icon:"st.motion.motion.active", backgroundColor:"#53a7c0"
|
state "active", label:'motion', icon:"st.motion.motion.active", backgroundColor:"#00A0DC"
|
||||||
state "inactive", label:'no motion', icon:"st.motion.motion.inactive", backgroundColor:"#ffffff"
|
state "inactive", label:'no motion', icon:"st.motion.motion.inactive", backgroundColor:"#cccccc"
|
||||||
}
|
}
|
||||||
valueTile("temperature", "device.temperature", width: 2, height: 2) {
|
valueTile("temperature", "device.temperature", width: 2, height: 2) {
|
||||||
state "temperature", label:'${currentValue}°', unit:"F", icon:"", // would be better if the units would switch to the desired units of the system (imperial or metric)
|
state "temperature", label:'${currentValue}°', unit:"F", icon:"", // would be better if the units would switch to the desired units of the system (imperial or metric)
|
||||||
backgroundColors:[
|
backgroundColors:[
|
||||||
[value: 0, color: "#1010ff"], // blue=cold
|
[value: 0, color: "#153591"], // blue=cold
|
||||||
[value: 65, color: "#a0a0f0"],
|
[value: 65, color: "#44b621"], // green
|
||||||
[value: 70, color: "#e0e050"],
|
[value: 70, color: "#44b621"], // green
|
||||||
[value: 75, color: "#f0d030"], // yellow
|
[value: 75, color: "#f1d801"], // yellow
|
||||||
[value: 80, color: "#fbf020"],
|
[value: 80, color: "#f1d801"], // yellow
|
||||||
[value: 85, color: "#fbdc01"],
|
[value: 85, color: "#f1d801"], // yellow
|
||||||
[value: 90, color: "#fb3a01"],
|
[value: 90, color: "#d04e00"], // red
|
||||||
[value: 95, color: "#fb0801"] // red=hot
|
[value: 95, color: "#bc2323"] // red=hot
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ metadata {
|
|||||||
tiles(scale: 2) {
|
tiles(scale: 2) {
|
||||||
multiAttributeTile(name:"FGMS", type:"lighting", width:6, height:4) {//with generic type secondary control text is not displayed in Android app
|
multiAttributeTile(name:"FGMS", type:"lighting", width:6, height:4) {//with generic type secondary control text is not displayed in Android app
|
||||||
tileAttribute("device.motion", key:"PRIMARY_CONTROL") {
|
tileAttribute("device.motion", key:"PRIMARY_CONTROL") {
|
||||||
attributeState("inactive", label:"no motion", icon:"st.motion.motion.inactive", backgroundColor:"#79b821")
|
attributeState("inactive", label:"no motion", icon:"st.motion.motion.inactive", backgroundColor:"#cccccc")
|
||||||
attributeState("active", label:"motion", icon:"st.motion.motion.active", backgroundColor:"#ffa81e")
|
attributeState("active", label:"motion", icon:"st.motion.motion.active", backgroundColor:"#00A0DC")
|
||||||
}
|
}
|
||||||
|
|
||||||
tileAttribute("device.tamper", key:"SECONDARY_CONTROL") {
|
tileAttribute("device.tamper", key:"SECONDARY_CONTROL") {
|
||||||
|
|||||||
@@ -95,10 +95,10 @@ metadata {
|
|||||||
multiAttributeTile(name:"doorCtrl", type:"generic", width:6, height:4) {tileAttribute("device.doorState", key: "PRIMARY_CONTROL")
|
multiAttributeTile(name:"doorCtrl", type:"generic", width:6, height:4) {tileAttribute("device.doorState", key: "PRIMARY_CONTROL")
|
||||||
{
|
{
|
||||||
attributeState "unknown", label: '${name}', action:"openDoor", icon: "st.Outdoor.outdoor20", nextState:"Sent"
|
attributeState "unknown", label: '${name}', action:"openDoor", icon: "st.Outdoor.outdoor20", nextState:"Sent"
|
||||||
attributeState "open", label: '${name}', action:"closeDoor", icon: "st.Outdoor.outdoor20", backgroundColor: "#0000ff" , nextState:"Sent"
|
attributeState "open", label: '${name}', action:"closeDoor", icon: "st.Outdoor.outdoor20", backgroundColor: "#00A0DC" , nextState:"Sent"
|
||||||
attributeState "opening", label: '${name}', action:"closeDoor", icon: "st.Outdoor.outdoor20", backgroundColor: "#ffa81e"
|
attributeState "opening", label: '${name}', action:"closeDoor", icon: "st.Outdoor.outdoor20", backgroundColor: "#00A0DC"
|
||||||
attributeState "closed", label: '${name}', action:"openDoor", icon: "st.Outdoor.outdoor20", backgroundColor: "#79b821", nextState:"Sent"
|
attributeState "closed", label: '${name}', action:"openDoor", icon: "st.Outdoor.outdoor20", backgroundColor: "#ffffff", nextState:"Sent"
|
||||||
attributeState "closing", label: '${name}', action:"openDoor", icon: "st.Outdoor.outdoor20", backgroundColor: "#ffa81e"
|
attributeState "closing", label: '${name}', action:"openDoor", icon: "st.Outdoor.outdoor20", backgroundColor: "#ffffff"
|
||||||
attributeState "jammed", label: '${name}', action:"closeDoorHiI", icon: "st.Outdoor.outdoor20", backgroundColor: "#ff0000", nextState:"Sent"
|
attributeState "jammed", label: '${name}', action:"closeDoorHiI", icon: "st.Outdoor.outdoor20", backgroundColor: "#ff0000", nextState:"Sent"
|
||||||
attributeState "forced close", label: 'forced\rclose', action:"openDoor", icon: "st.Outdoor.outdoor20", backgroundColor: "#ff8000", nextState:"Sent"
|
attributeState "forced close", label: 'forced\rclose', action:"openDoor", icon: "st.Outdoor.outdoor20", backgroundColor: "#ff8000", nextState:"Sent"
|
||||||
attributeState "fault", label: 'FAULT', action:"openDoor", icon: "st.Outdoor.outdoor20", backgroundColor: "#ff0000", nextState:"Sent"
|
attributeState "fault", label: 'FAULT', action:"openDoor", icon: "st.Outdoor.outdoor20", backgroundColor: "#ff0000", nextState:"Sent"
|
||||||
@@ -135,13 +135,13 @@ metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
standardTile("autoClose", "device.autoCloseEnable", width: 2, height: 2){
|
standardTile("autoClose", "device.autoCloseEnable", width: 2, height: 2){
|
||||||
state "on", label: 'Auto', action:"autoCloseOff", icon: "st.doors.garage.garage-closing", backgroundColor: "#79b821", nextState:"Sent"
|
state "on", label: 'Auto', action:"autoCloseOff", icon: "st.doors.garage.garage-closing", backgroundColor: "#00A0DC", nextState:"Sent"
|
||||||
state "off", label: 'Auto', action:"autoCloseOn", icon: "st.doors.garage.garage-closing", nextState:"Sent"
|
state "off", label: 'Auto', action:"autoCloseOn", icon: "st.doors.garage.garage-closing", nextState:"Sent"
|
||||||
state "Sent", label: 'wait', icon: "st.motion.motion.active", backgroundColor: "#ffa81e"
|
state "Sent", label: 'wait', icon: "st.motion.motion.active", backgroundColor: "#ffa81e"
|
||||||
}
|
}
|
||||||
|
|
||||||
standardTile("autoOpen", "device.autoOpenEnable", width: 2, height: 2){
|
standardTile("autoOpen", "device.autoOpenEnable", width: 2, height: 2){
|
||||||
state "on", label: 'Auto', action:"autoOpenOff", icon: "st.doors.garage.garage-opening", backgroundColor: "#79b821", nextState:"Sent"
|
state "on", label: 'Auto', action:"autoOpenOff", icon: "st.doors.garage.garage-opening", backgroundColor: "#00A0DC", nextState:"Sent"
|
||||||
state "off", label: 'Auto', action:"autoOpenOn", icon: "st.doors.garage.garage-opening", nextState:"Sent"
|
state "off", label: 'Auto', action:"autoOpenOn", icon: "st.doors.garage.garage-opening", nextState:"Sent"
|
||||||
state "Sent", label: 'wait', icon: "st.motion.motion.active", backgroundColor: "#ffa81e"
|
state "Sent", label: 'wait', icon: "st.motion.motion.active", backgroundColor: "#ffa81e"
|
||||||
}
|
}
|
||||||
@@ -189,13 +189,13 @@ metadata {
|
|||||||
|
|
||||||
standardTile("aux1", "device.Aux1", width: 2, height: 2, canChangeIcon: true) {
|
standardTile("aux1", "device.Aux1", width: 2, height: 2, canChangeIcon: true) {
|
||||||
state "off", label:'Aux 1', action:"Aux1On", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"Sent"
|
state "off", label:'Aux 1', action:"Aux1On", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"Sent"
|
||||||
state "on", label:'Aux 1', action:"Aux1Off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"Sent"
|
state "on", label:'Aux 1', action:"Aux1Off", icon:"st.switches.switch.on", backgroundColor:"#00A0DC", nextState:"Sent"
|
||||||
state "Sent", label: 'wait', icon: "st.motion.motion.active", backgroundColor: "#ffa81e"
|
state "Sent", label: 'wait', icon: "st.motion.motion.active", backgroundColor: "#ffa81e"
|
||||||
}
|
}
|
||||||
|
|
||||||
standardTile("aux2", "device.Aux2", width: 2, height: 2, canChangeIcon: true) {
|
standardTile("aux2", "device.Aux2", width: 2, height: 2, canChangeIcon: true) {
|
||||||
state "off", label:'Aux 2', action:"Aux2On", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"Sent"
|
state "off", label:'Aux 2', action:"Aux2On", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"Sent"
|
||||||
state "on", label:'Aux 2', action:"Aux2Off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"Sent"
|
state "on", label:'Aux 2', action:"Aux2Off", icon:"st.switches.switch.on", backgroundColor:"#00A0DC", nextState:"Sent"
|
||||||
state "Sent", label: 'wait', icon: "st.motion.motion.active", backgroundColor: "#ffa81e"
|
state "Sent", label: 'wait', icon: "st.motion.motion.active", backgroundColor: "#ffa81e"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
valueTile("illuminance", "device.illuminance", inactiveLabel: false, width: 2, height: 2) {
|
valueTile("illuminance", "device.illuminance", inactiveLabel: false, width: 2, height: 2) {
|
||||||
state "illuminance", label:'${currentValue} ${unit}', unit:"lux"
|
state "illuminance", label:'${currentValue} lux', unit:""
|
||||||
}
|
}
|
||||||
|
|
||||||
valueTile("ultravioletIndex", "device.ultravioletIndex", inactiveLabel: false, width: 2, height: 2) {
|
valueTile("ultravioletIndex", "device.ultravioletIndex", inactiveLabel: false, width: 2, height: 2) {
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ metadata {
|
|||||||
state "humidity", label:'${currentValue}% humidity', unit:""
|
state "humidity", label:'${currentValue}% humidity', unit:""
|
||||||
}
|
}
|
||||||
valueTile("illuminance", "device.illuminance", inactiveLabel: false, width: 2, height: 2) {
|
valueTile("illuminance", "device.illuminance", inactiveLabel: false, width: 2, height: 2) {
|
||||||
state "luminosity", label:'${currentValue} ${unit}', unit:"lux"
|
state "luminosity", label:'${currentValue} lux', unit:""
|
||||||
}
|
}
|
||||||
valueTile("battery", "device.battery", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
valueTile("battery", "device.battery", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||||
state "battery", label:'${currentValue}% battery', unit:""
|
state "battery", label:'${currentValue}% battery', unit:""
|
||||||
@@ -283,4 +283,3 @@ private secure(physicalgraph.zwave.Command cmd) {
|
|||||||
private secureSequence(commands, delay=200) {
|
private secureSequence(commands, delay=200) {
|
||||||
delayBetween(commands.collect{ secure(it) }, delay)
|
delayBetween(commands.collect{ secure(it) }, delay)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ metadata {
|
|||||||
state "humidity", label:'${currentValue}% humidity', unit:""
|
state "humidity", label:'${currentValue}% humidity', unit:""
|
||||||
}
|
}
|
||||||
valueTile("illuminance", "device.illuminance", inactiveLabel: false, width: 2, height: 2) {
|
valueTile("illuminance", "device.illuminance", inactiveLabel: false, width: 2, height: 2) {
|
||||||
state "luminosity", label:'${currentValue} ${unit}', unit:"lux"
|
state "luminosity", label:'${currentValue} lux', unit:""
|
||||||
}
|
}
|
||||||
valueTile("battery", "device.battery", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
valueTile("battery", "device.battery", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||||
state "battery", label:'${currentValue}% battery', unit:""
|
state "battery", label:'${currentValue}% battery', unit:""
|
||||||
|
|||||||
@@ -68,8 +68,8 @@
|
|||||||
|
|
||||||
tiles {
|
tiles {
|
||||||
standardTile("contact", "device.contact", width: 2, height: 2) {
|
standardTile("contact", "device.contact", width: 2, height: 2) {
|
||||||
state "open", label: '${name}', icon: "st.contact.contact.open", backgroundColor: "#ffa81e"
|
state "open", label: '${name}', icon: "st.contact.contact.open", backgroundColor: "#e86d13"
|
||||||
state "closed", label: '${name}', icon: "st.contact.contact.closed", backgroundColor: "#79b821"
|
state "closed", label: '${name}', icon: "st.contact.contact.closed", backgroundColor: "#00A0DC"
|
||||||
}
|
}
|
||||||
valueTile("temperature", "device.temperature", inactiveLabel: false) {
|
valueTile("temperature", "device.temperature", inactiveLabel: false) {
|
||||||
state "temperature", label:'${currentValue}°',
|
state "temperature", label:'${currentValue}°',
|
||||||
@@ -86,7 +86,7 @@
|
|||||||
}
|
}
|
||||||
standardTile("tamper", "device.alarm") {
|
standardTile("tamper", "device.alarm") {
|
||||||
state("secure", label:'secure', icon:"st.locks.lock.locked", backgroundColor:"#ffffff")
|
state("secure", label:'secure', icon:"st.locks.lock.locked", backgroundColor:"#ffffff")
|
||||||
state("tampered", label:'tampered', icon:"st.locks.lock.unlocked", backgroundColor:"#53a7c0")
|
state("tampered", label:'tampered', icon:"st.locks.lock.unlocked", backgroundColor:"#00a0dc")
|
||||||
}
|
}
|
||||||
valueTile("battery", "device.battery", inactiveLabel: false, decoration: "flat") {
|
valueTile("battery", "device.battery", inactiveLabel: false, decoration: "flat") {
|
||||||
state "battery", label:'${currentValue}% battery', unit:""
|
state "battery", label:'${currentValue}% battery', unit:""
|
||||||
|
|||||||
@@ -62,8 +62,8 @@ metadata {
|
|||||||
state "off", label: '${name}', action: "on", icon: "st.switches.switch.off", backgroundColor: "#ffffff"
|
state "off", label: '${name}', action: "on", icon: "st.switches.switch.off", backgroundColor: "#ffffff"
|
||||||
}
|
}
|
||||||
standardTile("contact", "device.contact", inactiveLabel: false) {
|
standardTile("contact", "device.contact", inactiveLabel: false) {
|
||||||
state "open", label: '${name}', icon: "st.contact.contact.open", backgroundColor: "#ffa81e"
|
state "open", label: '${name}', icon: "st.contact.contact.open", backgroundColor: "#e86d13"
|
||||||
state "closed", label: '${name}', icon: "st.contact.contact.closed", backgroundColor: "#79b821"
|
state "closed", label: '${name}', icon: "st.contact.contact.closed", backgroundColor: "#00A0DC"
|
||||||
}
|
}
|
||||||
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
|
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
|
||||||
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
|
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||||
|
|||||||
@@ -44,9 +44,9 @@ metadata {
|
|||||||
tiles(scale: 2) {
|
tiles(scale: 2) {
|
||||||
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
multiAttributeTile(name:"switch", 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.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
|
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
|
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||||
}
|
}
|
||||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
||||||
|
|||||||
@@ -56,8 +56,8 @@ metadata {
|
|||||||
state("closing", label:'${name}', icon:"st.doors.garage.garage-closing", backgroundColor:"#00A0DC")
|
state("closing", label:'${name}', icon:"st.doors.garage.garage-closing", backgroundColor:"#00A0DC")
|
||||||
}
|
}
|
||||||
standardTile("contact", "device.contact") {
|
standardTile("contact", "device.contact") {
|
||||||
state("open", label:'${name}', icon:"st.contact.contact.open", backgroundColor:"#ffa81e")
|
state("open", label:'${name}', icon:"st.contact.contact.open", backgroundColor:"#e86d13")
|
||||||
state("closed", label:'${name}', icon:"st.contact.contact.closed", backgroundColor:"#79b821")
|
state("closed", label:'${name}', icon:"st.contact.contact.closed", backgroundColor:"#00A0DC")
|
||||||
}
|
}
|
||||||
standardTile("acceleration", "device.acceleration", decoration: "flat") {
|
standardTile("acceleration", "device.acceleration", decoration: "flat") {
|
||||||
state("active", label:'${name}', icon:"st.motion.acceleration.active", backgroundColor:"#00A0DC")
|
state("active", label:'${name}', icon:"st.motion.acceleration.active", backgroundColor:"#00A0DC")
|
||||||
|
|||||||
@@ -22,16 +22,16 @@ metadata {
|
|||||||
|
|
||||||
tiles {
|
tiles {
|
||||||
standardTile("contact", "device.contact", width: 2, height: 2) {
|
standardTile("contact", "device.contact", width: 2, height: 2) {
|
||||||
state("closed", label:'${name}', icon:"st.contact.contact.closed", backgroundColor:"#79b821", action: "open")
|
state("closed", label:'${name}', icon:"st.contact.contact.closed", backgroundColor:"#00A0DC", action: "open")
|
||||||
state("open", label:'${name}', icon:"st.contact.contact.open", backgroundColor:"#ffa81e", action: "close")
|
state("open", label:'${name}', icon:"st.contact.contact.open", backgroundColor:"#e86d13", action: "close")
|
||||||
}
|
}
|
||||||
standardTile("freezerDoor", "device.contact", width: 2, height: 2, decoration: "flat") {
|
standardTile("freezerDoor", "device.contact", width: 2, height: 2, decoration: "flat") {
|
||||||
state("closed", label:'Freezer', icon:"st.contact.contact.closed", backgroundColor:"#79b821")
|
state("closed", label:'Freezer', icon:"st.contact.contact.closed", backgroundColor:"#00A0DC")
|
||||||
state("open", label:'Freezer', icon:"st.contact.contact.open", backgroundColor:"#ffa81e")
|
state("open", label:'Freezer', icon:"st.contact.contact.open", backgroundColor:"#e86d13")
|
||||||
}
|
}
|
||||||
standardTile("mainDoor", "device.contact", width: 2, height: 2, decoration: "flat") {
|
standardTile("mainDoor", "device.contact", width: 2, height: 2, decoration: "flat") {
|
||||||
state("closed", label:'Fridge', icon:"st.contact.contact.closed", backgroundColor:"#79b821")
|
state("closed", label:'Fridge', icon:"st.contact.contact.closed", backgroundColor:"#00A0DC")
|
||||||
state("open", label:'Fridge', icon:"st.contact.contact.open", backgroundColor:"#ffa81e")
|
state("open", label:'Fridge', icon:"st.contact.contact.open", backgroundColor:"#e86d13")
|
||||||
}
|
}
|
||||||
standardTile("control", "device.contact", width: 1, height: 1, decoration: "flat") {
|
standardTile("control", "device.contact", width: 1, height: 1, decoration: "flat") {
|
||||||
state("closed", label:'${name}', icon:"st.contact.contact.closed", action: "open")
|
state("closed", label:'${name}', icon:"st.contact.contact.closed", action: "open")
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ metadata {
|
|||||||
|
|
||||||
tiles(scale: 2) {
|
tiles(scale: 2) {
|
||||||
standardTile("contact", "device.contact", width: 4, height: 4) {
|
standardTile("contact", "device.contact", width: 4, height: 4) {
|
||||||
state("closed", label:'${name}', icon:"st.fridge.fridge-closed", backgroundColor:"#79b821")
|
state("closed", label:'${name}', icon:"st.fridge.fridge-closed", backgroundColor:"#00A0DC")
|
||||||
state("open", label:'${name}', icon:"st.fridge.fridge-open", backgroundColor:"#ffa81e")
|
state("open", label:'${name}', icon:"st.fridge.fridge-open", backgroundColor:"#e86d13")
|
||||||
}
|
}
|
||||||
childDeviceTile("freezerDoor", "freezerDoor", height: 2, width: 2, childTileName: "freezerDoor")
|
childDeviceTile("freezerDoor", "freezerDoor", height: 2, width: 2, childTileName: "freezerDoor")
|
||||||
childDeviceTile("mainDoor", "mainDoor", height: 2, width: 2, childTileName: "mainDoor")
|
childDeviceTile("mainDoor", "mainDoor", height: 2, width: 2, childTileName: "mainDoor")
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ metadata {
|
|||||||
tileAttribute("device.switch", key: "PRIMARY_CONTROL") {
|
tileAttribute("device.switch", key: "PRIMARY_CONTROL") {
|
||||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#00A0DC", nextState:"turningOff"
|
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
|
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -35,7 +35,7 @@ metadata {
|
|||||||
tileAttribute("device.switch", key: "PRIMARY_CONTROL") {
|
tileAttribute("device.switch", key: "PRIMARY_CONTROL") {
|
||||||
attributeState "on", label:'${name}', backgroundColor:"#00A0DC", nextState:"turningOff"
|
attributeState "on", label:'${name}', backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||||
attributeState "off", label:'${name}', backgroundColor:"#ffffff", nextState:"turningOn"
|
attributeState "off", label:'${name}', backgroundColor:"#ffffff", nextState:"turningOn"
|
||||||
attributeState "turningOn", label:'${name}', backgroundColor:"#79b821", nextState:"turningOff"
|
attributeState "turningOn", label:'${name}', backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||||
attributeState "turningOff", label:'${name}', backgroundColor:"#ffffff", nextState:"turningOn"
|
attributeState "turningOff", label:'${name}', backgroundColor:"#ffffff", nextState:"turningOn"
|
||||||
}
|
}
|
||||||
tileAttribute("device.level", key: "SECONDARY_CONTROL") {
|
tileAttribute("device.level", key: "SECONDARY_CONTROL") {
|
||||||
@@ -59,7 +59,7 @@ metadata {
|
|||||||
tileAttribute("device.switch", key: "SECONDARY_CONTROL") {
|
tileAttribute("device.switch", key: "SECONDARY_CONTROL") {
|
||||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#00A0DC", nextState:"turningOff"
|
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||||
attributeState "off", label:'${name}', action:"switch.on", backgroundColor:"#ffffff", nextState:"turningOn"
|
attributeState "off", label:'${name}', action:"switch.on", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||||
attributeState "turningOn", label:'…', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
|
attributeState "turningOn", label:'…', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||||
attributeState "turningOff", label:'…', action:"switch.on", backgroundColor:"#ffffff", nextState:"turningOn"
|
attributeState "turningOff", label:'…', action:"switch.on", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||||
}
|
}
|
||||||
tileAttribute("device.level", key: "VALUE_CONTROL") {
|
tileAttribute("device.level", key: "VALUE_CONTROL") {
|
||||||
|
|||||||
@@ -40,11 +40,11 @@ metadata {
|
|||||||
tiles(scale: 2) {
|
tiles(scale: 2) {
|
||||||
multiAttributeTile(name:"rich-control", type: "switch", canChangeIcon: true){
|
multiAttributeTile(name:"rich-control", type: "switch", canChangeIcon: true){
|
||||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.Home.home30", backgroundColor:"#79b821", nextState:"turningOff"
|
attributeState "on", label:'${name}', action:"switch.off", icon:"st.Home.home30", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.Home.home30", backgroundColor:"#ffffff", nextState:"turningOn"
|
attributeState "off", label:'${name}', action:"switch.on", icon:"st.Home.home30", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.Home.home30", backgroundColor:"#79b821", nextState:"turningOff"
|
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.Home.home30", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.Home.home30", backgroundColor:"#ffffff", nextState:"turningOn"
|
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.Home.home30", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||||
attributeState "offline", label:'${name}', icon:"st.Home.home30", backgroundColor:"#ff0000"
|
attributeState "offline", label:'${name}', icon:"st.Home.home30", backgroundColor:"#cccccc"
|
||||||
}
|
}
|
||||||
tileAttribute ("currentIP", key: "SECONDARY_CONTROL") {
|
tileAttribute ("currentIP", key: "SECONDARY_CONTROL") {
|
||||||
attributeState "currentIP", label: ''
|
attributeState "currentIP", label: ''
|
||||||
|
|||||||
@@ -38,11 +38,11 @@
|
|||||||
tiles(scale: 2) {
|
tiles(scale: 2) {
|
||||||
multiAttributeTile(name:"rich-control", type: "switch", canChangeIcon: true){
|
multiAttributeTile(name:"rich-control", type: "switch", canChangeIcon: true){
|
||||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.off", backgroundColor:"#79b821", nextState:"turningOff"
|
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.off", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.switch.on", backgroundColor:"#ffffff", nextState:"turningOn"
|
attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.switch.on", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.off", backgroundColor:"#79b821", nextState:"turningOff"
|
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.off", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.switch.on", backgroundColor:"#ffffff", nextState:"turningOn"
|
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.switch.on", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||||
attributeState "offline", label:'${name}', icon:"st.switches.switch.off", backgroundColor:"#ff0000"
|
attributeState "offline", label:'${name}', icon:"st.switches.switch.off", backgroundColor:"#cccccc"
|
||||||
}
|
}
|
||||||
tileAttribute ("currentIP", key: "SECONDARY_CONTROL") {
|
tileAttribute ("currentIP", key: "SECONDARY_CONTROL") {
|
||||||
attributeState "currentIP", label: ''
|
attributeState "currentIP", label: ''
|
||||||
@@ -50,11 +50,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
|
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
|
||||||
state "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.off", backgroundColor:"#79b821", nextState:"turningOff"
|
state "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.off", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||||
state "off", label:'${name}', action:"switch.on", icon:"st.switches.switch.on", backgroundColor:"#ffffff", nextState:"turningOn"
|
state "off", label:'${name}', action:"switch.on", icon:"st.switches.switch.on", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||||
state "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.off", backgroundColor:"#79b821", nextState:"turningOff"
|
state "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.off", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||||
state "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.switch.on", backgroundColor:"#ffffff", nextState:"turningOn"
|
state "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.switch.on", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||||
state "offline", label:'${name}', icon:"st.switches.switch.off", backgroundColor:"#ff0000"
|
state "offline", label:'${name}', icon:"st.switches.switch.off", backgroundColor:"#cccccc"
|
||||||
}
|
}
|
||||||
|
|
||||||
standardTile("refresh", "device.switch", inactiveLabel: false, height: 2, width: 2, decoration: "flat") {
|
standardTile("refresh", "device.switch", inactiveLabel: false, height: 2, width: 2, decoration: "flat") {
|
||||||
|
|||||||
@@ -46,9 +46,9 @@ metadata {
|
|||||||
tiles(scale: 2) {
|
tiles(scale: 2) {
|
||||||
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
multiAttributeTile(name:"switch", 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:"#79b821", nextState:"turningOff"
|
attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", 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:"#79b821", nextState:"turningOff"
|
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", 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") {
|
||||||
|
|||||||
@@ -41,9 +41,9 @@ metadata {
|
|||||||
tiles(scale: 2) {
|
tiles(scale: 2) {
|
||||||
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
multiAttributeTile(name:"switch", 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.switches.light.on", backgroundColor:"#79b821", nextState:"turningOff"
|
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", nextState:"turningOff"
|
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,9 +45,9 @@ metadata {
|
|||||||
tiles(scale: 2) {
|
tiles(scale: 2) {
|
||||||
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
multiAttributeTile(name:"switch", 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.switches.light.on", backgroundColor:"#79b821", nextState:"turningOff"
|
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", nextState:"turningOff"
|
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||||
}
|
}
|
||||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
||||||
|
|||||||
@@ -49,9 +49,9 @@ metadata {
|
|||||||
tiles(scale: 2) {
|
tiles(scale: 2) {
|
||||||
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
multiAttributeTile(name:"switch", 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.switches.light.on", backgroundColor:"#79b821", nextState:"turningOff"
|
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", nextState:"turningOff"
|
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||||
}
|
}
|
||||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
||||||
|
|||||||
@@ -1,273 +0,0 @@
|
|||||||
/**
|
|
||||||
* Lloyds Banking Group Connect & Protect
|
|
||||||
*
|
|
||||||
* Copyright 2016 Domotz
|
|
||||||
*
|
|
||||||
* 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: "Lloyds Banking Group Connect & Protect",
|
|
||||||
namespace: "domotz.dev",
|
|
||||||
author: "Domotz",
|
|
||||||
description: "The Lloyds Connect & Protect SmartApp is a bridge between SmartThings Cloud and Lloyds Banking Group to enable advanced connected device monitoring and alerting features to be included in the offering to their customers for the connected home service",
|
|
||||||
category: "Convenience",
|
|
||||||
singleInstance: true,
|
|
||||||
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: [displayName: "Lloyds Banking Group Connect & Protect", displayLink: ""]) {
|
|
||||||
appSetting "endpointRetrievalUrl"
|
|
||||||
appSetting "xApiKey"
|
|
||||||
}
|
|
||||||
|
|
||||||
def getSupportedTypes() {
|
|
||||||
return [
|
|
||||||
[obj: switches, name: "switches", attribute: "switch", capability: "switch", title: "Switches"],
|
|
||||||
[obj: motions, name: "motions", attribute: "motion", capability: "motionSensor", title: "Motion Sensors"],
|
|
||||||
[obj: temperature, name: "temperature", attribute: "temperature", capability: "temperatureMeasurement", title: "Temperature Sensors"],
|
|
||||||
[obj: contact, name: "contact", attribute: "contact", capability: "contactSensor", title: "Contact Sensors"],
|
|
||||||
[obj: presence, name: "presence", attribute: "presence", capability: "presenceSensor", title: "Presence Sensors"],
|
|
||||||
[obj: water, name: "water", attribute: "water", capability: "waterSensor", title: "Water Sensors"],
|
|
||||||
[obj: smoke, name: "smoke", attribute: "smoke", capability: "smokeDetector", title: "Smoke Sensors"],
|
|
||||||
[obj: battery, name: "battery", attribute: "battery", capability: "battery", title: "Batteries"]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
preferences {
|
|
||||||
section("Allow Lloyds Banking Group Connect & Protect service to monitor these devices") {
|
|
||||||
for (type in getSupportedTypes()) {
|
|
||||||
input type.get("name"), "capability.${type.get('capability')}", title: type.get('title'), multiple: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def installed() {
|
|
||||||
initialize()
|
|
||||||
}
|
|
||||||
|
|
||||||
def updated() {
|
|
||||||
unsubscribe()
|
|
||||||
initialize()
|
|
||||||
hubUpdateHandler()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
def getRequestHeaders() {
|
|
||||||
return ['Accept': '*/*', 'X-API-KEY': appSettings.xApiKey]
|
|
||||||
}
|
|
||||||
|
|
||||||
def subscribeToDeviceEvents() {
|
|
||||||
for (type in getSupportedTypes()) {
|
|
||||||
subscribe(type.get("obj"), "${type.get("attribute")}", genericDeviceEventHandler)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def initialize() {
|
|
||||||
if (atomicState.endpoint != null) {
|
|
||||||
log.debug "Detected endpoint: ${atomicState.endpoint}"
|
|
||||||
subscribeToDeviceEvents()
|
|
||||||
subscribe(location, "routineExecuted", modeChangeHandler)
|
|
||||||
subscribe(location, "mode", modeChangeHandler)
|
|
||||||
} else {
|
|
||||||
log.debug "There is no endpoint, requesting domotz for a new one"
|
|
||||||
requestNewEndpoint()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def getHubLocation() {
|
|
||||||
def location_info = [:]
|
|
||||||
location_info['uid'] = location.id
|
|
||||||
//location_info['hubs'] = location.hubs
|
|
||||||
location_info['latitude'] = location.latitude
|
|
||||||
location_info['longitude'] = location.longitude
|
|
||||||
location_info['current_mode'] = location.mode
|
|
||||||
//location_info['modes'] = location.modes
|
|
||||||
location_info['name'] = location.name
|
|
||||||
location_info['temperature_scale'] = location.temperatureScale
|
|
||||||
location_info['version'] = location.version
|
|
||||||
location_info['channel_name'] = location.channelName
|
|
||||||
location_info['zip_code'] = location.zipCode
|
|
||||||
log.debug "Triggered getHubLocation with properties: ${location_info}"
|
|
||||||
return location_info
|
|
||||||
}
|
|
||||||
|
|
||||||
def modeChangeHandler(evt) {
|
|
||||||
log.debug "mode changed to ${evt.value}"
|
|
||||||
def url = null
|
|
||||||
if (atomicState.endpoint != null) {
|
|
||||||
url = atomicState.endpoint + '/hub-change'
|
|
||||||
|
|
||||||
httpPutJson(
|
|
||||||
uri: url,
|
|
||||||
body: getHubLocation(),
|
|
||||||
headers: getRequestHeaders()
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
log.debug "There is no endpoint, requesting domotz for a new one"
|
|
||||||
requestNewEndpoint()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
def genericDeviceEventHandler(event) {
|
|
||||||
log.debug "Device Event Handler, event properties: ${event.getProperties().toString()}"
|
|
||||||
log.debug "Device Event Handler, value: ${event.value}"
|
|
||||||
def resp = [:]
|
|
||||||
def url = null
|
|
||||||
def device = null
|
|
||||||
|
|
||||||
device = getDevice(event.device, resp)
|
|
||||||
|
|
||||||
url = atomicState.endpoint + "/device/" + device.provider_uid + "/${event.name}"
|
|
||||||
|
|
||||||
log.debug "Device Event Handler, put url: ${url}"
|
|
||||||
log.debug "Device Event Handler, put body: value: ${event.value}\ndate: ${event.isoDate}"
|
|
||||||
httpPutJson(
|
|
||||||
uri: url,
|
|
||||||
body: [
|
|
||||||
"value": event.value,
|
|
||||||
"time" : event.isoDate
|
|
||||||
],
|
|
||||||
headers: getRequestHeaders()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def hubUpdateHandler() {
|
|
||||||
log.debug "Hub Update Handler, with settings: ${settings}"
|
|
||||||
def url = null
|
|
||||||
def deviceList = [:]
|
|
||||||
|
|
||||||
if (atomicState.endpoint != null) {
|
|
||||||
url = atomicState.endpoint + '/device-list'
|
|
||||||
log.debug "Hub Update Event Handler, put url: ${url}"
|
|
||||||
deviceList = getDeviceList()
|
|
||||||
httpPutJson(
|
|
||||||
uri: url,
|
|
||||||
body: deviceList,
|
|
||||||
headers: getRequestHeaders()
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
log.debug "There is no endpoint, requesting domotz for a new one"
|
|
||||||
requestNewEndpoint()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def getDeviceList() {
|
|
||||||
try {
|
|
||||||
|
|
||||||
def resp = [:]
|
|
||||||
def attribute = null
|
|
||||||
|
|
||||||
for (type in getSupportedTypes()) {
|
|
||||||
type.get("obj").each {
|
|
||||||
device = getDevice(it, resp)
|
|
||||||
attribute = type.get("attribute")
|
|
||||||
if (it.currentState(attribute)) {
|
|
||||||
device['attributes'][attribute] = [
|
|
||||||
"value": it.currentState(attribute).value,
|
|
||||||
"time" : it.currentState(attribute).getIsoDate(),
|
|
||||||
"unit" : it.currentState(attribute).unit
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return resp
|
|
||||||
} catch (e) {
|
|
||||||
log.debug("caught exception", e)
|
|
||||||
return [:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def getDevice(it, resp) {
|
|
||||||
if (resp[it.id]) {
|
|
||||||
return resp[it.id]
|
|
||||||
}
|
|
||||||
resp[it.id] = [name: it.name, display_name: it.displayName, provider_uid: it.id, type: it.typeName, label: it.label, manufacturer_name: it.manufacturerName, model: it.modelName, attributes: [:]]
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
def activateMonitoring(resp) {
|
|
||||||
unsubscribe()
|
|
||||||
log.debug "Event monitoring activated for endpoint: ${request.JSON.endpoint}"
|
|
||||||
atomicState.endpoint = request.JSON.endpoint
|
|
||||||
log.debug "Event monitoring activated for endpoint: ${atomicState.endpoint}"
|
|
||||||
initialize()
|
|
||||||
}
|
|
||||||
|
|
||||||
def deactivateMonitoring() {
|
|
||||||
log.debug "Event monitoring deactivated."
|
|
||||||
atomicState.endpoint = null
|
|
||||||
unsubscribe()
|
|
||||||
}
|
|
||||||
|
|
||||||
def requestNewEndpoint() {
|
|
||||||
log.debug "Requesting a new endpoint."
|
|
||||||
def hubId = location.id
|
|
||||||
def params = [
|
|
||||||
uri : "${appSettings.endpointRetrievalUrl}/${hubId}/endpoint",
|
|
||||||
headers: getRequestHeaders()
|
|
||||||
]
|
|
||||||
|
|
||||||
try {
|
|
||||||
httpGet(params) { response ->
|
|
||||||
log.debug "Request was successful, received endpoint: ${response.data.endpoint}"
|
|
||||||
atomicState.endpoint = response.data.endpoint
|
|
||||||
subscribeToDeviceEvents()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
log.debug "Unable to retrieve the endpoint"
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
def handleClientUninstall() {
|
|
||||||
log.info("Deactivated from client")
|
|
||||||
try {
|
|
||||||
app.delete()
|
|
||||||
} catch (e) {
|
|
||||||
unschedule()
|
|
||||||
unsubscribe()
|
|
||||||
httpError(500, "An error occurred during deleting SmartApp: ${e}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mappings {
|
|
||||||
path("/device") {
|
|
||||||
action:
|
|
||||||
[
|
|
||||||
GET: getDeviceList
|
|
||||||
]
|
|
||||||
}
|
|
||||||
path("/location") {
|
|
||||||
action:
|
|
||||||
[
|
|
||||||
GET: getHubLocation
|
|
||||||
]
|
|
||||||
}
|
|
||||||
path("/monitoring") {
|
|
||||||
action:
|
|
||||||
[
|
|
||||||
POST : activateMonitoring,
|
|
||||||
DELETE: deactivateMonitoring
|
|
||||||
]
|
|
||||||
}
|
|
||||||
path("/uninstall") {
|
|
||||||
action
|
|
||||||
[GET: handleClientUninstall]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -41,14 +41,14 @@ def updated() {
|
|||||||
def motionHandler(evt) {
|
def motionHandler(evt) {
|
||||||
log.debug "handler $evt.name: $evt.value"
|
log.debug "handler $evt.name: $evt.value"
|
||||||
if (evt.value == "inactive") {
|
if (evt.value == "inactive") {
|
||||||
runIn(delayMins * 60, scheduleCheck, [overwrite: false])
|
runIn(delayMins * 60, scheduleCheck, [overwrite: true])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def presenceHandler(evt) {
|
def presenceHandler(evt) {
|
||||||
log.debug "handler $evt.name: $evt.value"
|
log.debug "handler $evt.name: $evt.value"
|
||||||
if (evt.value == "not present") {
|
if (evt.value == "not present") {
|
||||||
runIn(delayMins * 60, scheduleCheck, [overwrite: false])
|
runIn(delayMins * 60, scheduleCheck, [overwrite: true])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
import javax.crypto.Mac;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OpenT2T SmartApp Test
|
* OpenT2T SmartApp Test
|
||||||
*
|
*
|
||||||
@@ -81,28 +85,33 @@ def getInputs() {
|
|||||||
//API external Endpoints
|
//API external Endpoints
|
||||||
mappings {
|
mappings {
|
||||||
path("/devices") {
|
path("/devices") {
|
||||||
action: [
|
action:
|
||||||
|
[
|
||||||
GET: "getDevices"
|
GET: "getDevices"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
path("/devices/:id") {
|
path("/devices/:id") {
|
||||||
action: [
|
action:
|
||||||
|
[
|
||||||
GET: "getDevice"
|
GET: "getDevice"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
path("/update/:id") {
|
path("/update/:id") {
|
||||||
action: [
|
action:
|
||||||
|
[
|
||||||
PUT: "updateDevice"
|
PUT: "updateDevice"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
path("/deviceSubscription") {
|
path("/deviceSubscription") {
|
||||||
action: [
|
action:
|
||||||
|
[
|
||||||
POST : "registerDeviceChange",
|
POST : "registerDeviceChange",
|
||||||
DELETE: "unregisterDeviceChange"
|
DELETE: "unregisterDeviceChange"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
path("/locationSubscription") {
|
path("/locationSubscription") {
|
||||||
action: [
|
action:
|
||||||
|
[
|
||||||
POST : "registerDeviceGraph",
|
POST : "registerDeviceGraph",
|
||||||
DELETE: "unregisterDeviceGraph"
|
DELETE: "unregisterDeviceGraph"
|
||||||
]
|
]
|
||||||
@@ -116,6 +125,8 @@ def installed() {
|
|||||||
|
|
||||||
def updated() {
|
def updated() {
|
||||||
log.debug "Updating with settings: ${settings}"
|
log.debug "Updating with settings: ${settings}"
|
||||||
|
|
||||||
|
//Initialize state variables if didn't exist.
|
||||||
if (state.deviceSubscriptionMap == null) {
|
if (state.deviceSubscriptionMap == null) {
|
||||||
state.deviceSubscriptionMap = [:]
|
state.deviceSubscriptionMap = [:]
|
||||||
log.debug "deviceSubscriptionMap created."
|
log.debug "deviceSubscriptionMap created."
|
||||||
@@ -124,6 +135,11 @@ def updated() {
|
|||||||
state.locationSubscriptionMap = [:]
|
state.locationSubscriptionMap = [:]
|
||||||
log.debug "locationSubscriptionMap created."
|
log.debug "locationSubscriptionMap created."
|
||||||
}
|
}
|
||||||
|
if (state.verificationKeyMap == null) {
|
||||||
|
state.verificationKeyMap = [:]
|
||||||
|
log.debug "verificationKeyMap created."
|
||||||
|
}
|
||||||
|
|
||||||
unsubscribe()
|
unsubscribe()
|
||||||
registerAllDeviceSubscriptions()
|
registerAllDeviceSubscriptions()
|
||||||
}
|
}
|
||||||
@@ -132,9 +148,11 @@ def initialize() {
|
|||||||
log.debug "Initializing with settings: ${settings}"
|
log.debug "Initializing with settings: ${settings}"
|
||||||
state.deviceSubscriptionMap = [:]
|
state.deviceSubscriptionMap = [:]
|
||||||
log.debug "deviceSubscriptionMap created."
|
log.debug "deviceSubscriptionMap created."
|
||||||
registerAllDeviceSubscriptions()
|
|
||||||
state.locationSubscriptionMap = [:]
|
state.locationSubscriptionMap = [:]
|
||||||
log.debug "locationSubscriptionMap created."
|
log.debug "locationSubscriptionMap created."
|
||||||
|
state.verificationKeyMap = [:]
|
||||||
|
log.debug "verificationKeyMap created."
|
||||||
|
registerAllDeviceSubscriptions()
|
||||||
}
|
}
|
||||||
|
|
||||||
/*** Subscription Functions ***/
|
/*** Subscription Functions ***/
|
||||||
@@ -144,22 +162,12 @@ def registerAllDeviceSubscriptions() {
|
|||||||
registerChangeHandler(inputs)
|
registerChangeHandler(inputs)
|
||||||
}
|
}
|
||||||
|
|
||||||
//Subscribe to events from a list of devices
|
|
||||||
def registerChangeHandler(myList) {
|
|
||||||
myList.each { myDevice ->
|
|
||||||
def theAtts = myDevice.supportedAttributes
|
|
||||||
theAtts.each {att ->
|
|
||||||
subscribe(myDevice, att.name, deviceEventHandler)
|
|
||||||
log.info "Registering for ${myDevice.displayName}.${att.name}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Endpoints function: Subscribe to events from a specific device
|
//Endpoints function: Subscribe to events from a specific device
|
||||||
def registerDeviceChange() {
|
def registerDeviceChange() {
|
||||||
def subscriptionEndpt = params.subscriptionURL
|
def subscriptionEndpt = params.subscriptionURL
|
||||||
def deviceId = params.deviceId
|
def deviceId = params.deviceId
|
||||||
def myDevice = findDevice(deviceId)
|
def myDevice = findDevice(deviceId)
|
||||||
|
|
||||||
if (myDevice == null) {
|
if (myDevice == null) {
|
||||||
httpError(404, "Cannot find device with device ID ${deviceId}.")
|
httpError(404, "Cannot find device with device ID ${deviceId}.")
|
||||||
}
|
}
|
||||||
@@ -179,12 +187,18 @@ def registerDeviceChange() {
|
|||||||
state.deviceSubscriptionMap[deviceId] << subscriptionEndpt
|
state.deviceSubscriptionMap[deviceId] << subscriptionEndpt
|
||||||
log.info "Added subscription URL: ${subscriptionEndpt} for ${myDevice.displayName}"
|
log.info "Added subscription URL: ${subscriptionEndpt} for ${myDevice.displayName}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (params.key != null) {
|
||||||
|
state.verificationKeyMap[subscriptionEndpt] = params.key
|
||||||
|
log.info "Added verification key: ${params.key} for ${subscriptionEndpt}"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
httpError(500, "something went wrong: $e")
|
httpError(500, "something went wrong: $e")
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info "Current subscription map is ${state.deviceSubscriptionMap}"
|
log.info "Current subscription map is ${state.deviceSubscriptionMap}"
|
||||||
|
log.info "Current verification key map is ${state.verificationKeyMap}"
|
||||||
return ["succeed"]
|
return ["succeed"]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,6 +220,7 @@ def unregisterDeviceChange() {
|
|||||||
} else {
|
} else {
|
||||||
state.deviceSubscriptionMap[deviceId].remove(subscriptionEndpt)
|
state.deviceSubscriptionMap[deviceId].remove(subscriptionEndpt)
|
||||||
}
|
}
|
||||||
|
state.verificationKeyMap.remove(subscriptionEndpt)
|
||||||
log.info "Removed subscription URL: ${subscriptionEndpt} for ${myDevice.displayName}"
|
log.info "Removed subscription URL: ${subscriptionEndpt} for ${myDevice.displayName}"
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -217,6 +232,7 @@ def unregisterDeviceChange() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.info "Current subscription map is ${state.deviceSubscriptionMap}"
|
log.info "Current subscription map is ${state.deviceSubscriptionMap}"
|
||||||
|
log.info "Current verification key map is ${state.verificationKeyMap}"
|
||||||
}
|
}
|
||||||
|
|
||||||
//Endpoints function: Subscribe to device additiona/removal updated in a location
|
//Endpoints function: Subscribe to device additiona/removal updated in a location
|
||||||
@@ -235,7 +251,14 @@ def registerDeviceGraph() {
|
|||||||
state.locationSubscriptionMap[location.id] << subscriptionEndpt
|
state.locationSubscriptionMap[location.id] << subscriptionEndpt
|
||||||
log.info "Added subscription URL: ${subscriptionEndpt} for Location ${location.name}"
|
log.info "Added subscription URL: ${subscriptionEndpt} for Location ${location.name}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (params.key != null) {
|
||||||
|
state.verificationKeyMap[subscriptionEndpt] = params.key
|
||||||
|
log.info "Added verification key: ${params.key} for ${subscriptionEndpt}"
|
||||||
|
}
|
||||||
|
|
||||||
log.info "Current location subscription map is ${state.locationSubscriptionMap}"
|
log.info "Current location subscription map is ${state.locationSubscriptionMap}"
|
||||||
|
log.info "Current verification key map is ${state.verificationKeyMap}"
|
||||||
return ["succeed"]
|
return ["succeed"]
|
||||||
} else {
|
} else {
|
||||||
httpError(400, "missing input parameter: subscriptionURL")
|
httpError(400, "missing input parameter: subscriptionURL")
|
||||||
@@ -254,6 +277,7 @@ def unregisterDeviceGraph() {
|
|||||||
} else {
|
} else {
|
||||||
state.locationSubscriptionMap[location.id].remove(subscriptionEndpt)
|
state.locationSubscriptionMap[location.id].remove(subscriptionEndpt)
|
||||||
}
|
}
|
||||||
|
state.verificationKeyMap.remove(subscriptionEndpt)
|
||||||
log.info "Removed subscription URL: ${subscriptionEndpt} for Location ${location.name}"
|
log.info "Removed subscription URL: ${subscriptionEndpt} for Location ${location.name}"
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -264,26 +288,38 @@ def unregisterDeviceGraph() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.info "Current location subscription map is ${state.locationSubscriptionMap}"
|
log.info "Current location subscription map is ${state.locationSubscriptionMap}"
|
||||||
|
log.info "Current verification key map is ${state.verificationKeyMap}"
|
||||||
}
|
}
|
||||||
|
|
||||||
//When events are triggered, send HTTP post to web socket servers
|
//When events are triggered, send HTTP post to web socket servers
|
||||||
def deviceEventHandler(evt) {
|
def deviceEventHandler(evt) {
|
||||||
def evt_device = evt.device
|
def evtDevice = evt.device
|
||||||
def evt_deviceType = getDeviceType(evt_device)
|
def evtDeviceType = getDeviceType(evtDevice)
|
||||||
def deviceInfo
|
def deviceData = [];
|
||||||
|
|
||||||
def params = [ body: [deviceName: evt_device.displayName, deviceId: evt_device.id, locationId: location.id] ]
|
|
||||||
|
|
||||||
if (evt.data != null) {
|
if (evt.data != null) {
|
||||||
def evtData = parseJson(evt.data)
|
def evtData = parseJson(evt.data)
|
||||||
log.info "Received event for ${evt_device.displayName}, data: ${evtData}, description: ${evt.descriptionText}"
|
log.info "Received event for ${evtDevice.displayName}, data: ${evtData}, description: ${evt.descriptionText}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (evtDeviceType == "thermostat") {
|
||||||
|
deviceData = [name: evtDevice.displayName, id: evtDevice.id, status: evtDevice.status, deviceType: evtDeviceType, manufacturer: evtDevice.manufacturerName, model: evtDevice.modelName, attributes: deviceAttributeList(evtDevice, evtDeviceType), locationMode: getLocationModeInfo(), locationId: location.id]
|
||||||
|
} else {
|
||||||
|
deviceData = [name: evtDevice.displayName, id: evtDevice.id, status: evtDevice.status, deviceType: evtDeviceType, manufacturer: evtDevice.manufacturerName, model: evtDevice.modelName, attributes: deviceAttributeList(evtDevice, evtDeviceType), locationId: location.id]
|
||||||
|
}
|
||||||
|
|
||||||
|
def params = [body: deviceData]
|
||||||
|
|
||||||
//send event to all subscriptions urls
|
//send event to all subscriptions urls
|
||||||
log.debug "Current subscription urls for ${evt_device.displayName} is ${state.deviceSubscriptionMap[evt_device.id]}"
|
log.debug "Current subscription urls for ${evtDevice.displayName} is ${state.deviceSubscriptionMap[evtDevice.id]}"
|
||||||
state.deviceSubscriptionMap[evt_device.id].each {
|
state.deviceSubscriptionMap[evtDevice.id].each {
|
||||||
params.uri = "${it}"
|
params.uri = "${it}"
|
||||||
|
if (state.verificationKeyMap[it] != null) {
|
||||||
|
def key = state.verificationKeyMap[it]
|
||||||
|
params.header = [Signature: ComputHMACValue(key, groovy.json.JsonOutput.toJson(params.body))]
|
||||||
|
}
|
||||||
log.trace "POST URI: ${params.uri}"
|
log.trace "POST URI: ${params.uri}"
|
||||||
|
log.trace "Header: ${params.header}"
|
||||||
log.trace "Payload: ${params.body}"
|
log.trace "Payload: ${params.body}"
|
||||||
try {
|
try {
|
||||||
httpPostJson(params) { resp ->
|
httpPostJson(params) { resp ->
|
||||||
@@ -301,15 +337,22 @@ def locationEventHandler(evt) {
|
|||||||
switch (evt.name) {
|
switch (evt.name) {
|
||||||
case "DeviceCreated":
|
case "DeviceCreated":
|
||||||
case "DeviceDeleted":
|
case "DeviceDeleted":
|
||||||
def evt_device = evt.device
|
def evtDevice = evt.device
|
||||||
def evt_deviceType = getDeviceType(evt_device)
|
def evtDeviceType = getDeviceType(evtDevice)
|
||||||
log.info "DeviceName: ${evt_device.displayName}, DeviceID: ${evt_device.id}, deviceType: ${evt_deviceType}"
|
def params = [body: [eventType: evt.name, deviceId: evtDevice.id, locationId: location.id]]
|
||||||
|
|
||||||
def params = [ body: [ eventType:evt.name, deviceId: evt_device.id, locationId: location.id ] ]
|
if (evt.name == "DeviceDeleted" && state.deviceSubscriptionMap[deviceId] != null) {
|
||||||
|
state.deviceSubscriptionMap.remove(evtDevice.id)
|
||||||
|
}
|
||||||
|
|
||||||
state.locationSubscriptionMap[location.id].each {
|
state.locationSubscriptionMap[location.id].each {
|
||||||
params.uri = "${it}"
|
params.uri = "${it}"
|
||||||
|
if (state.verificationKeyMap[it] != null) {
|
||||||
|
def key = state.verificationKeyMap[it]
|
||||||
|
params.header = [Signature: ComputHMACValue(key, groovy.json.JsonOutput.toJson(params.body))]
|
||||||
|
}
|
||||||
log.trace "POST URI: ${params.uri}"
|
log.trace "POST URI: ${params.uri}"
|
||||||
|
log.trace "Header: ${params.header}"
|
||||||
log.trace "Payload: ${params.body}"
|
log.trace "Payload: ${params.body}"
|
||||||
try {
|
try {
|
||||||
httpPostJson(params) { resp ->
|
httpPostJson(params) { resp ->
|
||||||
@@ -326,6 +369,23 @@ def locationEventHandler(evt) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ComputHMACValue(key, data) {
|
||||||
|
try {
|
||||||
|
SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA1")
|
||||||
|
Mac mac = Mac.getInstance("HmacSHA1")
|
||||||
|
mac.init(secretKeySpec)
|
||||||
|
byte[] digest = mac.doFinal(data.getBytes("UTF-8"))
|
||||||
|
return byteArrayToString(digest)
|
||||||
|
} catch (InvalidKeyException e) {
|
||||||
|
log.error "Invalid key exception while converting to HMac SHA1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def byteArrayToString(byte[] data) {
|
||||||
|
BigInteger bigInteger = new BigInteger(1, data)
|
||||||
|
String hash = bigInteger.toString(16)
|
||||||
|
return hash
|
||||||
|
}
|
||||||
|
|
||||||
/*** Device Query/Update Functions ***/
|
/*** Device Query/Update Functions ***/
|
||||||
|
|
||||||
@@ -433,8 +493,7 @@ private getDeviceType(device) {
|
|||||||
|
|
||||||
//Loop through the device capability list to determine the device type.
|
//Loop through the device capability list to determine the device type.
|
||||||
capabilities.each { capability ->
|
capabilities.each { capability ->
|
||||||
switch(capability.name.toLowerCase())
|
switch (capability.name.toLowerCase()) {
|
||||||
{
|
|
||||||
case "switch":
|
case "switch":
|
||||||
deviceType = "switch"
|
deviceType = "switch"
|
||||||
|
|
||||||
@@ -579,8 +638,7 @@ private mapDeviceCommands(command, value) {
|
|||||||
if (value == 1 || value == "1" || value == "lock") {
|
if (value == 1 || value == "1" || value == "lock") {
|
||||||
resultCommand = "lock"
|
resultCommand = "lock"
|
||||||
resultValue = ""
|
resultValue = ""
|
||||||
}
|
} else if (value == 0 || value == "0" || value == "unlock") {
|
||||||
else if (value == 0 || value == "0" || value == "unlock") {
|
|
||||||
resultCommand = "unlock"
|
resultCommand = "unlock"
|
||||||
resultValue = ""
|
resultValue = ""
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,12 +30,15 @@ preferences {
|
|||||||
section("Monitor this door or window") {
|
section("Monitor this door or window") {
|
||||||
input "contact", "capability.contactSensor"
|
input "contact", "capability.contactSensor"
|
||||||
}
|
}
|
||||||
|
|
||||||
section("And notify me if it's open for more than this many minutes (default 10)") {
|
section("And notify me if it's open for more than this many minutes (default 10)") {
|
||||||
input "openThreshold", "number", description: "Number of minutes", required: false
|
input "openThreshold", "number", description: "Number of minutes", required: false
|
||||||
}
|
}
|
||||||
|
|
||||||
section("Delay between notifications (default 10 minutes") {
|
section("Delay between notifications (default 10 minutes") {
|
||||||
input "frequency", "number", title: "Number of minutes", description: "", required: false
|
input "frequency", "number", title: "Number of minutes", description: "", required: false
|
||||||
}
|
}
|
||||||
|
|
||||||
section("Via text message at this number (or via push notification if not specified") {
|
section("Via text message at this number (or via push notification if not specified") {
|
||||||
input("recipients", "contact", title: "Send notifications to") {
|
input("recipients", "contact", title: "Send notifications to") {
|
||||||
input "phone", "phone", title: "Phone number (optional)", required: false
|
input "phone", "phone", title: "Phone number (optional)", required: false
|
||||||
@@ -59,18 +62,15 @@ def subscribe() {
|
|||||||
subscribe(contact, "contact.closed", doorClosed)
|
subscribe(contact, "contact.closed", doorClosed)
|
||||||
}
|
}
|
||||||
|
|
||||||
def doorOpen(evt)
|
def doorOpen(evt) {
|
||||||
{
|
|
||||||
log.trace "doorOpen($evt.name: $evt.value)"
|
log.trace "doorOpen($evt.name: $evt.value)"
|
||||||
def t0 = now()
|
|
||||||
def delay = (openThreshold != null && openThreshold != "") ? openThreshold * 60 : 600
|
def delay = (openThreshold != null && openThreshold != "") ? openThreshold * 60 : 600
|
||||||
runIn(delay, doorOpenTooLong, [overwrite: false])
|
runIn(delay, doorOpenTooLong, [overwrite: true])
|
||||||
log.debug "scheduled doorOpenTooLong in ${now() - t0} msec"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def doorClosed(evt)
|
def doorClosed(evt) {
|
||||||
{
|
|
||||||
log.trace "doorClosed($evt.name: $evt.value)"
|
log.trace "doorClosed($evt.name: $evt.value)"
|
||||||
|
unschedule(doorOpenTooLong)
|
||||||
}
|
}
|
||||||
|
|
||||||
def doorOpenTooLong() {
|
def doorOpenTooLong() {
|
||||||
@@ -92,15 +92,13 @@ def doorOpenTooLong() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void sendMessage()
|
void sendMessage() {
|
||||||
{
|
|
||||||
def minutes = (openThreshold != null && openThreshold != "") ? openThreshold : 10
|
def minutes = (openThreshold != null && openThreshold != "") ? openThreshold : 10
|
||||||
def msg = "${contact.displayName} has been left open for ${minutes} minutes."
|
def msg = "${contact.displayName} has been left open for ${minutes} minutes."
|
||||||
log.info msg
|
log.info msg
|
||||||
if (location.contactBookEnabled) {
|
if (location.contactBookEnabled) {
|
||||||
sendNotificationToContacts(msg, recipients)
|
sendNotificationToContacts(msg, recipients)
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
if (phone) {
|
if (phone) {
|
||||||
sendSms phone, msg
|
sendSms phone, msg
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user