mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-09 05:11:52 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc3c8a18f7 |
@@ -1,9 +1,12 @@
|
||||
// keen home smart vent
|
||||
// http://www.keenhome.io
|
||||
// SmartThings Device Handler v1.0.0
|
||||
/**
|
||||
* Keen Home Smart Vent
|
||||
*
|
||||
* Author: Keen Home
|
||||
* Date: 2015-06-23
|
||||
*/
|
||||
|
||||
metadata {
|
||||
definition (name: "Keen Home Smart Vent", namespace: "Keen Home", author: "Keen Home") {
|
||||
definition (name: "Keen Home Smart Vent", namespace: "Keen Home", author: "Gregg Altschul") {
|
||||
capability "Switch Level"
|
||||
capability "Switch"
|
||||
capability "Configuration"
|
||||
@@ -18,7 +21,6 @@ metadata {
|
||||
command "getBattery"
|
||||
command "getTemperature"
|
||||
command "setZigBeeIdTile"
|
||||
command "clearObstruction"
|
||||
|
||||
fingerprint endpoint: "1",
|
||||
profileId: "0104",
|
||||
@@ -40,10 +42,9 @@ metadata {
|
||||
// UI tile definitions
|
||||
tiles {
|
||||
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
|
||||
state "on", action: "switch.off", icon: "st.vents.vent-open-text", backgroundColor: "#53a7c0"
|
||||
state "off", action: "switch.on", icon: "st.vents.vent-closed", backgroundColor: "#ffffff"
|
||||
state "obstructed", action: "clearObstruction", icon: "st.vents.vent-closed", backgroundColor: "#ff0000"
|
||||
state "clearing", action: "", icon: "st.vents.vent-closed", backgroundColor: "#ffff33"
|
||||
state "on", action:"switch.off", icon:"st.vents.vent-open-text", backgroundColor:"#53a7c0"
|
||||
state "off", action:"switch.on", icon:"st.vents.vent-closed", backgroundColor:"#ffffff"
|
||||
state "obstructed", action: "switch.off", icon:"st.vents.vent-closed", backgroundColor:"#ff0000"
|
||||
}
|
||||
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false) {
|
||||
state "level", action:"switch level.setLevel"
|
||||
@@ -205,12 +206,12 @@ private Map makeOnOffResult(rawValue) {
|
||||
|
||||
private Map makeLevelResult(rawValue) {
|
||||
def linkText = getLinkText(device)
|
||||
// log.debug "rawValue: ${rawValue}"
|
||||
def value = Integer.parseInt(rawValue, 16)
|
||||
def rangeMax = 254
|
||||
|
||||
// catch obstruction level
|
||||
if (value == 255) {
|
||||
log.debug "${linkText} is obstructed"
|
||||
log.debug "obstructed"
|
||||
// Just return here. Once the vent is power cycled
|
||||
// it will go back to the previous level before obstruction.
|
||||
// Therefore, no need to update level on the display.
|
||||
@@ -219,9 +220,24 @@ private Map makeLevelResult(rawValue) {
|
||||
value: "obstructed",
|
||||
descriptionText: "${linkText} is obstructed. Please power cycle."
|
||||
]
|
||||
} else if ( device.currentValue("switch") == "obstructed" &&
|
||||
value == 254) {
|
||||
// When the device is reset after an obstruction, the switch
|
||||
// state will be obstructed and the value coming from the device
|
||||
// will be 254. Since we're not using heating/cooling mode from
|
||||
// the device type handler, we need to bump it down to the lower
|
||||
// (cooling) range
|
||||
sendEvent(makeOnOffResult(1)) // clear the obstructed switch state
|
||||
value = rangeMax
|
||||
}
|
||||
// else if (device.currentValue("switch") == "off") {
|
||||
// sendEvent(makeOnOffResult(1)) // turn back on if in off state
|
||||
// }
|
||||
|
||||
|
||||
// log.debug "pre-value: ${value}"
|
||||
value = Math.floor(value / rangeMax * 100)
|
||||
// log.debug "post-value: ${value}"
|
||||
|
||||
return [
|
||||
name: "level",
|
||||
@@ -311,79 +327,35 @@ private def makeSerialResult(serial) {
|
||||
value: serial,
|
||||
descriptionText: "${linkText} has serial ${serial}" ]
|
||||
}
|
||||
|
||||
// takes a level from 0 to 100 and translates it to a ZigBee move to level with on/off command
|
||||
private def makeLevelCommand(level) {
|
||||
def rangeMax = 254
|
||||
def scaledLevel = Math.round(level * rangeMax / 100)
|
||||
log.debug "scaled level for ${level}%: ${scaledLevel}"
|
||||
|
||||
// convert to hex string and pad to two digits
|
||||
def hexLevel = new BigInteger(scaledLevel.toString()).toString(16).padLeft(2, '0')
|
||||
|
||||
"st cmd 0x${device.deviceNetworkId} 1 8 4 {${hexLevel} 0000}"
|
||||
}
|
||||
|
||||
/**** COMMAND METHODS ****/
|
||||
// def mfgCode() {
|
||||
// ["zcl mfg-code 0x115B", "delay 200"]
|
||||
// }
|
||||
|
||||
def on() {
|
||||
def linkText = getLinkText(device)
|
||||
log.debug "open ${linkText}"
|
||||
|
||||
// only change the state if the vent is not obstructed
|
||||
if (device.currentValue("switch") == "obstructed") {
|
||||
log.error("cannot open because ${linkText} is obstructed")
|
||||
return
|
||||
}
|
||||
|
||||
log.debug "on()"
|
||||
sendEvent(makeOnOffResult(1))
|
||||
"st cmd 0x${device.deviceNetworkId} 1 6 1 {}"
|
||||
}
|
||||
|
||||
def off() {
|
||||
def linkText = getLinkText(device)
|
||||
log.debug "close ${linkText}"
|
||||
|
||||
// only change the state if the vent is not obstructed
|
||||
if (device.currentValue("switch") == "obstructed") {
|
||||
log.error("cannot close because ${linkText} is obstructed")
|
||||
return
|
||||
}
|
||||
|
||||
log.debug "off()"
|
||||
sendEvent(makeOnOffResult(0))
|
||||
"st cmd 0x${device.deviceNetworkId} 1 6 0 {}"
|
||||
}
|
||||
|
||||
def clearObstruction() {
|
||||
def linkText = getLinkText(device)
|
||||
log.debug "attempting to clear ${linkText} obstruction"
|
||||
// does this work?
|
||||
def toggle() {
|
||||
log.debug "toggle()"
|
||||
|
||||
sendEvent([
|
||||
name: "switch",
|
||||
value: "clearing",
|
||||
descriptionText: "${linkText} is clearing obstruction"
|
||||
])
|
||||
|
||||
// send a move command to ensure level attribute gets reset for old, buggy firmware
|
||||
// then send a reset to factory defaults
|
||||
// finally re-configure to ensure reports and binding is still properly set after the rtfd
|
||||
[
|
||||
makeLevelCommand(device.currentValue("level")), "delay 500",
|
||||
"st cmd 0x${device.deviceNetworkId} 1 0 0 {}", "delay 5000"
|
||||
] + configure()
|
||||
"st cmd 0x${device.deviceNetworkId} 1 6 2 {}"
|
||||
}
|
||||
|
||||
def setLevel(value) {
|
||||
log.debug "setting level: ${value}"
|
||||
|
||||
def linkText = getLinkText(device)
|
||||
|
||||
// only change the level if the vent is not obstructed
|
||||
def currentState = device.currentValue("switch")
|
||||
|
||||
if (currentState == "obstructed") {
|
||||
log.error("cannot set level because ${linkText} is obstructed")
|
||||
return
|
||||
}
|
||||
|
||||
sendEvent(name: "level", value: value)
|
||||
if (value > 0) {
|
||||
sendEvent(name: "switch", value: "on", descriptionText: "${linkText} is on by setting a level")
|
||||
@@ -391,26 +363,29 @@ def setLevel(value) {
|
||||
else {
|
||||
sendEvent(name: "switch", value: "off", descriptionText: "${linkText} is off by setting level to 0")
|
||||
}
|
||||
def rangeMax = 254
|
||||
def computedLevel = Math.round(value * rangeMax / 100)
|
||||
log.debug "computedLevel: ${computedLevel}"
|
||||
|
||||
makeLevelCommand(value)
|
||||
def level = new BigInteger(computedLevel.toString()).toString(16)
|
||||
log.debug "level: ${level}"
|
||||
|
||||
if (level.size() < 2){
|
||||
level = '0' + level
|
||||
}
|
||||
|
||||
"st cmd 0x${device.deviceNetworkId} 1 8 4 {${level} 0000}"
|
||||
}
|
||||
|
||||
|
||||
def getOnOff() {
|
||||
log.debug "getOnOff()"
|
||||
|
||||
// disallow on/off updates while vent is obstructed
|
||||
if (device.currentValue("switch") == "obstructed") {
|
||||
log.error("cannot update open/close status because ${getLinkText(device)} is obstructed")
|
||||
return []
|
||||
}
|
||||
|
||||
["st rattr 0x${device.deviceNetworkId} 1 0x0006 0"]
|
||||
}
|
||||
|
||||
def getPressure() {
|
||||
log.debug "getPressure()"
|
||||
|
||||
// using a Keen Home specific attribute in the pressure measurement cluster
|
||||
[
|
||||
"zcl mfg-code 0x115B", "delay 200",
|
||||
"zcl global read 0x0403 0x20", "delay 200",
|
||||
@@ -420,13 +395,12 @@ def getPressure() {
|
||||
|
||||
def getLevel() {
|
||||
log.debug "getLevel()"
|
||||
|
||||
// disallow level updates while vent is obstructed
|
||||
if (device.currentValue("switch") == "obstructed") {
|
||||
log.error("cannot update level status because ${getLinkText(device)} is obstructed")
|
||||
return []
|
||||
}
|
||||
|
||||
// rattr = read attribute
|
||||
// 0x${} = device net id
|
||||
// 1 = endpoint
|
||||
// 8 = cluster id (level control, in this case)
|
||||
// 0 = attribute within cluster
|
||||
// sendEvent(name: "level", value: value)
|
||||
["st rattr 0x${device.deviceNetworkId} 1 0x0008 0x0000"]
|
||||
}
|
||||
|
||||
@@ -451,59 +425,78 @@ def setZigBeeIdTile() {
|
||||
name: "zigbeeId",
|
||||
value: device.zigbeeId,
|
||||
descriptionText: "${linkText} has zigbeeId ${device.zigbeeId}" ])
|
||||
return [
|
||||
return [
|
||||
name: "zigbeeId",
|
||||
value: device.zigbeeId,
|
||||
descriptionText: "${linkText} has zigbeeId ${device.zigbeeId}" ]
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
getOnOff() +
|
||||
getOnOff() +
|
||||
getLevel() +
|
||||
getTemperature() +
|
||||
getPressure() +
|
||||
getBattery()
|
||||
}
|
||||
|
||||
private byte[] reverseArray(byte[] array) {
|
||||
int i = 0;
|
||||
int j = array.length - 1;
|
||||
byte tmp;
|
||||
while (j > i) {
|
||||
tmp = array[j];
|
||||
array[j] = array[i];
|
||||
array[i] = tmp;
|
||||
j--;
|
||||
i++;
|
||||
}
|
||||
return array
|
||||
}
|
||||
|
||||
private String swapEndianHex(String hex) {
|
||||
reverseArray(hex.decodeHex()).encodeHex()
|
||||
}
|
||||
|
||||
def configure() {
|
||||
log.debug "CONFIGURE"
|
||||
log.debug "zigbeeId: ${device.hub.zigbeeId}"
|
||||
|
||||
// get ZigBee ID by hidden tile because that's the only way we can do it
|
||||
setZigBeeIdTile()
|
||||
|
||||
def configCmds = [
|
||||
// bind reporting clusters to hub
|
||||
// binding commands
|
||||
"zdo bind 0x${device.deviceNetworkId} 1 1 0x0006 {${device.zigbeeId}} {}", "delay 500",
|
||||
"zdo bind 0x${device.deviceNetworkId} 1 1 0x0008 {${device.zigbeeId}} {}", "delay 500",
|
||||
"zdo bind 0x${device.deviceNetworkId} 1 1 0x0402 {${device.zigbeeId}} {}", "delay 500",
|
||||
"zdo bind 0x${device.deviceNetworkId} 1 1 0x0403 {${device.zigbeeId}} {}", "delay 500",
|
||||
"zdo bind 0x${device.deviceNetworkId} 1 1 0x0001 {${device.zigbeeId}} {}", "delay 500"
|
||||
|
||||
"zdo bind 0x${device.deviceNetworkId} 1 1 0x0001 {${device.zigbeeId}} {}", "delay 500",
|
||||
|
||||
// configure report commands
|
||||
// zcl global send-me-a-report [cluster] [attr] [type] [min-interval] [max-interval] [min-change]
|
||||
// [cluster] [attr] [type] [min-interval] [max-interval] [min-change]
|
||||
|
||||
// report with these parameters is preconfigured in firmware, can be overridden here
|
||||
// mike 2015/06/22: preconfigured; see tech spec
|
||||
// vent on/off state - type: boolean, change: 1
|
||||
// "zcl global send-me-a-report 6 0 0x10 5 60 {01}", "delay 200",
|
||||
// "send 0x${device.deviceNetworkId} 1 1", "delay 1500",
|
||||
|
||||
// report with these parameters is preconfigured in firmware, can be overridden here
|
||||
// mike 2015/06/22: preconfigured; see tech spec
|
||||
// vent level - type: int8u, change: 1
|
||||
// "zcl global send-me-a-report 8 0 0x20 5 60 {01}", "delay 200",
|
||||
// "send 0x${device.deviceNetworkId} 1 1", "delay 1500",
|
||||
|
||||
// report with these parameters is preconfigured in firmware, can be overridden here
|
||||
// mike 2015/06/22: temp and pressure reports are preconfigured, but
|
||||
// we'd like to override their settings for our own purposes
|
||||
// temperature - type: int16s, change: 0xA = 10 = 0.1C
|
||||
// "zcl global send-me-a-report 0x0402 0 0x29 60 60 {0A00}", "delay 200",
|
||||
// "send 0x${device.deviceNetworkId} 1 1", "delay 1500",
|
||||
"zcl global send-me-a-report 0x0402 0 0x29 10 60 {0A00}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 1500",
|
||||
|
||||
// report with these parameters is preconfigured in firmware, can be overridden here
|
||||
// keen home custom pressure (tenths of Pascals) - type: int32u, change: 1 = 0.1Pa
|
||||
// "zcl mfg-code 0x115B", "delay 200",
|
||||
// "zcl global send-me-a-report 0x0403 0x20 0x22 60 60 {010000}", "delay 200",
|
||||
// "send 0x${device.deviceNetworkId} 1 1", "delay 1500",
|
||||
// mike 2015/06/22: use new custom pressure attribute
|
||||
// pressure - type: int32u, change: 1 = 0.1Pa
|
||||
"zcl mfg-code 0x115B", "delay 200",
|
||||
"zcl global send-me-a-report 0x0403 0x20 0x22 10 60 {010000}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 1500"
|
||||
|
||||
// report with these parameters is preconfigured in firmware, can be overridden here
|
||||
// mike 2015/06/22: preconfigured; see tech spec
|
||||
// battery - type: int8u, change: 1
|
||||
// "zcl global send-me-a-report 1 0x21 0x20 60 3600 {01}", "delay 200",
|
||||
// "send 0x${device.deviceNetworkId} 1 1", "delay 1500",
|
||||
|
||||
@@ -24,8 +24,6 @@ metadata {
|
||||
capability "Battery"
|
||||
|
||||
attribute "tamper", "enum", ["detected", "clear"]
|
||||
attribute "batteryStatus", "string"
|
||||
attribute "powerSupply", "enum", ["USB Cable", "Battery"]
|
||||
|
||||
fingerprint deviceId: "0x2101", inClusters: "0x5E,0x86,0x72,0x59,0x85,0x73,0x71,0x84,0x80,0x30,0x31,0x70,0x7A", outClusters: "0x5A"
|
||||
}
|
||||
@@ -65,19 +63,6 @@ metadata {
|
||||
status "wake up" : "command: 8407, payload: "
|
||||
}
|
||||
|
||||
preferences {
|
||||
input description: "Please consult AEOTEC MULTISENSOR 6 operating manual for advanced setting options. You can skip this configuration to use default settings",
|
||||
title: "Advanced Configuration", displayDuringSetup: true, type: "paragraph", element: "paragraph"
|
||||
|
||||
input "motionDelayTime", "enum", title: "Motion Sensor Delay Time",
|
||||
options: ["20 seconds", "40 seconds", "1 minute", "2 minutes", "3 minutes", "4 minutes"], defaultValue: "${motionDelayTime}", displayDuringSetup: true
|
||||
|
||||
input "motionSensitivity", "enum", title: "Motion Sensor Sensitivity", options: ["normal","maximum","minimum"], defaultValue: "${motionSensitivity}", displayDuringSetup: true
|
||||
|
||||
input "reportInterval", "enum", title: "Sensors Report Interval",
|
||||
options: ["8 minutes", "15 minutes", "30 minutes", "1 hour", "6 hours", "12 hours", "18 hours", "24 hours"], defaultValue: "${reportInterval}", displayDuringSetup: true
|
||||
}
|
||||
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"motion", type: "generic", width: 6, height: 4){
|
||||
tileAttribute ("device.motion", key: "PRIMARY_CONTROL") {
|
||||
@@ -100,78 +85,53 @@ metadata {
|
||||
valueTile("humidity", "device.humidity", inactiveLabel: false, width: 2, height: 2) {
|
||||
state "humidity", label:'${currentValue}% humidity', unit:""
|
||||
}
|
||||
|
||||
valueTile("illuminance", "device.illuminance", inactiveLabel: false, width: 2, height: 2) {
|
||||
state "illuminance", label:'${currentValue} ${unit}', unit:"lux"
|
||||
state "luminosity", label:'${currentValue} ${unit}', unit:"lux"
|
||||
}
|
||||
|
||||
valueTile("ultravioletIndex", "device.ultravioletIndex", inactiveLabel: false, width: 2, height: 2) {
|
||||
state "ultravioletIndex", label:'${currentValue} UV index', unit:""
|
||||
}
|
||||
|
||||
valueTile("battery", "device.battery", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "battery", label:'${currentValue}% battery', unit:""
|
||||
}
|
||||
|
||||
valueTile("batteryStatus", "device.batteryStatus", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "batteryStatus", label:'${currentValue}', unit:""
|
||||
}
|
||||
|
||||
valueTile("powerSupply", "device.powerSupply", height: 2, width: 2, decoration: "flat") {
|
||||
state "powerSupply", label:'${currentValue} powered', backgroundColor:"#ffffff"
|
||||
}
|
||||
|
||||
main(["motion", "temperature", "humidity", "illuminance", "ultravioletIndex"])
|
||||
details(["motion", "temperature", "humidity", "illuminance", "ultravioletIndex", "batteryStatus"])
|
||||
main(["motion", "temperature", "humidity", "illuminance"])
|
||||
details(["motion", "temperature", "humidity", "illuminance", "battery"])
|
||||
}
|
||||
}
|
||||
|
||||
def updated() {
|
||||
log.debug "Updated with settings: ${settings}"
|
||||
log.debug "${device.displayName} is now ${device.latestValue("powerSupply")}"
|
||||
|
||||
if (device.latestValue("powerSupply") == "USB Cable") { //case1: USB powered
|
||||
def updated()
|
||||
{
|
||||
if (state.sec && !isConfigured()) {
|
||||
// in case we miss the SCSR
|
||||
response(configure())
|
||||
} else if (device.latestValue("powerSupply") == "Battery") { //case2: battery powered
|
||||
// setConfigured("false") is used by WakeUpNotification
|
||||
setConfigured("false") //wait until the next time device wakeup to send configure command after user change preference
|
||||
} else { //case3: power source is not identified, ask user to properly pair the sensor again
|
||||
log.warn "power source is not identified, check it sensor is powered by USB, if so > configure()"
|
||||
def request = []
|
||||
request << zwave.configurationV1.configurationGet(parameterNumber: 101)
|
||||
response(commands(request))
|
||||
}
|
||||
}
|
||||
|
||||
def parse(String description) {
|
||||
log.debug "parse() >> description: $description"
|
||||
def parse(String description)
|
||||
{
|
||||
def result = null
|
||||
if (description.startsWith("Err 106")) {
|
||||
log.debug "parse() >> Err 106"
|
||||
state.sec = 0
|
||||
result = createEvent( name: "secureInclusion", value: "failed", isStateChange: true,
|
||||
descriptionText: "This sensor failed to complete the network security key exchange. If you are unable to control it via SmartThings, you must remove it from your network and add it again.")
|
||||
descriptionText: "This sensor failed to complete the network security key exchange. If you are unable to control it via SmartThings, you must remove it from your network and add it again.")
|
||||
} else if (description != "updated") {
|
||||
log.debug "parse() >> zwave.parse(description)"
|
||||
def cmd = zwave.parse(description, [0x31: 5, 0x30: 2, 0x84: 1])
|
||||
if (cmd) {
|
||||
result = zwaveEvent(cmd)
|
||||
}
|
||||
}
|
||||
log.debug "After zwaveEvent(cmd) >> Parsed '${description}' to ${result.inspect()}"
|
||||
log.debug "Parsed '${description}' to ${result.inspect()}"
|
||||
return result
|
||||
}
|
||||
|
||||
//this notification will be sent only when device is battery powered
|
||||
def zwaveEvent(physicalgraph.zwave.commands.wakeupv1.WakeUpNotification cmd) {
|
||||
def zwaveEvent(physicalgraph.zwave.commands.wakeupv1.WakeUpNotification cmd)
|
||||
{
|
||||
def result = [createEvent(descriptionText: "${device.displayName} woke up", isStateChange: false)]
|
||||
def cmds = []
|
||||
|
||||
if (!isConfigured()) {
|
||||
// we're still in the process of configuring a newly joined device
|
||||
log.debug("late configure")
|
||||
result << response(configure())
|
||||
result += response(configure())
|
||||
} else {
|
||||
log.debug("Device has been configured sending >> wakeUpNoMoreInformation()")
|
||||
cmds << zwave.wakeUpV1.wakeUpNoMoreInformation().format()
|
||||
result << response(cmds)
|
||||
result += response(zwave.wakeUpV1.wakeUpNoMoreInformation())
|
||||
}
|
||||
result
|
||||
}
|
||||
@@ -189,29 +149,10 @@ def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulat
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityCommandsSupportedReport cmd) {
|
||||
log.info "Executing zwaveEvent 98 (SecurityV1): 03 (SecurityCommandsSupportedReport) with cmd: $cmd"
|
||||
state.sec = 1
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.securityv1.NetworkKeyVerify cmd) {
|
||||
state.sec = 1
|
||||
log.info "Executing zwaveEvent 98 (SecurityV1): 07 (NetworkKeyVerify) with cmd: $cmd (node is securely included)"
|
||||
def result = [createEvent(name:"secureInclusion", value:"success", descriptionText:"Secure inclusion was successful", isStateChange: true)]
|
||||
result
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
|
||||
log.info "Executing zwaveEvent 72 (ManufacturerSpecificV2) : 05 (ManufacturerSpecificReport) with cmd: $cmd"
|
||||
log.debug "manufacturerId: ${cmd.manufacturerId}"
|
||||
log.debug "manufacturerName: ${cmd.manufacturerName}"
|
||||
log.debug "productId: ${cmd.productId}"
|
||||
log.debug "productTypeId: ${cmd.productTypeId}"
|
||||
def msr = String.format("%04X-%04X-%04X", cmd.manufacturerId, cmd.productTypeId, cmd.productId)
|
||||
updateDataValue("MSR", msr)
|
||||
response(configure())
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) {
|
||||
def result = []
|
||||
def map = [ name: "battery", unit: "%" ]
|
||||
if (cmd.batteryLevel == 0xFF) {
|
||||
map.value = 1
|
||||
@@ -221,14 +162,11 @@ def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) {
|
||||
map.value = cmd.batteryLevel
|
||||
}
|
||||
state.lastbatt = now()
|
||||
result << createEvent(map)
|
||||
if (device.latestValue("powerSupply") != "USB Cable"){
|
||||
result << createEvent(name: "batteryStatus", value: "${map.value} % battery", displayed: false)
|
||||
}
|
||||
result
|
||||
createEvent(map)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv5.SensorMultilevelReport cmd){
|
||||
def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv5.SensorMultilevelReport cmd)
|
||||
{
|
||||
def map = [:]
|
||||
switch (cmd.sensorType) {
|
||||
case 1:
|
||||
@@ -270,6 +208,7 @@ def motionEvent(value) {
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.sensorbinaryv2.SensorBinaryReport cmd) {
|
||||
setConfigured()
|
||||
motionEvent(cmd.sensorValue)
|
||||
}
|
||||
|
||||
@@ -286,112 +225,47 @@ def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cm
|
||||
result << createEvent(name: "tamper", value: "clear", displayed: false)
|
||||
break
|
||||
case 3:
|
||||
result << createEvent(name: "tamper", value: "detected", descriptionText: "$device.displayName was tampered")
|
||||
result << createEvent(name: "tamper", value: "detected", descriptionText: "$device.displayName was moved")
|
||||
break
|
||||
case 7:
|
||||
result << motionEvent(1)
|
||||
break
|
||||
}
|
||||
} else {
|
||||
log.warn "Need to handle this cmd.notificationType: ${cmd.notificationType}"
|
||||
result << createEvent(descriptionText: cmd.toString(), isStateChange: false)
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.configurationv2.ConfigurationReport cmd) {
|
||||
log.debug "ConfigurationReport: $cmd"
|
||||
def result = []
|
||||
def value
|
||||
if (cmd.parameterNumber == 9 && cmd.configurationValue[0] == 0) {
|
||||
value = "USB Cable"
|
||||
if (!isConfigured()) {
|
||||
log.debug("ConfigurationReport: configuring device")
|
||||
result << response(configure())
|
||||
}
|
||||
result << createEvent(name: "batteryStatus", value: value, displayed: false)
|
||||
result << createEvent(name: "powerSupply", value: value, displayed: false)
|
||||
}else if (cmd.parameterNumber == 9 && cmd.configurationValue[0] == 1) {
|
||||
value = "Battery"
|
||||
result << createEvent(name: "powerSupply", value: value, displayed: false)
|
||||
} else if (cmd.parameterNumber == 101){
|
||||
result << response(configure())
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.Command cmd) {
|
||||
log.debug "General zwaveEvent cmd: ${cmd}"
|
||||
createEvent(descriptionText: cmd.toString(), isStateChange: false)
|
||||
}
|
||||
|
||||
def configure() {
|
||||
// This sensor joins as a secure device if you double-click the button to include it
|
||||
log.debug "${device.displayName} is configuring its settings"
|
||||
def request = []
|
||||
if (device.device.rawDescription =~ /98/ && !state.sec) {
|
||||
log.debug "Multi 6 not sending configure until secure"
|
||||
return []
|
||||
}
|
||||
log.debug "Multi 6 configure()"
|
||||
def request = [
|
||||
// send no-motion report 20 seconds after motion stops
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 3, size: 2, scaledConfigurationValue: 20),
|
||||
|
||||
//1. set association groups for hub
|
||||
request << zwave.associationV1.associationSet(groupingIdentifier:1, nodeId:zwaveHubNodeId)
|
||||
// report every 8 minutes (threshold reports don't work on battery power)
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 111, size: 4, scaledConfigurationValue: 8*60),
|
||||
|
||||
request << zwave.associationV1.associationSet(groupingIdentifier:2, nodeId:zwaveHubNodeId)
|
||||
|
||||
//2. automatic report flags
|
||||
// param 101 -103 [4 bytes] 128: light sensor, 64 humidity, 32 temperature sensor, 2 ultraviolet sensor, 1 battery sensor -> send command 227 to get all reports
|
||||
request << zwave.configurationV1.configurationSet(parameterNumber: 101, size: 4, scaledConfigurationValue: 226) //association group 1
|
||||
|
||||
request << zwave.configurationV1.configurationSet(parameterNumber: 102, size: 4, scaledConfigurationValue: 1) //association group 2
|
||||
|
||||
//3. no-motion report x seconds after motion stops (default 20 secs)
|
||||
request << zwave.configurationV1.configurationSet(parameterNumber: 3, size: 2, scaledConfigurationValue: timeOptionValueMap[motionDelayTime] ?: 20)
|
||||
|
||||
//4. motionSensitivity 3 levels: 64-normal (default), 127-maximum, 0-minimum
|
||||
request << zwave.configurationV1.configurationSet(parameterNumber: 6, size: 1,
|
||||
scaledConfigurationValue:
|
||||
motionSensitivity == "normal" ? 64 :
|
||||
motionSensitivity == "maximum" ? 127 :
|
||||
motionSensitivity == "minimum" ? 0 : 64)
|
||||
|
||||
//5. report every x minutes (threshold reports don't work on battery power, default 8 mins)
|
||||
request << zwave.configurationV1.configurationSet(parameterNumber: 111, size: 4, scaledConfigurationValue: timeOptionValueMap[reportInterval] ?: 8*60) //association group 1
|
||||
|
||||
request << zwave.configurationV1.configurationSet(parameterNumber: 112, size: 4, scaledConfigurationValue: 6*60*60) //association group 2
|
||||
|
||||
//6. report automatically on threshold change
|
||||
request << zwave.configurationV1.configurationSet(parameterNumber: 40, size: 1, scaledConfigurationValue: 1)
|
||||
|
||||
//7. query sensor data
|
||||
request << zwave.batteryV1.batteryGet()
|
||||
request << zwave.sensorBinaryV2.sensorBinaryGet(sensorType: 0x0C) //motion
|
||||
request << zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 0x01) //temperature
|
||||
request << zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 0x03) //illuminance
|
||||
request << zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 0x05) //humidity
|
||||
request << zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 0x1B) //ultravioletIndex
|
||||
|
||||
setConfigured("true")
|
||||
// report automatically on threshold change
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 40, size: 1, scaledConfigurationValue: 1),
|
||||
|
||||
zwave.batteryV1.batteryGet(),
|
||||
zwave.sensorBinaryV2.sensorBinaryGet(sensorType: 0x0C),
|
||||
]
|
||||
commands(request) + ["delay 20000", zwave.wakeUpV1.wakeUpNoMoreInformation().format()]
|
||||
}
|
||||
|
||||
private def getTimeOptionValueMap() { [
|
||||
"20 seconds" : 20,
|
||||
"40 seconds" : 40,
|
||||
"1 minute" : 60,
|
||||
"2 minutes" : 2*60,
|
||||
"3 minutes" : 3*60,
|
||||
"4 minutes" : 4*60,
|
||||
"5 minutes" : 5*60,
|
||||
"8 minutes" : 8*60,
|
||||
"15 minutes" : 15*60,
|
||||
"30 minutes" : 30*60,
|
||||
"1 hours" : 1*60*60,
|
||||
"6 hours" : 6*60*60,
|
||||
"12 hours" : 12*60*60,
|
||||
"18 hours" : 6*60*60,
|
||||
"24 hours" : 24*60*60,
|
||||
]}
|
||||
|
||||
private setConfigured(configure) {
|
||||
updateDataValue("configured", configure)
|
||||
private setConfigured() {
|
||||
updateDataValue("configured", "true")
|
||||
}
|
||||
|
||||
private isConfigured() {
|
||||
@@ -407,6 +281,5 @@ private command(physicalgraph.zwave.Command cmd) {
|
||||
}
|
||||
|
||||
private commands(commands, delay=200) {
|
||||
log.info "sending commands: ${commands}"
|
||||
delayBetween(commands.collect{ command(it) }, delay)
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ metadata {
|
||||
tiles {
|
||||
standardTile("presence", "device.presence", width: 2, height: 2, canChangeBackground: true) {
|
||||
state "present", labelIcon:"st.presence.tile.present", backgroundColor:"#53a7c0"
|
||||
state "not present", labelIcon:"st.presence.tile.not-present", backgroundColor:"#ebeef2"
|
||||
state "not present", labelIcon:"st.presence.tile.not-present", backgroundColor:"#ffffff"
|
||||
}
|
||||
standardTile("beep", "device.beep", decoration: "flat") {
|
||||
state "beep", label:'', action:"tone.beep", icon:"st.secondary.beep", backgroundColor:"#ffffff"
|
||||
|
||||
@@ -38,63 +38,168 @@ metadata {
|
||||
}
|
||||
|
||||
// UI tile definitions
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
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 "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
}
|
||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
||||
attributeState "level", action:"switch level.setLevel"
|
||||
}
|
||||
}
|
||||
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
main "switch"
|
||||
details(["switch", "refresh"])
|
||||
}
|
||||
tiles {
|
||||
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
|
||||
state "off", label: '${name}', action: "switch.on", icon: "st.switches.light.off", backgroundColor: "#ffffff"
|
||||
state "on", label: '${name}', action: "switch.off", icon: "st.switches.light.on", backgroundColor: "#79b821"
|
||||
}
|
||||
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
|
||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 3, inactiveLabel: false) {
|
||||
state "level", action:"switch level.setLevel"
|
||||
}
|
||||
valueTile("level", "device.level", inactiveLabel: false, decoration: "flat") {
|
||||
state "level", label: 'Level ${currentValue}%'
|
||||
}
|
||||
|
||||
|
||||
main(["switch"])
|
||||
details(["switch", "level", "levelSliderControl", "refresh"])
|
||||
}
|
||||
}
|
||||
|
||||
// Parse incoming device messages to generate events
|
||||
|
||||
def parse(String description) {
|
||||
log.debug "description is $description"
|
||||
|
||||
def resultMap = zigbee.getKnownDescription(description)
|
||||
if (resultMap) {
|
||||
log.info resultMap
|
||||
if (resultMap.type == "update") {
|
||||
log.info "$device updates: ${resultMap.value}"
|
||||
log.trace description
|
||||
if (description?.startsWith("catchall:")) {
|
||||
def msg = zigbee.parse(description)
|
||||
log.trace msg
|
||||
log.trace "data: $msg.data"
|
||||
|
||||
if(description?.endsWith("0100") ||description?.endsWith("1001"))
|
||||
{
|
||||
def result = createEvent(name: "switch", value: "on")
|
||||
log.debug "Parse returned ${result?.descriptionText}"
|
||||
return result
|
||||
}
|
||||
else {
|
||||
sendEvent(name: resultMap.type, value: resultMap.value)
|
||||
|
||||
if(description?.endsWith("0000") || description?.endsWith("1000"))
|
||||
{
|
||||
def result = createEvent(name: "switch", value: "off")
|
||||
log.debug "Parse returned ${result?.descriptionText}"
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (description?.startsWith("read attr")) {
|
||||
|
||||
log.debug description[-2..-1]
|
||||
def i = Math.round(convertHexToInt(description[-2..-1]) / 256 * 100 )
|
||||
|
||||
sendEvent( name: "level", value: i )
|
||||
}
|
||||
else {
|
||||
log.warn "DID NOT PARSE MESSAGE for description : $description"
|
||||
log.debug zigbee.parseDescriptionAsMap(description)
|
||||
}
|
||||
}
|
||||
|
||||
def off() {
|
||||
zigbee.off()
|
||||
|
||||
|
||||
}
|
||||
|
||||
def on() {
|
||||
zigbee.on()
|
||||
}
|
||||
log.debug "on()"
|
||||
sendEvent(name: "switch", value: "on")
|
||||
|
||||
"st cmd 0x${device.deviceNetworkId} ${endpointId} 6 1 {}"
|
||||
}
|
||||
|
||||
def setLevel(value) {
|
||||
zigbee.setLevel(value)
|
||||
def off() {
|
||||
log.debug "off()"
|
||||
sendEvent(name: "switch", value: "off")
|
||||
|
||||
"st cmd 0x${device.deviceNetworkId} ${endpointId} 6 0 {}"
|
||||
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig() + zigbee.levelConfig()
|
||||
// Schedule poll every 1 min
|
||||
//schedule("0 */1 * * * ?", poll)
|
||||
//poll()
|
||||
|
||||
[
|
||||
"st rattr 0x${device.deviceNetworkId} ${endpointId} 6 0", "delay 500",
|
||||
"st rattr 0x${device.deviceNetworkId} ${endpointId} 8 0"
|
||||
]
|
||||
}
|
||||
|
||||
def setLevel(value) {
|
||||
log.trace "setLevel($value)"
|
||||
def cmds = []
|
||||
|
||||
if (value == 0) {
|
||||
sendEvent(name: "switch", value: "off")
|
||||
cmds << "st cmd 0x${device.deviceNetworkId} ${endpointId} 6 0 {0000 0000}"
|
||||
}
|
||||
else if (device.latestValue("switch") == "off") {
|
||||
sendEvent(name: "switch", value: "on")
|
||||
}
|
||||
|
||||
sendEvent(name: "level", value: value)
|
||||
def level = hex(value * 255/100)
|
||||
cmds << "st cmd 0x${device.deviceNetworkId} ${endpointId} 8 4 {${level} 0000}"
|
||||
|
||||
//log.debug cmds
|
||||
cmds
|
||||
}
|
||||
|
||||
def configure() {
|
||||
log.debug "Configuring Reporting and Bindings."
|
||||
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh()
|
||||
|
||||
log.debug "Configuring Reporting and Bindings."
|
||||
def configCmds = [
|
||||
|
||||
//Switch Reporting
|
||||
"zcl global send-me-a-report 6 0 0x10 0 3600 {01}", "delay 500",
|
||||
"send 0x${device.deviceNetworkId} ${endpointId} 1", "delay 1000",
|
||||
|
||||
//Level Control Reporting
|
||||
"zcl global send-me-a-report 8 0 0x20 5 3600 {0010}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} ${endpointId} 1", "delay 1500",
|
||||
|
||||
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 6 {${device.zigbeeId}} {}", "delay 1000",
|
||||
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 8 {${device.zigbeeId}} {}", "delay 500",
|
||||
]
|
||||
return configCmds + refresh() // send refresh cmds as part of config
|
||||
}
|
||||
|
||||
def uninstalled() {
|
||||
|
||||
log.debug "uninstalled()"
|
||||
|
||||
response("zcl rftd")
|
||||
|
||||
}
|
||||
|
||||
private getEndpointId() {
|
||||
new BigInteger(device.endpointId, 16).toString()
|
||||
}
|
||||
|
||||
|
||||
|
||||
private hex(value, width=2) {
|
||||
def s = new BigInteger(Math.round(value).toString()).toString(16)
|
||||
while (s.size() < width) {
|
||||
s = "0" + s
|
||||
}
|
||||
s
|
||||
}
|
||||
|
||||
private Integer convertHexToInt(hex) {
|
||||
Integer.parseInt(hex,16)
|
||||
}
|
||||
|
||||
private String swapEndianHex(String hex) {
|
||||
reverseArray(hex.decodeHex()).encodeHex()
|
||||
}
|
||||
|
||||
private byte[] reverseArray(byte[] array) {
|
||||
int i = 0;
|
||||
int j = array.length - 1;
|
||||
byte tmp;
|
||||
while (j > i) {
|
||||
tmp = array[j];
|
||||
array[j] = array[i];
|
||||
array[i] = tmp;
|
||||
j--;
|
||||
i++;
|
||||
}
|
||||
return array
|
||||
}
|
||||
|
||||
@@ -55,136 +55,141 @@ metadata {
|
||||
}
|
||||
}
|
||||
|
||||
standardTile("indicator", "device.indicatorStatus", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
||||
standardTile("indicator", "device.indicatorStatus", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
|
||||
state "when off", action:"indicator.indicatorWhenOn", icon:"st.indicators.lit-when-off"
|
||||
state "when on", action:"indicator.indicatorNever", icon:"st.indicators.lit-when-on"
|
||||
state "never", action:"indicator.indicatorWhenOff", icon:"st.indicators.never-lit"
|
||||
}
|
||||
|
||||
standardTile("refresh", "device.switch", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
||||
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
|
||||
valueTile("level", "device.level", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "level", label:'${currentValue} %', unit:"%", backgroundColor:"#ffffff"
|
||||
standardTile("refresh", "device.switch", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
|
||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
|
||||
main(["switch"])
|
||||
details(["switch", "level", "indicator", "refresh"])
|
||||
|
||||
details(["switch", "refresh", "indicator"])
|
||||
}
|
||||
}
|
||||
|
||||
def parse(String description) {
|
||||
def result = null
|
||||
if (description != "updated") {
|
||||
log.debug "parse() >> zwave.parse($description)"
|
||||
def cmd = zwave.parse(description, [0x20: 1, 0x26: 1, 0x70: 1])
|
||||
if (cmd) {
|
||||
result = zwaveEvent(cmd)
|
||||
}
|
||||
def item1 = [
|
||||
canBeCurrentState: false,
|
||||
linkText: getLinkText(device),
|
||||
isStateChange: false,
|
||||
displayed: false,
|
||||
descriptionText: description,
|
||||
value: description
|
||||
]
|
||||
def result
|
||||
def cmd = zwave.parse(description, [0x20: 1, 0x26: 1, 0x70: 1])
|
||||
if (cmd) {
|
||||
result = createEvent(cmd, item1)
|
||||
}
|
||||
if (result?.name == 'hail' && hubFirmwareLessThan("000.011.00602")) {
|
||||
result = [result, response(zwave.basicV1.basicGet())]
|
||||
log.debug "Was hailed: requesting state update"
|
||||
} else {
|
||||
log.debug "Parse returned ${result?.descriptionText}"
|
||||
else {
|
||||
item1.displayed = displayed(description, item1.isStateChange)
|
||||
result = [item1]
|
||||
}
|
||||
return result
|
||||
log.debug "Parse returned ${result?.descriptionText}"
|
||||
result
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) {
|
||||
dimmerEvents(cmd)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd) {
|
||||
dimmerEvents(cmd)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelReport cmd) {
|
||||
dimmerEvents(cmd)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelSet cmd) {
|
||||
dimmerEvents(cmd)
|
||||
}
|
||||
|
||||
private dimmerEvents(physicalgraph.zwave.Command cmd) {
|
||||
def value = (cmd.value ? "on" : "off")
|
||||
def result = [createEvent(name: "switch", value: value)]
|
||||
if (cmd.value && cmd.value <= 100) {
|
||||
result << createEvent(name: "level", value: cmd.value, unit: "%")
|
||||
def createEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd, Map item1) {
|
||||
def result = doCreateEvent(cmd, item1)
|
||||
for (int i = 0; i < result.size(); i++) {
|
||||
result[i].type = "physical"
|
||||
}
|
||||
return result
|
||||
result
|
||||
}
|
||||
|
||||
def createEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd, Map item1) {
|
||||
def result = doCreateEvent(cmd, item1)
|
||||
for (int i = 0; i < result.size(); i++) {
|
||||
result[i].type = "physical"
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
def createEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelStartLevelChange cmd, Map item1) {
|
||||
[]
|
||||
}
|
||||
|
||||
def createEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelStopLevelChange cmd, Map item1) {
|
||||
[response(zwave.basicV1.basicGet())]
|
||||
}
|
||||
|
||||
def createEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelSet cmd, Map item1) {
|
||||
def result = doCreateEvent(cmd, item1)
|
||||
for (int i = 0; i < result.size(); i++) {
|
||||
result[i].type = "physical"
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
def createEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelReport cmd, Map item1) {
|
||||
def result = doCreateEvent(cmd, item1)
|
||||
result[0].descriptionText = "${item1.linkText} is ${item1.value}"
|
||||
result[0].handlerName = cmd.value ? "statusOn" : "statusOff"
|
||||
for (int i = 0; i < result.size(); i++) {
|
||||
result[i].type = "digital"
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
def doCreateEvent(physicalgraph.zwave.Command cmd, Map item1) {
|
||||
def result = [item1]
|
||||
|
||||
item1.name = "switch"
|
||||
item1.value = cmd.value ? "on" : "off"
|
||||
item1.handlerName = item1.value
|
||||
item1.descriptionText = "${item1.linkText} was turned ${item1.value}"
|
||||
item1.canBeCurrentState = true
|
||||
item1.isStateChange = isStateChange(device, item1.name, item1.value)
|
||||
item1.displayed = item1.isStateChange
|
||||
|
||||
if (cmd.value >= 5) {
|
||||
def item2 = new LinkedHashMap(item1)
|
||||
item2.name = "level"
|
||||
item2.value = cmd.value as String
|
||||
item2.unit = "%"
|
||||
item2.descriptionText = "${item1.linkText} dimmed ${item2.value} %"
|
||||
item2.canBeCurrentState = true
|
||||
item2.isStateChange = isStateChange(device, item2.name, item2.value)
|
||||
item2.displayed = false
|
||||
result << item2
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.configurationv1.ConfigurationReport cmd) {
|
||||
log.debug "ConfigurationReport $cmd"
|
||||
def value = "when off"
|
||||
if (cmd.configurationValue[0] == 1) {value = "when on"}
|
||||
if (cmd.configurationValue[0] == 2) {value = "never"}
|
||||
createEvent([name: "indicatorStatus", value: value])
|
||||
[name: "indicatorStatus", value: value, display: false]
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.hailv1.Hail cmd) {
|
||||
createEvent([name: "hail", value: "hail", descriptionText: "Switch button was pressed", displayed: false])
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
|
||||
log.debug "manufacturerId: ${cmd.manufacturerId}"
|
||||
log.debug "manufacturerName: ${cmd.manufacturerName}"
|
||||
log.debug "productId: ${cmd.productId}"
|
||||
log.debug "productTypeId: ${cmd.productTypeId}"
|
||||
def msr = String.format("%04X-%04X-%04X", cmd.manufacturerId, cmd.productTypeId, cmd.productId)
|
||||
updateDataValue("MSR", msr)
|
||||
createEvent([descriptionText: "$device.displayName MSR: $msr", isStateChange: false])
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelStopLevelChange cmd) {
|
||||
[createEvent(name:"switch", value:"on"), response(zwave.switchMultilevelV1.switchMultilevelGet().format())]
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.Command cmd) {
|
||||
// Handles all Z-Wave commands we aren't interested in
|
||||
[:]
|
||||
def createEvent(physicalgraph.zwave.Command cmd, Map map) {
|
||||
// Handles any Z-Wave commands we aren't interested in
|
||||
log.debug "UNHANDLED COMMAND $cmd"
|
||||
}
|
||||
|
||||
def on() {
|
||||
delayBetween([
|
||||
zwave.basicV1.basicSet(value: 0xFF).format(),
|
||||
zwave.switchMultilevelV1.switchMultilevelGet().format()
|
||||
],5000)
|
||||
log.info "on"
|
||||
delayBetween([zwave.basicV1.basicSet(value: 0xFF).format(), zwave.switchMultilevelV1.switchMultilevelGet().format()], 5000)
|
||||
}
|
||||
|
||||
def off() {
|
||||
delayBetween([
|
||||
zwave.basicV1.basicSet(value: 0x00).format(),
|
||||
zwave.switchMultilevelV1.switchMultilevelGet().format()
|
||||
],5000)
|
||||
delayBetween ([zwave.basicV1.basicSet(value: 0x00).format(), zwave.switchMultilevelV1.switchMultilevelGet().format()], 5000)
|
||||
}
|
||||
|
||||
def setLevel(value) {
|
||||
log.debug "setLevel >> value: $value"
|
||||
def valueaux = value as Integer
|
||||
def level = Math.max(Math.min(valueaux, 99), 0)
|
||||
if (level > 0) {
|
||||
sendEvent(name: "switch", value: "on")
|
||||
} else {
|
||||
sendEvent(name: "switch", value: "off")
|
||||
}
|
||||
sendEvent(name: "level", value: level, unit: "%")
|
||||
def level = Math.min(valueaux, 99)
|
||||
delayBetween ([zwave.basicV1.basicSet(value: level).format(), zwave.switchMultilevelV1.switchMultilevelGet().format()], 5000)
|
||||
}
|
||||
|
||||
def setLevel(value, duration) {
|
||||
log.debug "setLevel >> value: $value, duration: $duration"
|
||||
def valueaux = value as Integer
|
||||
def level = Math.max(Math.min(valueaux, 99), 0)
|
||||
def level = Math.min(valueaux, 99)
|
||||
def dimmingDuration = duration < 128 ? duration : 128 + Math.round(duration / 60)
|
||||
def getStatusDelay = duration < 128 ? (duration*1000)+2000 : (Math.round(duration / 60)*60*1000)+2000
|
||||
delayBetween ([zwave.switchMultilevelV2.switchMultilevelSet(value: level, dimmingDuration: dimmingDuration).format(),
|
||||
zwave.switchMultilevelV1.switchMultilevelGet().format()], getStatusDelay)
|
||||
zwave.switchMultilevelV2.switchMultilevelSet(value: level, dimmingDuration: dimmingDuration).format()
|
||||
}
|
||||
|
||||
def poll() {
|
||||
@@ -192,27 +197,21 @@ def poll() {
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
log.debug "refresh() is called"
|
||||
def commands = []
|
||||
commands << zwave.switchMultilevelV1.switchMultilevelGet().format()
|
||||
if (getDataValue("MSR") == null) {
|
||||
commands << zwave.manufacturerSpecificV1.manufacturerSpecificGet().format()
|
||||
}
|
||||
delayBetween(commands,100)
|
||||
zwave.switchMultilevelV1.switchMultilevelGet().format()
|
||||
}
|
||||
|
||||
def indicatorWhenOn() {
|
||||
sendEvent(name: "indicatorStatus", value: "when on")
|
||||
sendEvent(name: "indicatorStatus", value: "when on", display: false)
|
||||
zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 3, size: 1).format()
|
||||
}
|
||||
|
||||
def indicatorWhenOff() {
|
||||
sendEvent(name: "indicatorStatus", value: "when off")
|
||||
sendEvent(name: "indicatorStatus", value: "when off", display: false)
|
||||
zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 3, size: 1).format()
|
||||
}
|
||||
|
||||
def indicatorNever() {
|
||||
sendEvent(name: "indicatorStatus", value: "never")
|
||||
sendEvent(name: "indicatorStatus", value: "never", display: false)
|
||||
zwave.configurationV1.configurationSet(configurationValue: [2], parameterNumber: 3, size: 1).format()
|
||||
}
|
||||
|
||||
@@ -223,4 +222,4 @@ def invertSwitch(invert=true) {
|
||||
else {
|
||||
zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 4, size: 1).format()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,455 +0,0 @@
|
||||
/**
|
||||
* Copyright 2015 SmartThings
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* 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 Smoke Sensor", namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Battery" //attributes: battery
|
||||
capability "Configuration" //commands: configure()
|
||||
capability "Sensor"
|
||||
capability "Smoke Detector" //attributes: smoke ("detected","clear","tested")
|
||||
capability "Temperature Measurement" //attributes: temperature
|
||||
attribute "tamper", "enum", ["detected", "clear"]
|
||||
attribute "heatAlarm", "enum", ["overheat detected", "clear", "rapid temperature rise", "underheat detected"]
|
||||
fingerprint deviceId: "0x0701", inClusters: "0x5E, 0x86, 0x72, 0x5A, 0x59, 0x85, 0x73, 0x84, 0x80, 0x71, 0x56, 0x70, 0x31, 0x8E, 0x22, 0x9C, 0x98, 0x7A", outClusters: "0x20, 0x8B"
|
||||
}
|
||||
simulator {
|
||||
//battery
|
||||
for (int i in [0, 5, 10, 15, 50, 99, 100]) {
|
||||
status "battery ${i}%": new physicalgraph.zwave.Zwave().securityV1.securityMessageEncapsulation().encapsulate(
|
||||
new physicalgraph.zwave.Zwave().batteryV1.batteryReport(batteryLevel: i)
|
||||
).incomingMessage()
|
||||
}
|
||||
status "battery 100%": "command: 8003, payload: 64"
|
||||
status "battery 5%": "command: 8003, payload: 05"
|
||||
//smoke
|
||||
status "smoke detected": "command: 7105, payload: 01 01"
|
||||
status "smoke clear": "command: 7105, payload: 01 00"
|
||||
status "smoke tested": "command: 7105, payload: 01 03"
|
||||
//temperature
|
||||
for (int i = 0; i <= 100; i += 20) {
|
||||
status "temperature ${i}F": new physicalgraph.zwave.Zwave().securityV1.securityMessageEncapsulation().encapsulate(
|
||||
new physicalgraph.zwave.Zwave().sensorMultilevelV5.sensorMultilevelReport(scaledSensorValue: i, precision: 1, sensorType: 1, scale: 1)
|
||||
).incomingMessage()
|
||||
}
|
||||
}
|
||||
preferences {
|
||||
input description: "After successful installation, please click B-button at the Fibaro Smoke Sensor to update device status and configuration",
|
||||
title: "Instructions", displayDuringSetup: true, type: "paragraph", element: "paragraph"
|
||||
input description: "Enter the menu by press and hold B-button for 3 seconds. Once indicator glows WHITE, release the B-button. Visual indicator will start changing colours in sequence. Press B-button briefly when visual indicator glows GREEN",
|
||||
title: "To check smoke detection state", displayDuringSetup: true, type: "paragraph", element: "paragraph"
|
||||
input description: "Please consult Fibaro Smoke Sensor operating manual for advanced setting options. You can skip this configuration to use default settings",
|
||||
title: "Advanced Configuration", displayDuringSetup: true, type: "paragraph", element: "paragraph"
|
||||
input "smokeSensorSensitivity", "enum", title: "Smoke Sensor Sensitivity", options: ["High","Medium","Low"], defaultValue: "${smokeSensorSensitivity}", displayDuringSetup: true
|
||||
input "zwaveNotificationStatus", "enum", title: "Notifications Status", options: ["disabled","casing opened","exceeding temperature threshold", "lack of Z-Wave range", "all notifications"],
|
||||
defaultValue: "${zwaveNotificationStatus}", displayDuringSetup: true
|
||||
input "visualIndicatorNotificationStatus", "enum", title: "Visual Indicator Notifications Status",
|
||||
options: ["disabled","casing opened","exceeding temperature threshold", "lack of Z-Wave range", "all notifications"],
|
||||
defaultValue: "${visualIndicatorNotificationStatus}", displayDuringSetup: true
|
||||
input "soundNotificationStatus", "enum", title: "Sound Notifications Status",
|
||||
options: ["disabled","casing opened","exceeding temperature threshold", "lack of Z-Wave range", "all notifications"],
|
||||
defaultValue: "${soundNotificationStatus}", displayDuringSetup: true
|
||||
input "temperatureReportInterval", "enum", title: "Temperature Report Interval",
|
||||
options: ["reports inactive", "5 minutes", "15 minutes", "30 minutes", "1 hour", "6 hours", "12 hours", "18 hours", "24 hours"], defaultValue: "${temperatureReportInterval}", displayDuringSetup: true
|
||||
input "temperatureReportHysteresis", "number", title: "Temperature Report Hysteresis", description: "Available settings: 1-100 C", range: "1..100", displayDuringSetup: true
|
||||
input "temperatureThreshold", "number", title: "Overheat Temperature Threshold", description: "Available settings: 0 or 2-100 C", range: "0..100", displayDuringSetup: true
|
||||
input "excessTemperatureSignalingInterval", "enum", title: "Excess Temperature Signaling Interval",
|
||||
options: ["5 minutes", "15 minutes", "30 minutes", "1 hour", "6 hours", "12 hours", "18 hours", "24 hours"], defaultValue: "${excessTemperatureSignalingInterval}", displayDuringSetup: true
|
||||
input "lackOfZwaveRangeIndicationInterval", "enum", title: "Lack of Z-Wave Range Indication Interval",
|
||||
options: ["5 minutes", "15 minutes", "30 minutes", "1 hour", "6 hours", "12 hours", "18 hours", "24 hours"], defaultValue: "${lackOfZwaveRangeIndicationInterval}", displayDuringSetup: true
|
||||
}
|
||||
tiles (scale: 2){
|
||||
multiAttributeTile(name:"smoke", type: "lighting", width: 6, height: 4){
|
||||
tileAttribute ("device.smoke", key: "PRIMARY_CONTROL") {
|
||||
attributeState("clear", label:"CLEAR", icon:"st.alarm.smoke.clear", backgroundColor:"#ffffff")
|
||||
attributeState("detected", label:"SMOKE", icon:"st.alarm.smoke.smoke", backgroundColor:"#e86d13")
|
||||
attributeState("tested", label:"TEST", icon:"st.alarm.smoke.test", backgroundColor:"#e86d13")
|
||||
attributeState("replacement required", label:"REPLACE", icon:"st.alarm.smoke.test", backgroundColor:"#FFFF66")
|
||||
attributeState("unknown", label:"UNKNOWN", icon:"st.alarm.smoke.test", backgroundColor:"#ffffff")
|
||||
}
|
||||
tileAttribute ("device.battery", key: "SECONDARY_CONTROL") {
|
||||
attributeState "battery", label:'Battery: ${currentValue}%', unit:"%"
|
||||
}
|
||||
}
|
||||
valueTile("battery", "device.battery", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "battery", label:'${currentValue}% battery', unit:"%"
|
||||
}
|
||||
valueTile("temperature", "device.temperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "temperature", label:'${currentValue}°', unit:"C"
|
||||
}
|
||||
valueTile("heatAlarm", "device.heatAlarm", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "clear", label:'TEMPERATURE OK', backgroundColor:"#ffffff"
|
||||
state "overheat detected", label:'OVERHEAT DETECTED', backgroundColor:"#ffffff"
|
||||
state "rapid temperature rise", label:'RAPID TEMP RISE', backgroundColor:"#ffffff"
|
||||
state "underheat detected", label:'UNDERHEAT DETECTED', backgroundColor:"#ffffff"
|
||||
}
|
||||
valueTile("tamper", "device.tamper", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "clear", label:'NO TAMPER', backgroundColor:"#ffffff"
|
||||
state "detected", label:'TAMPER DETECTED', backgroundColor:"#ffffff"
|
||||
}
|
||||
|
||||
main "smoke"
|
||||
details(["smoke","temperature"])
|
||||
}
|
||||
}
|
||||
|
||||
def updated() {
|
||||
log.debug "Updated with settings: ${settings}"
|
||||
setConfigured("false") //wait until the next time device wakeup to send configure command
|
||||
}
|
||||
|
||||
def parse(String description) {
|
||||
log.debug "parse() >> description: $description"
|
||||
def result = null
|
||||
if (description.startsWith("Err 106")) {
|
||||
log.debug "parse() >> Err 106"
|
||||
result = createEvent( name: "secureInclusion", value: "failed", isStateChange: true,
|
||||
descriptionText: "This sensor failed to complete the network security key exchange. " +
|
||||
"If you are unable to control it via SmartThings, you must remove it from your network and add it again.")
|
||||
} else if (description != "updated") {
|
||||
log.debug "parse() >> zwave.parse(description)"
|
||||
def cmd = zwave.parse(description, [0x31: 5, 0x71: 3, 0x84: 1])
|
||||
if (cmd) {
|
||||
result = zwaveEvent(cmd)
|
||||
}
|
||||
}
|
||||
log.debug "After zwaveEvent(cmd) >> Parsed '${description}' to ${result.inspect()}"
|
||||
return result
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.versionv1.VersionReport cmd) {
|
||||
log.info "Executing zwaveEvent 86 (VersionV1): 12 (VersionReport) with cmd: $cmd"
|
||||
def fw = "${cmd.applicationVersion}.${cmd.applicationSubVersion}"
|
||||
updateDataValue("fw", fw)
|
||||
def text = "$device.displayName: firmware version: $fw, Z-Wave version: ${cmd.zWaveProtocolVersion}.${cmd.zWaveProtocolSubVersion}"
|
||||
createEvent(descriptionText: text, isStateChange: false)
|
||||
}
|
||||
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) {
|
||||
def map = [ name: "battery", unit: "%" ]
|
||||
if (cmd.batteryLevel == 0xFF) {
|
||||
map.value = 1
|
||||
map.descriptionText = "${device.displayName} battery is low"
|
||||
map.isStateChange = true
|
||||
} else {
|
||||
map.value = cmd.batteryLevel
|
||||
}
|
||||
setConfigured("true") //when battery is reported back meaning configuration is done
|
||||
//Store time of last battery update so we don't ask every wakeup, see WakeUpNotification handler
|
||||
state.lastbatt = now()
|
||||
createEvent(map)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.applicationstatusv1.ApplicationBusy cmd) {
|
||||
def msg = cmd.status == 0 ? "try again later" :
|
||||
cmd.status == 1 ? "try again in $cmd.waitTime seconds" :
|
||||
cmd.status == 2 ? "request queued" : "sorry"
|
||||
createEvent(displayed: true, descriptionText: "$device.displayName is busy, $msg")
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.applicationstatusv1.ApplicationRejectedRequest cmd) {
|
||||
createEvent(displayed: true, descriptionText: "$device.displayName rejected the last request")
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
|
||||
setSecured()
|
||||
def encapsulatedCommand = cmd.encapsulatedCommand([0x31: 5, 0x71: 3, 0x84: 1])
|
||||
if (encapsulatedCommand) {
|
||||
log.debug "command: 98 (Security) 81(SecurityMessageEncapsulation) encapsulatedCommand: $encapsulatedCommand"
|
||||
zwaveEvent(encapsulatedCommand)
|
||||
} else {
|
||||
log.warn "Unable to extract encapsulated cmd from $cmd"
|
||||
createEvent(descriptionText: cmd.toString())
|
||||
}
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityCommandsSupportedReport cmd) {
|
||||
log.info "Executing zwaveEvent 98 (SecurityV1): 03 (SecurityCommandsSupportedReport) with cmd: $cmd"
|
||||
setSecured()
|
||||
log.info "checking this MSR : ${getDataValue("MSR")} before sending configuration to device"
|
||||
if (getDataValue("MSR")?.startsWith("010F-0C02")){
|
||||
response(configure()) //configure device using SmartThings default settings
|
||||
}
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.securityv1.NetworkKeyVerify cmd) {
|
||||
log.info "Executing zwaveEvent 98 (SecurityV1): 07 (NetworkKeyVerify) with cmd: $cmd (node is securely included)"
|
||||
createEvent(name:"secureInclusion", value:"success", descriptionText:"Secure inclusion was successful", isStateChange: true, displayed: true)
|
||||
//after device securely joined the network, call configure() to config device
|
||||
setSecured()
|
||||
log.info "checking this MSR : ${getDataValue("MSR")} before sending configuration to device"
|
||||
if (getDataValue("MSR")?.startsWith("010F-0C02")){
|
||||
response(configure()) //configure device using SmartThings default settings
|
||||
}
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cmd) {
|
||||
log.info "Executing zwaveEvent 71 (NotificationV3): 05 (NotificationReport) with cmd: $cmd"
|
||||
def result = []
|
||||
if (cmd.notificationType == 7) {
|
||||
switch (cmd.event) {
|
||||
case 0:
|
||||
result << createEvent(name: "tamper", value: "clear", displayed: false)
|
||||
break
|
||||
case 3:
|
||||
result << createEvent(name: "tamper", value: "detected", descriptionText: "$device.displayName casing was opened")
|
||||
break
|
||||
}
|
||||
} else if (cmd.notificationType == 1) { //Smoke Alarm (V2)
|
||||
log.debug "notificationv3.NotificationReport: for Smoke Alarm (V2)"
|
||||
result << smokeAlarmEvent(cmd.event)
|
||||
} else if (cmd.notificationType == 4) { // Heat Alarm (V2)
|
||||
log.debug "notificationv3.NotificationReport: for Heat Alarm (V2)"
|
||||
result << heatAlarmEvent(cmd.event)
|
||||
} else {
|
||||
log.warn "Need to handle this cmd.notificationType: ${cmd.notificationType}"
|
||||
result << createEvent(descriptionText: cmd.toString(), isStateChange: false)
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
def smokeAlarmEvent(value) {
|
||||
log.debug "smokeAlarmEvent(value): $value"
|
||||
def map = [name: "smoke"]
|
||||
if (value == 1 || value == 2) {
|
||||
map.value = "detected"
|
||||
map.descriptionText = "$device.displayName detected smoke"
|
||||
} else if (value == 0) {
|
||||
map.value = "clear"
|
||||
map.descriptionText = "$device.displayName is clear (no smoke)"
|
||||
} else if (value == 3) {
|
||||
map.value = "tested"
|
||||
map.descriptionText = "$device.displayName smoke alarm test"
|
||||
} else if (value == 4) {
|
||||
map.value = "replacement required"
|
||||
map.descriptionText = "$device.displayName replacement required"
|
||||
} else {
|
||||
map.value = "unknown"
|
||||
map.descriptionText = "$device.displayName unknown event"
|
||||
}
|
||||
createEvent(map)
|
||||
}
|
||||
|
||||
def heatAlarmEvent(value) {
|
||||
log.debug "heatAlarmEvent(value): $value"
|
||||
def map = [name: "heatAlarm"]
|
||||
if (value == 1 || value == 2) {
|
||||
map.value = "overheat detected"
|
||||
map.descriptionText = "$device.displayName overheat detected"
|
||||
} else if (value == 0) {
|
||||
map.value = "clear"
|
||||
map.descriptionText = "$device.displayName heat alarm cleared (no overheat)"
|
||||
} else if (value == 3 || value == 4) {
|
||||
map.value = "rapid temperature rise"
|
||||
map.descriptionText = "$device.displayName rapid temperature rise"
|
||||
} else if (value == 5 || value == 6) {
|
||||
map.value = "underheat detected"
|
||||
map.descriptionText = "$device.displayName underheat detected"
|
||||
} else {
|
||||
map.value = "unknown"
|
||||
map.descriptionText = "$device.displayName unknown event"
|
||||
}
|
||||
createEvent(map)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.wakeupv1.WakeUpNotification cmd) {
|
||||
log.info "Executing zwaveEvent 84 (WakeUpV1): 07 (WakeUpNotification) with cmd: $cmd"
|
||||
log.info "checking this MSR : ${getDataValue("MSR")} before sending configuration to device"
|
||||
def result = [createEvent(descriptionText: "${device.displayName} woke up", isStateChange: false)]
|
||||
def cmds = []
|
||||
/* check MSR = "manufacturerId-productTypeId" to make sure configuration commands are sent to the right model */
|
||||
if (!isConfigured() && getDataValue("MSR")?.startsWith("010F-0C02")) {
|
||||
result << response(configure()) // configure a newly joined device or joined device with preference update
|
||||
} else {
|
||||
//Only ask for battery if we haven't had a BatteryReport in a while
|
||||
if (!state.lastbatt || (new Date().time) - state.lastbatt > 24*60*60*1000) {
|
||||
log.debug("Device has been configured sending >> batteryGet()")
|
||||
cmds << zwave.securityV1.securityMessageEncapsulation().encapsulate(zwave.batteryV1.batteryGet()).format()
|
||||
cmds << "delay 1200"
|
||||
}
|
||||
log.debug("Device has been configured sending >> wakeUpNoMoreInformation()")
|
||||
cmds << zwave.wakeUpV1.wakeUpNoMoreInformation().format()
|
||||
result << response(cmds) //tell device back to sleep
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv5.SensorMultilevelReport cmd) {
|
||||
log.info "Executing zwaveEvent 31 (SensorMultilevelV5): 05 (SensorMultilevelReport) with cmd: $cmd"
|
||||
def map = [:]
|
||||
switch (cmd.sensorType) {
|
||||
case 1:
|
||||
map.name = "temperature"
|
||||
def cmdScale = cmd.scale == 1 ? "F" : "C"
|
||||
map.value = convertTemperatureIfNeeded(cmd.scaledSensorValue, cmdScale, cmd.precision)
|
||||
map.unit = getTemperatureScale()
|
||||
break
|
||||
default:
|
||||
map.descriptionText = cmd.toString()
|
||||
}
|
||||
createEvent(map)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.deviceresetlocallyv1.DeviceResetLocallyNotification cmd) {
|
||||
log.info "Executing zwaveEvent 5A (DeviceResetLocallyV1) : 01 (DeviceResetLocallyNotification) with cmd: $cmd"
|
||||
createEvent(descriptionText: cmd.toString(), isStateChange: true, displayed: true)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
|
||||
log.info "Executing zwaveEvent 72 (ManufacturerSpecificV2) : 05 (ManufacturerSpecificReport) with cmd: $cmd"
|
||||
log.debug "manufacturerId: ${cmd.manufacturerId}"
|
||||
log.debug "manufacturerName: ${cmd.manufacturerName}"
|
||||
log.debug "productId: ${cmd.productId}"
|
||||
log.debug "productTypeId: ${cmd.productTypeId}"
|
||||
def result = []
|
||||
def msr = String.format("%04X-%04X-%04X", cmd.manufacturerId, cmd.productTypeId, cmd.productId)
|
||||
updateDataValue("MSR", msr)
|
||||
log.debug "After device is securely joined, send commands to update tiles"
|
||||
result << zwave.batteryV1.batteryGet()
|
||||
result << zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 0x01)
|
||||
result << zwave.wakeUpV1.wakeUpNoMoreInformation()
|
||||
[[descriptionText:"${device.displayName} MSR report"], response(commands(result, 5000))]
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.associationv2.AssociationReport cmd) {
|
||||
def result = []
|
||||
if (cmd.nodeId.any { it == zwaveHubNodeId }) {
|
||||
result << createEvent(descriptionText: "$device.displayName is associated in group ${cmd.groupingIdentifier}")
|
||||
} else if (cmd.groupingIdentifier == 1) {
|
||||
result << createEvent(descriptionText: "Associating $device.displayName in group ${cmd.groupingIdentifier}")
|
||||
result << response(zwave.associationV1.associationSet(groupingIdentifier:cmd.groupingIdentifier, nodeId:zwaveHubNodeId))
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.Command cmd) {
|
||||
log.warn "General zwaveEvent cmd: ${cmd}"
|
||||
createEvent(descriptionText: cmd.toString(), isStateChange: false)
|
||||
}
|
||||
|
||||
def configure() {
|
||||
// This sensor joins as a secure device if you tripple-click the button to include it
|
||||
log.debug "configure() >> isSecured() : ${isSecured()}"
|
||||
if (!isSecured()) {
|
||||
log.debug "Fibaro smoke sensor not sending configure until secure"
|
||||
return []
|
||||
} else {
|
||||
log.info "${device.displayName} is configuring its settings"
|
||||
def request = []
|
||||
|
||||
//1. configure wakeup interval : available: 0, 4200s-65535s, device default 21600s(6hr)
|
||||
request += zwave.wakeUpV1.wakeUpIntervalSet(seconds:6*3600, nodeid:zwaveHubNodeId)
|
||||
|
||||
//2. Smoke Sensitivity 3 levels: 1-HIGH , 2-MEDIUM (default), 3-LOW
|
||||
if (smokeSensorSensitivity && smokeSensorSensitivity != "null") {
|
||||
request += zwave.configurationV1.configurationSet(parameterNumber: 1, size: 1,
|
||||
scaledConfigurationValue:
|
||||
smokeSensorSensitivity == "High" ? 1 :
|
||||
smokeSensorSensitivity == "Medium" ? 2 :
|
||||
smokeSensorSensitivity == "Low" ? 3 : 2)
|
||||
}
|
||||
//3. Z-Wave notification status: 0-all disabled (default), 1-casing open enabled, 2-exceeding temp enable
|
||||
if (zwaveNotificationStatus && zwaveNotificationStatus != "null"){
|
||||
request += zwave.configurationV1.configurationSet(parameterNumber: 2, size: 1, scaledConfigurationValue: notificationOptionValueMap[zwaveNotificationStatus] ?: 0)
|
||||
}
|
||||
//4. Visual indicator notification status: 0-all disabled (default), 1-casing open enabled, 2-exceeding temp enable, 4-lack of range notification
|
||||
if (visualIndicatorNotificationStatus && visualIndicatorNotificationStatus != "null") {
|
||||
request += zwave.configurationV1.configurationSet(parameterNumber: 3, size: 1, scaledConfigurationValue: notificationOptionValueMap[visualIndicatorNotificationStatus] ?: 0)
|
||||
}
|
||||
//5. Sound notification status: 0-all disabled (default), 1-casing open enabled, 2-exceeding temp enable, 4-lack of range notification
|
||||
if (soundNotificationStatus && soundNotificationStatus != "null") {
|
||||
request += zwave.configurationV1.configurationSet(parameterNumber: 4, size: 1, scaledConfigurationValue: notificationOptionValueMap[soundNotificationStatus] ?: 0)
|
||||
}
|
||||
//6. Temperature report interval: 0-report inactive, 1-8640 (multiply by 10 secs) [10s-24hr], default 180 (30 minutes)
|
||||
if (temperatureReportInterval && temperatureReportInterval != "null") {
|
||||
request += zwave.configurationV1.configurationSet(parameterNumber: 20, size: 2, scaledConfigurationValue: timeOptionValueMap[temperatureReportInterval] ?: 180)
|
||||
} else { //send SmartThings default configuration
|
||||
request += zwave.configurationV1.configurationSet(parameterNumber: 20, size: 2, scaledConfigurationValue: 180)
|
||||
}
|
||||
//7. Temperature report hysteresis: 1-100 (in 0.1C step) [0.1C - 10C], default 10 (1 C)
|
||||
if (temperatureReportHysteresis && temperatureReportHysteresis != null) {
|
||||
request += zwave.configurationV1.configurationSet(parameterNumber: 21, size: 1, scaledConfigurationValue: temperatureReportHysteresis < 1 ? 1 : temperatureReportHysteresis > 100 ? 100 : temperatureReportHysteresis)
|
||||
}
|
||||
//8. Temperature threshold: 1-100 (C), default 55 (C)
|
||||
if (temperatureThreshold && temperatureThreshold != null) {
|
||||
request += zwave.configurationV1.configurationSet(parameterNumber: 30, size: 1, scaledConfigurationValue: temperatureThreshold < 1 ? 1 : temperatureThreshold > 100 ? 100 : temperatureThreshold)
|
||||
}
|
||||
//9. Excess temperature signaling interval: 1-8640 (multiply by 10 secs) [10s-24hr], default 180 (30 minutes)
|
||||
if (excessTemperatureSignalingInterval && excessTemperatureSignalingInterval != "null") {
|
||||
request += zwave.configurationV1.configurationSet(parameterNumber: 31, size: 2, scaledConfigurationValue: timeOptionValueMap[excessTemperatureSignalingInterval] ?: 180)
|
||||
} else { //send SmartThings default configuration
|
||||
request += zwave.configurationV1.configurationSet(parameterNumber: 31, size: 2, scaledConfigurationValue: 180)
|
||||
}
|
||||
//10. Lack of Z-Wave range indication interval: 1-8640 (multiply by 10 secs) [10s-24hr], default 2160 (6 hours)
|
||||
if (lackOfZwaveRangeIndicationInterval && lackOfZwaveRangeIndicationInterval != "null") {
|
||||
request += zwave.configurationV1.configurationSet(parameterNumber: 32, size: 2, scaledConfigurationValue: timeOptionValueMap[lackOfZwaveRangeIndicationInterval] ?: 2160)
|
||||
} else {
|
||||
request += zwave.configurationV1.configurationSet(parameterNumber: 32, size: 2, scaledConfigurationValue: 2160)
|
||||
}
|
||||
//11. get battery level when device is paired
|
||||
request += zwave.batteryV1.batteryGet()
|
||||
|
||||
//12. get temperature reading from device
|
||||
request += zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 0x01)
|
||||
|
||||
commands(request) + ["delay 10000", zwave.wakeUpV1.wakeUpNoMoreInformation().format()]
|
||||
}
|
||||
}
|
||||
|
||||
private def getTimeOptionValueMap() { [
|
||||
"5 minutes" : 30,
|
||||
"15 minutes" : 90,
|
||||
"30 minutes" : 180,
|
||||
"1 hour" : 360,
|
||||
"6 hours" : 2160,
|
||||
"12 hours" : 4320,
|
||||
"18 hours" : 6480,
|
||||
"24 hours" : 8640,
|
||||
"reports inactive" : 0,
|
||||
]}
|
||||
|
||||
private def getNotificationOptionValueMap() { [
|
||||
"disabled" : 0,
|
||||
"casing opened" : 1,
|
||||
"exceeding temperature threshold" : 2,
|
||||
"lack of Z-Wave range" : 4,
|
||||
"all notifications" : 7,
|
||||
]}
|
||||
|
||||
private command(physicalgraph.zwave.Command cmd) {
|
||||
if (isSecured()) {
|
||||
log.info "Sending secured command: ${cmd}"
|
||||
zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
|
||||
} else {
|
||||
log.info "Sending unsecured command: ${cmd}"
|
||||
cmd.format()
|
||||
}
|
||||
}
|
||||
|
||||
private commands(commands, delay=200) {
|
||||
log.info "inside commands: ${commands}"
|
||||
delayBetween(commands.collect{ command(it) }, delay)
|
||||
}
|
||||
|
||||
private setConfigured(configure) {
|
||||
updateDataValue("configured", configure)
|
||||
}
|
||||
private isConfigured() {
|
||||
getDataValue("configured") == "true"
|
||||
}
|
||||
private setSecured() {
|
||||
updateDataValue("secured", "true")
|
||||
}
|
||||
private isSecured() {
|
||||
getDataValue("secured") == "true"
|
||||
}
|
||||
@@ -50,7 +50,7 @@ metadata {
|
||||
capability "Switch Level"
|
||||
capability "Polling"
|
||||
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,1000", outClusters: "0019", manufacturer: "GE_Appliances", model: "ZLL Light", deviceJoinName: "GE Link Bulb"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,1000", outClusters: "0019"
|
||||
}
|
||||
|
||||
// UI tile definitions
|
||||
|
||||
@@ -26,6 +26,8 @@ metadata {
|
||||
capability "Actuator"
|
||||
capability "Sensor"
|
||||
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0B05,0702", outClusters: "000A,0019", manufacturer: "Jasco Products", model: "45852"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0B05,0702", outClusters: "000A,0019", manufacturer: "Jasco Products", model: "45857"
|
||||
}
|
||||
|
||||
// simulator metadata
|
||||
|
||||
@@ -26,6 +26,8 @@ metadata {
|
||||
capability "Actuator"
|
||||
capability "Sensor"
|
||||
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B05,0702", outClusters: "0003, 000A,0019", manufacturer: "Jasco Products", model: "45853"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B05,0702", outClusters: "000A,0019", manufacturer: "Jasco Products", model: "45856"
|
||||
}
|
||||
|
||||
// simulator metadata
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
/**
|
||||
* Logitech Harmony Activity
|
||||
*
|
||||
* Copyright 2015 Juan Risso
|
||||
*
|
||||
* 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: "Logitech Harmony Activity", namespace: "smartthings", author: "Juan Risso") {
|
||||
capability "Switch"
|
||||
capability "Actuator"
|
||||
capability "Refresh"
|
||||
|
||||
command "huboff"
|
||||
command "alloff"
|
||||
command "refresh"
|
||||
}
|
||||
|
||||
// simulator metadata
|
||||
simulator {
|
||||
}
|
||||
|
||||
// UI tile definitions
|
||||
tiles {
|
||||
standardTile("button", "device.switch", width: 2, height: 2, canChangeIcon: true) {
|
||||
state "off", label: 'Off', action: "switch.on", icon: "st.harmony.harmony-hub-icon", backgroundColor: "#ffffff", nextState: "on"
|
||||
state "on", label: 'On', action: "switch.off", icon: "st.harmony.harmony-hub-icon", backgroundColor: "#79b821", nextState: "off"
|
||||
}
|
||||
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
|
||||
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
standardTile("forceoff", "device.switch", inactiveLabel: false, decoration: "flat") {
|
||||
state "default", label:'Force End', action:"switch.off", icon:"st.secondary.off"
|
||||
}
|
||||
standardTile("huboff", "device.switch", inactiveLabel: false, decoration: "flat") {
|
||||
state "default", label:'End Hub Action', action:"huboff", icon:"st.harmony.harmony-hub-icon"
|
||||
}
|
||||
standardTile("alloff", "device.switch", inactiveLabel: false, decoration: "flat") {
|
||||
state "default", label:'All Actions', action:"alloff", icon:"st.secondary.off"
|
||||
}
|
||||
main "button"
|
||||
details(["button", "refresh", "forceoff", "huboff", "alloff"])
|
||||
}
|
||||
}
|
||||
|
||||
def parse(String description) {
|
||||
}
|
||||
|
||||
def on() {
|
||||
sendEvent(name: "switch", value: "on")
|
||||
log.trace parent.activity(device.deviceNetworkId,"start")
|
||||
}
|
||||
|
||||
def off() {
|
||||
sendEvent(name: "switch", value: "off")
|
||||
log.trace parent.activity(device.deviceNetworkId,"end")
|
||||
}
|
||||
|
||||
def huboff() {
|
||||
sendEvent(name: "switch", value: "off")
|
||||
log.trace parent.activity(device.deviceNetworkId,"hub")
|
||||
}
|
||||
|
||||
def alloff() {
|
||||
sendEvent(name: "switch", value: "off")
|
||||
log.trace parent.activity("all","end")
|
||||
}
|
||||
|
||||
|
||||
def refresh() {
|
||||
log.debug "Executing 'refresh'"
|
||||
log.trace parent.poll()
|
||||
}
|
||||
@@ -25,7 +25,7 @@ metadata {
|
||||
tiles {
|
||||
standardTile("presence", "device.presence", width: 2, height: 2, canChangeBackground: true) {
|
||||
state("present", labelIcon:"st.presence.tile.mobile-present", backgroundColor:"#53a7c0")
|
||||
state("not present", labelIcon:"st.presence.tile.mobile-not-present", backgroundColor:"#ebeef2")
|
||||
state("not present", labelIcon:"st.presence.tile.mobile-not-present", backgroundColor:"#ffffff")
|
||||
}
|
||||
main "presence"
|
||||
details "presence"
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
that issue by using state variables
|
||||
*/
|
||||
|
||||
//DEPRECATED - Using the generic DTH for this device. Users need to be moved before deleting this DTH
|
||||
|
||||
metadata {
|
||||
definition (name: "OSRAM LIGHTIFY LED Tunable White 60W", namespace: "smartthings", author: "SmartThings") {
|
||||
|
||||
@@ -22,7 +20,10 @@ metadata {
|
||||
|
||||
// indicates that device keeps track of heartbeat (in state.heartbeat)
|
||||
attribute "heartbeat", "string"
|
||||
|
||||
|
||||
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Classic A60 TW"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY A19 Tunable White"
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -346,8 +346,8 @@ def getTemperature(value) {
|
||||
log.debug "Acceleration"
|
||||
def name = "acceleration"
|
||||
def value = numValue.endsWith("1") ? "active" : "inactive"
|
||||
def linkText = getLinkText(device)
|
||||
def descriptionText = "$linkText was $value"
|
||||
//def linkText = getLinkText(device)
|
||||
def descriptionText = "was $value"
|
||||
def isStateChange = isStateChange(device, name, value)
|
||||
[
|
||||
name: name,
|
||||
|
||||
@@ -11,9 +11,6 @@
|
||||
* for the specific language governing permissions and limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
//DEPRECATED - Using the generic DTH for this device. Users need to be moved before deleting this DTH
|
||||
|
||||
metadata {
|
||||
definition (name: "Sylvania Ultra iQ", namespace:"smartthings", author: "SmartThings") {
|
||||
capability "Switch Level"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
metadata {
|
||||
definition (name: "Simulated Color Control", namespace: "smartthings/testing", author: "SmartThings") {
|
||||
definition (name: "Color Control Capability", namespace: "capabilities", author: "SmartThings") {
|
||||
capability "Color Control"
|
||||
}
|
||||
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
* Thanks to Chad Monroe @cmonroe and Patrick Stuart @pstuart
|
||||
*
|
||||
*/
|
||||
//DEPRECATED - Using the generic DTH for this device. Users need to be moved before deleting this DTH
|
||||
|
||||
metadata {
|
||||
definition (name: "WeMo Bulb", namespace: "smartthings", author: "SmartThings") {
|
||||
|
||||
@@ -27,6 +25,7 @@ metadata {
|
||||
capability "Switch"
|
||||
capability "Switch Level"
|
||||
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,FF00", outClusters: "0019"
|
||||
}
|
||||
|
||||
// simulator metadata
|
||||
|
||||
@@ -25,8 +25,6 @@ metadata {
|
||||
capability "Refresh"
|
||||
capability "Sensor"
|
||||
|
||||
attribute "currentIP", "string"
|
||||
|
||||
command "subscribe"
|
||||
command "resubscribe"
|
||||
command "unsubscribe"
|
||||
@@ -36,36 +34,21 @@ metadata {
|
||||
// simulator metadata
|
||||
simulator {}
|
||||
|
||||
// UI tile definitions
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"rich-control", type: "switch", canChangeIcon: true){
|
||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.Home.home30", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
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 "turningOff", label:'${name}', action:"switch.on", icon:"st.Home.home30", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
attributeState "offline", label:'${name}', icon:"st.Home.home30", backgroundColor:"#ff0000"
|
||||
}
|
||||
tileAttribute ("currentIP", key: "SECONDARY_CONTROL") {
|
||||
attributeState "currentIP", label: ''
|
||||
}
|
||||
}
|
||||
// UI tile definitions
|
||||
tiles {
|
||||
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
|
||||
state "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
state "off", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
state "turningOn", label:'${name}', icon:"st.switches.switch.on", backgroundColor:"#79b821"
|
||||
state "turningOff", label:'${name}', icon:"st.switches.switch.off", backgroundColor:"#ffffff"
|
||||
}
|
||||
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
|
||||
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
|
||||
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
|
||||
state "on", label:'${name}', action:"switch.off", icon:"st.Home.home30", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
state "off", label:'${name}', action:"switch.on", icon:"st.Home.home30", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
state "turningOn", label:'${name}', action:"switch.off", icon:"st.Home.home30", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
state "turningOff", label:'${name}', action:"switch.on", icon:"st.Home.home30", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
state "offline", label:'${name}', icon:"st.Home.home30", backgroundColor:"#ff0000"
|
||||
}
|
||||
|
||||
standardTile("refresh", "device.switch", inactiveLabel: false, height: 2, width: 2, decoration: "flat") {
|
||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
|
||||
main(["switch"])
|
||||
details(["rich-control", "refresh"])
|
||||
}
|
||||
main "switch"
|
||||
details (["switch", "refresh"])
|
||||
}
|
||||
}
|
||||
|
||||
// parse events into attributes
|
||||
@@ -85,7 +68,6 @@ def parse(String description) {
|
||||
def result = []
|
||||
def bodyString = msg.body
|
||||
if (bodyString) {
|
||||
unschedule("setOffline")
|
||||
def body = new XmlSlurper().parseText(bodyString)
|
||||
|
||||
if (body?.property?.TimeSyncRequest?.text()) {
|
||||
@@ -96,14 +78,13 @@ def parse(String description) {
|
||||
} else if (body?.property?.BinaryState?.text()) {
|
||||
def value = body?.property?.BinaryState?.text().toInteger() == 1 ? "on" : "off"
|
||||
log.trace "Notify: BinaryState = ${value}"
|
||||
result << createEvent(name: "switch", value: value, descriptionText: "Switch is ${value}")
|
||||
result << createEvent(name: "switch", value: value)
|
||||
} else if (body?.property?.TimeZoneNotification?.text()) {
|
||||
log.debug "Notify: TimeZoneNotification = ${body?.property?.TimeZoneNotification?.text()}"
|
||||
} else if (body?.Body?.GetBinaryStateResponse?.BinaryState?.text()) {
|
||||
def value = body?.Body?.GetBinaryStateResponse?.BinaryState?.text().toInteger() == 1 ? "on" : "off"
|
||||
log.trace "GetBinaryResponse: BinaryState = ${value}"
|
||||
def dispaux = device.currentValue("switch") != value
|
||||
result << createEvent(name: "switch", value: value, descriptionText: "Switch is ${value}", displayed: dispaux)
|
||||
result << createEvent(name: "switch", value: value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,6 +101,14 @@ private getCallBackAddress() {
|
||||
device.hub.getDataValue("localIP") + ":" + device.hub.getDataValue("localSrvPortTCP")
|
||||
}
|
||||
|
||||
private Integer convertHexToInt(hex) {
|
||||
Integer.parseInt(hex,16)
|
||||
}
|
||||
|
||||
private String convertHexToIP(hex) {
|
||||
[convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
|
||||
}
|
||||
|
||||
private getHostAddress() {
|
||||
def ip = getDataValue("ip")
|
||||
def port = getDataValue("port")
|
||||
@@ -206,8 +195,6 @@ def subscribe(ip, port) {
|
||||
if (ip && ip != existingIp) {
|
||||
log.debug "Updating ip from $existingIp to $ip"
|
||||
updateDataValue("ip", ip)
|
||||
def ipvalue = convertHexToIP(getDataValue("ip"))
|
||||
sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP changed to ${ipvalue}")
|
||||
}
|
||||
if (port && port != existingPort) {
|
||||
log.debug "Updating port from $existingPort to $port"
|
||||
@@ -272,8 +259,6 @@ User-Agent: CyberGarage-HTTP/1.0
|
||||
|
||||
def poll() {
|
||||
log.debug "Executing 'poll'"
|
||||
if (device.currentValue("currentIP") != "Offline")
|
||||
runIn(10, setOffline)
|
||||
new physicalgraph.device.HubAction("""POST /upnp/control/basicevent1 HTTP/1.1
|
||||
SOAPACTION: "urn:Belkin:service:basicevent:1#GetBinaryState"
|
||||
Content-Length: 277
|
||||
@@ -289,15 +274,3 @@ User-Agent: CyberGarage-HTTP/1.0
|
||||
</s:Body>
|
||||
</s:Envelope>""", physicalgraph.device.Protocol.LAN)
|
||||
}
|
||||
|
||||
def setOffline() {
|
||||
sendEvent(name: "switch", value: "offline", descriptionText: "The device is offline")
|
||||
}
|
||||
|
||||
private Integer convertHexToInt(hex) {
|
||||
Integer.parseInt(hex,16)
|
||||
}
|
||||
|
||||
private String convertHexToIP(hex) {
|
||||
[convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
|
||||
}
|
||||
|
||||
@@ -21,8 +21,6 @@
|
||||
capability "Refresh"
|
||||
capability "Sensor"
|
||||
|
||||
attribute "currentIP", "string"
|
||||
|
||||
command "subscribe"
|
||||
command "resubscribe"
|
||||
command "unsubscribe"
|
||||
@@ -33,30 +31,17 @@
|
||||
}
|
||||
|
||||
// UI tile definitions
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"rich-control", type: "motion", canChangeIcon: true){
|
||||
tileAttribute ("device.motion", key: "PRIMARY_CONTROL") {
|
||||
attributeState "active", label:'motion', icon:"st.motion.motion.active", backgroundColor:"#53a7c0"
|
||||
attributeState "inactive", label:'no motion', icon:"st.motion.motion.inactive", backgroundColor:"#ffffff"
|
||||
attributeState "offline", label:'${name}', icon:"st.motion.motion.active", backgroundColor:"#ff0000"
|
||||
}
|
||||
tileAttribute ("currentIP", key: "SECONDARY_CONTROL") {
|
||||
attributeState "currentIP", label: ''
|
||||
}
|
||||
}
|
||||
|
||||
tiles {
|
||||
standardTile("motion", "device.motion", width: 2, height: 2) {
|
||||
state("active", label:'motion', icon:"st.motion.motion.active", backgroundColor:"#53a7c0")
|
||||
state("inactive", label:'no motion', icon:"st.motion.motion.inactive", backgroundColor:"#ffffff")
|
||||
state("offline", label:'${name}', icon:"st.motion.motion.inactive", backgroundColor:"#ff0000")
|
||||
}
|
||||
standardTile("refresh", "device.motion", inactiveLabel: false, decoration: "flat") {
|
||||
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
|
||||
standardTile("refresh", "device.switch", inactiveLabel: false, height: 2, width: 2, decoration: "flat") {
|
||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
|
||||
main "motion"
|
||||
details (["rich-control", "refresh"])
|
||||
details (["motion", "refresh"])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +62,6 @@ def parse(String description) {
|
||||
def result = []
|
||||
def bodyString = msg.body
|
||||
if (bodyString) {
|
||||
unschedule("setOffline")
|
||||
def body = new XmlSlurper().parseText(bodyString)
|
||||
|
||||
if (body?.property?.TimeSyncRequest?.text()) {
|
||||
@@ -88,7 +72,7 @@ def parse(String description) {
|
||||
} else if (body?.property?.BinaryState?.text()) {
|
||||
def value = body?.property?.BinaryState?.text().toInteger() == 1 ? "active" : "inactive"
|
||||
log.debug "Notify - BinaryState = ${value}"
|
||||
result << createEvent(name: "motion", value: value, descriptionText: "Motion is ${value}")
|
||||
result << createEvent(name: "motion", value: value)
|
||||
} else if (body?.property?.TimeZoneNotification?.text()) {
|
||||
log.debug "Notify: TimeZoneNotification = ${body?.property?.TimeZoneNotification?.text()}"
|
||||
}
|
||||
@@ -107,6 +91,14 @@ private getCallBackAddress() {
|
||||
device.hub.getDataValue("localIP") + ":" + device.hub.getDataValue("localSrvPortTCP")
|
||||
}
|
||||
|
||||
private Integer convertHexToInt(hex) {
|
||||
Integer.parseInt(hex,16)
|
||||
}
|
||||
|
||||
private String convertHexToIP(hex) {
|
||||
[convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
|
||||
}
|
||||
|
||||
private getHostAddress() {
|
||||
def ip = getDataValue("ip")
|
||||
def port = getDataValue("port")
|
||||
@@ -133,8 +125,6 @@ def refresh() {
|
||||
////////////////////////////
|
||||
def getStatus() {
|
||||
log.debug "Executing WeMo Motion 'getStatus'"
|
||||
if (device.currentValue("currentIP") != "Offline")
|
||||
runIn(10, setOffline)
|
||||
new physicalgraph.device.HubAction("""POST /upnp/control/basicevent1 HTTP/1.1
|
||||
SOAPACTION: "urn:Belkin:service:basicevent:1#GetBinaryState"
|
||||
Content-Length: 277
|
||||
@@ -175,9 +165,7 @@ def subscribe(ip, port) {
|
||||
def existingPort = getDataValue("port")
|
||||
if (ip && ip != existingIp) {
|
||||
log.debug "Updating ip from $existingIp to $ip"
|
||||
updateDataValue("ip", ip)
|
||||
def ipvalue = convertHexToIP(getDataValue("ip"))
|
||||
sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP changed to ${ipvalue}")
|
||||
updateDataValue("ip", ip)
|
||||
}
|
||||
if (port && port != existingPort) {
|
||||
log.debug "Updating port from $existingPort to $port"
|
||||
@@ -238,15 +226,3 @@ User-Agent: CyberGarage-HTTP/1.0
|
||||
</s:Envelope>
|
||||
""", physicalgraph.device.Protocol.LAN)
|
||||
}
|
||||
|
||||
def setOffline() {
|
||||
sendEvent(name: "motion", value: "offline", descriptionText: "The device is offline")
|
||||
}
|
||||
|
||||
private Integer convertHexToInt(hex) {
|
||||
Integer.parseInt(hex,16)
|
||||
}
|
||||
|
||||
private String convertHexToIP(hex) {
|
||||
[convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
|
||||
}
|
||||
|
||||
@@ -10,142 +10,120 @@
|
||||
* 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.
|
||||
*
|
||||
* Wemo Switch
|
||||
* Wemo Switch
|
||||
*
|
||||
* Author: Juan Risso (SmartThings)
|
||||
* Date: 2015-10-11
|
||||
* Author: superuser
|
||||
* Date: 2013-10-11
|
||||
*/
|
||||
metadata {
|
||||
definition (name: "Wemo Switch", namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Actuator"
|
||||
capability "Switch"
|
||||
capability "Polling"
|
||||
capability "Refresh"
|
||||
capability "Sensor"
|
||||
definition (name: "Wemo Switch", namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Actuator"
|
||||
capability "Switch"
|
||||
capability "Polling"
|
||||
capability "Refresh"
|
||||
capability "Sensor"
|
||||
|
||||
attribute "currentIP", "string"
|
||||
command "subscribe"
|
||||
command "resubscribe"
|
||||
command "unsubscribe"
|
||||
}
|
||||
|
||||
command "subscribe"
|
||||
command "resubscribe"
|
||||
command "unsubscribe"
|
||||
}
|
||||
// simulator metadata
|
||||
simulator {}
|
||||
|
||||
// simulator metadata
|
||||
simulator {}
|
||||
// UI tile definitions
|
||||
tiles {
|
||||
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
|
||||
state "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821"
|
||||
state "off", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff"
|
||||
}
|
||||
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
|
||||
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
|
||||
// UI tile definitions
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"rich-control", type: "switch", canChangeIcon: true){
|
||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.off", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
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 "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"
|
||||
}
|
||||
tileAttribute ("currentIP", key: "SECONDARY_CONTROL") {
|
||||
attributeState "currentIP", label: ''
|
||||
}
|
||||
}
|
||||
|
||||
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 "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 "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"
|
||||
}
|
||||
|
||||
standardTile("refresh", "device.switch", inactiveLabel: false, height: 2, width: 2, decoration: "flat") {
|
||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
|
||||
main(["switch"])
|
||||
details(["rich-control", "refresh"])
|
||||
}
|
||||
main "switch"
|
||||
details (["switch", "refresh"])
|
||||
}
|
||||
}
|
||||
|
||||
// parse events into attributes
|
||||
def parse(String description) {
|
||||
log.debug "Parsing '${description}'"
|
||||
log.debug "Parsing '${description}'"
|
||||
|
||||
def msg = parseLanMessage(description)
|
||||
def headerString = msg.header
|
||||
def msg = parseLanMessage(description)
|
||||
def headerString = msg.header
|
||||
|
||||
if (headerString?.contains("SID: uuid:")) {
|
||||
def sid = (headerString =~ /SID: uuid:.*/) ? ( headerString =~ /SID: uuid:.*/)[0] : "0"
|
||||
sid -= "SID: uuid:".trim()
|
||||
if (headerString?.contains("SID: uuid:")) {
|
||||
def sid = (headerString =~ /SID: uuid:.*/) ? ( headerString =~ /SID: uuid:.*/)[0] : "0"
|
||||
sid -= "SID: uuid:".trim()
|
||||
|
||||
updateDataValue("subscriptionId", sid)
|
||||
}
|
||||
updateDataValue("subscriptionId", sid)
|
||||
}
|
||||
|
||||
def result = []
|
||||
def bodyString = msg.body
|
||||
if (bodyString) {
|
||||
unschedule("setOffline")
|
||||
def body = new XmlSlurper().parseText(bodyString)
|
||||
if (body?.property?.TimeSyncRequest?.text()) {
|
||||
log.trace "Got TimeSyncRequest"
|
||||
result << timeSyncResponse()
|
||||
} else if (body?.Body?.SetBinaryStateResponse?.BinaryState?.text()) {
|
||||
log.trace "Got SetBinaryStateResponse = ${body?.Body?.SetBinaryStateResponse?.BinaryState?.text()}"
|
||||
} else if (body?.property?.BinaryState?.text()) {
|
||||
def value = body?.property?.BinaryState?.text().substring(0, 1).toInteger() == 0 ? "off" : "on"
|
||||
log.trace "Notify: BinaryState = ${value}, ${body.property.BinaryState}"
|
||||
def dispaux = device.currentValue("switch") != value
|
||||
result << createEvent(name: "switch", value: value, descriptionText: "Switch is ${value}", displayed: dispaux)
|
||||
} else if (body?.property?.TimeZoneNotification?.text()) {
|
||||
log.debug "Notify: TimeZoneNotification = ${body?.property?.TimeZoneNotification?.text()}"
|
||||
} else if (body?.Body?.GetBinaryStateResponse?.BinaryState?.text()) {
|
||||
def value = body?.Body?.GetBinaryStateResponse?.BinaryState?.text().substring(0, 1).toInteger() == 0 ? "off" : "on"
|
||||
log.trace "GetBinaryResponse: BinaryState = ${value}, ${body.property.BinaryState}"
|
||||
log.info "Connection: ${device.currentValue("connection")}"
|
||||
if (device.currentValue("currentIP") == "Offline") {
|
||||
def ipvalue = convertHexToIP(getDataValue("ip"))
|
||||
sendEvent(name: "IP", value: ipvalue, descriptionText: "IP is ${ipvalue}")
|
||||
}
|
||||
def dispaux2 = device.currentValue("switch") != value
|
||||
result << createEvent(name: "switch", value: value, descriptionText: "Switch is ${value}", displayed: dispaux2)
|
||||
}
|
||||
}
|
||||
result
|
||||
def result = []
|
||||
def bodyString = msg.body
|
||||
if (bodyString) {
|
||||
def body = new XmlSlurper().parseText(bodyString)
|
||||
|
||||
if (body?.property?.TimeSyncRequest?.text()) {
|
||||
log.trace "Got TimeSyncRequest"
|
||||
result << timeSyncResponse()
|
||||
} else if (body?.Body?.SetBinaryStateResponse?.BinaryState?.text()) {
|
||||
log.trace "Got SetBinaryStateResponse = ${body?.Body?.SetBinaryStateResponse?.BinaryState?.text()}"
|
||||
} else if (body?.property?.BinaryState?.text()) {
|
||||
def value = body?.property?.BinaryState?.text().toInteger() == 1 ? "on" : "off"
|
||||
log.trace "Notify: BinaryState = ${value}"
|
||||
result << createEvent(name: "switch", value: value)
|
||||
} else if (body?.property?.TimeZoneNotification?.text()) {
|
||||
log.debug "Notify: TimeZoneNotification = ${body?.property?.TimeZoneNotification?.text()}"
|
||||
} else if (body?.Body?.GetBinaryStateResponse?.BinaryState?.text()) {
|
||||
def value = body?.Body?.GetBinaryStateResponse?.BinaryState?.text().toInteger() == 1 ? "on" : "off"
|
||||
log.trace "GetBinaryResponse: BinaryState = ${value}"
|
||||
result << createEvent(name: "switch", value: value)
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
private getTime() {
|
||||
// This is essentially System.currentTimeMillis()/1000, but System is disallowed by the sandbox.
|
||||
((new GregorianCalendar().time.time / 1000l).toInteger()).toString()
|
||||
// This is essentially System.currentTimeMillis()/1000, but System is disallowed by the sandbox.
|
||||
((new GregorianCalendar().time.time / 1000l).toInteger()).toString()
|
||||
}
|
||||
|
||||
private getCallBackAddress() {
|
||||
device.hub.getDataValue("localIP") + ":" + device.hub.getDataValue("localSrvPortTCP")
|
||||
device.hub.getDataValue("localIP") + ":" + device.hub.getDataValue("localSrvPortTCP")
|
||||
}
|
||||
|
||||
private Integer convertHexToInt(hex) {
|
||||
Integer.parseInt(hex,16)
|
||||
Integer.parseInt(hex,16)
|
||||
}
|
||||
|
||||
private String convertHexToIP(hex) {
|
||||
[convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
|
||||
[convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
|
||||
}
|
||||
|
||||
private getHostAddress() {
|
||||
def ip = getDataValue("ip")
|
||||
def port = getDataValue("port")
|
||||
if (!ip || !port) {
|
||||
def parts = device.deviceNetworkId.split(":")
|
||||
if (parts.length == 2) {
|
||||
ip = parts[0]
|
||||
port = parts[1]
|
||||
} else {
|
||||
log.warn "Can't figure out ip and port for device: ${device.id}"
|
||||
}
|
||||
}
|
||||
log.debug "Using ip: ${ip} and port: ${port} for device: ${device.id}"
|
||||
return convertHexToIP(ip) + ":" + convertHexToInt(port)
|
||||
def ip = getDataValue("ip")
|
||||
def port = getDataValue("port")
|
||||
|
||||
if (!ip || !port) {
|
||||
def parts = device.deviceNetworkId.split(":")
|
||||
if (parts.length == 2) {
|
||||
ip = parts[0]
|
||||
port = parts[1]
|
||||
} else {
|
||||
log.warn "Can't figure out ip and port for device: ${device.id}"
|
||||
}
|
||||
}
|
||||
log.debug "Using ip: ${ip} and port: ${port} for device: ${device.id}"
|
||||
return convertHexToIP(ip) + ":" + convertHexToInt(port)
|
||||
}
|
||||
|
||||
|
||||
def on() {
|
||||
log.debug "Executing 'on'"
|
||||
log.debug "Executing 'on'"
|
||||
sendEvent(name: "switch", value: "on")
|
||||
def turnOn = new physicalgraph.device.HubAction("""POST /upnp/control/basicevent1 HTTP/1.1
|
||||
SOAPAction: "urn:Belkin:service:basicevent:1#SetBinaryState"
|
||||
Host: ${getHostAddress()}
|
||||
@@ -155,16 +133,17 @@ Content-Length: 333
|
||||
<?xml version="1.0"?>
|
||||
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
|
||||
<SOAP-ENV:Body>
|
||||
<m:SetBinaryState xmlns:m="urn:Belkin:service:basicevent:1">
|
||||
<m:SetBinaryState xmlns:m="urn:Belkin:service:basicevent:1">
|
||||
<BinaryState>1</BinaryState>
|
||||
</m:SetBinaryState>
|
||||
</m:SetBinaryState>
|
||||
</SOAP-ENV:Body>
|
||||
</SOAP-ENV:Envelope>""", physicalgraph.device.Protocol.LAN)
|
||||
}
|
||||
|
||||
def off() {
|
||||
log.debug "Executing 'off'"
|
||||
def turnOff = new physicalgraph.device.HubAction("""POST /upnp/control/basicevent1 HTTP/1.1
|
||||
log.debug "Executing 'off'"
|
||||
sendEvent(name: "switch", value: "off")
|
||||
def turnOff = new physicalgraph.device.HubAction("""POST /upnp/control/basicevent1 HTTP/1.1
|
||||
SOAPAction: "urn:Belkin:service:basicevent:1#SetBinaryState"
|
||||
Host: ${getHostAddress()}
|
||||
Content-Type: text/xml
|
||||
@@ -173,13 +152,36 @@ Content-Length: 333
|
||||
<?xml version="1.0"?>
|
||||
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
|
||||
<SOAP-ENV:Body>
|
||||
<m:SetBinaryState xmlns:m="urn:Belkin:service:basicevent:1">
|
||||
<m:SetBinaryState xmlns:m="urn:Belkin:service:basicevent:1">
|
||||
<BinaryState>0</BinaryState>
|
||||
</m:SetBinaryState>
|
||||
</m:SetBinaryState>
|
||||
</SOAP-ENV:Body>
|
||||
</SOAP-ENV:Envelope>""", physicalgraph.device.Protocol.LAN)
|
||||
}
|
||||
|
||||
/*def refresh() {
|
||||
log.debug "Executing 'refresh'"
|
||||
new physicalgraph.device.HubAction("""POST /upnp/control/basicevent1 HTTP/1.1
|
||||
SOAPACTION: "urn:Belkin:service:basicevent:1#GetBinaryState"
|
||||
Content-Length: 277
|
||||
Content-Type: text/xml; charset="utf-8"
|
||||
HOST: ${getHostAddress()}
|
||||
User-Agent: CyberGarage-HTTP/1.0
|
||||
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
|
||||
<s:Body>
|
||||
<u:GetBinaryState xmlns:u="urn:Belkin:service:basicevent:1">
|
||||
</u:GetBinaryState>
|
||||
</s:Body>
|
||||
</s:Envelope>""", physicalgraph.device.Protocol.LAN)
|
||||
}*/
|
||||
|
||||
def refresh() {
|
||||
log.debug "Executing WeMo Switch 'subscribe', then 'timeSyncResponse', then 'poll'"
|
||||
[subscribe(), timeSyncResponse(), poll()]
|
||||
}
|
||||
|
||||
def subscribe(hostAddress) {
|
||||
log.debug "Executing 'subscribe()'"
|
||||
def address = getCallBackAddress()
|
||||
@@ -198,30 +200,27 @@ def subscribe() {
|
||||
subscribe(getHostAddress())
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
log.debug "Executing WeMo Switch 'subscribe', then 'timeSyncResponse', then 'poll'"
|
||||
[subscribe(), timeSyncResponse(), poll()]
|
||||
}
|
||||
|
||||
def subscribe(ip, port) {
|
||||
def existingIp = getDataValue("ip")
|
||||
def existingPort = getDataValue("port")
|
||||
if (ip && ip != existingIp) {
|
||||
log.debug "Updating ip from $existingIp to $ip"
|
||||
updateDataValue("ip", ip)
|
||||
def ipvalue = convertHexToIP(getDataValue("ip"))
|
||||
sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP changed to ${ipvalue}")
|
||||
}
|
||||
if (port && port != existingPort) {
|
||||
log.debug "Updating port from $existingPort to $port"
|
||||
updateDataValue("port", port)
|
||||
def existingIp = getDataValue("ip")
|
||||
def existingPort = getDataValue("port")
|
||||
if (ip && ip != existingIp) {
|
||||
log.debug "Updating ip from $existingIp to $ip"
|
||||
updateDataValue("ip", ip)
|
||||
}
|
||||
if (port && port != existingPort) {
|
||||
log.debug "Updating port from $existingPort to $port"
|
||||
updateDataValue("port", port)
|
||||
}
|
||||
|
||||
subscribe("${ip}:${port}")
|
||||
}
|
||||
|
||||
////////////////////////////
|
||||
def resubscribe() {
|
||||
log.debug "Executing 'resubscribe()'"
|
||||
def sid = getDeviceDataByName("subscriptionId")
|
||||
log.debug "Executing 'resubscribe()'"
|
||||
|
||||
def sid = getDeviceDataByName("subscriptionId")
|
||||
|
||||
new physicalgraph.device.HubAction("""SUBSCRIBE /upnp/event/basicevent1 HTTP/1.1
|
||||
HOST: ${getHostAddress()}
|
||||
SID: uuid:${sid}
|
||||
@@ -229,11 +228,12 @@ TIMEOUT: Second-5400
|
||||
|
||||
|
||||
""", physicalgraph.device.Protocol.LAN)
|
||||
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////
|
||||
def unsubscribe() {
|
||||
def sid = getDeviceDataByName("subscriptionId")
|
||||
def sid = getDeviceDataByName("subscriptionId")
|
||||
new physicalgraph.device.HubAction("""UNSUBSCRIBE publisher path HTTP/1.1
|
||||
HOST: ${getHostAddress()}
|
||||
SID: uuid:${sid}
|
||||
@@ -242,7 +242,7 @@ SID: uuid:${sid}
|
||||
""", physicalgraph.device.Protocol.LAN)
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////
|
||||
//TODO: Use UTC Timezone
|
||||
def timeSyncResponse() {
|
||||
log.debug "Executing 'timeSyncResponse()'"
|
||||
@@ -267,15 +267,9 @@ User-Agent: CyberGarage-HTTP/1.0
|
||||
""", physicalgraph.device.Protocol.LAN)
|
||||
}
|
||||
|
||||
def setOffline() {
|
||||
//sendEvent(name: "currentIP", value: "Offline", displayed: false)
|
||||
sendEvent(name: "switch", value: "offline", descriptionText: "The device is offline")
|
||||
}
|
||||
|
||||
def poll() {
|
||||
log.debug "Executing 'poll'"
|
||||
if (device.currentValue("currentIP") != "Offline")
|
||||
runIn(10, setOffline)
|
||||
new physicalgraph.device.HubAction("""POST /upnp/control/basicevent1 HTTP/1.1
|
||||
SOAPACTION: "urn:Belkin:service:basicevent:1#GetBinaryState"
|
||||
Content-Length: 277
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
/**
|
||||
* Copyright 2015 SmartThings
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* 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: "ZigBee Dimmer Power", namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Actuator"
|
||||
capability "Configuration"
|
||||
capability "Refresh"
|
||||
capability "Power Meter"
|
||||
capability "Sensor"
|
||||
capability "Switch"
|
||||
capability "Switch Level"
|
||||
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0B04"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0702"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0702, 0B05", outClusters: "0019", manufacturer: "sengled", model: "Z01-CIA19NAE26", deviceJoinName: "Sengled Element touch"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0B05,0702", outClusters: "000A,0019", manufacturer: "Jasco Products", model: "45852", deviceJoinName: "GE Zigbee Plug-In Dimmer"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0B05,0702", outClusters: "000A,0019", manufacturer: "Jasco Products", model: "45857", deviceJoinName: "GE Zigbee In-Wall Dimmer"
|
||||
}
|
||||
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
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 "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
}
|
||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
||||
attributeState "level", action:"switch level.setLevel"
|
||||
}
|
||||
tileAttribute ("power", key: "SECONDARY_CONTROL") {
|
||||
attributeState "power", label:'${currentValue} W'
|
||||
}
|
||||
}
|
||||
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
main "switch"
|
||||
details(["switch", "refresh"])
|
||||
}
|
||||
}
|
||||
|
||||
// Parse incoming device messages to generate events
|
||||
def parse(String description) {
|
||||
log.debug "description is $description"
|
||||
|
||||
def resultMap = zigbee.getKnownDescription(description)
|
||||
if (resultMap) {
|
||||
log.info resultMap
|
||||
if (resultMap.type == "update") {
|
||||
log.info "$device updates: ${resultMap.value}"
|
||||
}
|
||||
else if (resultMap.type == "power") {
|
||||
def powerValue
|
||||
if (device.getDataValue("manufacturer") != "OSRAM") { //OSRAM devices do not reliably update power
|
||||
powerValue = (resultMap.value as Integer)/10 //TODO: The divisor value needs to be set as part of configuration
|
||||
sendEvent(name: "power", value: powerValue)
|
||||
}
|
||||
}
|
||||
else {
|
||||
sendEvent(name: resultMap.type, value: resultMap.value)
|
||||
}
|
||||
}
|
||||
else {
|
||||
log.warn "DID NOT PARSE MESSAGE for description : $description"
|
||||
log.debug zigbee.parseDescriptionAsMap(description)
|
||||
}
|
||||
}
|
||||
|
||||
def off() {
|
||||
zigbee.off()
|
||||
}
|
||||
|
||||
def on() {
|
||||
zigbee.on()
|
||||
}
|
||||
|
||||
def setLevel(value) {
|
||||
zigbee.setLevel(value)
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.simpleMeteringPowerRefresh() + zigbee.electricMeasurementPowerRefresh() + zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.simpleMeteringPowerConfig() + zigbee.electricMeasurementPowerConfig()
|
||||
}
|
||||
|
||||
def configure() {
|
||||
log.debug "Configuring Reporting and Bindings."
|
||||
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.simpleMeteringPowerConfig() + zigbee.electricMeasurementPowerConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.simpleMeteringPowerRefresh() + zigbee.electricMeasurementPowerRefresh()
|
||||
}
|
||||
@@ -11,80 +11,133 @@
|
||||
* for the specific language governing permissions and limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
metadata {
|
||||
definition (name: "ZigBee Dimmer", namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Actuator"
|
||||
capability "Configuration"
|
||||
capability "Refresh"
|
||||
capability "Sensor"
|
||||
capability "Switch"
|
||||
capability "Switch Level"
|
||||
definition (name: "ZigBee Dimmer", namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Switch Level"
|
||||
capability "Actuator"
|
||||
capability "Switch"
|
||||
capability "Configuration"
|
||||
capability "Sensor"
|
||||
capability "Refresh"
|
||||
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0B05", outClusters: "0019"
|
||||
}
|
||||
|
||||
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: "OSRAM LIGHTIFY LED Smart Connected Light"
|
||||
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"
|
||||
}
|
||||
// simulator metadata
|
||||
simulator {
|
||||
// status messages
|
||||
status "on": "on/off: 1"
|
||||
status "off": "on/off: 0"
|
||||
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
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 "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
}
|
||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
||||
attributeState "level", action:"switch level.setLevel"
|
||||
}
|
||||
}
|
||||
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
main "switch"
|
||||
details(["switch", "refresh"])
|
||||
}
|
||||
// reply messages
|
||||
reply "zcl on-off on": "on/off: 1"
|
||||
reply "zcl on-off off": "on/off: 0"
|
||||
}
|
||||
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
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 "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
}
|
||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
||||
attributeState "level", action:"switch level.setLevel"
|
||||
}
|
||||
}
|
||||
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
valueTile("level", "device.level", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "level", label:'${currentValue} %', unit:"%", backgroundColor:"#ffffff"
|
||||
}
|
||||
main "switch"
|
||||
details(["switch", "refresh", "level", "levelSliderControl"])
|
||||
}
|
||||
}
|
||||
|
||||
// Parse incoming device messages to generate events
|
||||
def parse(String description) {
|
||||
log.debug "description is $description"
|
||||
log.info description
|
||||
if (description?.startsWith("catchall:")) {
|
||||
def msg = zigbee.parse(description)
|
||||
log.trace msg
|
||||
log.trace "data: $msg.data"
|
||||
}
|
||||
else {
|
||||
def name = description?.startsWith("on/off: ") ? "switch" : null
|
||||
def value = name == "switch" ? (description?.endsWith(" 1") ? "on" : "off") : null
|
||||
def result = createEvent(name: name, value: value)
|
||||
log.debug "Parse returned ${result?.descriptionText}"
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
def resultMap = zigbee.getKnownDescription(description)
|
||||
if (resultMap) {
|
||||
log.info resultMap
|
||||
if (resultMap.type == "update") {
|
||||
log.info "$device updates: ${resultMap.value}"
|
||||
}
|
||||
else {
|
||||
sendEvent(name: resultMap.type, value: resultMap.value)
|
||||
}
|
||||
}
|
||||
else {
|
||||
log.warn "DID NOT PARSE MESSAGE for description : $description"
|
||||
log.debug zigbee.parseDescriptionAsMap(description)
|
||||
}
|
||||
// Commands to device
|
||||
def on() {
|
||||
log.debug "on()"
|
||||
sendEvent(name: "switch", value: "on")
|
||||
"st cmd 0x${device.deviceNetworkId} ${endpointId} 6 1 {}"
|
||||
}
|
||||
|
||||
def off() {
|
||||
zigbee.off()
|
||||
log.debug "off()"
|
||||
sendEvent(name: "switch", value: "off")
|
||||
"st cmd 0x${device.deviceNetworkId} ${endpointId} 6 0 {}"
|
||||
}
|
||||
|
||||
def on() {
|
||||
zigbee.on()
|
||||
}
|
||||
|
||||
def setLevel(value) {
|
||||
zigbee.setLevel(value)
|
||||
log.trace "setLevel($value)"
|
||||
def cmds = []
|
||||
|
||||
if (value == 0) {
|
||||
sendEvent(name: "switch", value: "off")
|
||||
cmds << "st cmd 0x${device.deviceNetworkId} ${endpointId} 6 0 {}"
|
||||
}
|
||||
else if (device.latestValue("switch") == "off") {
|
||||
sendEvent(name: "switch", value: "on")
|
||||
cmds << "st cmd 0x${device.deviceNetworkId} ${endpointId} 6 1 {}"
|
||||
|
||||
}
|
||||
|
||||
sendEvent(name: "level", value: value)
|
||||
def level = hexString(Math.round(value * 255/100))
|
||||
cmds << "st cmd 0x${device.deviceNetworkId} ${endpointId} 8 4 {${level} 0000}"
|
||||
|
||||
//log.debug cmds
|
||||
cmds
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig() + zigbee.levelConfig()
|
||||
[
|
||||
"st wattr 0x${device.deviceNetworkId} 1 6 0", "delay 200",
|
||||
"st wattr 0x${device.deviceNetworkId} 1 8 0"
|
||||
]
|
||||
}
|
||||
|
||||
def configure() {
|
||||
log.debug "Configuring Reporting and Bindings."
|
||||
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh()
|
||||
|
||||
/*log.debug "binding to switch and level control cluster"
|
||||
[
|
||||
"zdo bind 0x${device.deviceNetworkId} 1 1 6 {${device.zigbeeId}} {}", "delay 200",
|
||||
"zdo bind 0x${device.deviceNetworkId} 1 1 8 {${device.zigbeeId}} {}"
|
||||
]
|
||||
*/
|
||||
|
||||
//set transition time to 2 seconds. Not currently working.
|
||||
"st wattr 0x${device.deviceNetworkId} 1 8 0x10 0x21 {1400}"
|
||||
}
|
||||
|
||||
|
||||
|
||||
private hex(value, width=2) {
|
||||
def s = new BigInteger(Math.round(value).toString()).toString(16)
|
||||
while (s.size() < width) {
|
||||
s = "0" + s
|
||||
}
|
||||
s
|
||||
}
|
||||
|
||||
private getEndpointId() {
|
||||
new BigInteger(device.endpointId, 16).toString()
|
||||
}
|
||||
|
||||
@@ -1,184 +0,0 @@
|
||||
/**
|
||||
* ZigBee Lock
|
||||
*
|
||||
* Copyright 2015 SmartThings
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* 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: "ZigBee Lock", namespace: "smartthings", author: "SmartThings")
|
||||
{
|
||||
capability "Actuator"
|
||||
capability "Lock"
|
||||
capability "Refresh"
|
||||
capability "Sensor"
|
||||
capability "Battery"
|
||||
capability "Configuration"
|
||||
|
||||
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0004,0005,0009,0020,0101,0402,0B05,FDBD", outClusters: "000A,0019",
|
||||
manufacturer: "Kwikset", model: "SMARTCODE_DEADBOLT_5", deviceJoinName: "Kwikset 5-Button Deadbolt"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0004,0005,0009,0020,0101,0402,0B05,FDBD", outClusters: "000A,0019",
|
||||
manufacturer: "Kwikset", model: "SMARTCODE_LEVER_5", deviceJoinName: "Kwikset 5-Button Lever"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0004,0005,0009,0020,0101,0402,0B05,FDBD", outClusters: "000A,0019",
|
||||
manufacturer: "Kwikset", model: "SMARTCODE_DEADBOLT_10", deviceJoinName: "Kwikset 10-Button Deadbolt"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0004,0005,0009,0020,0101,0402,0B05,FDBD", outClusters: "000A,0019",
|
||||
manufacturer: "Kwikset", model: "SMARTCODE_DEADBOLT_10T", deviceJoinName: "Kwikset 10-Button Touch Deadbolt"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019",
|
||||
manufacturer: "Yale", model: "YRL220 TS LL", deviceJoinName: "Yale Touch Screen Lever Lock"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019",
|
||||
manufacturer: "Yale", model: "YRD210 PB DB", deviceJoinName: "Yale Push Button Deadbolt Lock"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019",
|
||||
manufacturer: "Yale", model: "YRD220/240 TSDB", deviceJoinName: "Yale Touch Screen Deadbolt Lock"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019",
|
||||
manufacturer: "Yale", model: "YRL210 PB LL", deviceJoinName: "Yale Push Button Lever Lock"
|
||||
}
|
||||
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"toggle", type:"generic", width:6, height:4){
|
||||
tileAttribute ("device.lock", key:"PRIMARY_CONTROL") {
|
||||
attributeState "locked", label:'locked', action:"lock.unlock", icon:"st.locks.lock.locked", backgroundColor:"#79b821", nextState:"unlocking"
|
||||
attributeState "unlocked", label:'unlocked', action:"lock.lock", icon:"st.locks.lock.unlocked", backgroundColor:"#ffffff", nextState:"locking"
|
||||
attributeState "unknown", label:"unknown", action:"lock.lock", icon:"st.locks.lock.unknown", backgroundColor:"#ffffff", nextState:"locking"
|
||||
attributeState "locking", label:'locking', icon:"st.locks.lock.locked", backgroundColor:"#79b821"
|
||||
attributeState "unlocking", label:'unlocking', icon:"st.locks.lock.unlocked", backgroundColor:"#ffffff"
|
||||
}
|
||||
}
|
||||
standardTile("lock", "device.lock", inactiveLabel:false, decoration:"flat", width:2, height:2) {
|
||||
state "default", label:'lock', action:"lock.lock", icon:"st.locks.lock.locked", nextState:"locking"
|
||||
}
|
||||
standardTile("unlock", "device.lock", inactiveLabel:false, decoration:"flat", width:2, height:2) {
|
||||
state "default", label:'unlock', action:"lock.unlock", icon:"st.locks.lock.unlocked", nextState:"unlocking"
|
||||
}
|
||||
valueTile("battery", "device.battery", inactiveLabel:false, decoration:"flat", width:2, height:2) {
|
||||
state "battery", label:'${currentValue}% battery', unit:""
|
||||
}
|
||||
standardTile("refresh", "device.lock", inactiveLabel:false, decoration:"flat", width:2, height:2) {
|
||||
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
|
||||
main "toggle"
|
||||
details(["toggle", "lock", "unlock", "battery", "refresh"])
|
||||
}
|
||||
}
|
||||
|
||||
// Globals
|
||||
private getCLUSTER_POWER() { 0x0001 }
|
||||
private getCLUSTER_DOORLOCK() { 0x0101 }
|
||||
|
||||
private getDOORLOCK_CMD_LOCK_DOOR() { 0x00 }
|
||||
private getDOORLOCK_CMD_UNLOCK_DOOR() { 0x01 }
|
||||
private getDOORLOCK_ATTR_LOCKSTATE() { 0x0000 }
|
||||
private getPOWER_ATTR_BATTERY_PERCENTAGE_REMAINING() { 0x0021 }
|
||||
|
||||
private getTYPE_U8() { 0x20 }
|
||||
private getTYPE_ENUM8() { 0x30 }
|
||||
|
||||
// Public methods
|
||||
def installed() {
|
||||
log.trace "installed()"
|
||||
}
|
||||
|
||||
def uninstalled() {
|
||||
log.trace "uninstalled()"
|
||||
}
|
||||
|
||||
def configure() {
|
||||
/*
|
||||
def cmds =
|
||||
zigbee.configSetup("${CLUSTER_DOORLOCK}", "${DOORLOCK_ATTR_LOCKSTATE}",
|
||||
"${TYPE_ENUM8}", 0, 3600, "{01}") +
|
||||
zigbee.configSetup("${CLUSTER_POWER}", "${POWER_ATTR_BATTERY_PERCENTAGE_REMAINING}",
|
||||
"${TYPE_U8}", 600, 21600, "{01}")
|
||||
*/
|
||||
def zigbeeId = device.zigbeeId
|
||||
def cmds =
|
||||
[
|
||||
"zdo bind 0x${device.deviceNetworkId} 0x${device.endpointId} 1 ${CLUSTER_DOORLOCK} {$zigbeeId} {}", "delay 200",
|
||||
"zcl global send-me-a-report ${CLUSTER_DOORLOCK} ${DOORLOCK_ATTR_LOCKSTATE} ${TYPE_ENUM8} 0 3600 {01}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 0x${device.endpointId}", "delay 200",
|
||||
|
||||
"zdo bind 0x${device.deviceNetworkId} 0x${device.endpointId} 1 ${CLUSTER_POWER} {$zigbeeId} {}", "delay 200",
|
||||
"zcl global send-me-a-report ${CLUSTER_POWER} ${POWER_ATTR_BATTERY_PERCENTAGE_REMAINING} ${TYPE_U8} 600 21600 {01}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 0x${device.endpointId}", "delay 200",
|
||||
]
|
||||
log.info "configure() --- cmds: $cmds"
|
||||
return cmds + refresh() // send refresh cmds as part of config
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
def cmds =
|
||||
zigbee.refreshData("${CLUSTER_DOORLOCK}", "${DOORLOCK_ATTR_LOCKSTATE}") +
|
||||
zigbee.refreshData("${CLUSTER_POWER}", "${POWER_ATTR_BATTERY_PERCENTAGE_REMAINING}")
|
||||
log.info "refresh() --- cmds: $cmds"
|
||||
return cmds
|
||||
}
|
||||
|
||||
def parse(String description) {
|
||||
log.trace "parse() --- description: $description"
|
||||
|
||||
Map map = [:]
|
||||
if (description?.startsWith('read attr -')) {
|
||||
map = parseReportAttributeMessage(description)
|
||||
}
|
||||
|
||||
log.debug "parse() --- Parse returned $map"
|
||||
def result = map ? createEvent(map) : null
|
||||
return result
|
||||
}
|
||||
|
||||
// Lock capability commands
|
||||
def lock() {
|
||||
def cmds = zigbee.zigbeeCommand("${CLUSTER_DOORLOCK}", "${DOORLOCK_CMD_LOCK_DOOR}", "{}")
|
||||
log.info "lock() -- cmds: $cmds"
|
||||
//return cmds
|
||||
"st cmd 0x${device.deviceNetworkId} 0x${device.endpointId} ${CLUSTER_DOORLOCK} ${DOORLOCK_CMD_LOCK_DOOR} {}"
|
||||
}
|
||||
|
||||
def unlock() {
|
||||
def cmds = zigbee.zigbeeCommand("${CLUSTER_DOORLOCK}", "${DOORLOCK_CMD_UNLOCK_DOOR}", "{}")
|
||||
log.info "unlock() -- cmds: $cmds"
|
||||
//return cmds
|
||||
"st cmd 0x${device.deviceNetworkId} 0x${device.endpointId} ${CLUSTER_DOORLOCK} ${DOORLOCK_CMD_UNLOCK_DOOR} {}"
|
||||
}
|
||||
|
||||
// Private methods
|
||||
private Map parseReportAttributeMessage(String description) {
|
||||
log.trace "parseReportAttributeMessage() --- description: $description"
|
||||
|
||||
Map descMap = zigbee.parseDescriptionAsMap(description)
|
||||
|
||||
log.debug "parseReportAttributeMessage() --- descMap: $descMap"
|
||||
|
||||
Map resultMap = [:]
|
||||
if (descMap.clusterInt == CLUSTER_POWER && descMap.attrInt == POWER_ATTR_BATTERY_PERCENTAGE_REMAINING) {
|
||||
resultMap.name = "battery"
|
||||
resultMap.value = Math.round(Integer.parseInt(descMap.value, 16) / 2)
|
||||
if (device.getDataValue("manufacturer") == "Yale") { //Handling issue with Yale locks incorrect battery reporting
|
||||
resultMap.value = Integer.parseInt(descMap.value, 16)
|
||||
}
|
||||
log.info "parseReportAttributeMessage() --- battery: ${resultMap.value}"
|
||||
}
|
||||
else if (descMap.clusterInt == CLUSTER_DOORLOCK && descMap.attrInt == DOORLOCK_ATTR_LOCKSTATE) {
|
||||
def value = Integer.parseInt(descMap.value, 16)
|
||||
resultMap.name = "lock"
|
||||
resultMap.putAll([0:["value":"unknown",
|
||||
"descriptionText":"Not fully locked"],
|
||||
1:["value":"locked"],
|
||||
2:["value":"unlocked"]].get(value,
|
||||
["value":"unknown",
|
||||
"descriptionText":"Unknown lock state"]))
|
||||
log.info "parseReportAttributeMessage() --- lock: ${resultMap.value}"
|
||||
}
|
||||
else {
|
||||
log.debug "parseReportAttributeMessage() --- ignoring attribute"
|
||||
}
|
||||
return resultMap
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
/**
|
||||
* Copyright 2015 SmartThings
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* 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: "ZigBee Switch Power", namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Actuator"
|
||||
capability "Configuration"
|
||||
capability "Refresh"
|
||||
capability "Power Meter"
|
||||
capability "Sensor"
|
||||
capability "Switch"
|
||||
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0B04"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0702"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B05,0702", outClusters: "0003, 000A,0019", manufacturer: "Jasco Products", model: "45853", deviceJoinName: "GE ZigBee Plug-In Switch"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B05,0702", outClusters: "000A,0019", manufacturer: "Jasco Products", model: "45856", deviceJoinName: "GE ZigBee In-Wall Switch"
|
||||
}
|
||||
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
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 "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
}
|
||||
tileAttribute ("power", key: "SECONDARY_CONTROL") {
|
||||
attributeState "power", label:'${currentValue} W'
|
||||
}
|
||||
}
|
||||
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
main "switch"
|
||||
details(["switch", "refresh"])
|
||||
}
|
||||
}
|
||||
|
||||
// Parse incoming device messages to generate events
|
||||
def parse(String description) {
|
||||
log.debug "description is $description"
|
||||
|
||||
def resultMap = zigbee.getKnownDescription(description)
|
||||
if (resultMap) {
|
||||
log.info resultMap
|
||||
if (resultMap.type == "update") {
|
||||
log.info "$device updates: ${resultMap.value}"
|
||||
}
|
||||
else if (resultMap.type == "power") {
|
||||
def powerValue
|
||||
if (device.getDataValue("manufacturer") != "OSRAM") { //OSRAM devices do not reliably update power
|
||||
powerValue = (resultMap.value as Integer)/10 //TODO: The divisor value needs to be set as part of configuration
|
||||
sendEvent(name: "power", value: powerValue)
|
||||
}
|
||||
}
|
||||
else {
|
||||
sendEvent(name: resultMap.type, value: resultMap.value)
|
||||
}
|
||||
}
|
||||
else {
|
||||
log.warn "DID NOT PARSE MESSAGE for description : $description"
|
||||
log.debug zigbee.parseDescriptionAsMap(description)
|
||||
}
|
||||
}
|
||||
|
||||
def off() {
|
||||
zigbee.off()
|
||||
}
|
||||
|
||||
def on() {
|
||||
zigbee.on()
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
zigbee.onOffRefresh() + zigbee.simpleMeteringPowerRefresh() + zigbee.electricMeasurementPowerRefresh() + zigbee.onOffConfig() + zigbee.simpleMeteringPowerConfig() + zigbee.electricMeasurementPowerConfig()
|
||||
}
|
||||
|
||||
def configure() {
|
||||
log.debug "Configuring Reporting and Bindings."
|
||||
zigbee.onOffConfig() + zigbee.simpleMeteringPowerConfig() + zigbee.electricMeasurementPowerConfig() + zigbee.onOffRefresh() + zigbee.simpleMeteringPowerRefresh() + zigbee.electricMeasurementPowerRefresh()
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
/**
|
||||
* Copyright 2015 SmartThings
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* 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: "ZigBee Switch", namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Actuator"
|
||||
capability "Configuration"
|
||||
capability "Refresh"
|
||||
capability "Sensor"
|
||||
capability "Switch"
|
||||
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006"
|
||||
}
|
||||
|
||||
// simulator metadata
|
||||
simulator {
|
||||
// status messages
|
||||
status "on": "on/off: 1"
|
||||
status "off": "on/off: 0"
|
||||
|
||||
// reply messages
|
||||
reply "zcl on-off on": "on/off: 1"
|
||||
reply "zcl on-off off": "on/off: 0"
|
||||
}
|
||||
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
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 "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
}
|
||||
}
|
||||
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
main "switch"
|
||||
details(["switch", "refresh"])
|
||||
}
|
||||
}
|
||||
|
||||
// Parse incoming device messages to generate events
|
||||
def parse(String description) {
|
||||
log.debug "description is $description"
|
||||
|
||||
def resultMap = zigbee.getKnownDescription(description)
|
||||
if (resultMap) {
|
||||
log.info resultMap
|
||||
if (resultMap.type == "update") {
|
||||
log.info "$device updates: ${resultMap.value}"
|
||||
}
|
||||
else {
|
||||
sendEvent(name: resultMap.type, value: resultMap.value)
|
||||
}
|
||||
}
|
||||
else {
|
||||
log.warn "DID NOT PARSE MESSAGE for description : $description"
|
||||
log.debug zigbee.parseDescriptionAsMap(description)
|
||||
}
|
||||
}
|
||||
|
||||
def off() {
|
||||
zigbee.off()
|
||||
}
|
||||
|
||||
def on() {
|
||||
zigbee.on()
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
zigbee.onOffRefresh() + zigbee.onOffConfig()
|
||||
}
|
||||
|
||||
def configure() {
|
||||
log.debug "Configuring Reporting and Bindings."
|
||||
zigbee.onOffConfig() + zigbee.onOffRefresh()
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
/**
|
||||
* Copyright 2015 SmartThings
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* ZigBee White Color Temperature Bulb
|
||||
*
|
||||
* Author: SmartThings
|
||||
* Date: 2015-09-22
|
||||
*/
|
||||
|
||||
metadata {
|
||||
definition (name: "ZigBee White Color Temperature Bulb", namespace: "smartthings", author: "SmartThings") {
|
||||
|
||||
capability "Actuator"
|
||||
capability "Color Temperature"
|
||||
capability "Configuration"
|
||||
capability "Refresh"
|
||||
capability "Sensor"
|
||||
capability "Switch"
|
||||
capability "Switch Level"
|
||||
|
||||
attribute "colorName", "string"
|
||||
command "setGenericName"
|
||||
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04", outClusters: "0019"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY BR Tunable White", deviceJoinName: "OSRAM LIGHTIFY LED Flood BR30 Tunable White"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY RT Tunable White", deviceJoinName: "OSRAM LIGHTIFY LED Recessed Kit RT 5/6 Tunable White"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Classic A60 TW", deviceJoinName: "OSRAM LIGHTIFY LED Tunable White 60W"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY A19 Tunable White", deviceJoinName: "OSRAM LIGHTIFY LED Tunable White 60W"
|
||||
}
|
||||
|
||||
// UI tile definitions
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
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 "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
}
|
||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
||||
attributeState "level", action:"switch level.setLevel"
|
||||
}
|
||||
tileAttribute ("colorName", key: "SECONDARY_CONTROL") {
|
||||
attributeState "colorName", label:'${currentValue}'
|
||||
}
|
||||
}
|
||||
|
||||
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
|
||||
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2700..6500)") {
|
||||
state "colorTemperature", action:"color temperature.setColorTemperature"
|
||||
}
|
||||
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "colorTemperature", label: '${currentValue} K'
|
||||
}
|
||||
|
||||
main(["switch"])
|
||||
details(["switch", "colorTempSliderControl", "colorTemp", "refresh"])
|
||||
}
|
||||
}
|
||||
|
||||
// Parse incoming device messages to generate events
|
||||
def parse(String description) {
|
||||
log.debug "description is $description"
|
||||
|
||||
def finalResult = zigbee.getKnownDescription(description)
|
||||
if (finalResult) {
|
||||
log.info finalResult
|
||||
if (finalResult.type == "update") {
|
||||
log.info "$device updates: ${finalResult.value}"
|
||||
}
|
||||
else {
|
||||
sendEvent(name: finalResult.type, value: finalResult.value)
|
||||
}
|
||||
}
|
||||
else {
|
||||
log.warn "DID NOT PARSE MESSAGE for description : $description"
|
||||
log.debug zigbee.parseDescriptionAsMap(description)
|
||||
}
|
||||
}
|
||||
|
||||
def off() {
|
||||
zigbee.off()
|
||||
}
|
||||
|
||||
def on() {
|
||||
zigbee.on()
|
||||
}
|
||||
|
||||
def setLevel(value) {
|
||||
zigbee.setLevel(value)
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh() + zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig()
|
||||
}
|
||||
|
||||
def configure() {
|
||||
log.debug "Configuring Reporting and Bindings."
|
||||
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh()
|
||||
}
|
||||
|
||||
def setColorTemperature(value) {
|
||||
setGenericName(value)
|
||||
zigbee.setColorTemperature(value)
|
||||
}
|
||||
|
||||
//Naming based on the wiki article here: http://en.wikipedia.org/wiki/Color_temperature
|
||||
def setGenericName(value){
|
||||
if (value != null) {
|
||||
def genericName = "White"
|
||||
if (value < 3300) {
|
||||
genericName = "Soft White"
|
||||
} else if (value < 4150) {
|
||||
genericName = "Moonlight"
|
||||
} else if (value <= 5000) {
|
||||
genericName = "Cool White"
|
||||
} else if (value >= 5000) {
|
||||
genericName = "Daylight"
|
||||
}
|
||||
sendEvent(name: "colorName", value: genericName)
|
||||
}
|
||||
}
|
||||
48
devicetypes/superuser/switch-too.src/switch-too.groovy
Normal file
48
devicetypes/superuser/switch-too.src/switch-too.groovy
Normal file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Switch Too
|
||||
*
|
||||
* Copyright 2015 Bob Florian
|
||||
*
|
||||
* 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: "Switch Too", author: "Bob Florian") {
|
||||
capability "Switch"
|
||||
}
|
||||
|
||||
simulator {
|
||||
// TODO: define status and reply messages here
|
||||
}
|
||||
|
||||
tiles {
|
||||
// TODO: define your main and details tiles here
|
||||
}
|
||||
}
|
||||
|
||||
// parse events into attributes
|
||||
def parse(String description) {
|
||||
log.debug "Parsing '${description}'"
|
||||
// TODO: handle 'switch' attribute
|
||||
|
||||
}
|
||||
|
||||
// handle commands
|
||||
def on() {
|
||||
log.debug "Executing 'on'"
|
||||
// TODO: handle 'on' command
|
||||
}
|
||||
|
||||
def off() {
|
||||
log.debug "Executing 'off'"
|
||||
// TODO: handle 'off' command
|
||||
}
|
||||
|
||||
|
||||
@@ -20,8 +20,7 @@ definition(
|
||||
description: "Use this free SmartApp in conjunction with the ObyThing Music app for your Mac to control and automate music and more with iTunes and SmartThings.",
|
||||
category: "SmartThings Labs",
|
||||
iconUrl: "http://obycode.com/obything/ObyThingSTLogo.png",
|
||||
iconX2Url: "http://obycode.com/obything/ObyThingSTLogo@2x.png",
|
||||
singleInstance: true)
|
||||
iconX2Url: "http://obycode.com/obything/ObyThingSTLogo@2x.png")
|
||||
|
||||
|
||||
preferences {
|
||||
|
||||
@@ -22,8 +22,7 @@ definition(
|
||||
category: "SmartThings Labs",
|
||||
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/netamo-icon-1.png",
|
||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/netamo-icon-1%402x.png",
|
||||
oauth: true,
|
||||
singleInstance: true
|
||||
oauth: true
|
||||
){
|
||||
appSetting "clientId"
|
||||
appSetting "clientSecret"
|
||||
|
||||
@@ -13,11 +13,11 @@ definition(
|
||||
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/jawbone-up.png",
|
||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/jawbone-up@2x.png",
|
||||
oauth: true,
|
||||
usePreferencesForAuthorization: false,
|
||||
singleInstance: true
|
||||
usePreferencesForAuthorization: false
|
||||
) {
|
||||
appSetting "clientId"
|
||||
appSetting "clientSecret"
|
||||
appSetting "serverUrl"
|
||||
}
|
||||
|
||||
preferences {
|
||||
@@ -28,13 +28,16 @@ mappings {
|
||||
path("/receivedToken") { action: [ POST: "receivedToken", GET: "receivedToken"] }
|
||||
path("/receiveToken") { action: [ POST: "receiveToken", GET: "receiveToken"] }
|
||||
path("/hookCallback") { action: [ POST: "hookEventHandler", GET: "hookEventHandler"] }
|
||||
path("/oauth/initialize") {action: [GET: "oauthInitUrl"]}
|
||||
path("/oauth/callback") { action: [ GET: "callback" ] }
|
||||
}
|
||||
|
||||
def getServerUrl() { return "https://graph.api.smartthings.com" }
|
||||
def getBuildRedirectUrl() { "${serverUrl}/oauth/initialize?appId=${app.id}&access_token=${state.accessToken}&apiServerUrl=${apiServerUrl}" }
|
||||
def buildRedirectUrl(page) { return buildActionUrl(page) }
|
||||
def getSmartThingsClientId() {
|
||||
return appSettings.clientId
|
||||
}
|
||||
|
||||
def getSmartThingsClientSecret() {
|
||||
return appSettings.clientSecret
|
||||
}
|
||||
|
||||
def callback() {
|
||||
def redirectUrl = null
|
||||
@@ -60,8 +63,9 @@ def callback() {
|
||||
// SmartThings code, which we ignore, as we don't need to exchange for an access token.
|
||||
// Instead, go initiate the Jawbone OAuth flow.
|
||||
log.debug "Executing callback redirect to auth page"
|
||||
def stcid = getSmartThingsClientId()
|
||||
state.oauthInitState = UUID.randomUUID().toString()
|
||||
def oauthParams = [response_type: "code", client_id: appSettings.clientId, scope: "move_read sleep_read", redirect_uri: "${serverUrl}/oauth/callback"]
|
||||
def oauthParams = [response_type: "code", client_id: stcid, scope: "move_read sleep_read", redirect_uri: "${serverUrl}/oauth/callback"]
|
||||
redirect(location: "https://jawbone.com/auth/oauth2/auth?${toQueryString(oauthParams)}")
|
||||
}
|
||||
} else {
|
||||
@@ -80,11 +84,10 @@ def authPage() {
|
||||
createAccessToken()
|
||||
}
|
||||
description = "Click to enter Jawbone Credentials"
|
||||
def redirectUrl = buildRedirectUrl
|
||||
log.debug "RedirectURL = ${redirectUrl}"
|
||||
def donebutton= state.JawboneAccessToken != null
|
||||
return dynamicPage(name: "Credentials", title: "Jawbone UP", nextPage: null, uninstall: true, install: donebutton) {
|
||||
section { href url:redirectUrl, style:"embedded", required:true, title:"Jawbone UP", state: hast ,description:description }
|
||||
def redirectUrl = oauthInitUrl()
|
||||
// log.debug "RedirectURL = ${redirectUrl}"
|
||||
return dynamicPage(name: "Credentials", title: "Jawbone UP", nextPage: null, uninstall: true, install:false) {
|
||||
section { href url:redirectUrl, style:"embedded", required:true, title:"Jawbone UP", description:description }
|
||||
}
|
||||
} else {
|
||||
description = "Jawbone Credentials Already Entered."
|
||||
@@ -96,14 +99,17 @@ def authPage() {
|
||||
|
||||
def oauthInitUrl() {
|
||||
log.debug "oauthInitUrl"
|
||||
def stcid = getSmartThingsClientId()
|
||||
state.oauthInitState = UUID.randomUUID().toString()
|
||||
def oauthParams = [ response_type: "code", client_id: appSettings.clientId, scope: "move_read sleep_read", redirect_uri: "${serverUrl}/oauth/callback" ]
|
||||
redirect(location: "https://jawbone.com/auth/oauth2/auth?${toQueryString(oauthParams)}")
|
||||
def oauthParams = [ response_type: "code", client_id: stcid, scope: "move_read sleep_read", redirect_uri: buildRedirectUrl("receiveToken") ]
|
||||
return "https://jawbone.com/auth/oauth2/auth?${toQueryString(oauthParams)}"
|
||||
}
|
||||
|
||||
def receiveToken(redirectUrl = null) {
|
||||
log.debug "receiveToken"
|
||||
def oauthParams = [ client_id: appSettings.clientId, client_secret: appSettings.clientSecret, grant_type: "authorization_code", code: params.code ]
|
||||
def stcid = getSmartThingsClientId()
|
||||
def oauthClientSecret = getSmartThingsClientSecret()
|
||||
def oauthParams = [ client_id: stcid, client_secret: oauthClientSecret, grant_type: "authorization_code", code: params.code ]
|
||||
def params = [
|
||||
uri: "https://jawbone.com/auth/oauth2/token?${toQueryString(oauthParams)}",
|
||||
]
|
||||
@@ -225,10 +231,18 @@ String toQueryString(Map m) {
|
||||
return m.collect { k, v -> "${k}=${URLEncoder.encode(v.toString())}" }.sort().join("&")
|
||||
}
|
||||
|
||||
def getServerUrl() { return appSettings.serverUrl ?: "https://graph.api.smartthings.com" }
|
||||
|
||||
def buildRedirectUrl(page) {
|
||||
// log.debug "buildRedirectUrl"
|
||||
// /api/token/:st_token/smartapps/installations/:id/something
|
||||
return "${serverUrl}/api/token/${state.accessToken}/smartapps/installations/${app.id}/${page}"
|
||||
}
|
||||
|
||||
def validateCurrentToken() {
|
||||
log.debug "validateCurrentToken"
|
||||
def url = "https://jawbone.com/nudge/api/v.1.1/users/@me/refreshToken"
|
||||
def requestBody = "secret=${appSettings.clientSecret}"
|
||||
def requestBody = "secret=${getSmartThingsClientSecret()}"
|
||||
|
||||
try {
|
||||
httpPost(uri: url, headers: ["Authorization": "Bearer ${state.JawboneAccessToken}" ], body: requestBody) {response ->
|
||||
@@ -242,7 +256,9 @@ def validateCurrentToken() {
|
||||
if (e.statusCode == 401) { // token is expired
|
||||
log.debug "Access token is expired"
|
||||
if (state.refreshToken) { // if we have this we are okay
|
||||
def oauthParams = [client_id: appSettings.clientId, client_secret: appSettings.clientSecret, grant_type: "refresh_token", refresh_token: state.refreshToken]
|
||||
def stcid = getSmartThingsClientId()
|
||||
def oauthClientSecret = getSmartThingsClientSecret()
|
||||
def oauthParams = [client_id: stcid, client_secret: oauthClientSecret, grant_type: "refresh_token", refresh_token: state.refreshToken]
|
||||
def tokenUrl = "https://jawbone.com/auth/oauth2/token?${toQueryString(oauthParams)}"
|
||||
def params = [
|
||||
uri: tokenUrl
|
||||
@@ -271,10 +287,9 @@ def validateCurrentToken() {
|
||||
}
|
||||
|
||||
def initialize() {
|
||||
log.debug "Callback URL - Webhook"
|
||||
def localServerUrl = getApiServerUrl()
|
||||
def hookUrl = "${localServerUrl}/api/token/${state.accessToken}/smartapps/installations/${app.id}/hookCallback"
|
||||
def webhook = "https://jawbone.com/nudge/api/v.1.1/users/@me/pubsub?webhook=$hookUrl"
|
||||
def hookUrl = "${serverUrl}/api/token/${state.accessToken}/smartapps/installations/${app.id}/hookCallback"
|
||||
def webhook = "https://jawbone.com/nudge/api/v.1.1/users/@me/pubsub?webhook=$hookUrl"
|
||||
log.debug "Callback URL: $webhook"
|
||||
httpPost(uri: webhook, headers: ["Authorization": "Bearer ${state.JawboneAccessToken}" ])
|
||||
}
|
||||
|
||||
@@ -312,6 +327,7 @@ def setup() {
|
||||
}
|
||||
|
||||
def installed() {
|
||||
enableCallback()
|
||||
|
||||
if (!state.accessToken) {
|
||||
log.debug "About to create access token"
|
||||
@@ -324,6 +340,7 @@ def installed() {
|
||||
}
|
||||
|
||||
def updated() {
|
||||
enableCallback()
|
||||
|
||||
if (!state.accessToken) {
|
||||
log.debug "About to create access token"
|
||||
@@ -482,4 +499,4 @@ def hookEventHandler() {
|
||||
|
||||
def html = """{"code":200,"message":"OK"}"""
|
||||
render contentType: 'application/json', data: html
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
*
|
||||
* Lights On When Door Open After Sundown
|
||||
*
|
||||
* Based on "Turn It On When It Opens" by SmartThings
|
||||
*
|
||||
* Author: Aaron Crocco
|
||||
*/
|
||||
preferences {
|
||||
section("When the door opens..."){
|
||||
input "contact1", "capability.contactSensor", title: "Where?"
|
||||
}
|
||||
section("Turn on these lights..."){
|
||||
input "switches", "capability.switch", multiple: true
|
||||
}
|
||||
section("and change mode to...") {
|
||||
input "HomeAfterDarkMode", "mode", title: "Mode?"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def installed()
|
||||
{
|
||||
subscribe(contact1, "contact.open", contactOpenHandler)
|
||||
}
|
||||
|
||||
def updated()
|
||||
{
|
||||
unsubscribe()
|
||||
subscribe(contact1, "contact.open", contactOpenHandler)
|
||||
}
|
||||
|
||||
def contactOpenHandler(evt) {
|
||||
log.debug "$evt.value: $evt, $settings"
|
||||
|
||||
//Check current time to see if it's after sundown.
|
||||
def s = getSunriseAndSunset(zipCode: zipCode, sunriseOffset: sunriseOffset, sunsetOffset: sunsetOffset)
|
||||
def now = new Date()
|
||||
def setTime = s.sunset
|
||||
log.debug "Sunset is at $setTime. Current time is $now"
|
||||
|
||||
|
||||
if (setTime.before(now)) { //Executes only if it's after sundown.
|
||||
|
||||
log.trace "Turning on switches: $switches"
|
||||
switches.on()
|
||||
log.trace "Changing house mode to $HomeAfterDarkMode"
|
||||
setLocationMode(HomeAfterDarkMode)
|
||||
sendPush("Welcome home! Changing mode to $HomeAfterDarkMode.")
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,8 +26,7 @@ definition(
|
||||
iconUrl: "http://i.imgur.com/HU0ANBp.png",
|
||||
iconX2Url: "http://i.imgur.com/HU0ANBp.png",
|
||||
iconX3Url: "http://i.imgur.com/HU0ANBp.png",
|
||||
oauth: true,
|
||||
singleInstance: true)
|
||||
oauth: true)
|
||||
|
||||
|
||||
preferences {
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* The timeout perid begins when the contact is closed, or motion stops, so leaving a door open won't start the timer until it's closed.
|
||||
*
|
||||
* Author: andersheie@gmail.com
|
||||
* Date: 2015-10-30
|
||||
* Date: 2014-08-31
|
||||
*/
|
||||
|
||||
definition(
|
||||
@@ -23,18 +23,12 @@ definition(
|
||||
iconX2Url: "http://upload.wikimedia.org/wikipedia/commons/6/6a/Light_bulb_icon_tips.svg")
|
||||
|
||||
preferences {
|
||||
section("Turn on when there is movement..."){
|
||||
section("Turn on when there's movement..."){
|
||||
input "motions", "capability.motionSensor", multiple: true, title: "Select motion detectors", required: false
|
||||
}
|
||||
section("Or, turn on when one of these contacts opened"){
|
||||
input "contacts", "capability.contactSensor", multiple: true, title: "Select Contacts", required: false
|
||||
}
|
||||
section("Or, turn on when any of these people come home") {
|
||||
input "peoplearrive", "capability.presenceSensor", multiple: true, required: false
|
||||
}
|
||||
section("Or, turn on when any of these people leave") {
|
||||
input "peopleleave", "capability.presenceSensor", multiple: true, required: false
|
||||
}
|
||||
section("And off after no more triggers after..."){
|
||||
input "minutes1", "number", title: "Minutes?", defaultValue: "5"
|
||||
}
|
||||
@@ -46,232 +40,109 @@ preferences {
|
||||
|
||||
def installed()
|
||||
{
|
||||
log.debug "installed()"
|
||||
initialize()
|
||||
subscribe(switches, "switch", switchChange)
|
||||
subscribe(motions, "motion", motionHandler)
|
||||
subscribe(contacts, "contact", contactHandler)
|
||||
schedule("0 * * * * ?", "scheduleCheck")
|
||||
state.myState = "ready"
|
||||
}
|
||||
|
||||
|
||||
def updated()
|
||||
{
|
||||
log.debug "updated()"
|
||||
initialize()
|
||||
}
|
||||
|
||||
def initialize()
|
||||
{
|
||||
log.debug "initialize()"
|
||||
// Reset to new set of Switches, just in case.
|
||||
unsubscribe()
|
||||
|
||||
state.switches = [:]
|
||||
|
||||
switches.each {
|
||||
// log.debug "ID: $it.id"
|
||||
// Set ready state for each switch
|
||||
state.switches["$it.id"] = "ready"
|
||||
}
|
||||
logStates()
|
||||
unsubscribe()
|
||||
subscribe(motions, "motion", motionHandler)
|
||||
subscribe(switches, "switch", switchChange)
|
||||
subscribe(contacts, "contact", contactHandler)
|
||||
subscribe(peoplearrive, "presence.present", presencePresentHandler)
|
||||
subscribe(peopleleave, "presence.not present", presenceNotPresentHandler)
|
||||
state.lastAction = null
|
||||
schedule("0 * * * * ?", "scheduleCheck")
|
||||
|
||||
}
|
||||
|
||||
def logStates() {
|
||||
if(state.switches == null) {
|
||||
state.switches = [:]
|
||||
}
|
||||
state.switches.each {
|
||||
log.debug "State: $it.key = $it.value"
|
||||
}
|
||||
}
|
||||
|
||||
def turnSwitchOn(id) {
|
||||
for(S in switches) {
|
||||
if(S.id == id) {
|
||||
S.on()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def turnSwitchOff(id) {
|
||||
for(S in switches) {
|
||||
if(S.id == id) {
|
||||
S.off()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def initState(deviceID) {
|
||||
// Have to add this so existing apps that are neither updated nor initiated can slowly get 'reset' with states.
|
||||
// Smartthings doesn't call any specifc method when new version is published.
|
||||
|
||||
if(state.switches == null) {
|
||||
state.switches = [:]
|
||||
}
|
||||
if(state.switches[deviceID] == null) {
|
||||
state.switches[deviceID] = "ready"
|
||||
}
|
||||
|
||||
state.myState = "ready"
|
||||
log.debug "state: " + state.myState
|
||||
}
|
||||
|
||||
def switchChange(evt) {
|
||||
def deviceID = evt.device.id
|
||||
log.debug "SwitchChange: $evt.name: $evt.value $evt.device.id current state = " + state.switches[deviceID]
|
||||
initState(deviceID)
|
||||
log.debug "SwitchChange: $evt.name: $evt.value"
|
||||
|
||||
if(evt.value == "on") {
|
||||
if(state.switches[deviceID] == "activating") {
|
||||
// OK, probably an event from Activating something, and not the switch itself. Go to Active mode.
|
||||
log.debug "$deviceID = " + state.switches[deviceID] + " -> active"
|
||||
state.switches[deviceID] = "active"
|
||||
} else if(state.switches[deviceID] != "active") {
|
||||
log.debug "$deviceID = " + state.switches[deviceID] + " -> already on"
|
||||
state.switches[deviceID] = "already on"
|
||||
}
|
||||
if(evt.value == "on") {
|
||||
// Slight change of Race condition between motion or contact turning the switch on,
|
||||
// versus user turning the switch on. Since we can't pass event parameters :-(, we rely
|
||||
// on the state and hope the best.
|
||||
if(state.myState == "activating") {
|
||||
// OK, probably an event from Activating something, and not the switch itself. Go to Active mode.
|
||||
state.myState = "active"
|
||||
} else if(state.myState != "active") {
|
||||
state.myState = "already on"
|
||||
}
|
||||
} else {
|
||||
// If active and switch is turned of manually, then stop the schedule and go to ready state
|
||||
state.switches[deviceID] = "ready"
|
||||
// If active and switch is turned of manually, then stop the schedule and go to ready state
|
||||
if(state.myState == "active" || state.myState == "activating") {
|
||||
unschedule()
|
||||
}
|
||||
state.myState = "ready"
|
||||
}
|
||||
logStates()
|
||||
|
||||
log.debug "state: " + state.myState
|
||||
}
|
||||
|
||||
def contactHandler(evt) {
|
||||
log.debug "contactHandler: $evt.name: $evt.value"
|
||||
|
||||
state.switches.each { thisswitch ->
|
||||
initState(thisswitch.key)
|
||||
//log.debug "Looking for $thisswitch.key"
|
||||
if (evt.value == "open") {
|
||||
if(state.switches[thisswitch.key] == "ready") {
|
||||
log.debug "Turning on lights by contact opening: $thisswitch"
|
||||
log.debug "$thisswitch.key = " + state.switches[thisswitch.key] + " -> activating"
|
||||
state.switches[thisswitch.key] = "activating"
|
||||
turnSwitchOn(thisswitch.key)
|
||||
setActiveAndSchedule()
|
||||
|
||||
}
|
||||
} else if (evt.value == "closed") {
|
||||
if (!state.lastAction && (state.switches[thisswitch.key] == "active"
|
||||
|| state.switches[thisswitch.key] == "activating")) {
|
||||
// When contact closes, we reset the timer if not already set
|
||||
log.debug "$thisswitch.key = " + state.switches[thisswitch.key] + " -> active"
|
||||
state.switches[thisswitch.key] = "active"
|
||||
setActiveAndSchedule()
|
||||
}
|
||||
if (evt.value == "open") {
|
||||
if(state.myState == "ready") {
|
||||
log.debug "Turning on lights by contact opening"
|
||||
switches.on()
|
||||
state.inactiveAt = null
|
||||
state.myState = "activating"
|
||||
}
|
||||
} else if (evt.value == "closed") {
|
||||
if (!state.inactiveAt && state.myState == "active" || state.myState == "activating") {
|
||||
// When contact closes, we reset the timer if not already set
|
||||
setActiveAndSchedule()
|
||||
}
|
||||
}
|
||||
logStates()
|
||||
log.debug "state: " + state.myState
|
||||
}
|
||||
|
||||
def scheduleCheck() {
|
||||
log.debug "schedule check, ts = ${state.lastAction}"
|
||||
boolean resetInactive = false
|
||||
state.switches.each { thisswitch ->
|
||||
initState(thisswitch.key)
|
||||
if(state.switches[thisswitch.key] != "already on") {
|
||||
if(state.lastAction != null) {
|
||||
def elapsed = now() - state.lastAction
|
||||
log.debug "${elapsed / 1000} sec since events stopped"
|
||||
def threshold = 1000 * 60 * minutes1
|
||||
if (elapsed >= threshold) {
|
||||
if (state.switches[thisswitch.key] == "active"
|
||||
|| state.switches[thisswitch.key] == "activating") {
|
||||
state.switches[thisswitch.key] = "ready"
|
||||
log.debug "Turning off lights by switch closing: $thisswitch"
|
||||
turnSwitchOff(thisswitch.key)
|
||||
}
|
||||
resetInactive = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(resetInactive) {
|
||||
state.lastAction = null
|
||||
unschedule()
|
||||
}
|
||||
logStates()
|
||||
}
|
||||
|
||||
|
||||
/**********************************************************************/
|
||||
|
||||
def motionHandler(evt) {
|
||||
log.debug "motionHandler: $evt.name: $evt.value (current state: " + state.myState + ")"
|
||||
log.debug "motionHandler: $evt.name: $evt.value"
|
||||
|
||||
|
||||
state.switches.each { thisswitch ->
|
||||
initState(thisswitch.key)
|
||||
if (evt.value == "active") {
|
||||
if(state.switches[thisswitch.key] == "ready"
|
||||
|| state.switches[thisswitch.key] == "active"
|
||||
|| state.switches[thisswitch.key] == "activating" ) {
|
||||
log.debug "$thisswitch.key = " + state.switches[thisswitch.key] + " -> activating"
|
||||
state.switches[thisswitch.key] = "activating"
|
||||
turnSwitchOn(thisswitch.key)
|
||||
setActiveAndSchedule()
|
||||
}
|
||||
} else if (evt.value == "inactive") {
|
||||
if (state.switches[thisswitch.key] == "active" || state.switches[thisswitch.key] == "activating") {
|
||||
// When Motion ends, we reset the timer if not already set
|
||||
log.debug "$thisswitch.key = " + state.switches[thisswitch.key] + " -> active"
|
||||
state.switches[thisswitch.key] = "active"
|
||||
setActiveAndSchedule()
|
||||
}
|
||||
if (evt.value == "active") {
|
||||
if(state.myState == "ready" || state.myState == "active" || state.myState == "activating" ) {
|
||||
log.debug "turning on lights"
|
||||
switches.on()
|
||||
state.inactiveAt = null
|
||||
state.myState = "activating"
|
||||
}
|
||||
} else if (evt.value == "inactive") {
|
||||
if (!state.inactiveAt && state.myState == "active" || state.myState == "activating") {
|
||||
// When Motion ends, we reset the timer if not already set
|
||||
setActiveAndSchedule()
|
||||
}
|
||||
}
|
||||
logStates()
|
||||
}
|
||||
|
||||
def presencePresentHandler(evt) {
|
||||
log.debug "presence: $evt.linkText is now $evt.value"
|
||||
|
||||
state.switches.each { thisswitch ->
|
||||
initState(thisswitch.key)
|
||||
if (evt.value == "present") {
|
||||
if(state.switches[thisswitch.key] == "ready"
|
||||
|| state.switches[thisswitch.key] == "active"
|
||||
|| state.switches[thisswitch.key] == "activating" ) {
|
||||
log.debug "Presence turning on switch $thisswitch"
|
||||
state.switches[thisswitch.key] = "active"
|
||||
turnSwitchOn(thisswitch.key)
|
||||
// We don't wait until the person leave, but instead start timer immediately.
|
||||
setActiveAndSchedule()
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
logStates()
|
||||
}
|
||||
|
||||
def presenceNotPresentHandler(evt) {
|
||||
log.debug "presence: $evt.linkText is now $evt.value"
|
||||
|
||||
state.switches.each { thisswitch ->
|
||||
initState(thisswitch.key)
|
||||
if (evt.value == "not present") {
|
||||
if(state.switches[thisswitch.key] == "ready"
|
||||
|| state.switches[thisswitch.key] == "active"
|
||||
|| state.switches[thisswitch.key] == "activating" ) {
|
||||
log.debug "No Presence turning on lights $thisswitch"
|
||||
state.switches[thisswitch.key] = "active"
|
||||
turnSwitchOn(thisswitch.key)
|
||||
// We don't wait until the person arrive back, but instead start timer immediately.
|
||||
setActiveAndSchedule()
|
||||
}
|
||||
}
|
||||
}
|
||||
logStates()
|
||||
log.debug "state: " + state.myState
|
||||
}
|
||||
|
||||
def setActiveAndSchedule() {
|
||||
unschedule()
|
||||
state.lastAction = now()
|
||||
state.myState = "active"
|
||||
state.inactiveAt = now()
|
||||
schedule("0 * * * * ?", "scheduleCheck")
|
||||
log.debug "Scheduled new timer"
|
||||
}
|
||||
|
||||
def scheduleCheck() {
|
||||
log.debug "schedule check, ts = ${state.inactiveAt}"
|
||||
if(state.myState != "already on") {
|
||||
if(state.inactiveAt != null) {
|
||||
def elapsed = now() - state.inactiveAt
|
||||
log.debug "${elapsed / 1000} sec since motion stopped"
|
||||
def threshold = 1000 * 60 * minutes1
|
||||
if (elapsed >= threshold) {
|
||||
if (state.myState == "active") {
|
||||
log.debug "turning off lights"
|
||||
switches.off()
|
||||
}
|
||||
state.inactiveAt = null
|
||||
state.myState = "ready"
|
||||
}
|
||||
}
|
||||
}
|
||||
log.debug "state: " + state.myState
|
||||
}
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
*
|
||||
*/
|
||||
definition(
|
||||
name: "Smart Auto Lock / Unlock",
|
||||
namespace: "smart-auto-lock-unlock",
|
||||
name: "Smart Lock / Unlock",
|
||||
namespace: "",
|
||||
author: "Arnaud",
|
||||
description: "Automatically locks door X minutes after being closed and keeps door unlocked if door is open.",
|
||||
category: "Safety & Security",
|
||||
@@ -21,8 +21,7 @@
|
||||
category: "SmartThings Labs",
|
||||
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
|
||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
|
||||
iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
|
||||
singleInstance: true
|
||||
iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png"
|
||||
)
|
||||
|
||||
preferences {
|
||||
|
||||
@@ -26,8 +26,7 @@ definition(
|
||||
description: "Connect your Ecobee thermostat to SmartThings.",
|
||||
category: "SmartThings Labs",
|
||||
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/ecobee.png",
|
||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/ecobee@2x.png",
|
||||
singleInstance: true
|
||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/ecobee@2x.png"
|
||||
) {
|
||||
appSetting "clientId"
|
||||
appSetting "serverUrl"
|
||||
|
||||
@@ -23,8 +23,7 @@ definition(
|
||||
description: "Connect and take pictures using your Foscam camera from inside the Smartthings app.",
|
||||
category: "SmartThings Internal",
|
||||
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/foscam.png",
|
||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/foscam@2x.png",
|
||||
singleInstance: true
|
||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/foscam@2x.png"
|
||||
)
|
||||
|
||||
preferences {
|
||||
|
||||
@@ -23,8 +23,7 @@ definition(
|
||||
description: "Allows you to connect your Philips Hue lights with SmartThings and control them from your Things area or Dashboard in the SmartThings Mobile app. Adjust colors by going to the Thing detail screen for your Hue lights (tap the gear on Hue tiles).\n\nPlease update your Hue Bridge first, outside of the SmartThings app, using the Philips Hue app.",
|
||||
category: "SmartThings Labs",
|
||||
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/hue.png",
|
||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/hue@2x.png",
|
||||
singleInstance: true
|
||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/hue@2x.png"
|
||||
)
|
||||
|
||||
preferences {
|
||||
@@ -144,7 +143,7 @@ def bulbDiscovery() {
|
||||
if (numFound == 0)
|
||||
app.updateSetting("selectedBulbs", "")
|
||||
|
||||
if((bulbRefreshCount % 5) == 0) {
|
||||
if((bulbRefreshCount % 3) == 0) {
|
||||
discoverHueBulbs()
|
||||
}
|
||||
|
||||
@@ -319,15 +318,11 @@ def addBulbs() {
|
||||
def newHueBulb
|
||||
if (bulbs instanceof java.util.Map) {
|
||||
newHueBulb = bulbs.find { (app.id + "/" + it.value.id) == dni }
|
||||
if (newHueBulb != null) {
|
||||
if (newHueBulb?.value?.type?.equalsIgnoreCase("Dimmable light") ) {
|
||||
d = addChildDevice("smartthings", "Hue Lux Bulb", dni, newHueBulb?.value.hub, ["label":newHueBulb?.value.name])
|
||||
} else {
|
||||
d = addChildDevice("smartthings", "Hue Bulb", dni, newHueBulb?.value.hub, ["label":newHueBulb?.value.name])
|
||||
}
|
||||
} else {
|
||||
log.debug "$dni in not longer paired to the Hue Bridge or ID changed"
|
||||
}
|
||||
if (newHueBulb?.value?.type?.equalsIgnoreCase("Dimmable light")) {
|
||||
d = addChildDevice("smartthings", "Hue Lux Bulb", dni, newHueBulb?.value.hub, ["label":newHueBulb?.value.name])
|
||||
} else {
|
||||
d = addChildDevice("smartthings", "Hue Bulb", dni, newHueBulb?.value.hub, ["label":newHueBulb?.value.name])
|
||||
}
|
||||
} else {
|
||||
//backwards compatable
|
||||
newHueBulb = bulbs.find { (app.id + "/" + it.id) == dni }
|
||||
@@ -609,22 +604,23 @@ def parse(childDevice, description) {
|
||||
}
|
||||
}
|
||||
|
||||
def on(childDevice) {
|
||||
def on(childDevice, transition_deprecated = 0) {
|
||||
log.debug "Executing 'on'"
|
||||
put("lights/${getId(childDevice)}/state", [on: true])
|
||||
return "Bulb is On"
|
||||
def percent = childDevice.device?.currentValue("level") as Integer
|
||||
def level = Math.min(Math.round(percent * 255 / 100), 255)
|
||||
put("lights/${getId(childDevice)}/state", [bri: level, on: true])
|
||||
return "level: $percent"
|
||||
}
|
||||
|
||||
def off(childDevice) {
|
||||
def off(childDevice, transition_deprecated = 0) {
|
||||
log.debug "Executing 'off'"
|
||||
put("lights/${getId(childDevice)}/state", [on: false])
|
||||
return "Bulb is Off"
|
||||
return "level: 0"
|
||||
}
|
||||
|
||||
def setLevel(childDevice, percent) {
|
||||
log.debug "Executing 'setLevel'"
|
||||
def level
|
||||
if (percent == 1) level = 1 else level = Math.min(Math.round(percent * 255 / 100), 255)
|
||||
def level = Math.min(Math.round(percent * 255 / 100), 255)
|
||||
put("lights/${getId(childDevice)}/state", [bri: level, on: percent > 0])
|
||||
}
|
||||
|
||||
@@ -640,7 +636,7 @@ def setHue(childDevice, percent) {
|
||||
put("lights/${getId(childDevice)}/state", [hue: level])
|
||||
}
|
||||
|
||||
def setColor(childDevice, huesettings) {
|
||||
def setColor(childDevice, huesettings, alert_deprecated = "", transition_deprecated = 0) {
|
||||
log.debug "Executing 'setColor($huesettings)'"
|
||||
def hue = Math.min(Math.round(huesettings.hue * 65535 / 100), 65535)
|
||||
def sat = Math.min(Math.round(huesettings.saturation * 255 / 100), 255)
|
||||
@@ -649,7 +645,7 @@ def setColor(childDevice, huesettings) {
|
||||
|
||||
def value = [sat: sat, hue: hue, alert: alert, transitiontime: transition]
|
||||
if (huesettings.level != null) {
|
||||
if (huesettings.level == 1) value.bri = 1 else value.bri = Math.min(Math.round(huesettings.level * 255 / 100), 255)
|
||||
value.bri = Math.min(Math.round(huesettings.level * 255 / 100), 255)
|
||||
value.on = value.bri > 0
|
||||
}
|
||||
|
||||
@@ -727,6 +723,8 @@ private getBridgeIP() {
|
||||
def serialNumber = selectedHue
|
||||
def bridge = getHueBridges().find { it?.value?.serialNumber?.equalsIgnoreCase(serialNumber) }?.value
|
||||
if (!bridge) {
|
||||
//failed because mac address sent from hub is wrong and doesn't match the hue's real mac address and serial number
|
||||
//in this case we will look up the bridge by comparing the incorrect mac addresses
|
||||
bridge = getHueBridges().find { it?.value?.mac?.equalsIgnoreCase(serialNumber) }?.value
|
||||
}
|
||||
if (bridge?.ip && bridge?.port) {
|
||||
|
||||
@@ -98,15 +98,6 @@ def installed() {
|
||||
}
|
||||
|
||||
def updated() {
|
||||
def currentDeviceIds = settings.collect { k, devices -> devices }.flatten().collect { it.id }.unique()
|
||||
def subscriptionDevicesToRemove = app.subscriptions*.device.findAll { device ->
|
||||
!currentDeviceIds.contains(device.id)
|
||||
}
|
||||
subscriptionDevicesToRemove.each { device ->
|
||||
log.debug "Removing $device.displayName subscription"
|
||||
state.remove(device.id)
|
||||
unsubscribe(device)
|
||||
}
|
||||
log.debug settings
|
||||
}
|
||||
|
||||
|
||||
@@ -22,8 +22,7 @@ definition(
|
||||
category: "SmartThings Labs",
|
||||
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/life360.png",
|
||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/life360@2x.png",
|
||||
oauth: [displayName: "Life360", displayLink: "Life360"],
|
||||
singleInstance: true
|
||||
oauth: [displayName: "Life360", displayLink: "Life360"]
|
||||
) {
|
||||
appSetting "clientId"
|
||||
appSetting "clientSecret"
|
||||
|
||||
@@ -13,8 +13,7 @@ definition(
|
||||
iconUrl: "https://cloud.lifx.com/images/lifx.png",
|
||||
iconX2Url: "https://cloud.lifx.com/images/lifx.png",
|
||||
iconX3Url: "https://cloud.lifx.com/images/lifx.png",
|
||||
oauth: true,
|
||||
singleInstance: true) {
|
||||
oauth: true) {
|
||||
appSetting "clientId"
|
||||
appSetting "clientSecret"
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
* locks | lock | lock, unlock | locked, unlocked
|
||||
* ---------------------+-------------------+-----------------------------+------------------------------------
|
||||
*/
|
||||
|
||||
|
||||
definition(
|
||||
name: "Logitech Harmony (Connect)",
|
||||
namespace: "smartthings",
|
||||
@@ -43,11 +43,11 @@ definition(
|
||||
category: "SmartThings Labs",
|
||||
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/harmony.png",
|
||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/harmony%402x.png",
|
||||
oauth: [displayName: "Logitech Harmony", displayLink: "http://www.logitech.com/en-us/harmony-remotes"],
|
||||
singleInstance: true
|
||||
oauth: [displayName: "Logitech Harmony", displayLink: "http://www.logitech.com/en-us/harmony-remotes"]
|
||||
){
|
||||
appSetting "clientId"
|
||||
appSetting "clientSecret"
|
||||
appSetting "callbackUrl"
|
||||
}
|
||||
|
||||
preferences(oauthPage: "deviceAuthorization") {
|
||||
@@ -89,18 +89,16 @@ mappings {
|
||||
}
|
||||
|
||||
def getServerUrl() { return "https://graph.api.smartthings.com" }
|
||||
def getCallbackUrl() { "https://graph.api.smartthings.com/oauth/callback" }
|
||||
def getBuildRedirectUrl() { "${serverUrl}/oauth/initialize?appId=${app.id}&access_token=${state.accessToken}&apiServerUrl=${apiServerUrl}" }
|
||||
|
||||
def authPage() {
|
||||
def description = null
|
||||
def description = null
|
||||
if (!state.HarmonyAccessToken) {
|
||||
if (!state.accessToken) {
|
||||
log.debug "About to create access token"
|
||||
createAccessToken()
|
||||
}
|
||||
description = "Click to enter Harmony Credentials"
|
||||
def redirectUrl = buildRedirectUrl
|
||||
def redirectUrl = "${serverUrl}/oauth/initialize?appId=${app.id}&access_token=${state.accessToken}"
|
||||
return dynamicPage(name: "Credentials", title: "Harmony", nextPage: null, uninstall: true, install:false) {
|
||||
section { href url:redirectUrl, style:"embedded", required:true, title:"Harmony", description:description }
|
||||
}
|
||||
@@ -112,7 +110,7 @@ def authPage() {
|
||||
|
||||
def huboptions = state.HarmonyHubs ?: []
|
||||
def actoptions = state.HarmonyActivities ?: []
|
||||
|
||||
|
||||
def numFoundHub = huboptions.size() ?: 0
|
||||
def numFoundAct = actoptions.size() ?: 0
|
||||
if((deviceRefreshCount % 5) == 0) {
|
||||
@@ -122,14 +120,13 @@ def authPage() {
|
||||
section("Please wait while we discover your Harmony Hubs and Activities. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") {
|
||||
input "selectedhubs", "enum", required:false, title:"Select Harmony Hubs (${numFoundHub} found)", multiple:true, options:huboptions
|
||||
}
|
||||
// Virtual activity flag
|
||||
if (numFoundHub > 0 && numFoundAct > 0 && true)
|
||||
if (numFoundHub > 0 && numFoundAct > 0 && false)
|
||||
section("You can also add activities as virtual switches for other convenient integrations") {
|
||||
input "selectedactivities", "enum", required:false, title:"Select Harmony Activities (${numFoundAct} found)", multiple:true, options:actoptions
|
||||
}
|
||||
}
|
||||
if (state.resethub)
|
||||
section("Connection to the hub timed out. Please restart the hub and try again.") {}
|
||||
}
|
||||
section("Connection to the hub timed out. Please restart the hub and try again.") {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,7 +138,7 @@ def callback() {
|
||||
} else {
|
||||
log.warn "No authQueryString"
|
||||
}
|
||||
|
||||
|
||||
if (state.HarmonyAccessToken) {
|
||||
log.debug "Access token already exists"
|
||||
discovery()
|
||||
@@ -166,8 +163,8 @@ def callback() {
|
||||
|
||||
def init() {
|
||||
log.debug "Requesting Code"
|
||||
def oauthParams = [client_id: "${appSettings.clientId}", scope: "remote", response_type: "code", redirect_uri: "${callbackUrl}" ]
|
||||
redirect(location: "https://home.myharmony.com/oauth2/authorize?${toQueryString(oauthParams)}")
|
||||
def oauthParams = [client_id: "${appSettings.clientId}", scope: "remote", response_type: "code", redirect_uri: "${appSettings.callbackUrl}" ]
|
||||
redirect(location: "https://home.myharmony.com/oauth2/authorize?${toQueryString(oauthParams)}")
|
||||
}
|
||||
|
||||
def receiveToken(redirectUrl = null) {
|
||||
@@ -177,7 +174,7 @@ def receiveToken(redirectUrl = null) {
|
||||
uri: "https://home.myharmony.com/oauth2/token?${toQueryString(oauthParams)}",
|
||||
]
|
||||
try {
|
||||
httpPost(params) { response ->
|
||||
httpPost(params) { response ->
|
||||
state.HarmonyAccessToken = response.data.access_token
|
||||
}
|
||||
} catch (java.util.concurrent.TimeoutException e) {
|
||||
@@ -224,7 +221,7 @@ def connectionStatus(message, redirectUrl = null) {
|
||||
<meta http-equiv="refresh" content="3; url=${redirectUrl}" />
|
||||
"""
|
||||
}
|
||||
|
||||
|
||||
def html = """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
@@ -305,28 +302,30 @@ def buildRedirectUrl(page) {
|
||||
}
|
||||
|
||||
def installed() {
|
||||
enableCallback()
|
||||
if (!state.accessToken) {
|
||||
log.debug "About to create access token"
|
||||
createAccessToken()
|
||||
} else {
|
||||
} else {
|
||||
initialize()
|
||||
}
|
||||
}
|
||||
|
||||
def updated() {
|
||||
unsubscribe()
|
||||
unschedule()
|
||||
unschedule()
|
||||
enableCallback()
|
||||
if (!state.accessToken) {
|
||||
log.debug "About to create access token"
|
||||
createAccessToken()
|
||||
} else {
|
||||
} else {
|
||||
initialize()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def uninstalled() {
|
||||
if (state.HarmonyAccessToken) {
|
||||
try {
|
||||
try {
|
||||
state.HarmonyAccessToken = ""
|
||||
log.debug "Success disconnecting Harmony from SmartThings"
|
||||
} catch (groovyx.net.http.HttpResponseException e) {
|
||||
@@ -340,7 +339,7 @@ def initialize() {
|
||||
if (selectedhubs || selectedactivities) {
|
||||
addDevice()
|
||||
runEvery5Minutes("discovery")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def getHarmonydevices() {
|
||||
@@ -360,20 +359,20 @@ Map discoverDevices() {
|
||||
def hubname = getHubName(it.key)
|
||||
def hubvalue = "${hubname}"
|
||||
hubs["harmony-${hubkey}"] = hubvalue
|
||||
it.value.response.data.activities.each {
|
||||
it.value.response.data.activities.each {
|
||||
def value = "${it.value.name}"
|
||||
def key = "harmony-${hubkey}-${it.key}"
|
||||
activities["${key}"] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
state.HarmonyHubs = hubs
|
||||
state.HarmonyActivities = activities
|
||||
}
|
||||
state.HarmonyActivities = activities
|
||||
}
|
||||
}
|
||||
|
||||
//CHILD DEVICE METHODS
|
||||
def discovery() {
|
||||
def Params = [auth: state.HarmonyAccessToken]
|
||||
def Params = [auth: state.HarmonyAccessToken]
|
||||
def url = "https://home.myharmony.com/cloudapi/activity/all?${toQueryString(Params)}"
|
||||
try {
|
||||
httpGet(uri: url, headers: ["Accept": "application/json"]) {response ->
|
||||
@@ -385,11 +384,11 @@ def discovery() {
|
||||
poll()
|
||||
} else {
|
||||
log.debug "Error: $response.status"
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (groovyx.net.http.HttpResponseException e) {
|
||||
if (e.statusCode == 401) { // token is expired
|
||||
state.remove("HarmonyAccessToken")
|
||||
state.remove("HarmonyAccessToken")
|
||||
log.warn "Harmony Access token has expired"
|
||||
}
|
||||
} catch (java.net.SocketTimeoutException e) {
|
||||
@@ -397,12 +396,12 @@ def discovery() {
|
||||
state.resethub = true
|
||||
} catch (e) {
|
||||
log.warn "Hostname in certificate didn't match. Please try again later."
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
def addDevice() {
|
||||
log.trace "Adding Hubs"
|
||||
log.trace "Adding Hubs"
|
||||
selectedhubs.each { dni ->
|
||||
def d = getChildDevice(dni)
|
||||
if(!d) {
|
||||
@@ -413,8 +412,8 @@ def addDevice() {
|
||||
} else {
|
||||
log.trace "found ${d.displayName} with id $dni already exists"
|
||||
}
|
||||
}
|
||||
log.trace "Adding Activities"
|
||||
}
|
||||
log.trace "Adding Activities"
|
||||
selectedactivities.each { dni ->
|
||||
def d = getChildDevice(dni)
|
||||
if(!d) {
|
||||
@@ -425,7 +424,7 @@ def addDevice() {
|
||||
} else {
|
||||
log.trace "found ${d.displayName} with id $dni already exists"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def activity(dni,mode) {
|
||||
@@ -433,26 +432,26 @@ def activity(dni,mode) {
|
||||
def msg = "Command failed"
|
||||
def url = ''
|
||||
if (dni == "all") {
|
||||
url = "https://home.myharmony.com/cloudapi/activity/off?${toQueryString(Params)}"
|
||||
} else {
|
||||
url = "https://home.myharmony.com/cloudapi/activity/off?${toQueryString(Params)}"
|
||||
} else {
|
||||
def aux = dni.split('-')
|
||||
def hubId = aux[1]
|
||||
if (mode == "hub" || (aux.size() <= 2) || (aux[2] == "off")){
|
||||
url = "https://home.myharmony.com/cloudapi/hub/${hubId}/activity/off?${toQueryString(Params)}"
|
||||
if (mode == "hub" || (aux.size() <= 2) || (aux[2] == "off")){
|
||||
url = "https://home.myharmony.com/cloudapi/hub/${hubId}/activity/off?${toQueryString(Params)}"
|
||||
} else {
|
||||
def activityId = aux[2]
|
||||
url = "https://home.myharmony.com/cloudapi/hub/${hubId}/activity/${activityId}/${mode}?${toQueryString(Params)}"
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
httpPostJson(uri: url) { response ->
|
||||
httpPostJson(uri: url) { response ->
|
||||
if (response.data.code == 200 || dni == "all") {
|
||||
msg = "Command sent succesfully"
|
||||
state.aux = 0
|
||||
state.aux = 0
|
||||
} else {
|
||||
msg = "Command failed. Error: $response.data.code"
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (groovyx.net.http.HttpResponseException ex) {
|
||||
log.error ex
|
||||
if (state.aux == 0) {
|
||||
@@ -460,7 +459,7 @@ def activity(dni,mode) {
|
||||
activity(dni,mode)
|
||||
} else {
|
||||
msg = ex
|
||||
state.aux = 0
|
||||
state.aux = 0
|
||||
}
|
||||
} catch(Exception ex) {
|
||||
msg = ex
|
||||
@@ -473,10 +472,10 @@ def poll() {
|
||||
// GET THE LIST OF ACTIVITIES
|
||||
if (state.HarmonyAccessToken) {
|
||||
getActivityList()
|
||||
def Params = [auth: state.HarmonyAccessToken]
|
||||
def Params = [auth: state.HarmonyAccessToken]
|
||||
def url = "https://home.myharmony.com/cloudapi/state?${toQueryString(Params)}"
|
||||
try {
|
||||
httpGet(uri: url, headers: ["Accept": "application/json"]) {response ->
|
||||
httpGet(uri: url, headers: ["Accept": "application/json"]) {response ->
|
||||
def map = [:]
|
||||
response.data.hubs.each {
|
||||
if (it.value.message == "OK") {
|
||||
@@ -489,20 +488,20 @@ def poll() {
|
||||
def currentActivity = getActivityName(it.value.response.data.currentAvActivity,it.key)
|
||||
hub.sendEvent(name: "currentActivity", value: currentActivity, descriptionText: "Current activity is ${currentActivity}", display: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.trace it.value.message
|
||||
}
|
||||
}
|
||||
}
|
||||
def activities = getChildDevices()
|
||||
def activities = getChildDevices()
|
||||
def activitynotrunning = true
|
||||
activities.each { activity ->
|
||||
def act = activity.deviceNetworkId.split('-')
|
||||
def act = activity.deviceNetworkId.split('-')
|
||||
if (act.size() > 2) {
|
||||
def aux = map.find { it.key == act[1] }
|
||||
if (aux) {
|
||||
def aux2 = aux.value.split(',')
|
||||
def childDevice = getChildDevice(activity.deviceNetworkId)
|
||||
def childDevice = getChildDevice(activity.deviceNetworkId)
|
||||
if ((act[2] == aux2[0]) && (aux2[1] == "1" || aux2[1] == "2")) {
|
||||
childDevice?.sendEvent(name: "switch", value: "on")
|
||||
if (aux2[1] == "1")
|
||||
@@ -512,30 +511,30 @@ def poll() {
|
||||
if (aux2[1] == "3")
|
||||
runIn(5, "poll", [overwrite: true])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return "Poll completed $map - $state.hubs"
|
||||
}
|
||||
} catch (groovyx.net.http.HttpResponseException e) {
|
||||
if (e.statusCode == 401) { // token is expired
|
||||
state.remove("HarmonyAccessToken")
|
||||
state.remove("HarmonyAccessToken")
|
||||
return "Harmony Access token has expired"
|
||||
}
|
||||
}
|
||||
} catch(Exception e) {
|
||||
log.trace e
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def getActivityList() {
|
||||
// GET ACTIVITY'S NAME
|
||||
if (state.HarmonyAccessToken) {
|
||||
def Params = [auth: state.HarmonyAccessToken]
|
||||
def Params = [auth: state.HarmonyAccessToken]
|
||||
def url = "https://home.myharmony.com/cloudapi/activity/all?${toQueryString(Params)}"
|
||||
try {
|
||||
httpGet(uri: url, headers: ["Accept": "application/json"]) {response ->
|
||||
httpGet(uri: url, headers: ["Accept": "application/json"]) {response ->
|
||||
response.data.hubs.each {
|
||||
def hub = getChildDevice("harmony-${it.key}")
|
||||
if (hub) {
|
||||
@@ -548,10 +547,10 @@ def getActivityList() {
|
||||
}
|
||||
activities += [id: "off", name: "Activity OFF", type: "0"]
|
||||
log.trace activities
|
||||
}
|
||||
hub.sendEvent(name: "activities", value: new groovy.json.JsonBuilder(activities).toString(), descriptionText: "Activities are ${activities.collect { it.name }?.join(', ')}", display: false)
|
||||
}
|
||||
hub.sendEvent(name: "activities", value: new groovy.json.JsonBuilder(activities).toString(), descriptionText: "Activities are ${activities.collect { it.name }?.join(', ')}", display: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (groovyx.net.http.HttpResponseException e) {
|
||||
log.trace e
|
||||
@@ -560,7 +559,7 @@ def getActivityList() {
|
||||
} catch(Exception e) {
|
||||
log.trace e
|
||||
}
|
||||
}
|
||||
}
|
||||
return activity
|
||||
}
|
||||
|
||||
@@ -568,16 +567,16 @@ def getActivityName(activity,hubId) {
|
||||
// GET ACTIVITY'S NAME
|
||||
def actname = activity
|
||||
if (state.HarmonyAccessToken) {
|
||||
def Params = [auth: state.HarmonyAccessToken]
|
||||
def Params = [auth: state.HarmonyAccessToken]
|
||||
def url = "https://home.myharmony.com/cloudapi/hub/${hubId}/activity/all?${toQueryString(Params)}"
|
||||
try {
|
||||
httpGet(uri: url, headers: ["Accept": "application/json"]) {response ->
|
||||
httpGet(uri: url, headers: ["Accept": "application/json"]) {response ->
|
||||
actname = response.data.data.activities[activity].name
|
||||
}
|
||||
} catch(Exception e) {
|
||||
log.trace e
|
||||
}
|
||||
}
|
||||
}
|
||||
return actname
|
||||
}
|
||||
|
||||
@@ -585,19 +584,19 @@ def getActivityId(activity,hubId) {
|
||||
// GET ACTIVITY'S NAME
|
||||
def actid = activity
|
||||
if (state.HarmonyAccessToken) {
|
||||
def Params = [auth: state.HarmonyAccessToken]
|
||||
def Params = [auth: state.HarmonyAccessToken]
|
||||
def url = "https://home.myharmony.com/cloudapi/hub/${hubId}/activity/all?${toQueryString(Params)}"
|
||||
try {
|
||||
httpGet(uri: url, headers: ["Accept": "application/json"]) {response ->
|
||||
httpGet(uri: url, headers: ["Accept": "application/json"]) {response ->
|
||||
response.data.data.activities.each {
|
||||
if (it.value.name == activity)
|
||||
actid = it.key
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch(Exception e) {
|
||||
log.trace e
|
||||
}
|
||||
}
|
||||
}
|
||||
return actid
|
||||
}
|
||||
|
||||
@@ -605,16 +604,16 @@ def getHubName(hubId) {
|
||||
// GET HUB'S NAME
|
||||
def hubname = hubId
|
||||
if (state.HarmonyAccessToken) {
|
||||
def Params = [auth: state.HarmonyAccessToken]
|
||||
def Params = [auth: state.HarmonyAccessToken]
|
||||
def url = "https://home.myharmony.com/cloudapi/hub/${hubId}/discover?${toQueryString(Params)}"
|
||||
try {
|
||||
httpGet(uri: url, headers: ["Accept": "application/json"]) {response ->
|
||||
httpGet(uri: url, headers: ["Accept": "application/json"]) {response ->
|
||||
hubname = response.data.data.name
|
||||
}
|
||||
} catch(Exception e) {
|
||||
log.trace e
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return hubname
|
||||
}
|
||||
|
||||
@@ -625,8 +624,8 @@ def sendNotification(msg) {
|
||||
def hookEventHandler() {
|
||||
// log.debug "In hookEventHandler method."
|
||||
log.debug "request = ${request}"
|
||||
|
||||
def json = request.JSON
|
||||
|
||||
def json = request.JSON
|
||||
|
||||
def html = """{"code":200,"message":"OK"}"""
|
||||
render contentType: 'application/json', data: html
|
||||
@@ -660,16 +659,12 @@ def updateDevice() {
|
||||
} else {
|
||||
def device = allDevices.find { it.id == params.id }
|
||||
if (device) {
|
||||
if (device.hasCommand("$command")) {
|
||||
if (arguments) {
|
||||
device."$command"(*arguments)
|
||||
} else {
|
||||
device."$command"()
|
||||
}
|
||||
render status: 204, data: "{}"
|
||||
} else {
|
||||
render status: 404, data: '{"msg": "Command not supported by this Device"}'
|
||||
if (arguments) {
|
||||
device."$command"(*arguments)
|
||||
} else {
|
||||
device."$command"()
|
||||
}
|
||||
render status: 204, data: "{}"
|
||||
} else {
|
||||
render status: 404, data: '{"msg": "Device not found"}'
|
||||
}
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
/**
|
||||
* Copyright 2015 SmartThings
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* lg kahn smart app to turn device on when temp is between two values
|
||||
* and off otherwise. Also has to be between 2 date ranges.
|
||||
* also give alerts when turning on off.
|
||||
* I use for a roof heater wire/coil.
|
||||
*
|
||||
* Author: LGKahn kahn-st@lgk.com
|
||||
*/
|
||||
|
||||
definition(
|
||||
name: "Roof Coil Controller",
|
||||
namespace: "smartthings",
|
||||
author: "lgkahn",
|
||||
description: "Control a roof coil or othe device(s) when temperature is between two values turns on and also has to be within a date range. Automatically turns off if one of the conditions is not met. Alerting option also.",
|
||||
category: "Green Living",
|
||||
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Meta/temp_thermo-switch.png",
|
||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Meta/temp_thermo-switch@2x.png"
|
||||
)
|
||||
|
||||
preferences {
|
||||
section("Choose a temperature sensor... "){
|
||||
input "sensor", "capability.temperatureMeasurement", title: "Sensor"
|
||||
}
|
||||
section("Select the outlet(s)... "){
|
||||
input "outlets", "capability.switch", title: "Outlets", multiple: true
|
||||
}
|
||||
section("Turn on when temp is above ..."){
|
||||
input "onSetPoint", "decimal", title: "Set On Temp"
|
||||
}
|
||||
section("Turn off when temp is above ..."){
|
||||
input "offSetPoint", "decimal", title: "Set Off Temp"
|
||||
}
|
||||
section("Start after Date format (yyyymmdd)..."){
|
||||
input "startDate", "number", title: "Date?"
|
||||
}
|
||||
section("End after Date format (yyyymmdd)..."){
|
||||
input "endDate", "number", title: "Date?"
|
||||
}
|
||||
section("Time Zone Offset ie -5 etc...."){
|
||||
input "tzOffset", "number", title: "Offset?", range: "-12..12"
|
||||
}
|
||||
section( "Notifications" ) {
|
||||
input("recipients", "contact", title: "Send notifications to") {
|
||||
input "sendPushMessage", "enum", title: "Send a push notification?", options: ["Yes", "No"], required: false
|
||||
input "phone1", "phone", title: "Send a Text Message?", required: false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
def installed()
|
||||
{
|
||||
log.debug "in coil controller installed ... currenttemp = $sensor.currentTemperature"
|
||||
subscribe(sensor, "temperature", temperatureHandler)
|
||||
}
|
||||
|
||||
def updated()
|
||||
{
|
||||
log.debug "in coil controller updated ... currenttemp = $sensor.currentTemperature"
|
||||
unsubscribe()
|
||||
subscribe(sensor, "temperature", temperatureHandler)
|
||||
// for debugging and testing uncomment temperatureHandlerTest()
|
||||
}
|
||||
|
||||
|
||||
def temperatureHandler(evt)
|
||||
{
|
||||
def currenttemp = sensor.currentTemperature
|
||||
/*
|
||||
log.debug "in temp handler"
|
||||
log.debug "current temp = $currenttemp"
|
||||
log.debug "onSetPoint = $onSetPoint"
|
||||
log.debug "offSetPoint = $offSetPoint"
|
||||
log.debug "set offset = $tzOffset"
|
||||
*/
|
||||
def today = new Date();
|
||||
|
||||
def ltf = new java.text.SimpleDateFormat("yyyyMMdd")
|
||||
ltf.setTimeZone(TimeZone.getTimeZone("GMT${tzOffset}"))
|
||||
|
||||
String date1 = ltf.format(today);
|
||||
int intdate = Integer.parseInt(date1)
|
||||
// log.debug "int date = $intdate"
|
||||
//log.debug "enddate = $endDate"
|
||||
// log.debug "startdate = $startDate"
|
||||
def currSwitches = outlets.currentSwitch
|
||||
def onOutlets = currSwitches.findAll { switchVal ->
|
||||
switchVal == "on" ? true : false }
|
||||
|
||||
if (((intdate >= startDate) && (intdate <= endDate))
|
||||
&& ((currenttemp > onSetPoint) && (currenttemp < offSetPoint)))
|
||||
{
|
||||
// dont do anything if already on
|
||||
if (onOutlets.size() != outlets.size())
|
||||
{
|
||||
log.debug "turning outlets On as $sensor.displayName is reporting $currenttemp which is between $onSetPoint and $offSetPoint, and we are within the date range ($startDate - $endDate)!"
|
||||
mysend("Turning device(s) On as $sensor.displayName is reporting a temperature of $currenttemp which is between $onSetPoint and $offSetPoint, and we are within the date range ($startDate - $endDate)!")
|
||||
outlets.on()
|
||||
}
|
||||
else log.debug "Not turning on again, all already on!"
|
||||
}
|
||||
else
|
||||
{
|
||||
// dont do anything if already off
|
||||
if (onOutlets.size() != 0)
|
||||
{
|
||||
log.debug "turning outlets Off! as $sensor.displayName is reporting $currenttemp which is Not between $onSetPoint and $offSetPoint, or we are no longer within the date range ($startDate - $endDate)!"
|
||||
mysend("Turning device(s) Off as $sensor.displayName is reporting a temperature of $currenttemp which is not between $onSetPoint and $offSetPoint, or we are no longer within the date range ($startDate - $endDate)!")
|
||||
outlets.off()
|
||||
}
|
||||
else log.debug "All outlets already off!"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def temperatureHandlerTest()
|
||||
{
|
||||
//log.trace "temperature: $evt.value, $evt"
|
||||
// this routine is only for testing and debugging. to test or make changes uncomment the call in update.
|
||||
// this is so we dont have to wait 15 minutes till a half an hour for the event to fire for testing.
|
||||
|
||||
def currenttemp = sensor.currentTemperature
|
||||
|
||||
log.debug "in temp handler test"
|
||||
log.debug "current temp = $currenttemp"
|
||||
log.debug "onSetPoint = $onSetPoint"
|
||||
log.debug "offSetPoint = $offSetPoint"
|
||||
log.debug "set offset = $tzOffset"
|
||||
def today = new Date();
|
||||
|
||||
def ltf = new java.text.SimpleDateFormat("yyyyMMdd")
|
||||
ltf.setTimeZone(TimeZone.getTimeZone("GMT${tzOffset}"))
|
||||
|
||||
String date1 = ltf.format(today);
|
||||
int intdate = Integer.parseInt(date1)
|
||||
log.debug "int date = $intdate"
|
||||
log.debug "enddate = $endDate"
|
||||
log.debug "startdate = $startDate"
|
||||
def currSwitches = outlets.currentSwitch
|
||||
def onOutlets = currSwitches.findAll { switchVal ->
|
||||
switchVal == "on" ? true : false }
|
||||
|
||||
log.debug "how many on = ${onOutlets.size()} "
|
||||
|
||||
if (((intdate >= startDate) && (intdate <= endDate))
|
||||
&& ((currenttemp > onSetPoint) && (currenttemp < offSetPoint)))
|
||||
{
|
||||
// dont do anything if already on
|
||||
if (onOutlets.size() != outlets.size())
|
||||
{
|
||||
log.debug "turning outlets On as $sensor.displayName is reporting $currenttemp which is between $onSetPoint and $offSetPoint, and we are within the date range ($startDate - $endDate)!"
|
||||
//mysend("Turning device(s) On as $sensor.displayName is reporting a temperature of $currenttemp which is between $onSetPoint and $offSetPoint, and we are within the date range ($startDate - $endDate)!")
|
||||
outlets.on()
|
||||
}
|
||||
else log.debug "Not turning on again, all already on!"
|
||||
}
|
||||
else
|
||||
{
|
||||
// dont do anything if already off
|
||||
if (onOutlets.size() != 0)
|
||||
{
|
||||
log.debug "turning outlets Off! as $sensor.displayName is reporting $currenttemp which is Not between $onSetPoint and $offSetPoint, or we are no longer within the date range ($startDate - $endDate)!"
|
||||
//mysend("Turning device(s) Off as $sensor.displayName is reporting a temperature of $currenttemp which is not between $onSetPoint and $offSetPoint, or we are no longer within the date range ($startDate - $endDate)!")
|
||||
outlets.off()
|
||||
}
|
||||
else log.debug "All outlets already off!"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private mysend(msg) {
|
||||
if (location.contactBookEnabled) {
|
||||
log.debug("sending notifications to: ${recipients?.size()}")
|
||||
sendNotificationToContacts(msg, recipients)
|
||||
}
|
||||
else {
|
||||
if (sendPushMessage != "No") {
|
||||
log.debug("sending push message")
|
||||
sendPush(msg)
|
||||
}
|
||||
|
||||
if (phone1) {
|
||||
log.debug("sending text message")
|
||||
sendSms(phone1, msg)
|
||||
}
|
||||
}
|
||||
|
||||
log.debug msg
|
||||
}
|
||||
@@ -22,8 +22,7 @@ definition(
|
||||
description: "Allows you to control your Samsung TV from the SmartThings app. Perform basic functions like power Off, source, volume, channels and other remote control functions.",
|
||||
category: "SmartThings Labs",
|
||||
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Samsung/samsung-remote%402x.png",
|
||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Samsung/samsung-remote%403x.png",
|
||||
singleInstance: true
|
||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Samsung/samsung-remote%403x.png"
|
||||
)
|
||||
|
||||
preferences {
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
*
|
||||
*/
|
||||
definition(
|
||||
name: "Send HAM Bridge Command When",
|
||||
namespace: "smartthings",
|
||||
name: "Send HAM Bridge Command When…",
|
||||
namespace: "soletc.com",
|
||||
author: "Scottin Pollock",
|
||||
description: "Sends a command to your HAM Bridge server when SmartThings are activated.",
|
||||
category: "Convenience",
|
||||
@@ -25,6 +25,7 @@ definition(
|
||||
iconX2Url: "http://solutionsetcetera.com/stuff/STIcons/HB@2x.png"
|
||||
)
|
||||
|
||||
|
||||
preferences {
|
||||
section("Choose one or more, when..."){
|
||||
input "motion", "capability.motionSensor", title: "Motion Here", required: false, multiple: true
|
||||
|
||||
@@ -23,8 +23,7 @@ definition(
|
||||
description: "Integrate your Tesla car with SmartThings.",
|
||||
category: "SmartThings Labs",
|
||||
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/tesla-app%402x.png",
|
||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/tesla-app%403x.png",
|
||||
singleInstance: true
|
||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/tesla-app%403x.png"
|
||||
)
|
||||
|
||||
preferences {
|
||||
|
||||
@@ -22,8 +22,7 @@ definition(
|
||||
description: "Allows you to integrate your WeMo Switch and Wemo Motion sensor with SmartThings.",
|
||||
category: "SmartThings Labs",
|
||||
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/wemo.png",
|
||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/wemo@2x.png",
|
||||
singleInstance: true
|
||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/wemo@2x.png"
|
||||
)
|
||||
|
||||
preferences {
|
||||
@@ -62,7 +61,10 @@ def firstPage()
|
||||
|
||||
log.debug "REFRESH COUNT :: ${refreshCount}"
|
||||
|
||||
subscribe(location, null, locationHandler, [filterEvents:false])
|
||||
if(!state.subscribe) {
|
||||
subscribe(location, null, locationHandler, [filterEvents:false])
|
||||
state.subscribe = true
|
||||
}
|
||||
|
||||
//ssdp request every 25 seconds
|
||||
if((refreshCount % 5) == 0) {
|
||||
@@ -166,30 +168,21 @@ def getWemoLightSwitches()
|
||||
def installed() {
|
||||
log.debug "Installed with settings: ${settings}"
|
||||
initialize()
|
||||
|
||||
runIn(5, "subscribeToDevices") //initial subscriptions delayed by 5 seconds
|
||||
runIn(10, "refreshDevices") //refresh devices, delayed by 10 seconds
|
||||
runIn(900, "doDeviceSync" , [overwrite: false]) //setup ip:port syncing every 15 minutes
|
||||
|
||||
// SUBSCRIBE responses come back with TIMEOUT-1801 (30 minutes), so we refresh things a bit before they expire (29 minutes)
|
||||
runIn(1740, "refresh", [overwrite: false])
|
||||
}
|
||||
|
||||
def updated() {
|
||||
log.debug "Updated with settings: ${settings}"
|
||||
initialize()
|
||||
}
|
||||
|
||||
def initialize() {
|
||||
unsubscribe()
|
||||
unschedule()
|
||||
subscribe(location, null, locationHandler, [filterEvents:false])
|
||||
|
||||
if (selectedSwitches)
|
||||
addSwitches()
|
||||
|
||||
if (selectedMotions)
|
||||
addMotions()
|
||||
|
||||
if (selectedLightSwitches)
|
||||
addLightSwitches()
|
||||
|
||||
runIn(5, "subscribeToDevices") //initial subscriptions delayed by 5 seconds
|
||||
runIn(10, "refreshDevices") //refresh devices, delayed by 10 seconds
|
||||
runEvery5Minutes("refresh")
|
||||
runIn(5, "subscribeToDevices") //subscribe again to new/old devices wait 5 seconds
|
||||
runIn(10, "refreshDevices") //refresh devices again, delayed by 10 seconds
|
||||
}
|
||||
|
||||
def resubscribe() {
|
||||
@@ -199,7 +192,8 @@ def resubscribe() {
|
||||
|
||||
def refresh() {
|
||||
log.debug "refresh() called"
|
||||
doDeviceSync()
|
||||
//reschedule the refreshes
|
||||
runIn(1740, "refresh", [overwrite: false])
|
||||
refreshDevices()
|
||||
}
|
||||
|
||||
@@ -242,8 +236,7 @@ def addSwitches() {
|
||||
"port": selectedSwitch.value.port
|
||||
]
|
||||
])
|
||||
def ipvalue = convertHexToIP(selectedSwitch.value.ip)
|
||||
d.sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP is ${ipvalue}")
|
||||
|
||||
log.debug "Created ${d.displayName} with id: ${d.id}, dni: ${d.deviceNetworkId}"
|
||||
} else {
|
||||
log.debug "found ${d.displayName} with id $dni already exists"
|
||||
@@ -273,9 +266,8 @@ def addMotions() {
|
||||
"port": selectedMotion.value.port
|
||||
]
|
||||
])
|
||||
def ipvalue = convertHexToIP(selectedMotion.value.ip)
|
||||
d.sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP is ${ipvalue}")
|
||||
log.debug "Created ${d.displayName} with id: ${d.id}, dni: ${d.deviceNetworkId}"
|
||||
|
||||
log.debug "Created ${d.displayName} with id: ${d.id}, dni: ${d.deviceNetworkId}"
|
||||
} else {
|
||||
log.debug "found ${d.displayName} with id $dni already exists"
|
||||
}
|
||||
@@ -304,8 +296,7 @@ def addLightSwitches() {
|
||||
"port": selectedLightSwitch.value.port
|
||||
]
|
||||
])
|
||||
def ipvalue = convertHexToIP(selectedLightSwitch.value.ip)
|
||||
d.sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP is ${ipvalue}")
|
||||
|
||||
log.debug "created ${d.displayName} with id $dni"
|
||||
} else {
|
||||
log.debug "found ${d.displayName} with id $dni already exists"
|
||||
@@ -313,6 +304,27 @@ def addLightSwitches() {
|
||||
}
|
||||
}
|
||||
|
||||
def initialize() {
|
||||
// remove location subscription afterwards
|
||||
unsubscribe()
|
||||
state.subscribe = false
|
||||
|
||||
if (selectedSwitches)
|
||||
{
|
||||
addSwitches()
|
||||
}
|
||||
|
||||
if (selectedMotions)
|
||||
{
|
||||
addMotions()
|
||||
}
|
||||
|
||||
if (selectedLightSwitches)
|
||||
{
|
||||
addLightSwitches()
|
||||
}
|
||||
}
|
||||
|
||||
def locationHandler(evt) {
|
||||
def description = evt.description
|
||||
def hub = evt?.hubId
|
||||
@@ -321,32 +333,53 @@ def locationHandler(evt) {
|
||||
log.debug parsedEvent
|
||||
|
||||
if (parsedEvent?.ssdpTerm?.contains("Belkin:device:controllee") || parsedEvent?.ssdpTerm?.contains("Belkin:device:insight")) {
|
||||
|
||||
def switches = getWemoSwitches()
|
||||
if (!(switches."${parsedEvent.ssdpUSN.toString()}")) {
|
||||
//if it doesn't already exist
|
||||
|
||||
if (!(switches."${parsedEvent.ssdpUSN.toString()}"))
|
||||
{ //if it doesn't already exist
|
||||
switches << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent]
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{ // just update the values
|
||||
|
||||
log.debug "Device was already found in state..."
|
||||
|
||||
def d = switches."${parsedEvent.ssdpUSN.toString()}"
|
||||
boolean deviceChangedValues = false
|
||||
log.debug "$d.ip <==> $parsedEvent.ip"
|
||||
|
||||
if(d.ip != parsedEvent.ip || d.port != parsedEvent.port) {
|
||||
d.ip = parsedEvent.ip
|
||||
d.port = parsedEvent.port
|
||||
deviceChangedValues = true
|
||||
log.debug "Device's port or ip changed..."
|
||||
def child = getChildDevice(parsedEvent.mac)
|
||||
child.subscribe(parsedEvent.ip, parsedEvent.port)
|
||||
child.poll()
|
||||
}
|
||||
|
||||
if (deviceChangedValues) {
|
||||
def children = getChildDevices()
|
||||
log.debug "Found children ${children}"
|
||||
children.each {
|
||||
if (it.getDeviceDataByName("mac") == parsedEvent.mac) {
|
||||
log.debug "updating ip and port, and resubscribing, for device ${it} with mac ${parsedEvent.mac}"
|
||||
it.subscribe(parsedEvent.ip, parsedEvent.port)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
else if (parsedEvent?.ssdpTerm?.contains("Belkin:device:sensor")) {
|
||||
|
||||
def motions = getWemoMotions()
|
||||
if (!(motions."${parsedEvent.ssdpUSN.toString()}")) {
|
||||
//if it doesn't already exist
|
||||
|
||||
if (!(motions."${parsedEvent.ssdpUSN.toString()}"))
|
||||
{ //if it doesn't already exist
|
||||
motions << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent]
|
||||
} else { // just update the values
|
||||
}
|
||||
else
|
||||
{ // just update the values
|
||||
|
||||
log.debug "Device was already found in state..."
|
||||
|
||||
def d = motions."${parsedEvent.ssdpUSN.toString()}"
|
||||
@@ -379,7 +412,10 @@ def locationHandler(evt) {
|
||||
if (!(lightSwitches."${parsedEvent.ssdpUSN.toString()}"))
|
||||
{ //if it doesn't already exist
|
||||
lightSwitches << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent]
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{ // just update the values
|
||||
|
||||
log.debug "Device was already found in state..."
|
||||
|
||||
def d = lightSwitches."${parsedEvent.ssdpUSN.toString()}"
|
||||
@@ -390,11 +426,21 @@ def locationHandler(evt) {
|
||||
d.port = parsedEvent.port
|
||||
deviceChangedValues = true
|
||||
log.debug "Device's port or ip changed..."
|
||||
def child = getChildDevice(parsedEvent.mac)
|
||||
log.debug "updating ip and port, and resubscribing, for device with mac ${parsedEvent.mac}"
|
||||
child.subscribe(parsedEvent.ip, parsedEvent.port)
|
||||
}
|
||||
|
||||
if (deviceChangedValues) {
|
||||
def children = getChildDevices()
|
||||
log.debug "Found children ${children}"
|
||||
children.each {
|
||||
if (it.getDeviceDataByName("mac") == parsedEvent.mac) {
|
||||
log.debug "updating ip and port, and resubscribing, for device ${it} with mac ${parsedEvent.mac}"
|
||||
it.subscribe(parsedEvent.ip, parsedEvent.port)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
else if (parsedEvent.headers && parsedEvent.body) {
|
||||
String headerString = new String(parsedEvent.headers.decodeBase64())?.toLowerCase()
|
||||
@@ -534,30 +580,73 @@ private def parseDiscoveryMessage(String description) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
device
|
||||
}
|
||||
|
||||
def doDeviceSync(){
|
||||
log.debug "Doing Device Sync!"
|
||||
runIn(900, "doDeviceSync" , [overwrite: false]) //schedule to run again in 15 minutes
|
||||
|
||||
if(!state.subscribe) {
|
||||
subscribe(location, null, locationHandler, [filterEvents:false])
|
||||
state.subscribe = true
|
||||
}
|
||||
|
||||
discoverAllWemoTypes()
|
||||
}
|
||||
|
||||
private String convertHexToIP(hex) {
|
||||
[convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
|
||||
def pollChildren() {
|
||||
def devices = getAllChildDevices()
|
||||
devices.each { d ->
|
||||
//only poll switches?
|
||||
d.poll()
|
||||
}
|
||||
}
|
||||
|
||||
private Integer convertHexToInt(hex) {
|
||||
Integer.parseInt(hex,16)
|
||||
def delayPoll() {
|
||||
log.debug "Executing 'delayPoll'"
|
||||
|
||||
runIn(5, "pollChildren")
|
||||
}
|
||||
|
||||
private Boolean canInstallLabs() {
|
||||
/*def poll() {
|
||||
log.debug "Executing 'poll'"
|
||||
runIn(600, "poll", [overwrite: false]) //schedule to run again in 10 minutes
|
||||
|
||||
def lastPoll = getLastPollTime()
|
||||
def currentTime = now()
|
||||
def lastPollDiff = currentTime - lastPoll
|
||||
log.debug "lastPoll: $lastPoll, currentTime: $currentTime, lastPollDiff: $lastPollDiff"
|
||||
setLastPollTime(currentTime)
|
||||
|
||||
doDeviceSync()
|
||||
}
|
||||
|
||||
|
||||
def setLastPollTime(currentTime) {
|
||||
state.lastpoll = currentTime
|
||||
}
|
||||
|
||||
def getLastPollTime() {
|
||||
state.lastpoll ?: now()
|
||||
}
|
||||
|
||||
def now() {
|
||||
new Date().getTime()
|
||||
}*/
|
||||
|
||||
private Boolean canInstallLabs()
|
||||
{
|
||||
return hasAllHubsOver("000.011.00603")
|
||||
}
|
||||
|
||||
private Boolean hasAllHubsOver(String desiredFirmware) {
|
||||
private Boolean hasAllHubsOver(String desiredFirmware)
|
||||
{
|
||||
return realHubFirmwareVersions.every { fw -> fw >= desiredFirmware }
|
||||
}
|
||||
|
||||
private List getRealHubFirmwareVersions() {
|
||||
private List getRealHubFirmwareVersions()
|
||||
{
|
||||
return location.hubs*.firmwareVersionString.findAll { it }
|
||||
}
|
||||
|
||||
@@ -24,8 +24,7 @@ definition(
|
||||
category: "Connections",
|
||||
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/withings.png",
|
||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/withings%402x.png",
|
||||
oauth: true,
|
||||
singleInstance: true
|
||||
oauth: true
|
||||
) {
|
||||
appSetting "clientId"
|
||||
appSetting "clientSecret"
|
||||
|
||||
@@ -24,8 +24,7 @@ definition(
|
||||
category: "SmartThings Internal",
|
||||
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
|
||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience%402x.png",
|
||||
oauth: true,
|
||||
singleInstance: true
|
||||
oauth: true
|
||||
) {
|
||||
appSetting "serverUrl"
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
definition(
|
||||
name: "Sprayer Controller 2",
|
||||
namespace: "sprayercontroller",
|
||||
namespace: "",
|
||||
author: "Cooper Lee",
|
||||
description: "Control Sprayers for a period of time a number of times per hour",
|
||||
category: "My Apps",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/**
|
||||
/**
|
||||
* Magic Home
|
||||
*
|
||||
* Copyright 2014 Tim Slagle
|
||||
@@ -198,7 +198,7 @@
|
||||
|
||||
//set home mode when house is occupied
|
||||
def setHome() {
|
||||
sendOutOfDateNotification()
|
||||
|
||||
log.info("Setting Home Mode!!")
|
||||
if(anyoneIsHome()) {
|
||||
if(state.sunMode == "sunset"){
|
||||
@@ -319,14 +319,3 @@
|
||||
private hideOptionsSection() {
|
||||
(starting || ending || days || modes) ? false : true
|
||||
}
|
||||
|
||||
def sendOutOfDateNotification(){
|
||||
if(!state.lastTime){
|
||||
state.lastTime = (new Date() + 31).getTime()
|
||||
sendNotification("Your version of Hello, Home Phrase Director is currently out of date. Please look for the new version of Hello, Home Phrase Director now called 'Routine Director' in the marketplace.")
|
||||
}
|
||||
else if (((new Date()).getTime()) >= state.lastTime){
|
||||
sendNotification("Your version of Hello, Home Phrase Director is currently out of date. Please look for the new version of Hello, Home Phrase Director now called 'Routine Director' in the marketplace.")
|
||||
state.lastTime = (new Date() + 31).getTime()
|
||||
}
|
||||
}
|
||||
@@ -1,346 +0,0 @@
|
||||
/**
|
||||
* Rotuine Director
|
||||
*
|
||||
*
|
||||
* Changelog
|
||||
*
|
||||
* 2015-09-01
|
||||
* --Added Contact Book
|
||||
* --Removed references to phrases and replaced with routines
|
||||
* --Added bool logic to inputs instead of enum for "yes" "no" options
|
||||
* --Fixed halting error with code installation
|
||||
*
|
||||
* Copyright 2015 Tim Slagle
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* 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: "Routine Director",
|
||||
namespace: "tslagle13",
|
||||
author: "Tim Slagle",
|
||||
description: "Monitor a set of presence sensors and activate routines based on whether your home is empty or occupied. Each presence status change will check against the current 'sun state' to run routines based on occupancy and whether the sun is up or down.",
|
||||
category: "Convenience",
|
||||
iconUrl: "http://icons.iconarchive.com/icons/icons8/ios7/512/Very-Basic-Home-Filled-icon.png",
|
||||
iconX2Url: "http://icons.iconarchive.com/icons/icons8/ios7/512/Very-Basic-Home-Filled-icon.png"
|
||||
)
|
||||
|
||||
preferences {
|
||||
page(name: "selectRoutines")
|
||||
|
||||
page(name: "Settings", title: "Settings", uninstall: true, install: true) {
|
||||
section("False alarm threshold (defaults to 10 min)") {
|
||||
input "falseAlarmThreshold", "decimal", title: "Number of minutes", required: false
|
||||
}
|
||||
|
||||
section("Zip code (for sunrise/sunset)") {
|
||||
input "zip", "text", required: true
|
||||
}
|
||||
|
||||
section("Notifications") {
|
||||
input "sendPushMessage", "bool", title: "Send notifications when house is empty?"
|
||||
input "sendPushMessageHome", "bool", title: "Send notifications when home is occupied?"
|
||||
}
|
||||
section("Send Notifications?") {
|
||||
input("recipients", "contact", title: "Send notifications to") {
|
||||
input "phone", "phone", title: "Send an SMS to this number?", required:false
|
||||
}
|
||||
}
|
||||
|
||||
section(title: "More options", hidden: hideOptionsSection(), hideable: true) {
|
||||
label title: "Assign a name", required: false
|
||||
input "days", "enum", title: "Only on certain days of the week", multiple: true, required: false,
|
||||
options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
|
||||
input "modes", "mode", title: "Only when mode is", multiple: true, required: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def selectRoutines() {
|
||||
def configured = (settings.awayDay && settings.awayNight && settings.homeDay && settings.homeNight)
|
||||
dynamicPage(name: "selectRoutines", title: "Configure", nextPage: "Settings", uninstall: true) {
|
||||
section("Who?") {
|
||||
input "people", "capability.presenceSensor", title: "Monitor These Presences", required: true, multiple: true, submitOnChange: true
|
||||
}
|
||||
|
||||
def routines = location.helloHome?.getPhrases()*.label
|
||||
if (routines) {
|
||||
routines.sort()
|
||||
section("Run This Routine When...") {
|
||||
log.trace routines
|
||||
input "awayDay", "enum", title: "Everyone Is Away And It's Day", required: true, options: routines, submitOnChange: true
|
||||
input "awayNight", "enum", title: "Everyone Is Away And It's Night", required: true, options: routines, submitOnChange: true
|
||||
input "homeDay", "enum", title: "At Least One Person Is Home And It's Day", required: true, options: routines, submitOnChange: true
|
||||
input "homeNight", "enum", title: "At Least One Person Is Home And It's Night", required: true, options: routines, submitOnChange: true
|
||||
}
|
||||
/* section("Select modes used for each condition.") { This allows the director to know which rotuine has already been ran so it does not run again if someone else comes home.
|
||||
input "homeModeDay", "mode", title: "Select Mode Used for 'Home Day'", required: true
|
||||
input "homeModeNight", "mode", title: "Select Mode Used for 'Home Night'", required: true
|
||||
}*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def installed() {
|
||||
log.debug "Updated with settings: ${settings}"
|
||||
initialize()
|
||||
}
|
||||
|
||||
def updated() {
|
||||
log.debug "Updated with settings: ${settings}"
|
||||
unsubscribe()
|
||||
initialize()
|
||||
}
|
||||
|
||||
def initialize() {
|
||||
subscribe(people, "presence", presence)
|
||||
checkSun()
|
||||
subscribe(location, "sunrise", setSunrise)
|
||||
subscribe(location, "sunset", setSunset)
|
||||
state.homestate = null
|
||||
}
|
||||
|
||||
//check current sun state when installed.
|
||||
def checkSun() {
|
||||
def zip = settings.zip as String
|
||||
def sunInfo = getSunriseAndSunset(zipCode: zip)
|
||||
def current = now()
|
||||
|
||||
if (sunInfo.sunrise.time < current && sunInfo.sunset.time > current) {
|
||||
state.sunMode = "sunrise"
|
||||
runIn(60,"setSunrise")
|
||||
}
|
||||
else {
|
||||
state.sunMode = "sunset"
|
||||
runIn(60,"setSunset")
|
||||
}
|
||||
}
|
||||
|
||||
//change to sunrise mode on sunrise event
|
||||
def setSunrise(evt) {
|
||||
state.sunMode = "sunrise";
|
||||
changeSunMode(newMode);
|
||||
log.debug "Current sun mode is ${state.sunMode}"
|
||||
}
|
||||
|
||||
//change to sunset mode on sunset event
|
||||
def setSunset(evt) {
|
||||
state.sunMode = "sunset";
|
||||
changeSunMode(newMode)
|
||||
log.debug "Current sun mode is ${state.sunMode}"
|
||||
}
|
||||
|
||||
//change mode on sun event
|
||||
def changeSunMode(newMode) {
|
||||
if (allOk) {
|
||||
|
||||
if (everyoneIsAway()) /*&& (state.sunMode == "sunrise")*/ {
|
||||
log.info("Home is Empty Setting New Away Mode")
|
||||
def delay = (falseAlarmThreshold != null && falseAlarmThreshold != "") ? falseAlarmThreshold * 60 : 10 * 60
|
||||
setAway()
|
||||
}
|
||||
/*
|
||||
else if (everyoneIsAway() && (state.sunMode == "sunset")) {
|
||||
log.info("Home is Empty Setting New Away Mode")
|
||||
def delay = (falseAlarmThreshold != null && falseAlarmThreshold != "") ? falseAlarmThreshold * 60 : 10 * 60
|
||||
setAway()
|
||||
}*/
|
||||
else if (anyoneIsHome()) {
|
||||
log.info("Home is Occupied Setting New Home Mode")
|
||||
setHome()
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//presence change run logic based on presence state of home
|
||||
def presence(evt) {
|
||||
if (allOk) {
|
||||
if (evt.value == "not present") {
|
||||
log.debug("Checking if everyone is away")
|
||||
|
||||
if (everyoneIsAway()) {
|
||||
log.info("Nobody is home, running away sequence")
|
||||
def delay = (falseAlarmThreshold != null && falseAlarmThreshold != "") ? falseAlarmThreshold * 60 : 10 * 60
|
||||
runIn(delay, "setAway")
|
||||
}
|
||||
}
|
||||
else {
|
||||
def lastTime = state[evt.deviceId]
|
||||
if (lastTime == null || now() - lastTime >= 1 * 60000) {
|
||||
log.info("Someone is home, running home sequence")
|
||||
setHome()
|
||||
}
|
||||
state[evt.deviceId] = now()
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//if empty set home to one of the away modes
|
||||
def setAway() {
|
||||
if (everyoneIsAway()) {
|
||||
if (state.sunMode == "sunset") {
|
||||
def message = "Performing \"${awayNight}\" for you as requested."
|
||||
log.info(message)
|
||||
sendAway(message)
|
||||
location.helloHome.execute(settings.awayNight)
|
||||
state.homestate = "away"
|
||||
|
||||
}
|
||||
else if (state.sunMode == "sunrise") {
|
||||
def message = "Performing \"${awayDay}\" for you as requested."
|
||||
log.info(message)
|
||||
sendAway(message)
|
||||
location.helloHome.execute(settings.awayDay)
|
||||
state.homestate = "away"
|
||||
}
|
||||
else {
|
||||
log.debug("Mode is the same, not evaluating")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//set home mode when house is occupied
|
||||
def setHome() {
|
||||
log.info("Setting Home Mode!!")
|
||||
if (anyoneIsHome()) {
|
||||
if (state.sunMode == "sunset") {
|
||||
if (state.homestate != "homeNight") {
|
||||
def message = "Performing \"${homeNight}\" for you as requested."
|
||||
log.info(message)
|
||||
sendHome(message)
|
||||
location.helloHome.execute(settings.homeNight)
|
||||
state.homestate = "homeNight"
|
||||
}
|
||||
}
|
||||
|
||||
if (state.sunMode == "sunrise") {
|
||||
if (state.homestate != "homeDay") {
|
||||
def message = "Performing \"${homeDay}\" for you as requested."
|
||||
log.info(message)
|
||||
sendHome(message)
|
||||
location.helloHome.execute(settings.homeDay)
|
||||
state.homestate = "homeDay"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private everyoneIsAway() {
|
||||
def result = true
|
||||
|
||||
if(people.findAll { it?.currentPresence == "present" }) {
|
||||
result = false
|
||||
}
|
||||
|
||||
log.debug("everyoneIsAway: ${result}")
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private anyoneIsHome() {
|
||||
def result = false
|
||||
|
||||
if(people.findAll { it?.currentPresence == "present" }) {
|
||||
result = true
|
||||
}
|
||||
|
||||
log.debug("anyoneIsHome: ${result}")
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
def sendAway(msg) {
|
||||
if (sendPushMessage) {
|
||||
if (recipients) {
|
||||
sendNotificationToContacts(msg, recipients)
|
||||
}
|
||||
else {
|
||||
sendPush(msg)
|
||||
if(phone){
|
||||
sendSms(phone, msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.debug(msg)
|
||||
}
|
||||
|
||||
def sendHome(msg) {
|
||||
if (sendPushMessageHome) {
|
||||
if (recipients) {
|
||||
sendNotificationToContacts(msg, recipients)
|
||||
}
|
||||
else {
|
||||
sendPush(msg)
|
||||
if(phone){
|
||||
sendSms(phone, msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.debug(msg)
|
||||
}
|
||||
|
||||
private getAllOk() {
|
||||
modeOk && daysOk && timeOk
|
||||
}
|
||||
|
||||
private getModeOk() {
|
||||
def result = !modes || modes.contains(location.mode)
|
||||
log.trace "modeOk = $result"
|
||||
result
|
||||
}
|
||||
|
||||
private getDaysOk() {
|
||||
def result = true
|
||||
if (days) {
|
||||
def df = new java.text.SimpleDateFormat("EEEE")
|
||||
if (location.timeZone) {
|
||||
df.setTimeZone(location.timeZone)
|
||||
}
|
||||
else {
|
||||
df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
|
||||
}
|
||||
def day = df.format(new Date())
|
||||
result = days.contains(day)
|
||||
}
|
||||
log.trace "daysOk = $result"
|
||||
result
|
||||
}
|
||||
|
||||
private getTimeOk() {
|
||||
def result = true
|
||||
if (starting && ending) {
|
||||
def currTime = now()
|
||||
def start = timeToday(starting, location?.timeZone).time
|
||||
def stop = timeToday(ending, location?.timeZone).time
|
||||
result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start
|
||||
}
|
||||
log.trace "timeOk = $result"
|
||||
result
|
||||
}
|
||||
|
||||
private hhmm(time, fmt = "h:mm a") {
|
||||
def t = timeToday(time, location.timeZone)
|
||||
def f = new java.text.SimpleDateFormat(fmt)
|
||||
f.setTimeZone(location.timeZone?:timeZone(time))
|
||||
f.format(t)
|
||||
}
|
||||
|
||||
private getTimeIntervalLabel() {
|
||||
(starting && ending) ? hhmm(starting) + "-" + hhmm(ending, "h:mm a z"): ""
|
||||
}
|
||||
|
||||
private hideOptionsSection() {
|
||||
(starting || ending || days || modes) ? false: true
|
||||
}
|
||||
@@ -58,8 +58,7 @@ definition(
|
||||
description: "Connect your Quirky to SmartThings.",
|
||||
category: "SmartThings Labs",
|
||||
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/quirky.png",
|
||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/quirky@2x.png",
|
||||
singleInstance: true
|
||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/quirky@2x.png"
|
||||
) {
|
||||
appSetting "clientId"
|
||||
appSetting "clientSecret"
|
||||
|
||||
@@ -25,8 +25,7 @@ definition(
|
||||
description: "Connect your TCP bulbs to SmartThings using Cloud to Cloud integration. You must create a remote login acct on TCP Mobile App.",
|
||||
category: "SmartThings Labs",
|
||||
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/tcp.png",
|
||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/tcp@2x.png",
|
||||
singleInstance: true
|
||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/tcp@2x.png"
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user