Compare commits

..

1 Commits

45 changed files with 598 additions and 1439 deletions

View File

@@ -274,7 +274,6 @@ private Map makeTemperatureResult(value) {
name: 'temperature', name: 'temperature',
value: "" + value, value: "" + value,
descriptionText: "${linkText} is ${value}°${temperatureScale}", descriptionText: "${linkText} is ${value}°${temperatureScale}",
unit: temperatureScale
] ]
} }

View File

@@ -254,8 +254,7 @@ private Map getTemperatureResult(value) {
return [ return [
name: 'temperature', name: 'temperature',
value: value, value: value,
descriptionText: descriptionText, descriptionText: descriptionText
unit: temperatureScale
] ]
} }

View File

@@ -22,6 +22,7 @@ metadata {
capability "Configuration" capability "Configuration"
capability "Sensor" capability "Sensor"
capability "Battery" capability "Battery"
capability "Health Check"
attribute "tamper", "enum", ["detected", "clear"] attribute "tamper", "enum", ["detected", "clear"]
attribute "batteryStatus", "string" attribute "batteryStatus", "string"
@@ -327,6 +328,9 @@ def zwaveEvent(physicalgraph.zwave.Command cmd) {
} }
def configure() { def configure() {
// allow device user configured or default 16 min to check in; double the periodic reporting interval
sendEvent(name: "checkInterval", value: 2* (timeOptionValueMap[reportInterval] ?: (2*8*60)), displayed: false)
// This sensor joins as a secure device if you double-click the button to include it // This sensor joins as a secure device if you double-click the button to include it
log.debug "${device.displayName} is configuring its settings" log.debug "${device.displayName} is configuring its settings"
def request = [] def request = []

View File

@@ -20,6 +20,7 @@ metadata {
capability "Configuration" capability "Configuration"
capability "Sensor" capability "Sensor"
capability "Battery" capability "Battery"
capability "Health Check"
command "configureAfterSecure" command "configureAfterSecure"
@@ -247,6 +248,8 @@ def configureAfterSecure() {
def configure() { def configure() {
// log.debug "configure()" // log.debug "configure()"
//["delay 30000"] + secure(zwave.securityV1.securityCommandsSupportedGet()) //["delay 30000"] + secure(zwave.securityV1.securityCommandsSupportedGet())
// allow device 16 min to check in; double the periodic reporting interval
sendEvent(name: "checkInterval", value: 2*8*60, displayed: false)
} }
private setConfigured() { private setConfigured() {

View File

@@ -20,6 +20,7 @@ metadata {
capability "Illuminance Measurement" capability "Illuminance Measurement"
capability "Sensor" capability "Sensor"
capability "Battery" capability "Battery"
capability "Health Check"
fingerprint deviceId: "0x2001", inClusters: "0x30,0x31,0x80,0x84,0x70,0x85,0x72,0x86" fingerprint deviceId: "0x2001", inClusters: "0x30,0x31,0x80,0x84,0x70,0x85,0x72,0x86"
} }
@@ -180,6 +181,9 @@ def zwaveEvent(physicalgraph.zwave.Command cmd) {
} }
def configure() { def configure() {
// allow device 10 min to check in; double the periodic reporting interval
sendEvent(name: "checkInterval", value: 2*5*60, displayed: false)
delayBetween([ delayBetween([
// send binary sensor report instead of basic set for motion // send binary sensor report instead of basic set for motion
zwave.configurationV1.configurationSet(parameterNumber: 5, size: 1, scaledConfigurationValue: 2).format(), zwave.configurationV1.configurationSet(parameterNumber: 5, size: 1, scaledConfigurationValue: 2).format(),

View File

@@ -47,9 +47,6 @@ metadata {
command "everywhereJoin" command "everywhereJoin"
command "everywhereLeave" command "everywhereLeave"
command "forceOff"
command "forceOn"
} }
/** /**
@@ -67,9 +64,9 @@ metadata {
} }
standardTile("switch", "device.switch", width: 1, height: 1, canChangeIcon: true) { standardTile("switch", "device.switch", width: 1, height: 1, canChangeIcon: true) {
state "on", label: '${name}', action: "forceOff", icon: "st.Electronics.electronics16", backgroundColor: "#79b821", nextState:"turningOff" state "on", label: '${name}', action: "switch.off", icon: "st.Electronics.electronics16", backgroundColor: "#79b821", nextState:"turningOff"
state "turningOff", label:'TURNING OFF', icon:"st.Electronics.electronics16", backgroundColor:"#ffffff" state "turningOff", label:'TURNING OFF', icon:"st.Electronics.electronics16", backgroundColor:"#ffffff"
state "off", label: '${name}', action: "forceOn", icon: "st.Electronics.electronics16", backgroundColor: "#ffffff", nextState:"turningOn" state "off", label: '${name}', action: "switch.on", icon: "st.Electronics.electronics16", backgroundColor: "#ffffff", nextState:"turningOn"
state "turningOn", label:'TURNING ON', icon:"st.Electronics.electronics16", backgroundColor:"#79b821" state "turningOn", label:'TURNING ON', icon:"st.Electronics.electronics16", backgroundColor:"#79b821"
} }
valueTile("1", "device.station1", decoration: "flat", canChangeIcon: false) { valueTile("1", "device.station1", decoration: "flat", canChangeIcon: false) {
@@ -143,22 +140,8 @@ metadata {
* one place. * one place.
* *
*/ */
def off() { def off() { onAction("off") }
if (device.currentState("switch")?.value == "on") { def on() { onAction("on") }
onAction("off")
}
}
def forceOff() {
onAction("off")
}
def on() {
if (device.currentState("switch")?.value == "off") {
onAction("on")
}
}
def forceOn() {
onAction("on")
}
def volup() { onAction("volup") } def volup() { onAction("volup") }
def voldown() { onAction("voldown") } def voldown() { onAction("voldown") }
def preset1() { onAction("1") } def preset1() { onAction("1") }
@@ -257,11 +240,11 @@ def onAction(String user, data=null) {
def actions = null def actions = null
switch (user) { switch (user) {
case "on": case "on":
boseSetPowerState(true) actions = boseSetPowerState(true)
break break
case "off": case "off":
boseSetNowPlaying(null, "STANDBY") boseSetNowPlaying(null, "STANDBY")
boseSetPowerState(false) actions = boseSetPowerState(false)
break break
case "volume": case "volume":
actions = boseSetVolume(data) actions = boseSetVolume(data)

View File

@@ -89,17 +89,14 @@ def parse(String description) {
log.debug "TEMP" log.debug "TEMP"
map.name = "temperature" map.name = "temperature"
map.value = getTemperature(descMap.value) map.value = getTemperature(descMap.value)
map.unit = temperatureScale
} else if (descMap.cluster == "0201" && descMap.attrId == "0011") { } else if (descMap.cluster == "0201" && descMap.attrId == "0011") {
log.debug "COOLING SETPOINT" log.debug "COOLING SETPOINT"
map.name = "coolingSetpoint" map.name = "coolingSetpoint"
map.value = getTemperature(descMap.value) map.value = getTemperature(descMap.value)
map.unit = temperatureScale
} else if (descMap.cluster == "0201" && descMap.attrId == "0012") { } else if (descMap.cluster == "0201" && descMap.attrId == "0012") {
log.debug "HEATING SETPOINT" log.debug "HEATING SETPOINT"
map.name = "heatingSetpoint" map.name = "heatingSetpoint"
map.value = getTemperature(descMap.value) map.value = getTemperature(descMap.value)
map.unit = temperatureScale
} else if (descMap.cluster == "0201" && descMap.attrId == "001c") { } else if (descMap.cluster == "0201" && descMap.attrId == "001c") {
log.debug "MODE" log.debug "MODE"
map.name = "thermostatMode" map.name = "thermostatMode"
@@ -172,7 +169,7 @@ def setHeatingSetpoint(degrees) {
def degreesInteger = Math.round(degrees) def degreesInteger = Math.round(degrees)
log.debug "setHeatingSetpoint({$degreesInteger} ${temperatureScale})" log.debug "setHeatingSetpoint({$degreesInteger} ${temperatureScale})"
sendEvent("name": "heatingSetpoint", "value": degreesInteger, "unit": temperatureScale) sendEvent("name": "heatingSetpoint", "value": degreesInteger)
def celsius = (getTemperatureScale() == "C") ? degreesInteger : (fahrenheitToCelsius(degreesInteger) as Double).round(2) def celsius = (getTemperatureScale() == "C") ? degreesInteger : (fahrenheitToCelsius(degreesInteger) as Double).round(2)
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x12 0x29 {" + hex(celsius * 100) + "}" "st wattr 0x${device.deviceNetworkId} 1 0x201 0x12 0x29 {" + hex(celsius * 100) + "}"
@@ -183,7 +180,7 @@ def setCoolingSetpoint(degrees) {
if (degrees != null) { if (degrees != null) {
def degreesInteger = Math.round(degrees) def degreesInteger = Math.round(degrees)
log.debug "setCoolingSetpoint({$degreesInteger} ${temperatureScale})" log.debug "setCoolingSetpoint({$degreesInteger} ${temperatureScale})"
sendEvent("name": "coolingSetpoint", "value": degreesInteger, "unit": temperatureScale) sendEvent("name": "coolingSetpoint", "value": degreesInteger)
def celsius = (getTemperatureScale() == "C") ? degreesInteger : (fahrenheitToCelsius(degreesInteger) as Double).round(2) def celsius = (getTemperatureScale() == "C") ? degreesInteger : (fahrenheitToCelsius(degreesInteger) as Double).round(2)
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x11 0x29 {" + hex(celsius * 100) + "}" "st wattr 0x${device.deviceNetworkId} 1 0x201 0x11 0x29 {" + hex(celsius * 100) + "}"
} }

View File

@@ -1,2 +0,0 @@
.st-ignore
README.md

View File

@@ -1,36 +0,0 @@
# Connected Cree LED Bulb
Works with:
* [Connected Cree LED Bulb](https://support.smartthings.com/hc/en-us/articles/204258280-Cree-Connected-LED-Bulb)
## Table of contents
* [Capabilities](#capabilities)
* [Health](#device-health)
## Capabilities
* **Actuator** - represents that a Device has commands
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
* **Polling** - represents that poll() can be implemented for the device
* **Refresh** - _refresh()_ command for status updates
* **Switch** - can detect state (possible values: on/off)
* **Switch Level** - represents current light level, usually 0-100 in percent
* **Health Check** - indicates ability to get device health notifications
## Device Health
A Category C6 Connected Cree LED Bulb with maxReportTime of 10 min.
Check-in interval is double the value of maxReportTime for Zigbee device.
This gives the device twice the amount of time to respond before it is marked as offline.
Check-in interval = 2*10 = 20 min
## Troubleshooting
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the device is out of range.
Pairing needs to be tried again by placing the device closer to the hub.
Instructions related to pairing, resetting and removing the device from SmartThings can be found in the following link:
* [Cree Connected LED Bulb Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/204258280-Cree-Connected-LED-Bulb)

View File

@@ -23,7 +23,6 @@ metadata {
capability "Refresh" capability "Refresh"
capability "Switch" capability "Switch"
capability "Switch Level" capability "Switch Level"
capability "Health Check"
fingerprint profileId: "C05E", inClusters: "0000,0003,0004,0005,0006,0008,1000", outClusters: "0000,0019" fingerprint profileId: "C05E", inClusters: "0000,0003,0004,0005,0006,0008,1000", outClusters: "0000,0019"
} }
@@ -67,12 +66,6 @@ def parse(String description) {
def resultMap = zigbee.getEvent(description) def resultMap = zigbee.getEvent(description)
if (resultMap) { if (resultMap) {
sendEvent(resultMap) sendEvent(resultMap)
// Temporary fix for the case when Device is OFFLINE and is connected again
if (state.lastActivity == null){
state.lastActivity = now()
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
state.lastActivity = now()
} }
else { else {
log.debug "DID NOT PARSE MESSAGE for description : $description" log.debug "DID NOT PARSE MESSAGE for description : $description"
@@ -92,21 +85,6 @@ def setLevel(value) {
zigbee.setLevel(value) + ["delay 500"] + zigbee.levelRefresh() //adding refresh because of ZLL bulb not conforming to send-me-a-report zigbee.setLevel(value) + ["delay 500"] + zigbee.levelRefresh() //adding refresh because of ZLL bulb not conforming to send-me-a-report
} }
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
state.lastActivity = null
return zigbee.levelRefresh()
} else {
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
}
def refresh() { def refresh() {
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig() + zigbee.levelConfig() zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig() + zigbee.levelConfig()
} }
@@ -117,6 +95,5 @@ def poll() {
def configure() { def configure() {
log.debug "Configuring Reporting and Bindings." log.debug "Configuring Reporting and Bindings."
sendEvent(name: "checkInterval", value: 1200, displayed: false, data: [protocol: "zigbee"])
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh() zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh()
} }

View File

@@ -682,7 +682,7 @@ def setHeatingSetpoint(degrees) {
def temperatureScale = getTemperatureScale() def temperatureScale = getTemperatureScale()
def degreesInteger = degrees as Integer def degreesInteger = degrees as Integer
sendEvent("name":"heatingSetpoint", "value":degreesInteger, "unit":temperatureScale) sendEvent("name":"heatingSetpoint", "value":degreesInteger)
def celsius = (getTemperatureScale() == "C") ? degreesInteger : (fahrenheitToCelsius(degreesInteger) as Double).round(2) def celsius = (getTemperatureScale() == "C") ? degreesInteger : (fahrenheitToCelsius(degreesInteger) as Double).round(2)
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x12 0x29 {" + hex(celsius*100) + "}" "st wattr 0x${device.deviceNetworkId} 1 0x201 0x12 0x29 {" + hex(celsius*100) + "}"
@@ -691,7 +691,7 @@ def setHeatingSetpoint(degrees) {
def setCoolingSetpoint(degrees) { def setCoolingSetpoint(degrees) {
def degreesInteger = degrees as Integer def degreesInteger = degrees as Integer
sendEvent("name":"coolingSetpoint", "value":degreesInteger, "unit":temperatureScale) sendEvent("name":"coolingSetpoint", "value":degreesInteger)
def celsius = (getTemperatureScale() == "C") ? degreesInteger : (fahrenheitToCelsius(degreesInteger) as Double).round(2) def celsius = (getTemperatureScale() == "C") ? degreesInteger : (fahrenheitToCelsius(degreesInteger) as Double).round(2)
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x11 0x29 {" + hex(celsius*100) + "}" "st wattr 0x${device.deviceNetworkId} 1 0x201 0x11 0x29 {" + hex(celsius*100) + "}"

View File

@@ -43,7 +43,7 @@ metadata {
} }
standardTile("reset", "device.reset", height: 2, width: 2, inactiveLabel: false, decoration: "flat") { standardTile("reset", "device.reset", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
state "default", label:"Reset To White", action:"reset", icon:"st.lights.philips.hue-single" state "default", label:"Reset Color", action:"reset", icon:"st.lights.philips.hue-single"
} }
standardTile("refresh", "device.refresh", height: 2, width: 2, inactiveLabel: false, decoration: "flat") { standardTile("refresh", "device.refresh", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
@@ -51,7 +51,7 @@ metadata {
} }
main(["rich-control"]) main(["rich-control"])
details(["rich-control", "reset", "refresh"]) details(["rich-control", "colorTempSliderControl", "colorTemp", "reset", "refresh"])
} }
} }
@@ -75,78 +75,118 @@ def parse(description) {
// handle commands // handle commands
void on() { void on() {
log.trace parent.on(this) log.trace parent.on(this)
sendEvent(name: "switch", value: "on")
} }
void off() { void off() {
log.trace parent.off(this) log.trace parent.off(this)
sendEvent(name: "switch", value: "off")
}
void nextLevel() {
def level = device.latestValue("level") as Integer ?: 0
if (level <= 100) {
level = Math.min(25 * (Math.round(level / 25) + 1), 100) as Integer
}
else {
level = 25
}
setLevel(level)
} }
void setLevel(percent) { void setLevel(percent) {
log.debug "Executing 'setLevel'" log.debug "Executing 'setLevel'"
if (verifyPercent(percent)) { if (verifyPercent(percent)) {
log.trace parent.setLevel(this, percent) parent.setLevel(this, percent)
sendEvent(name: "level", value: percent, descriptionText: "Level has changed to ${percent}%")
sendEvent(name: "switch", value: "on")
} }
} }
void setSaturation(percent) { void setSaturation(percent) {
log.debug "Executing 'setSaturation'" log.debug "Executing 'setSaturation'"
if (verifyPercent(percent)) { if (verifyPercent(percent)) {
log.trace parent.setSaturation(this, percent) parent.setSaturation(this, percent)
sendEvent(name: "saturation", value: percent, displayed: false)
} }
} }
void setHue(percent) { void setHue(percent) {
log.debug "Executing 'setHue'" log.debug "Executing 'setHue'"
if (verifyPercent(percent)) { if (verifyPercent(percent)) {
log.trace parent.setHue(this, percent) parent.setHue(this, percent)
sendEvent(name: "hue", value: percent, displayed: false)
} }
} }
void setColor(value) { void setColor(value) {
log.debug "setColor: ${value}, $this"
def events = [] def events = []
def validValues = [:] def validValues = [:]
if (verifyPercent(value.hue)) { if (verifyPercent(value.hue)) {
events << createEvent(name: "hue", value: value.hue, displayed: false)
validValues.hue = value.hue validValues.hue = value.hue
} }
if (verifyPercent(value.saturation)) { if (verifyPercent(value.saturation)) {
events << createEvent(name: "saturation", value: value.saturation, displayed: false)
validValues.saturation = value.saturation validValues.saturation = value.saturation
} }
if (value.hex != null) { if (value.hex != null) {
if (value.hex ==~ /^\#([A-Fa-f0-9]){6}$/) { if (value.hex ==~ /^\#([A-Fa-f0-9]){6}$/) {
events << createEvent(name: "color", value: value.hex)
validValues.hex = value.hex validValues.hex = value.hex
} else { } else {
log.warn "$value.hex is not a valid color" log.warn "$value.hex is not a valid color"
} }
} }
if (verifyPercent(value.level)) { if (verifyPercent(value.level)) {
events << createEvent(name: "level", value: value.level, descriptionText: "Level has changed to ${value.level}%")
validValues.level = value.level validValues.level = value.level
} }
if (value.switch == "off" || (value.level != null && value.level <= 0)) { if (value.switch == "off" || (value.level != null && value.level <= 0)) {
events << createEvent(name: "switch", value: "off")
validValues.switch = "off" validValues.switch = "off"
} else { } else {
events << createEvent(name: "switch", value: "on")
validValues.switch = "on" validValues.switch = "on"
} }
if (!validValues.isEmpty()) { if (!events.isEmpty()) {
log.trace parent.setColor(this, validValues) parent.setColor(this, validValues)
}
events.each {
sendEvent(it)
} }
} }
void reset() { void reset() {
log.debug "Executing 'reset'" log.debug "Executing 'reset'"
def value = [hue:20, saturation:2] def value = [level:100, saturation:56, hue:23]
setAdjustedColor(value) setAdjustedColor(value)
parent.poll()
} }
void setAdjustedColor(value) { void setAdjustedColor(value) {
if (value) { if (value) {
log.trace "setAdjustedColor: ${value}" log.trace "setAdjustedColor: ${value}"
def adjusted = value + [:] def adjusted = value + [:]
adjusted.hue = adjustOutgoingHue(value.hue)
// Needed because color picker always sends 100 // Needed because color picker always sends 100
adjusted.level = null adjusted.level = null
setColor(adjusted) setColor(adjusted)
} else { } else {
log.warn "Invalid color input $value" log.warn "Invalid color input"
}
}
void setColorTemperature(value) {
if (value) {
log.trace "setColorTemperature: ${value}k"
parent.setColorTemperature(this, value)
sendEvent(name: "colorTemperature", value: value)
sendEvent(name: "switch", value: "on")
} else {
log.warn "Invalid color temperature"
} }
} }
@@ -155,6 +195,22 @@ void refresh() {
parent.manualRefresh() parent.manualRefresh()
} }
def adjustOutgoingHue(percent) {
def adjusted = percent
if (percent > 31) {
if (percent < 63.0) {
adjusted = percent + (7 * (percent -30 ) / 32)
}
else if (percent < 73.0) {
adjusted = 69 + (5 * (percent - 62) / 10)
}
else {
adjusted = percent + (2 * (100 - percent) / 28)
}
}
log.info "percent: $percent, adjusted: $adjusted"
adjusted
}
def verifyPercent(percent) { def verifyPercent(percent) {
if (percent == null) if (percent == null)

View File

@@ -7,13 +7,8 @@
metadata { metadata {
// Automatically generated. Make future change here. // Automatically generated. Make future change here.
definition (name: "Hue Bridge", namespace: "smartthings", author: "SmartThings") { definition (name: "Hue Bridge", namespace: "smartthings", author: "SmartThings") {
attribute "serialNumber", "string"
attribute "networkAddress", "string" attribute "networkAddress", "string"
// Used to indicate if bridge is reachable or not, i.e. is the bridge connected to the network
// Possible values "Online" or "Offline"
attribute "status", "string"
// Id is the number on the back of the hub, Hue uses last six digits of Mac address
// This is also used in the Hue application as ID
attribute "idNumber", "string"
} }
simulator { simulator {
@@ -22,23 +17,22 @@ metadata {
tiles(scale: 2) { tiles(scale: 2) {
multiAttributeTile(name:"rich-control"){ multiAttributeTile(name:"rich-control"){
tileAttribute ("device.status", key: "PRIMARY_CONTROL") { tileAttribute ("", key: "PRIMARY_CONTROL") {
attributeState "Offline", label: '${currentValue}', action: "", icon: "st.Lighting.light99-hue", backgroundColor: "#ffffff" attributeState "default", label: "Hue Bridge", action: "", icon: "st.Lighting.light99-hue", backgroundColor: "#F3C200"
attributeState "Online", label: '${currentValue}', action: "", icon: "st.Lighting.light99-hue", backgroundColor: "#79b821"
} }
tileAttribute ("serialNumber", key: "SECONDARY_CONTROL") {
attributeState "default", label:'SN: ${currentValue}'
} }
valueTile("doNotRemove", "v", decoration: "flat", height: 2, width: 6, inactiveLabel: false) { }
state "default", label:'If removed, Hue lights will not work properly' valueTile("serialNumber", "device.serialNumber", decoration: "flat", height: 1, width: 2, inactiveLabel: false) {
state "default", label:'SN: ${currentValue}'
} }
valueTile("idNumber", "device.idNumber", decoration: "flat", height: 2, width: 6, inactiveLabel: false) { valueTile("networkAddress", "device.networkAddress", decoration: "flat", height: 2, width: 4, inactiveLabel: false) {
state "default", label:'ID: ${currentValue}' state "default", label:'${currentValue}', height: 1, width: 2, inactiveLabel: false
}
valueTile("networkAddress", "device.networkAddress", decoration: "flat", height: 2, width: 6, inactiveLabel: false) {
state "default", label:'IP: ${currentValue}'
} }
main (["rich-control"]) main (["rich-control"])
details(["rich-control", "doNotRemove", "idNumber", "networkAddress"]) details(["rich-control", "networkAddress"])
} }
} }
@@ -62,7 +56,7 @@ def parse(description) {
log.trace "HUE BRIDGE, GENERATING EVENT: $map.name: $map.value" log.trace "HUE BRIDGE, GENERATING EVENT: $map.name: $map.value"
results << createEvent(name: "${map.name}", value: "${map.value}") results << createEvent(name: "${map.name}", value: "${map.value}")
} else { } else {
log.trace "Parsing description" log.trace "Parsing description"
def msg = parseLanMessage(description) def msg = parseLanMessage(description)
if (msg.body) { if (msg.body) {
def contentType = msg.headers["Content-Type"] def contentType = msg.headers["Content-Type"]
@@ -72,13 +66,13 @@ def parse(description) {
log.info "Bridge response: $msg.body" log.info "Bridge response: $msg.body"
} else { } else {
// Sending Bulbs List to parent" // Sending Bulbs List to parent"
if (parent.isInBulbDiscovery()) if (parent.state.inBulbDiscovery)
log.info parent.bulbListHandler(device.hub.id, msg.body) log.info parent.bulbListHandler(device.hub.id, msg.body)
} }
} }
else if (contentType?.contains("xml")) { else if (contentType?.contains("xml")) {
log.debug "HUE BRIDGE ALREADY PRESENT" log.debug "HUE BRIDGE ALREADY PRESENT"
parent.hubVerification(device.hub.id, msg.body) parent.hubVerification(device.hub.id, msg.body)
} }
} }
} }

View File

@@ -43,16 +43,16 @@ metadata {
} }
} }
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2000..6500)") { controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2000..6500)") {
state "colorTemperature", action:"color temperature.setColorTemperature" state "colorTemperature", action:"color temperature.setColorTemperature"
} }
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "colorTemperature", label: 'WHITES' state "colorTemperature", label: '${currentValue} K'
} }
standardTile("reset", "device.reset", height: 2, width: 2, inactiveLabel: false, decoration: "flat") { standardTile("reset", "device.reset", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
state "default", label:"Reset To White", action:"reset", icon:"st.lights.philips.hue-single" state "default", label:"Reset Color", action:"reset", icon:"st.lights.philips.hue-single"
} }
standardTile("refresh", "device.refresh", height: 2, width: 2, inactiveLabel: false, decoration: "flat") { standardTile("refresh", "device.refresh", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
@@ -84,86 +84,118 @@ def parse(description) {
// handle commands // handle commands
void on() { void on() {
log.trace parent.on(this) log.trace parent.on(this)
sendEvent(name: "switch", value: "on")
} }
void off() { void off() {
log.trace parent.off(this) log.trace parent.off(this)
sendEvent(name: "switch", value: "off")
}
void nextLevel() {
def level = device.latestValue("level") as Integer ?: 0
if (level <= 100) {
level = Math.min(25 * (Math.round(level / 25) + 1), 100) as Integer
}
else {
level = 25
}
setLevel(level)
} }
void setLevel(percent) { void setLevel(percent) {
log.debug "Executing 'setLevel'" log.debug "Executing 'setLevel'"
if (verifyPercent(percent)) { if (verifyPercent(percent)) {
log.trace parent.setLevel(this, percent) parent.setLevel(this, percent)
sendEvent(name: "level", value: percent, descriptionText: "Level has changed to ${percent}%")
sendEvent(name: "switch", value: "on")
} }
} }
void setSaturation(percent) { void setSaturation(percent) {
log.debug "Executing 'setSaturation'" log.debug "Executing 'setSaturation'"
if (verifyPercent(percent)) { if (verifyPercent(percent)) {
log.trace parent.setSaturation(this, percent) parent.setSaturation(this, percent)
sendEvent(name: "saturation", value: percent, displayed: false)
} }
} }
void setHue(percent) { void setHue(percent) {
log.debug "Executing 'setHue'" log.debug "Executing 'setHue'"
if (verifyPercent(percent)) { if (verifyPercent(percent)) {
log.trace parent.setHue(this, percent) parent.setHue(this, percent)
sendEvent(name: "hue", value: percent, displayed: false)
} }
} }
void setColor(value) { void setColor(value) {
log.debug "setColor: ${value}, $this"
def events = [] def events = []
def validValues = [:] def validValues = [:]
if (verifyPercent(value.hue)) { if (verifyPercent(value.hue)) {
events << createEvent(name: "hue", value: value.hue, displayed: false)
validValues.hue = value.hue validValues.hue = value.hue
} }
if (verifyPercent(value.saturation)) { if (verifyPercent(value.saturation)) {
events << createEvent(name: "saturation", value: value.saturation, displayed: false)
validValues.saturation = value.saturation validValues.saturation = value.saturation
} }
if (value.hex != null) { if (value.hex != null) {
if (value.hex ==~ /^\#([A-Fa-f0-9]){6}$/) { if (value.hex ==~ /^\#([A-Fa-f0-9]){6}$/) {
events << createEvent(name: "color", value: value.hex)
validValues.hex = value.hex validValues.hex = value.hex
} else { } else {
log.warn "$value.hex is not a valid color" log.warn "$value.hex is not a valid color"
} }
} }
if (verifyPercent(value.level)) { if (verifyPercent(value.level)) {
events << createEvent(name: "level", value: value.level, descriptionText: "Level has changed to ${value.level}%")
validValues.level = value.level validValues.level = value.level
} }
if (value.switch == "off" || (value.level != null && value.level <= 0)) { if (value.switch == "off" || (value.level != null && value.level <= 0)) {
events << createEvent(name: "switch", value: "off")
validValues.switch = "off" validValues.switch = "off"
} else { } else {
events << createEvent(name: "switch", value: "on")
validValues.switch = "on" validValues.switch = "on"
} }
if (!validValues.isEmpty()) { if (!events.isEmpty()) {
log.trace parent.setColor(this, validValues) parent.setColor(this, validValues)
}
events.each {
sendEvent(it)
} }
} }
void reset() { void reset() {
log.debug "Executing 'reset'" log.debug "Executing 'reset'"
setColorTemperature(4000) def value = [level:100, saturation:56, hue:23]
setAdjustedColor(value)
parent.poll()
} }
void setAdjustedColor(value) { void setAdjustedColor(value) {
if (value) { if (value) {
log.trace "setAdjustedColor: ${value}" log.trace "setAdjustedColor: ${value}"
def adjusted = value + [:] def adjusted = value + [:]
adjusted.hue = adjustOutgoingHue(value.hue)
// Needed because color picker always sends 100 // Needed because color picker always sends 100
adjusted.level = null adjusted.level = null
setColor(adjusted) setColor(adjusted)
} else { } else {
log.warn "Invalid color input $value" log.warn "Invalid color input"
} }
} }
void setColorTemperature(value) { void setColorTemperature(value) {
if (value) { if (value) {
log.trace "setColorTemperature: ${value}k" log.trace "setColorTemperature: ${value}k"
log.trace parent.setColorTemperature(this, value) parent.setColorTemperature(this, value)
sendEvent(name: "colorTemperature", value: value)
sendEvent(name: "switch", value: "on")
} else { } else {
log.warn "Invalid color temperature $value" log.warn "Invalid color temperature"
} }
} }
@@ -172,6 +204,23 @@ void refresh() {
parent.manualRefresh() parent.manualRefresh()
} }
def adjustOutgoingHue(percent) {
def adjusted = percent
if (percent > 31) {
if (percent < 63.0) {
adjusted = percent + (7 * (percent -30 ) / 32)
}
else if (percent < 73.0) {
adjusted = 69 + (5 * (percent - 62) / 10)
}
else {
adjusted = percent + (2 * (100 - percent) / 28)
}
}
log.info "percent: $percent, adjusted: $adjusted"
adjusted
}
def verifyPercent(percent) { def verifyPercent(percent) {
if (percent == null) if (percent == null)
return false return false

View File

@@ -68,16 +68,20 @@ def parse(description) {
// handle commands // handle commands
void on() { void on() {
log.trace parent.on(this) log.trace parent.on(this)
sendEvent(name: "switch", value: "on")
} }
void off() { void off() {
log.trace parent.off(this) log.trace parent.off(this)
sendEvent(name: "switch", value: "off")
} }
void setLevel(percent) { void setLevel(percent) {
log.debug "Executing 'setLevel'" log.debug "Executing 'setLevel'"
if (percent != null && percent >= 0 && percent <= 100) { if (percent != null && percent >= 0 && percent <= 100) {
parent.setLevel(this, percent) parent.setLevel(this, percent)
sendEvent(name: "level", value: percent)
sendEvent(name: "switch", value: "on")
} else { } else {
log.warn "$percent is not 0-100" log.warn "$percent is not 0-100"
} }

View File

@@ -36,12 +36,12 @@ metadata {
} }
} }
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2200..6500)") { controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2000..6500)") {
state "colorTemperature", action:"color temperature.setColorTemperature" state "colorTemperature", action:"color temperature.setColorTemperature"
} }
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "colorTemperature", label: 'WHITES' state "colorTemperature", label: '${currentValue} K'
} }
standardTile("refresh", "device.refresh", height: 2, width: 2, inactiveLabel: false, decoration: "flat") { standardTile("refresh", "device.refresh", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
@@ -73,16 +73,20 @@ def parse(description) {
// handle commands // handle commands
void on() { void on() {
log.trace parent.on(this) log.trace parent.on(this)
sendEvent(name: "switch", value: "on")
} }
void off() { void off() {
log.trace parent.off(this) log.trace parent.off(this)
sendEvent(name: "switch", value: "off")
} }
void setLevel(percent) { void setLevel(percent) {
log.debug "Executing 'setLevel'" log.debug "Executing 'setLevel'"
if (percent != null && percent >= 0 && percent <= 100) { if (percent != null && percent >= 0 && percent <= 100) {
log.trace parent.setLevel(this, percent) parent.setLevel(this, percent)
sendEvent(name: "level", value: percent)
sendEvent(name: "switch", value: "on")
} else { } else {
log.warn "$percent is not 0-100" log.warn "$percent is not 0-100"
} }
@@ -91,7 +95,9 @@ void setLevel(percent) {
void setColorTemperature(value) { void setColorTemperature(value) {
if (value) { if (value) {
log.trace "setColorTemperature: ${value}k" log.trace "setColorTemperature: ${value}k"
log.trace parent.setColorTemperature(this, value) parent.setColorTemperature(this, value)
sendEvent(name: "colorTemperature", value: value)
sendEvent(name: "switch", value: "on")
} else { } else {
log.warn "Invalid color temperature" log.warn "Invalid color temperature"
} }
@@ -101,3 +107,4 @@ void refresh() {
log.debug "Executing 'refresh'" log.debug "Executing 'refresh'"
parent.manualRefresh() parent.manualRefresh()
} }

View File

@@ -147,8 +147,8 @@ private Map parseIasMessage(String description) {
ZoneStatus zs = zigbee.parseZoneStatus(description) ZoneStatus zs = zigbee.parseZoneStatus(description)
Map resultMap = [:] Map resultMap = [:]
resultMap.name = 'motion' result.name = 'motion'
resultMap.value = zs.isAlarm2Set() ? 'active' : 'inactive' result.value = zs.isAlarm2Set() ? 'active' : 'inactive'
log.debug(zs.isAlarm2Set() ? 'motion' : 'no motion') log.debug(zs.isAlarm2Set() ? 'motion' : 'no motion')
return resultMap return resultMap

View File

@@ -1,2 +0,0 @@
.st-ignore
README.md

View File

@@ -1,38 +0,0 @@
# SmartPower Outlet
Works with:
* [Samsung SmartPower Outlet](https://shop.smartthings.com/#!/products/smartpower-outlet)
## Table of contents
* [Capabilities](#capabilities)
* [Health](#device-health)
## Capabilities
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
* **Actuator** - represents that a Device has commands
* **Switch** - can detect state (possible values: on/off)
* **Refresh** - _refresh()_ command for status updates
* **Power Meter** - detects power meter for device in either w or kw.
* **Health Check** - indicates ability to get device health notifications
* **Sensor** - detects sensor events
## Device Health
A Category C1 smart power outlet with maxReportTime of 10 min.
Check-in interval is double the value of maxReportTime for Zigbee device.
This gives the device twice the amount of time to respond before it is marked as offline.
Check-in interval = 2*10 = 20 min
## Troubleshooting
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the device is out of range.
Pairing needs to be tried again by placing the device closer to the hub.
Instructions related to pairing, resetting and removing the device from SmartThings can be found in the following links
for the different models:
* [SmartPower Outlet Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/201084854-SmartPower-Outlet)
* [Samsung SmartThings Outlet Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/205957620)

View File

@@ -102,11 +102,11 @@ def parse(String description) {
def descriptionText = finalResult.value == "on" ? '{{ device.displayName }} is On' : '{{ device.displayName }} is Off' def descriptionText = finalResult.value == "on" ? '{{ device.displayName }} is On' : '{{ device.displayName }} is Off'
sendEvent(name: finalResult.type, value: finalResult.value, descriptionText: descriptionText, translatable: true) sendEvent(name: finalResult.type, value: finalResult.value, descriptionText: descriptionText, translatable: true)
// Temporary fix for the case when Device is OFFLINE and is connected again // Temporary fix for the case when Device is OFFLINE and is connected again
if (state.lastActivity == null){ if (state.lastOnOff == null){
state.lastActivity = now() state.lastOnOff = now()
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true) sendEvent(name: "deviceWatch-lastActivity", value: state.lastOnOff, description: "Last Activity is on ${new Date(state.lastOnOff)}", displayed: false, isStateChange: true)
} }
state.lastActivity = now() state.lastOnOff = now()
} }
} }
else { else {
@@ -123,17 +123,18 @@ def on() {
zigbee.on() zigbee.on()
} }
/** /**
* PING is used by Device-Watch in attempt to reach the Device * PING is used by Device-Watch in attempt to reach the Outlet
* */ * */
def ping() { def ping() {
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){ // send read attribute onOFF if the last time we heard from the outlet is outside of the checkInterval
log.info "ping, alive=no, lastActivity=${state.lastActivity}" if (state.lastOnOff < (now() - (1000 * device.currentValue("checkInterval"))) ){
state.lastActivity = null log.info "ping, alive=no, lastOnOff=${new Date(state.lastOnOff)}"
state.lastOnOff = null
return zigbee.onOffRefresh() return zigbee.onOffRefresh()
} else { } else { // if the last onOff activity is within the checkInterval we artificially create a Device-Watch event
log.info "ping, alive=yes, lastActivity=${state.lastActivity}" log.info "ping, alive=yes, lastOnOff=${new Date(state.lastOnOff)}"
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true) sendEvent(name: "deviceWatch-lastActivity", value: state.lastOnOff, description: "Last Activity is on ${new Date(state.lastOnOff)}", displayed: false, isStateChange: true)
} }
} }
@@ -142,7 +143,7 @@ def refresh() {
} }
def configure() { def configure() {
sendEvent(name: "checkInterval", value: 1200, displayed: false, data: [protocol: "zigbee"]) sendEvent(name: "checkInterval", value: 1200, displayed: false)
zigbee.onOffConfig() + powerConfig() + refresh() zigbee.onOffConfig() + powerConfig() + refresh()
} }

View File

@@ -1,2 +0,0 @@
.st-ignore
README.md

View File

@@ -1,44 +0,0 @@
# Smartsense Moisture Sensor
Works with:
* [Samsung SmartThings Moisture Sensor](https://shop.smartthings.com/#!/products/samsung-smartthings-water-leak-sensor)
## Table of contents
* [Capabilities](#capabilities)
* [Health](#device-health)
* [Battery](#battery-specification)
## Capabilities
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
* **Battery** - defines device uses a battery
* **Refresh** - _refresh()_ command for status updates
* **Temperature Measurement** - defines device measures current temperature
* **Water Sensor** - can detect presence of water (dry or wet)
* **Health Check** - indicates ability to get device health notifications
## Device Health
A Category C2 moisture sensor with maxReportTime of 1 hr.
Check-in interval is double the value of maxReportTime for Zigbee device.
This gives the device twice the amount of time to respond before it is marked as offline.
Check-in interval = 2*60 = 120 min
## Battery Specification
One CR2 3V battery required.
## Troubleshooting
If the sensor doesn't pair when trying from the SmartThings mobile app, it is possible that the sensor is out of range.
Pairing needs to be tried again by placing the sensor closer to the hub.
Instructions related to pairing, resetting and removing the different sensors from SmartThings can be found in the following links
for the different models:
* [SmartSense Moisture Sensor Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/202847044-SmartSense-Moisture-Sensor)
* [Samsung SmartThings Water Leak Sensor Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/205957630)
Other troubleshooting tips are listed as follows:
* [Troubleshooting: Samsung SmartThings Water Leak Sensor wont pair after removing pull-tab](https://support.smartthings.com/hc/en-us/articles/204966616-Troubleshooting-Samsung-SmartThings-device-won-t-pair-after-removing-pull-tab)

View File

@@ -24,7 +24,7 @@ metadata {
capability "Temperature Measurement" capability "Temperature Measurement"
capability "Water Sensor" capability "Water Sensor"
capability "Health Check" capability "Health Check"
capability "Sensor" capability "Sensor"
command "enrollResponse" command "enrollResponse"
@@ -101,13 +101,6 @@ def parse(String description) {
map = parseIasMessage(description) map = parseIasMessage(description)
} }
// Temporary fix for the case when Device is OFFLINE and is connected again
if (state.lastActivity == null){
state.lastActivity = now()
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
state.lastActivity = now()
log.debug "Parse returned $map" log.debug "Parse returned $map"
def result = map ? createEvent(map) : null def result = map ? createEvent(map) : null
@@ -259,8 +252,7 @@ private Map getTemperatureResult(value) {
name: 'temperature', name: 'temperature',
value: value, value: value,
descriptionText: descriptionText, descriptionText: descriptionText,
translatable: true, translatable: true
unit: temperatureScale
] ]
} }
@@ -279,21 +271,6 @@ private Map getMoistureResult(value) {
] ]
} }
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
state.lastActivity = null
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
} else {
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
}
def refresh() { def refresh() {
log.debug "Refreshing Temperature and Battery" log.debug "Refreshing Temperature and Battery"
def refreshCmds = [ def refreshCmds = [
@@ -305,7 +282,7 @@ def refresh() {
} }
def configure() { def configure() {
sendEvent(name: "checkInterval", value: 14400, displayed: false, data: [protocol: "zigbee"]) sendEvent(name: "checkInterval", value: 7200, displayed: false)
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui) String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
log.debug "Configuring Reporting, IAS CIE, and Bindings." log.debug "Configuring Reporting, IAS CIE, and Bindings."

View File

@@ -9,8 +9,7 @@ Works with:
## Table of contents ## Table of contents
* [Capabilities](#capabilities) * [Capabilities](#capabilities)
* [Health](#device-health) * [Health]($health)
* [Battery](#battery-specification)
## Capabilities ## Capabilities
@@ -22,24 +21,10 @@ Works with:
## Device Health ## Device Health
A Category C2 motion sensor with maxReportTime of 1 hr. A Category C2 motion sensor that has 120min check-in interval
Check-in interval is double the value of maxReportTime for Zigbee device.
This gives the device twice the amount of time to respond before it is marked as offline.
Check-in interval = 2*60 = 120 min
## Battery Specification
One CR2477 (for Samsung SmartThings Motion Sensor) / CR123A (SmartSense Motion Sensor) 3V battery is required.
## Troubleshooting
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the sensor is out of range.
Pairing needs to be tried again by placing the sensor closer to the hub.
Instructions related to pairing, resetting and removing the different motion sensors from SmartThings can be found in the following links
for the different models:
* [SmartSense Motion Sensor (original model) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/200903280-SmartSense-Motion-Sensor-original-model-)
* [SmartSense Motion Sensor (2014 model) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/203077520-SmartSense-Motion-Sensor-2014-model-)
* [Samsung SmartThings Motion Sensor (2015 model) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/205957580-Samsung-SmartThings-Motion-Sensor-2015-model-)
Other troubleshooting tips are listed as follows:
* [Troubleshooting: Samsung SmartThings Motion Sensor is stuck showing "Motion Detected" or "No Motion"](https://support.smartthings.com/hc/en-us/articles/200961130-Troubleshooting-Samsung-SmartThings-Motion-Sensor-is-stuck-showing-Motion-Detected-or-No-Motion-)
* [Troubleshooting: Samsung SmartThings Motion Sensor wont pair after removing pull-tab](https://support.smartthings.com/hc/en-us/articles/204966616-Troubleshooting-Samsung-SmartThings-device-won-t-pair-after-removing-pull-tab)

View File

@@ -24,7 +24,7 @@ metadata {
capability "Temperature Measurement" capability "Temperature Measurement"
capability "Refresh" capability "Refresh"
capability "Health Check" capability "Health Check"
capability "Sensor" capability "Sensor"
command "enrollResponse" command "enrollResponse"
@@ -105,13 +105,6 @@ def parse(String description) {
map = parseIasMessage(description) map = parseIasMessage(description)
} }
// Temporary fix for the case when Device is OFFLINE and is connected again
if (state.lastActivity == null){
state.lastActivity = now()
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
state.lastActivity = now()
log.debug "Parse returned $map" log.debug "Parse returned $map"
def result = map ? createEvent(map) : null def result = map ? createEvent(map) : null
@@ -274,8 +267,7 @@ private Map getTemperatureResult(value) {
name: 'temperature', name: 'temperature',
value: value, value: value,
descriptionText: descriptionText, descriptionText: descriptionText,
translatable: true, translatable: true
unit: temperatureScale
] ]
} }
@@ -290,21 +282,6 @@ private Map getMotionResult(value) {
] ]
} }
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
state.lastActivity = null
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
} else {
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
}
def refresh() { def refresh() {
log.debug "refresh called" log.debug "refresh called"
def refreshCmds = [ def refreshCmds = [
@@ -316,7 +293,7 @@ def refresh() {
} }
def configure() { def configure() {
sendEvent(name: "checkInterval", value: 14400, displayed: false, data: [protocol: "zigbee"]) sendEvent(name: "checkInterval", value: 7200, displayed: false)
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui) String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
log.debug "Configuring Reporting, IAS CIE, and Bindings." log.debug "Configuring Reporting, IAS CIE, and Bindings."

View File

@@ -170,7 +170,7 @@ private Map parseCustomMessage(String description) {
private Map parseIasMessage(String description) { private Map parseIasMessage(String description) {
ZoneStatus zs = zigbee.parseZoneStatus(description) ZoneStatus zs = zigbee.parseZoneStatus(description)
return (zs.isAlarm1Set() || zs.isAlarm2Set()) ? getMotionResult('active') : getMotionResult('inactive') return zs.isAlarm1Set() ? getMotionResult('active') : getMotionResult('inactive')
} }
def getTemperature(value) { def getTemperature(value) {
@@ -226,8 +226,7 @@ private Map getTemperatureResult(value) {
return [ return [
name: 'temperature', name: 'temperature',
value: value, value: value,
descriptionText: descriptionText, descriptionText: descriptionText
unit: temperatureScale
] ]
} }

View File

@@ -1,2 +0,0 @@
.st-ignore
README.md

View File

@@ -1,45 +0,0 @@
# Smartsense Multi Sensor
Works with:
* [Samsung SmartThings Multi Sensor](https://shop.smartthings.com/#!/products/smartsense-multi)
## Table of contents
* [Capabilities](#capabilities)
* [Health](#device-health)
* [Battery](#battery-specification)
## Capabilities
* **Three Axis** - monitors the state of a single axis
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
* **Battery** - defines device uses a battery
* **Sensor** - detects sensor events
* **Contact Sensor** - can detect contact (possible values: open,closed)
* **Acceleration Sensor** - allows for acceleration detection.
* **Refresh** - _refresh()_ command for status updates
* **Temperature Measurement** - defines device measures current temperature
* **Health Check** - indicates ability to get device health notifications
## Device Health
A Category C2 multi sensor with maxReportTime of 1 hr.
Check-in interval is double the value of maxReportTime for Zigbee device.
This gives the device twice the amount of time to respond before it is marked as offline.
Check-in interval = 2*60 = 120 min
## Battery Specification
One CR2450 (for Samsung SmartThings Multipurpose Sensor) battery / Two AAAA (for SmartSense Multi Sensor) batteries required.
## Troubleshooting
If the sensor doesn't pair when trying from the SmartThings mobile app, it is possible that the sensor is out of range.
Pairing needs to be tried again by placing the sensor closer to the hub.
Other troubleshooting tips are listed as follows:
* [Troubleshooting: Samsung SmartThings Multipurpose Sensor is stuck on "open" or "closed"](https://support.smartthings.com/hc/en-us/articles/200955940-Troubleshooting-Samsung-SmartThings-Multipurpose-Sensor-is-stuck-on-open-or-closed-)
* [Troubleshooting: Temperature reading for the Samsung SmartThings Multipurpose Sensor is off](https://support.smartthings.com/hc/en-us/articles/200756845-Troubleshooting-Temperature-reading-for-the-Samsung-SmartThings-Multipurpose-Sensor-is-off)
* [Troubleshooting: Samsung SmartThings Multipurpose Sensor wont pair after removing pull-tab](https://support.smartthings.com/hc/en-us/articles/204966616-Troubleshooting-Samsung-SmartThings-device-won-t-pair-after-removing-pull-tab)

View File

@@ -127,13 +127,6 @@ def parse(String description) {
map = parseIasMessage(description) map = parseIasMessage(description)
} }
// Temporary fix for the case when Device is OFFLINE and is connected again
if (state.lastActivity == null){
state.lastActivity = now()
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
state.lastActivity = now()
def result = map ? createEvent(map) : null def result = map ? createEvent(map) : null
if (description?.startsWith('enroll request')) { if (description?.startsWith('enroll request')) {
@@ -235,9 +228,9 @@ private Map parseIasMessage(String description) {
ZoneStatus zs = zigbee.parseZoneStatus(description) ZoneStatus zs = zigbee.parseZoneStatus(description)
Map resultMap = [:] Map resultMap = [:]
if (garageSensor != "Yes"){ if(garageSensor != "Yes") {
resultMap = zs.isAlarm1Set() ? getContactResult('open') : getContactResult('closed') resultMap = zs.isAlarm1Set() ? getContactResult('open') : getContactResult('closed')
} }
return resultMap return resultMap
} }
@@ -333,11 +326,10 @@ private Map getTemperatureResult(value) {
'{{ device.displayName }} was {{ value }}°F' '{{ device.displayName }} was {{ value }}°F'
return [ return [
name: 'temperature', name: 'temperature',
value: value, value: value,
descriptionText: descriptionText, descriptionText: descriptionText,
translatable: true, translatable: true
unit: temperatureScale
] ]
} }
@@ -372,21 +364,6 @@ private getAccelerationResult(numValue) {
] ]
} }
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
state.lastActivity = null
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
} else {
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
}
def refresh() { def refresh() {
log.debug "Refreshing Values " log.debug "Refreshing Values "
@@ -414,7 +391,7 @@ def refresh() {
} }
def configure() { def configure() {
sendEvent(name: "checkInterval", value: 14400, displayed: false, data: [protocol: "zigbee"]) sendEvent(name: "checkInterval", value: 7200, displayed: false)
log.debug "Configuring Reporting" log.debug "Configuring Reporting"

View File

@@ -223,10 +223,9 @@ def getTemperature(value) {
} }
def descriptionText = "${linkText} was ${value}°${temperatureScale}" def descriptionText = "${linkText} was ${value}°${temperatureScale}"
return [ return [
name: 'temperature', name: 'temperature',
value: value, value: value,
descriptionText: descriptionText, descriptionText: descriptionText
unit: temperatureScale
] ]
} }

View File

@@ -1,2 +0,0 @@
.st-ignore
README.md

View File

@@ -1,41 +0,0 @@
# Smartsense Open/Closed Sensor
Works with:
* [Samsung SmartThings Open/Closed Sensor](https://shop.smartthings.com/#!/packs/smartsense-open-closed-sensor/)
## Table of contents
* [Capabilities](#capabilities)
* [Health](#device-health)
* [Battery](#battery-specification)
## Capabilities
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
* **Battery** - defines device uses a battery
* **Contact Sensor** - can detect contact (possible values: open,closed)
* **Refresh** - _refresh()_ command for status updates
* **Temperature Measurement** - defines device measures current temperature
* **Health Check** - indicates ability to get device health notifications
* **Sensor** - detects sensor events
## Device Health
A Category C2 open/closed sensor with maxReportTime of 1 hr.
Check-in interval is double the value of maxReportTime for Zigbee device.
This gives the device twice the amount of time to respond before it is marked as offline.
Check-in interval = 2*60 = 120 min
## Battery Specification
One CR2 3V battery required.
## Troubleshooting
If the sensor doesn't pair when trying from the SmartThings mobile app, it is possible that the sensor is out of range.
Pairing needs to be tried again by placing the sensor closer to the hub.
Instructions related to pairing, resetting and removing the sensor from SmartThings can be found in the following link:
* [SmartSense Open/Closed Sensor Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/202836844-SmartSense-Open-Closed-Sensor)

View File

@@ -92,13 +92,6 @@ def parse(String description) {
map = parseIasMessage(description) map = parseIasMessage(description)
} }
// Temporary fix for the case when Device is OFFLINE and is connected again
if (state.lastActivity == null){
state.lastActivity = now()
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
state.lastActivity = now()
log.debug "Parse returned $map" log.debug "Parse returned $map"
def result = map ? createEvent(map) : null def result = map ? createEvent(map) : null
@@ -226,8 +219,7 @@ private Map getTemperatureResult(value) {
return [ return [
name: 'temperature', name: 'temperature',
value: value, value: value,
descriptionText: descriptionText, descriptionText: descriptionText
unit: temperatureScale
] ]
} }
@@ -242,21 +234,6 @@ private Map getContactResult(value) {
] ]
} }
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
state.lastActivity = null
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
} else {
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
}
def refresh() { def refresh() {
log.debug "Refreshing Temperature and Battery" log.debug "Refreshing Temperature and Battery"
def refreshCmds = [ def refreshCmds = [
@@ -268,7 +245,7 @@ def refresh() {
} }
def configure() { def configure() {
sendEvent(name: "checkInterval", value: 14400, displayed: false, data: [protocol: "zigbee"]) sendEvent(name: "checkInterval", value: 7200, displayed: false)
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui) String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
log.debug "Configuring Reporting, IAS CIE, and Bindings." log.debug "Configuring Reporting, IAS CIE, and Bindings."

View File

@@ -1,2 +0,0 @@
.st-ignore
README.md

View File

@@ -1,41 +0,0 @@
# SmartSense Temp/Humidity Sensor
Works with:
* [Samsung SmartSense Temp/Humidity Sensor](https://shop.smartthings.com/#!/products/smartsense-temp-humidity-sensor)
## Table of contents
* [Capabilities](#capabilities)
* [Health](#device-health)
* [Battery](#battery-specification)
## Capabilities
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
* **Battery** - defines device uses a battery
* **Relative Humidity Measurement** - defines device measures relative humidity
* **Refresh** - _refresh()_ command for status updates
* **Temperature Measurement** - defines device measures current temperature
* **Health Check** - indicates ability to get device health notifications
* **Sensor** - detects sensor events
## Device Health
A Category C2 SmartSense Temp/Humidity Sensor with maxReportTime of 1 hr.
Check-in interval is double the value of maxReportTime for Zigbee device.
This gives the device twice the amount of time to respond before it is marked as offline.
Check-in interval = 2*60 = 120 min
## Battery Specification
One CR2 battery is required.
## Troubleshooting
If the sensor doesn't pair when trying from the SmartThings mobile app, it is possible that the sensor is out of range.
Pairing needs to be tried by placing the sensor closer to the hub.
Instructions related to pairing, resetting and removing the sensor from SmartThings can be found in the following link:
* [Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/203040294)

View File

@@ -83,13 +83,6 @@ def parse(String description) {
map = parseCustomMessage(description) map = parseCustomMessage(description)
} }
// Temporary fix for the case when Device is OFFLINE and is connected again
if (state.lastActivity == null){
state.lastActivity = now()
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
state.lastActivity = now()
log.debug "Parse returned $map" log.debug "Parse returned $map"
return map ? createEvent(map) : null return map ? createEvent(map) : null
} }
@@ -233,8 +226,7 @@ private Map getTemperatureResult(value) {
return [ return [
name: 'temperature', name: 'temperature',
value: value, value: value,
descriptionText: descriptionText, descriptionText: descriptionText
unit: temperatureScale
] ]
} }
@@ -247,20 +239,6 @@ private Map getHumidityResult(value) {
] ]
} }
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
state.lastActivity = null
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
} else {
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
}
def refresh() def refresh()
{ {
log.debug "refresh temperature, humidity, and battery" log.debug "refresh temperature, humidity, and battery"
@@ -276,7 +254,7 @@ def refresh()
} }
def configure() { def configure() {
sendEvent(name: "checkInterval", value: 14400, displayed: false, data: [protocol: "zigbee"]) sendEvent(name: "checkInterval", value: 7200, displayed: false)
log.debug "Configuring Reporting and Bindings." log.debug "Configuring Reporting and Bindings."
def configCmds = [ def configCmds = [

View File

@@ -213,8 +213,7 @@ private Map getTemperatureResult(value) {
return [ return [
name: 'temperature', name: 'temperature',
value: value, value: value,
descriptionText: descriptionText, descriptionText: descriptionText
unit: temperatureScale
] ]
} }

View File

@@ -19,7 +19,6 @@ metadata {
capability "Refresh" capability "Refresh"
capability "Switch" capability "Switch"
capability "Switch Level" capability "Switch Level"
capability "Health Check"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008" fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008"
@@ -54,16 +53,7 @@ def parse(String description) {
def event = zigbee.getEvent(description) def event = zigbee.getEvent(description)
if (event) { if (event) {
// Temporary fix for the case when Device is OFFLINE and is connected again sendEvent(event)
if (state.lastActivity == null){
state.lastActivity = now()
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
state.lastActivity = now()
if (event.name=="level" && event.value==0) {}
else {
sendEvent(event)
}
} }
else { else {
log.warn "DID NOT PARSE MESSAGE for description : $description" log.warn "DID NOT PARSE MESSAGE for description : $description"
@@ -82,20 +72,6 @@ def on() {
def setLevel(value) { def setLevel(value) {
zigbee.setLevel(value) zigbee.setLevel(value)
} }
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
state.lastActivity = null
return zigbee.onOffRefresh()
} else {
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
}
def refresh() { def refresh() {
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig() + zigbee.levelConfig() zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig() + zigbee.levelConfig()
@@ -103,7 +79,5 @@ def refresh() {
def configure() { def configure() {
log.debug "Configuring Reporting and Bindings." log.debug "Configuring Reporting and Bindings."
// Enrolls device to Device-Watch with 3 x Reporting interval 30min
sendEvent(name: "checkInterval", value: 1800, displayed: false, data: [protocol: "zigbee"])
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh() zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh()
} }

View File

@@ -27,10 +27,6 @@ metadata {
capability "Refresh" capability "Refresh"
capability "Switch" capability "Switch"
capability "Switch Level" capability "Switch Level"
capability "Health Check"
attribute "colorName", "string"
command "setGenericName"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY Flex RGBW", deviceJoinName: "OSRAM LIGHTIFY LED FLEXIBLE STRIP RGBW" fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY Flex RGBW", deviceJoinName: "OSRAM LIGHTIFY LED FLEXIBLE STRIP RGBW"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Flex RGBW", deviceJoinName: "OSRAM LIGHTIFY LED FLEXIBLE STRIP RGBW" fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Flex RGBW", deviceJoinName: "OSRAM LIGHTIFY LED FLEXIBLE STRIP RGBW"
@@ -58,15 +54,15 @@ metadata {
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2700..6500)") { controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2700..6500)") {
state "colorTemperature", action:"color temperature.setColorTemperature" state "colorTemperature", action:"color temperature.setColorTemperature"
} }
valueTile("colorName", "device.colorName", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "colorName", label: '${currentValue}' state "colorTemperature", label: '${currentValue} K'
} }
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh" state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
} }
main(["switch"]) main(["switch"])
details(["switch", "colorTempSliderControl", "colorName", "refresh"]) details(["switch", "colorTempSliderControl", "colorTemp", "refresh"])
} }
} }
@@ -82,22 +78,10 @@ private getATTRIBUTE_COLOR_TEMPERATURE() { 0x0007 }
def parse(String description) { def parse(String description) {
log.debug "description is $description" log.debug "description is $description"
def event = zigbee.getEvent(description) def finalResult = zigbee.getEvent(description)
if (event) { if (finalResult) {
log.debug event log.debug finalResult
// Temporary fix for the case when Device is OFFLINE and is connected again sendEvent(finalResult)
if (state.lastActivity == null){
state.lastActivity = now()
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
state.lastActivity = now()
if (event.name=="level" && event.value==0) {}
else {
if (event.name=="colorTemperature") {
setGenericName(event.value)
}
sendEvent(event)
}
} }
else { else {
def zigbeeMap = zigbee.parseDescriptionAsMap(description) def zigbeeMap = zigbee.parseDescriptionAsMap(description)
@@ -106,11 +90,11 @@ def parse(String description) {
if (zigbeeMap?.clusterInt == COLOR_CONTROL_CLUSTER) { if (zigbeeMap?.clusterInt == COLOR_CONTROL_CLUSTER) {
if(zigbeeMap.attrInt == ATTRIBUTE_HUE){ //Hue Attribute if(zigbeeMap.attrInt == ATTRIBUTE_HUE){ //Hue Attribute
def hueValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 255 * 360) def hueValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 255 * 360)
sendEvent(name: "hue", value: hueValue, descriptionText: "Color has changed") sendEvent(name: "hue", value: hueValue, displayed:false)
} }
else if(zigbeeMap.attrInt == ATTRIBUTE_SATURATION){ //Saturation Attribute else if(zigbeeMap.attrInt == ATTRIBUTE_SATURATION){ //Saturation Attribute
def saturationValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 255 * 100) def saturationValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 255 * 100)
sendEvent(name: "saturation", value: saturationValue, descriptionText: "Color has changed", displayed: false) sendEvent(name: "saturation", value: saturationValue, displayed:false)
} }
} }
else { else {
@@ -126,54 +110,20 @@ def on() {
def off() { def off() {
zigbee.off() zigbee.off()
} }
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
state.lastActivity = null
return zigbee.onOffRefresh()
} else {
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
}
def refresh() { def refresh() {
zigbee.onOffRefresh() + zigbee.readAttribute(0x0008, 0x00) + zigbee.readAttribute(0x0300, 0x00) + zigbee.readAttribute(0x0300, ATTRIBUTE_COLOR_TEMPERATURE) + zigbee.readAttribute(0x0300, ATTRIBUTE_HUE) + zigbee.readAttribute(0x0300, ATTRIBUTE_SATURATION) + zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE, 0x20, 1, 3600, 0x01) + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION, 0x20, 1, 3600, 0x01) zigbee.readAttribute(0x0006, 0x00) + zigbee.readAttribute(0x0008, 0x00) + zigbee.readAttribute(0x0300, 0x00) + zigbee.readAttribute(0x0300, ATTRIBUTE_COLOR_TEMPERATURE) + zigbee.readAttribute(0x0300, ATTRIBUTE_HUE) + zigbee.readAttribute(0x0300, ATTRIBUTE_SATURATION) + zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE, 0x20, 1, 3600, 0x01) + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION, 0x20, 1, 3600, 0x01)
} }
def configure() { def configure() {
log.debug "Configuring Reporting and Bindings." log.debug "Configuring Reporting and Bindings."
// Enrolls device to Device-Watch with 3 x Reporting interval 30min
sendEvent(name: "checkInterval", value: 1800, displayed: false, data: [protocol: "zigbee"])
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE, 0x20, 1, 3600, 0x01) + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION, 0x20, 1, 3600, 0x01) + zigbee.readAttribute(0x0006, 0x00) + zigbee.readAttribute(0x0008, 0x00) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, 0x00) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_COLOR_TEMPERATURE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION) zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE, 0x20, 1, 3600, 0x01) + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION, 0x20, 1, 3600, 0x01) + zigbee.readAttribute(0x0006, 0x00) + zigbee.readAttribute(0x0008, 0x00) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, 0x00) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_COLOR_TEMPERATURE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION)
} }
def setColorTemperature(value) { def setColorTemperature(value) {
setGenericName(value)
zigbee.setColorTemperature(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)
}
}
def setLevel(value) { def setLevel(value) {
zigbee.setLevel(value) zigbee.setLevel(value)
} }

View File

@@ -22,7 +22,6 @@ metadata {
capability "Actuator" capability "Actuator"
capability "Color Temperature" capability "Color Temperature"
capability "Configuration" capability "Configuration"
capability "Health Check"
capability "Refresh" capability "Refresh"
capability "Switch" capability "Switch"
capability "Switch Level" capability "Switch Level"
@@ -36,7 +35,6 @@ metadata {
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: "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: "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" 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"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Classic B40 TW - LIGHTIFY", deviceJoinName: "OSRAM LIGHTIFY Classic B40 Tunable White"
} }
// UI tile definitions // UI tile definitions
@@ -51,6 +49,9 @@ metadata {
tileAttribute ("device.level", key: "SLIDER_CONTROL") { tileAttribute ("device.level", key: "SLIDER_CONTROL") {
attributeState "level", action:"switch level.setLevel" attributeState "level", action:"switch level.setLevel"
} }
tileAttribute ("colorName", key: "SECONDARY_CONTROL") {
attributeState "colorName", label:'${currentValue}'
}
} }
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
@@ -60,12 +61,12 @@ metadata {
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2700..6500)") { controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2700..6500)") {
state "colorTemperature", action:"color temperature.setColorTemperature" state "colorTemperature", action:"color temperature.setColorTemperature"
} }
valueTile("colorName", "device.colorName", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "colorName", label: '${currentValue}' state "colorTemperature", label: '${currentValue} K'
} }
main(["switch"]) main(["switch"])
details(["switch", "colorTempSliderControl", "colorName", "refresh"]) details(["switch", "colorTempSliderControl", "colorTemp", "refresh"])
} }
} }
@@ -74,19 +75,7 @@ def parse(String description) {
log.debug "description is $description" log.debug "description is $description"
def event = zigbee.getEvent(description) def event = zigbee.getEvent(description)
if (event) { if (event) {
// Temporary fix for the case when Device is OFFLINE and is connected again sendEvent(event)
if (state.lastActivity == null){
state.lastActivity = now()
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
state.lastActivity = now()
if (event.name=="level" && event.value==0) {}
else {
if (event.name=="colorTemperature") {
setGenericName(event.value)
}
sendEvent(event)
}
} }
else { else {
log.warn "DID NOT PARSE MESSAGE for description : $description" log.warn "DID NOT PARSE MESSAGE for description : $description"
@@ -106,29 +95,12 @@ def setLevel(value) {
zigbee.setLevel(value) zigbee.setLevel(value)
} }
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
if (state.lastActivity < (now() - (1000 * device.currentValue("checkInterval"))) ){
log.info "ping, alive=no, lastActivity=${state.lastActivity}"
state.lastActivity = null
return zigbee.onOffRefresh()
} else {
log.info "ping, alive=yes, lastActivity=${state.lastActivity}"
sendEvent(name: "deviceWatch-lastActivity", value: state.lastActivity, description: "Last Activity is on ${new Date((long)state.lastActivity)}", displayed: false, isStateChange: true)
}
}
def refresh() { def refresh() {
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh() + zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig() zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh() + zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig()
} }
def configure() { def configure() {
log.debug "Configuring Reporting and Bindings." log.debug "Configuring Reporting and Bindings."
// Enrolls device to Device-Watch with 3 x Reporting interval 30min
sendEvent(name: "checkInterval", value: 1800, displayed: false, data: [protocol: "zigbee"])
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh() zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh()
} }

View File

@@ -0,0 +1,146 @@
definition(
name: "Door & Lock Manager",
namespace: "airoscar",
author: "Oscar Chen",
description: "Manages door and lock behaviors. Send push notification or text message if the door is left open or if the lock is left unlocked for a preset amount of time; as well as automatically locking the lock with a preset delay after the door has been closed.",
category: "Convenience",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/ModeMagic/bon-voyage.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/ModeMagic/bon-voyage%402x.png"
)
preferences {
section("Monitor this door") {
input "contact", "capability.contactSensor"
}
section ("Monitor this lock") {
input "lock", "capability.lock"
}
section("And notify me if it's open for more than this many minutes (default: 3)") {
input "openThreshold", "number", description: "Number of minutes", required: false
}
section("Delay between notifications (default 10 minutes") {
input "frequency", "number", title: "Number of minutes", description: "", required: false
}
section("Automatically lock this door after this many minutes (default: 0.5)") {
input "lockDelay", "number", title: "Number of minutes", required: false
}
section("Via text message at this number (or via push notification if not specified") {
input("recipients", "contact", title: "Send notifications to") {
input "phone", "phone", title: "Phone number (optional)", required: false
}
}
}
def installed() {
log.trace "installed()"
subscribe()
}
def updated() {
log.trace "updated()"
unsubscribe()
subscribe()
}
def subscribe() {
subscribe(contact, "contact", doorHandler)
subscribe(lock, "lock", lockHandler)
}
def lockHandler(evt)
{
log.trace "LockHandler($evt.name: $evt.value)"
def t0 = now()
def delay = (openThreshold != null && openThreshold != "") ? openThreshold * 60 : 180 //unlocked notification delay
runIn(delay, doorOpenTooLong, [overwrite: false])
log.debug "scheduled doorOpenTooLong in ${now() - t0} msec"
}
def doorHandler(evt)
{
log.trace "doorHandler($evt.name: $evt.value)"
def t0 = now()
def delay = (openThreshold != null && openThreshold != "") ? openThreshold * 60 : 180 //door open notification delay
def delay2 = (lockDelay != null && lockDelay != "") ? lockDelay * 60 : 30 // auto lock delay
runIn(delay2, autoLock, [overwrite: false])
runIn(delay, doorOpenTooLong, [overwrite: false])
log.debug "scheduled doorOpenTooLong in ${now() - t0} msec"
}
def autoLock() {
def contactState = contact.currentState("contact")
def lockState = lock.currentState("lock")
def elapsed = now() - contactState.rawDateCreated.time
def autoLockDelay = ((delay2 != null && delay2 != "") ? delay2 * 60000 : 60000) - 1000
if (elapsed >= threshold) {
if (contactState.value == "closed" && lockState.value == "unlocked") {
lock.lock()
}
}
}
def doorOpenTooLong() {
def contactState = contact.currentState("contact")
def lockState = lock.currentState("lock")
def freq = (frequency != null && frequency != "") ? frequency * 60 : 600 //notification frequency
if (contactState.value == "open") {
def elapsed = now() - contactState.rawDateCreated.time
def threshold = ((openThreshold != null && openThreshold != "") ? openThreshold * 60000 : 60000) - 1000
if (elapsed >= threshold) {
log.debug "contact has stayed open long enough since last check ($elapsed ms): calling sendMessage()"
sendMessage()
runIn(freq, doorOpenTooLong, [overwrite: false])
} else {
log.debug "contact has not stayed open long enough since last check ($elapsed ms): doing nothing"
}
} else if (lockState.value =="unlocked") {
def elapsed = now() - contactState.rawDateCreated.time
def threshold = ((openThreshold != null && openThreshold != "") ? openThreshold * 60000 : 60000) - 1000
if (elapsed >= threshold) {
log.debug "lock has stayed unlocked long enough since last check ($elapsed ms): calling sendMessage()"
sendMessage()
runIn(freq, doorOpenTooLong, [overwrite: false])
} else {
log.debug "lock has not stayed unlocked long enough since last check ($elapsed ms): doing nothing"
}
} else {
log.warn "doing nothing"
}
}
void sendMessage()
{
def minutes = (openThreshold != null && openThreshold != "") ? openThreshold : 3
def msg = ""
if (contact.currentState("contact").value == "open") {
msg = "${contact.displayName} has been left open for ${minutes} minutes."
} else if (lock.currentState("lock").value == "unlocked") {
msg = "${contact.displayName} is closed but ${lock.displayName} has been left unlocked for ${minutes} minutes."
} else {
msg = "No message"
}
log.info msg
if (location.contactBookEnabled) {
sendNotificationToContacts(msg, recipients)
}
else {
if (phone) {
sendSms phone, msg
} else {
sendPush msg
}
}
}

View File

@@ -62,7 +62,7 @@ def initialize() {
} }
def sendit(evt) { def sendit(evt) {
log.debug "$evt.value: $evt" log.debug "$evt.value: $evt, $settings"
sendMessage() sendMessage()
} }
@@ -80,6 +80,6 @@ def sendMessage() {
sendSms phone3, msg sendSms phone3, msg
} }
if (!phone1 && !phone2 && !phone3) { if (!phone1 && !phone2 && !phone3) {
sendPush msg sendPush msg
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -74,6 +74,8 @@ def authPage()
def redirectUrl = oauthInitUrl() def redirectUrl = oauthInitUrl()
log.debug "RedirectURL = ${redirectUrl}"
return dynamicPage(name: "Credentials", title: "Life360", nextPage:"listCirclesPage", uninstall: uninstallOption, install:false) { return dynamicPage(name: "Credentials", title: "Life360", nextPage:"listCirclesPage", uninstall: uninstallOption, install:false) {
section { section {
href url:redirectUrl, style:"embedded", required:false, title:"Life360", description:description href url:redirectUrl, style:"embedded", required:false, title:"Life360", description:description
@@ -255,6 +257,8 @@ def initializeLife360Connection() {
def oauthClientId = appSettings.clientId def oauthClientId = appSettings.clientId
def oauthClientSecret = appSettings.clientSecret def oauthClientSecret = appSettings.clientSecret
log.debug "Installed with settings: ${settings}"
initialize() initialize()
def username = settings.username def username = settings.username
@@ -265,6 +269,8 @@ def initializeLife360Connection() {
def basicCredentials = "${oauthClientId}:${oauthClientSecret}" def basicCredentials = "${oauthClientId}:${oauthClientSecret}"
def encodedCredentials = basicCredentials.encodeAsBase64().toString() def encodedCredentials = basicCredentials.encodeAsBase64().toString()
log.debug "Encoded Creds: ${encodedCredentials}"
// call life360, get OAUTH token using password flow, save // call life360, get OAUTH token using password flow, save
// curl -X POST -H "Authorization: Basic cFJFcXVnYWJSZXRyZTRFc3RldGhlcnVmcmVQdW1hbUV4dWNyRUh1YzptM2ZydXBSZXRSZXN3ZXJFQ2hBUHJFOTZxYWtFZHI0Vg==" // curl -X POST -H "Authorization: Basic cFJFcXVnYWJSZXRyZTRFc3RldGhlcnVmcmVQdW1hbUV4dWNyRUh1YzptM2ZydXBSZXRSZXN3ZXJFQ2hBUHJFOTZxYWtFZHI0Vg=="
@@ -278,6 +284,8 @@ def initializeLife360Connection() {
"username=${username}&"+ "username=${username}&"+
"password=${password}" "password=${password}"
log.debug "Post Body: ${postBody}"
def result = null def result = null
try { try {
@@ -287,6 +295,7 @@ def initializeLife360Connection() {
} }
if (result.data.access_token) { if (result.data.access_token) {
state.life360AccessToken = result.data.access_token state.life360AccessToken = result.data.access_token
log.debug "Access Token = ${state.life360AccessToken}"
return true; return true;
} }
log.debug "Response=${result.data}" log.debug "Response=${result.data}"
@@ -524,6 +533,8 @@ def createCircleSubscription() {
def postBody = "url=${hookUrl}" def postBody = "url=${hookUrl}"
log.debug "Post Body: ${postBody}"
def result = null def result = null
try { try {
@@ -575,6 +586,8 @@ def updated() {
// log.debug "After Find Attempt." // log.debug "After Find Attempt."
log.debug "Member Id = ${member.id}, Name = ${member.firstName} ${member.lastName}, Email Address = ${member.loginEmail}"
// log.debug "External Id=${app.id}:${member.id}" // log.debug "External Id=${app.id}:${member.id}"
// create the device // create the device

View File

@@ -48,9 +48,9 @@ preferences {
} }
section("Via a push notification and/or an SMS message"){ section("Via a push notification and/or an SMS message"){
input("recipients", "contact", title: "Send notifications to") { input("recipients", "contact", title: "Send notifications to") {
input "phone", "phone", title: "Enter a phone number to get SMS", required: false input "phone", "phone", title: "Phone Number (for SMS, optional)", required: false
paragraph "If outside the US please make sure to enter the proper country code" paragraph "If outside the US please make sure to enter the proper country code"
input "pushAndPhone", "enum", title: "Notify me via Push Notification", required: false, options: ["Yes", "No"] input "pushAndPhone", "enum", title: "Both Push and SMS?", required: false, options: ["Yes", "No"]
} }
} }
section("Minimum time between messages (optional, defaults to every message)") { section("Minimum time between messages (optional, defaults to every message)") {
@@ -111,24 +111,19 @@ private sendMessage(evt) {
if (location.contactBookEnabled) { if (location.contactBookEnabled) {
sendNotificationToContacts(msg, recipients, options) sendNotificationToContacts(msg, recipients, options)
} else { } else {
if (!phone || pushAndPhone != 'No') {
log.debug 'sending push'
options.method = 'push'
//sendPush(msg)
}
if (phone) { if (phone) {
options.phone = phone options.phone = phone
if (pushAndPhone != 'No') { log.debug 'sending SMS'
log.debug 'Sending push and SMS' //sendSms(phone, msg)
options.method = 'both'
} else {
log.debug 'Sending SMS'
options.method = 'phone'
}
} else if (pushAndPhone != 'No') {
log.debug 'Sending push'
options.method = 'push'
} else {
log.debug 'Sending nothing'
options.method = 'none'
} }
sendNotification(msg, options) sendNotification(msg, options)
} }
if (frequency) { if (frequency) {
state[evt.deviceId] = now() state[evt.deviceId] = now()
} }