mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-27 13:23:06 +00:00
Compare commits
13 Commits
MSA-1970-1
...
MSA-1985-1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
92fb0b8c38 | ||
|
|
40b75ce665 | ||
|
|
9b01a7d8be | ||
|
|
7cf8bb1917 | ||
|
|
d4fd778a64 | ||
|
|
eb870e5f31 | ||
|
|
d60657e466 | ||
|
|
d8dc70ae9e | ||
|
|
3457bbad42 | ||
|
|
c6c4b09fbb | ||
|
|
7e25d32c70 | ||
|
|
abc5683ed3 | ||
|
|
1d629cfc9a |
@@ -1,735 +0,0 @@
|
|||||||
/**
|
|
||||||
|
|
||||||
* 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]
|
|
||||||
|
|
||||||
]}
|
|
||||||
@@ -4,7 +4,7 @@ Cloud Execution
|
|||||||
|
|
||||||
Works with:
|
Works with:
|
||||||
|
|
||||||
* [FortrezZ Siren Strobe Alarm](https://www.smartthings.com/works-with-smartthings/other/fortrezz-water-valve)
|
* [FortrezZ Siren Strobe Alarm](https://www.smartthings.com/products/fortrezz-siren-strobe-alarm)
|
||||||
|
|
||||||
## Table of contents
|
## Table of contents
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ metadata {
|
|||||||
|
|
||||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008"
|
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008"
|
||||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY A19 ON/OFF/DIM", deviceJoinName: "SYLVANIA Smart A19 Soft White"
|
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY A19 ON/OFF/DIM", deviceJoinName: "SYLVANIA Smart A19 Soft White"
|
||||||
|
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY A19 ON/OFF/DIM 10 Year", deviceJoinName: "SYLVANIA Smart 10-Year A19"
|
||||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, FF00", outClusters: "0019", manufacturer: "MRVL", model: "MZ100", deviceJoinName: "Wemo Bulb"
|
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, FF00", outClusters: "0019", manufacturer: "MRVL", model: "MZ100", deviceJoinName: "Wemo Bulb"
|
||||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0B05", outClusters: "0019", manufacturer: "OSRAM SYLVANIA", model: "iQBR30", deviceJoinName: "Sylvania Ultra iQ"
|
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0B05", outClusters: "0019", manufacturer: "OSRAM SYLVANIA", model: "iQBR30", deviceJoinName: "Sylvania Ultra iQ"
|
||||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY PAR38 ON/OFF/DIM", deviceJoinName: "SYLVANIA Smart PAR38 Soft White"
|
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY PAR38 ON/OFF/DIM", deviceJoinName: "SYLVANIA Smart PAR38 Soft White"
|
||||||
|
|||||||
@@ -765,7 +765,6 @@ def turnOffSwitch() {
|
|||||||
} else {
|
} else {
|
||||||
|
|
||||||
device.off();
|
device.off();
|
||||||
|
|
||||||
return [Device_id: params.id, result_action: "200"]
|
return [Device_id: params.id, result_action: "200"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -789,6 +788,7 @@ def getTempSensorsStatus(id) {
|
|||||||
return []
|
return []
|
||||||
} else {
|
} else {
|
||||||
def bat = getBatteryStatus(device.id)
|
def bat = getBatteryStatus(device.id)
|
||||||
return [temperature: device.currentValue('temperature')] + bat
|
def scale = [Scale: location.temperatureScale]
|
||||||
|
return [temperature: device.currentValue('temperature')] + bat + scale
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -162,6 +162,17 @@ 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
|
||||||
|
|||||||
@@ -0,0 +1,373 @@
|
|||||||
|
/**
|
||||||
|
* SmartThingsToStart REST Api
|
||||||
|
*
|
||||||
|
* Copyright 2017 Dr1rrb
|
||||||
|
*
|
||||||
|
* 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: "SmartThingsToStart",
|
||||||
|
namespace: "torick.net",
|
||||||
|
author: "Dr1rrb",
|
||||||
|
description: "SmartThingsToStart REST Api",
|
||||||
|
category: "My Apps",
|
||||||
|
iconUrl: "http://smartthingstostartproxy.azurewebsites.net/Assets/AppLogo.png",
|
||||||
|
iconX2Url: "http://smartthingstostartproxy.azurewebsites.net/Assets/AppLogo@2X.png",
|
||||||
|
iconX3Url: "http://smartthingstostartproxy.azurewebsites.net/Assets/AppLogo@3X.png",
|
||||||
|
oauth: true)
|
||||||
|
|
||||||
|
|
||||||
|
preferences {
|
||||||
|
section("Control these devices") {
|
||||||
|
input "switches", "capability.switch", title: "Select switches", multiple: true, required: false
|
||||||
|
input "bubls", "capability.bulb", title: "Select bubls", hideWhenEmpty: true, multiple: true, required: false
|
||||||
|
input "lights", "capability.light", title: "Select lights", hideWhenEmpty: true, multiple: true, required: false
|
||||||
|
input "outlets", "capability.outlet", title: "Select outlets", hideWhenEmpty: true, multiple: true, required: false
|
||||||
|
input "relaySwitches", "capability.relaySwitch", title: "Select relay switches", hideWhenEmpty: true, multiple: true, required: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mappings {
|
||||||
|
path("/infos") {
|
||||||
|
action: [GET: "retreiveServerInfos"]
|
||||||
|
}
|
||||||
|
path("/items") {
|
||||||
|
action: [GET: "retreiveDevicesAndRoutines"]
|
||||||
|
}
|
||||||
|
path("/device/:id") {
|
||||||
|
action: [GET: "retreiveDevice"]
|
||||||
|
}
|
||||||
|
path("/device/:id/subscription/:subscriptionId") {
|
||||||
|
action: [
|
||||||
|
PUT: "updateOrCreateSubscription",
|
||||||
|
POST: "updateOrCreateSubscription",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
// TODO
|
||||||
|
//path("/device/:id/unsubscribe") {
|
||||||
|
// action: [POST: "unsubscribeFromDevice"]
|
||||||
|
//}
|
||||||
|
path("/device/:id/:command") {
|
||||||
|
action: [ PUT: "updateDevice" ]
|
||||||
|
}
|
||||||
|
path("/routine/:id/execute") {
|
||||||
|
action: [PUT: "executeRoutine"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Region: App lifecycle
|
||||||
|
def installed() {
|
||||||
|
log.debug "Installed with settings: ${settings}"
|
||||||
|
|
||||||
|
initialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
def updated() {
|
||||||
|
log.debug "Updated with settings: ${settings}"
|
||||||
|
|
||||||
|
unsubscribe()
|
||||||
|
//state.pushChannels = [:]
|
||||||
|
initialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
def initialize() {
|
||||||
|
def channels = state.pushChannels = state.pushChannels ?: [:];
|
||||||
|
channels.each
|
||||||
|
{
|
||||||
|
def device = findDevice(it.key);
|
||||||
|
if (device != null)
|
||||||
|
{
|
||||||
|
subscribeToDevice(device);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Region: Http request handlers
|
||||||
|
def retreiveServerInfos()
|
||||||
|
{
|
||||||
|
return [version: 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
def retreiveDevicesAndRoutines() {
|
||||||
|
def details = params.details == "true" ? true : false;
|
||||||
|
|
||||||
|
return [
|
||||||
|
devices: getDevices().collect { getDeviceInfos(it, details) },
|
||||||
|
routines: location.helloHome?.getPhrases().collect { getRoutineInfos(it, details) }
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
def retreiveDevice() {
|
||||||
|
def device = getDevice(params.id);
|
||||||
|
def details = params.details == "true" ? true : false;
|
||||||
|
|
||||||
|
return getDeviceInfos(device, details);
|
||||||
|
}
|
||||||
|
|
||||||
|
def updateOrCreateSubscription() {
|
||||||
|
def device = getDevice(params.id);
|
||||||
|
def channelUri = notNull("channelUri", request.JSON?.channelUri);
|
||||||
|
def token = notNull("token", request.JSON?.token);
|
||||||
|
|
||||||
|
log.debug "Subscribing to device '${device.id}' (target: '${channelUri}' / token: '${token}')"
|
||||||
|
|
||||||
|
// Get or create the push notification channel from / into the local state
|
||||||
|
def subscriptionId = params.subscriptionId ?: UUID.randomUUID().toString() ;
|
||||||
|
def allSubscriptions = state.pushChannels ?: (state.pushChannels = [:]);
|
||||||
|
def deviceSubscriptions = allSubscriptions[device.id] ?: (allSubscriptions[device.id] = []);
|
||||||
|
def subscription = deviceSubscriptions.find { it.id == subscriptionId };
|
||||||
|
if (subscription == null)
|
||||||
|
{
|
||||||
|
deviceSubscriptions << [
|
||||||
|
id: subscriptionId,
|
||||||
|
deviceId: device.id,
|
||||||
|
channelUri: channelUri,
|
||||||
|
token: token
|
||||||
|
];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
subscription["channelUri"] = channelUri;
|
||||||
|
subscription["token"] = token;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug "Active subscriptions: \n" + state.pushChannels.collect { "** Device ${it.key} **\n" + it.value.collect{c -> "- - - > ${c.channelUri} : ${c.token.substring(0, 10)}..."}.join('\n') + "\n***************************" }.join('\n\n')
|
||||||
|
|
||||||
|
// (Re)create the subscription(s)
|
||||||
|
subscribeToDevice(device)
|
||||||
|
|
||||||
|
return [subscriptionId: subscriptionId];
|
||||||
|
}
|
||||||
|
|
||||||
|
def subscribeToDevice(device)
|
||||||
|
{
|
||||||
|
log.debug "Subscribing to device '${device.id}'"
|
||||||
|
|
||||||
|
unsubscribe(device);
|
||||||
|
subscribe(device, "switch", switchStateChanged)
|
||||||
|
|
||||||
|
if (device.hasCapability("Color Control"))
|
||||||
|
{
|
||||||
|
log.debug "Device '${device.id}' has also the color capability. Subscribe to it."
|
||||||
|
subscribe(device, "color", colorStateChanged)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def switchStateChanged(eventArgs) { sendPushNotification("switch", eventArgs) }
|
||||||
|
def colorStateChanged(eventArgs) { sendPushNotification("color", eventArgs) }
|
||||||
|
|
||||||
|
def updateDevice() {
|
||||||
|
def device = getDevice(params.id)
|
||||||
|
def command = notNull("command", params.command)
|
||||||
|
|
||||||
|
log.debug "Executing '${command}' on device '${device.id}'."
|
||||||
|
|
||||||
|
switch(command) {
|
||||||
|
case "on":
|
||||||
|
case "On":
|
||||||
|
device.on()
|
||||||
|
break
|
||||||
|
|
||||||
|
case "off":
|
||||||
|
case "Off":
|
||||||
|
device.off()
|
||||||
|
break
|
||||||
|
|
||||||
|
case "toggle":
|
||||||
|
case "Toggle":
|
||||||
|
if (device.currentSwitch == "on")
|
||||||
|
device.off();
|
||||||
|
else
|
||||||
|
device.on();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
httpError(501, "'${command}' is not a valid command for '${device.id}'")
|
||||||
|
}
|
||||||
|
|
||||||
|
return getDeviceInfos(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
def executeRoutine() {
|
||||||
|
def routine = getRoutine(params.id);
|
||||||
|
log.debug "Executing routine '${routine.id}' (${routine.label})"
|
||||||
|
|
||||||
|
location.helloHome?.execute(routine.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Region: Get device
|
||||||
|
def getDevices()
|
||||||
|
{
|
||||||
|
return switches
|
||||||
|
+ bubls
|
||||||
|
+ lights
|
||||||
|
+ outlets
|
||||||
|
+ relaySwitches;
|
||||||
|
}
|
||||||
|
|
||||||
|
def findDevice(deviceId)
|
||||||
|
{
|
||||||
|
notNull("deviceId", deviceId);
|
||||||
|
|
||||||
|
return getDevices().find { it.id == deviceId };
|
||||||
|
}
|
||||||
|
|
||||||
|
def getDevice(deviceId)
|
||||||
|
{
|
||||||
|
def device = findDevice(deviceId);
|
||||||
|
if (device == null)
|
||||||
|
{
|
||||||
|
httpError(404, "Device '${deviceId}' not found.")
|
||||||
|
}
|
||||||
|
return device;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Region: Get routine
|
||||||
|
def findRoutine(routineId)
|
||||||
|
{
|
||||||
|
return location.helloHome?.getPhrases().find{ it.id == routineId};
|
||||||
|
}
|
||||||
|
|
||||||
|
def getRoutine(routineId)
|
||||||
|
{
|
||||||
|
def routine = findRoutine(routineId);
|
||||||
|
if (routine == null)
|
||||||
|
{
|
||||||
|
httpError(404, "Routine '${routineId}' not found.")
|
||||||
|
}
|
||||||
|
return routine;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Region: Parameters assertion helpers
|
||||||
|
def notNull(parameterName, value)
|
||||||
|
{
|
||||||
|
if(value == null || value == "")
|
||||||
|
{
|
||||||
|
httpError(404, "Missing parameter '${parameterName}'.")
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Region: Get infos
|
||||||
|
def getDeviceInfos(device, details = false)
|
||||||
|
{
|
||||||
|
def infos = [
|
||||||
|
id: device.id,
|
||||||
|
name: device.displayName,
|
||||||
|
state: device.currentValue("switch"),
|
||||||
|
color: device.currentValue("color"),
|
||||||
|
hue: device.currentValue("hue"),
|
||||||
|
saturation: device.currentValue("saturation"),
|
||||||
|
capabilities: device.capabilities.collect { getCapabilityInfos(it, details) }
|
||||||
|
]
|
||||||
|
|
||||||
|
if (details)
|
||||||
|
{
|
||||||
|
infos["attributes"] = device.supportedAttributes.collect { getAttributeInfos(it, details) }
|
||||||
|
infos["commands"] = device.supportedCommands.collect { getCommandInfos(it, details) }
|
||||||
|
}
|
||||||
|
|
||||||
|
return infos;
|
||||||
|
}
|
||||||
|
|
||||||
|
def getCapabilityInfos(capablity, details = false)
|
||||||
|
{
|
||||||
|
def infos = [name: capablity.name]
|
||||||
|
|
||||||
|
if(details)
|
||||||
|
{
|
||||||
|
infos["attributes"] = capablity.attributes.collect { getAttributeInfos(it, details) }
|
||||||
|
infos["commands"] = capablity.commands.collect { getCommandInfos(it, details) }
|
||||||
|
}
|
||||||
|
|
||||||
|
return infos;
|
||||||
|
}
|
||||||
|
|
||||||
|
def getCommandInfos(command, details = false)
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
name: command.name,
|
||||||
|
arguments: command.arguments
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
def getAttributeInfos(attribute, details = false)
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
name: attribute.name,
|
||||||
|
arguments: attribute.dataType,
|
||||||
|
values: attribute.values
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
def getRoutineInfos(routine, details = false)
|
||||||
|
{
|
||||||
|
def infos = [
|
||||||
|
id: routine.id,
|
||||||
|
name: routine.label
|
||||||
|
];
|
||||||
|
|
||||||
|
if (details)
|
||||||
|
{
|
||||||
|
infos["hasSecureActions"] = routine.hasSecureActions;
|
||||||
|
infos["action"] = routine.action;
|
||||||
|
}
|
||||||
|
|
||||||
|
return infos;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Region: Push notification
|
||||||
|
def sendPushNotification(capability, eventArgs)
|
||||||
|
{
|
||||||
|
def deviceId = eventArgs.deviceId;
|
||||||
|
log.debug "Received notification for '${capability}' for device '${deviceId}'.";
|
||||||
|
|
||||||
|
def subscriptions = state.pushChannels.get(deviceId);
|
||||||
|
if (subscriptions == null || subscriptions.empty)
|
||||||
|
{
|
||||||
|
log.error "No subscription found for device ${deviceId}, unsubscribing!";
|
||||||
|
unsubscribe(eventArgs.device);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
subscriptions.groupBy { it.channelUri }.each { sendPushNotification(capability, eventArgs, it.key, it.value) }
|
||||||
|
}
|
||||||
|
|
||||||
|
def sendPushNotification(capability, eventArgs, channelUri, subscriptions)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
def request = [
|
||||||
|
uri: channelUri,
|
||||||
|
//headers: [name: "Authorization", value: "Bearer ${subscription.token}"],
|
||||||
|
body: [
|
||||||
|
location: [
|
||||||
|
id: eventArgs.locationId,
|
||||||
|
],
|
||||||
|
device: getDeviceInfos(eventArgs.device),
|
||||||
|
event: [
|
||||||
|
source: capability,
|
||||||
|
date: eventArgs.isoDate,
|
||||||
|
value: eventArgs.value,
|
||||||
|
name: eventArgs.name,
|
||||||
|
],
|
||||||
|
subscriptions: subscriptions.collect { [id: it.id, token: it.token] }
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
// Async post is still in beta stage ...
|
||||||
|
httpPostJson(request) { resp -> log.debug "response: ${resp.status}." }
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
log.error "Failed to push notification: ${e}"
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user