Compare commits

..

1 Commits

Author SHA1 Message Date
Vinay Rao
aae7f23a22 Merge pull request #1302 from SmartThingsCommunity/staging
Rolling up staging for production deployment
2016-09-27 14:14:57 -07:00
61 changed files with 575 additions and 1489 deletions

View File

@@ -127,10 +127,9 @@ def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv5.SensorMultilevelR
def map = [ displayed: true ] def map = [ displayed: true ]
switch (cmd.sensorType) { switch (cmd.sensorType) {
case 1: case 1:
def cmdScale = cmd.scale == 1 ? "F" : "C" map.name = "temperature"
map.name = "temperature" map.unit = cmd.scale == 1 ? "F" : "C"
map.unit = getTemperatureScale() map.value = convertTemperatureIfNeeded(cmd.scaledSensorValue, map.unit, cmd.precision)
map.value = convertTemperatureIfNeeded(cmd.scaledSensorValue, cmdScale, cmd.precision)
break break
case 3: case 3:
map.name = "illuminance" map.name = "illuminance"

View File

@@ -87,27 +87,16 @@ def beep() {
up to this long from the time you send the message to the time you hear a sound. up to this long from the time you send the message to the time you hear a sound.
*/ */
// Used source endpoint of 0x02 because we are using smartthings manufacturer specific cluster.
[ [
"raw 0xFC05 {15 0A 11 00 00 15 01}", "raw 0xFC05 {15 0A 11 00 00 15 01}",
"delay 200",
"send 0x$zigbee.deviceNetworkId 0x02 0x$zigbee.endpointId",
"delay 7000", "delay 7000",
"raw 0xFC05 {15 0A 11 00 00 15 01}", "raw 0xFC05 {15 0A 11 00 00 15 01}",
"delay 200",
"send 0x$zigbee.deviceNetworkId 0x02 0x$zigbee.endpointId",
"delay 7000", "delay 7000",
"raw 0xFC05 {15 0A 11 00 00 15 01}", "raw 0xFC05 {15 0A 11 00 00 15 01}",
"delay 200",
"send 0x$zigbee.deviceNetworkId 0x02 0x$zigbee.endpointId",
"delay 7000", "delay 7000",
"raw 0xFC05 {15 0A 11 00 00 15 01}", "raw 0xFC05 {15 0A 11 00 00 15 01}",
"delay 200",
"send 0x$zigbee.deviceNetworkId 0x02 0x$zigbee.endpointId",
"delay 7000", "delay 7000",
"raw 0xFC05 {15 0A 11 00 00 15 01}", "raw 0xFC05 {15 0A 11 00 00 15 01}"
"delay 200",
"send 0x$zigbee.deviceNetworkId 0x02 0x$zigbee.endpointId",
] ]
} }

View File

@@ -105,21 +105,11 @@ def parseDescriptionAsMap(description) {
// Commands to device // Commands to device
def on() { def on() {
[ 'zcl on-off on'
'zcl on-off on',
'delay 200',
"send 0x${zigbee.deviceNetworkId} 0x01 0x${zigbee.endpointId}",
'delay 500'
]
} }
def off() { def off() {
[ 'zcl on-off off'
'zcl on-off off',
'delay 200',
"send 0x${zigbee.deviceNetworkId} 0x01 0x${zigbee.endpointId}",
'delay 500'
]
} }
def setLevel(value) { def setLevel(value) {

View File

@@ -23,8 +23,10 @@ Works with:
## Device Health ## Device Health
A Category C6 Connected Cree LED Bulb with maxReportTime of 5 mins. A Category C6 Connected Cree LED Bulb with maxReportTime of 10 min.
Check-in interval = 12 mins 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 ## Troubleshooting

View File

@@ -93,19 +93,20 @@ def ping() {
} }
def refresh() { def refresh() {
zigbee.onOffRefresh() + zigbee.levelRefresh() zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig() + zigbee.levelConfig()
} }
def healthPoll() { def healthPoll() {
log.debug "healthPoll()"
def cmds = zigbee.onOffRefresh() + zigbee.levelRefresh() def cmds = zigbee.onOffRefresh() + zigbee.levelRefresh()
cmds.each{ sendHubCommand(new physicalgraph.device.HubAction(it))} cmds.each{ sendHubCommand(new physicalgraph.device.HubAction(it))}
} }
def configure() { def configure() {
unschedule() unschedule()
runEvery5Minutes("healthPoll") schedule("0 0/5 * * * ? *", "healthPoll")
log.debug "Configuring Reporting and Bindings."
// Device-Watch allows 2 check-in misses from device // Device-Watch allows 2 check-in misses from device
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID]) sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee"])
zigbee.onOffRefresh() + zigbee.levelRefresh() // minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh()
} }

View File

@@ -31,13 +31,13 @@ metadata {
command "switchMode" command "switchMode"
command "switchFanMode" command "switchFanMode"
attribute "thermostatSetpoint", "number" attribute "thermostatSetpoint","number"
attribute "thermostatStatus", "string" attribute "thermostatStatus","string"
attribute "maxHeatingSetpoint", "number" attribute "maxHeatingSetpoint", "number"
attribute "minHeatingSetpoint", "number" attribute "minHeatingSetpoint", "number"
attribute "maxCoolingSetpoint", "number" attribute "maxCoolingSetpoint", "number"
attribute "minCoolingSetpoint", "number" attribute "minCoolingSetpoint", "number"
attribute "deviceTemperatureUnit", "string" attribute "deviceTemperatureUnit", "number"
} }
tiles { tiles {
@@ -655,60 +655,55 @@ void lowerSetpoint() {
void alterSetpoint(temp) { void alterSetpoint(temp) {
def mode = device.currentValue("thermostatMode") def mode = device.currentValue("thermostatMode")
def heatingSetpoint = device.currentValue("heatingSetpoint")
def coolingSetpoint = device.currentValue("coolingSetpoint")
def deviceId = device.deviceNetworkId.split(/\./).last()
if (mode == "off" || mode == "auto") { def targetHeatingSetpoint
log.warn "this mode: $mode does not allow alterSetpoint" def targetCoolingSetpoint
} else {
def heatingSetpoint = device.currentValue("heatingSetpoint")
def coolingSetpoint = device.currentValue("coolingSetpoint")
def deviceId = device.deviceNetworkId.split(/\./).last()
def targetHeatingSetpoint //step1: check thermostatMode, enforce limits before sending request to cloud
def targetCoolingSetpoint if (mode == "heat" || mode == "auxHeatOnly"){
if (temp.value > coolingSetpoint){
//step1: check thermostatMode, enforce limits before sending request to cloud targetHeatingSetpoint = temp.value
if (mode == "heat" || mode == "auxHeatOnly"){ targetCoolingSetpoint = temp.value
if (temp.value > coolingSetpoint){
targetHeatingSetpoint = temp.value
targetCoolingSetpoint = temp.value
} else {
targetHeatingSetpoint = temp.value
targetCoolingSetpoint = coolingSetpoint
}
} else if (mode == "cool") {
//enforce limits before sending request to cloud
if (temp.value < heatingSetpoint){
targetHeatingSetpoint = temp.value
targetCoolingSetpoint = temp.value
} else {
targetHeatingSetpoint = heatingSetpoint
targetCoolingSetpoint = temp.value
}
}
log.debug "alterSetpoint >> in mode ${mode} trying to change heatingSetpoint to $targetHeatingSetpoint " +
"coolingSetpoint to $targetCoolingSetpoint with holdType : ${holdType}"
def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite"
def coolingValue = location.temperatureScale == "C"? convertCtoF(targetCoolingSetpoint) : targetCoolingSetpoint
def heatingValue = location.temperatureScale == "C"? convertCtoF(targetHeatingSetpoint) : targetHeatingSetpoint
if (parent.setHold(heatingValue, coolingValue, deviceId, sendHoldType)) {
sendEvent("name": "thermostatSetpoint", "value": temp.value, displayed: false)
sendEvent("name": "heatingSetpoint", "value": targetHeatingSetpoint, "unit": location.temperatureScale)
sendEvent("name": "coolingSetpoint", "value": targetCoolingSetpoint, "unit": location.temperatureScale)
log.debug "alterSetpoint in mode $mode succeed change setpoint to= ${temp.value}"
} else { } else {
log.error "Error alterSetpoint()" targetHeatingSetpoint = temp.value
if (mode == "heat" || mode == "auxHeatOnly"){ targetCoolingSetpoint = coolingSetpoint
sendEvent("name": "thermostatSetpoint", "value": heatingSetpoint.toString(), displayed: false) }
} else if (mode == "cool") { } else if (mode == "cool") {
sendEvent("name": "thermostatSetpoint", "value": coolingSetpoint.toString(), displayed: false) //enforce limits before sending request to cloud
} if (temp.value < heatingSetpoint){
targetHeatingSetpoint = temp.value
targetCoolingSetpoint = temp.value
} else {
targetHeatingSetpoint = heatingSetpoint
targetCoolingSetpoint = temp.value
} }
generateStatusEvent()
} }
log.debug "alterSetpoint >> in mode ${mode} trying to change heatingSetpoint to $targetHeatingSetpoint " +
"coolingSetpoint to $targetCoolingSetpoint with holdType : ${holdType}"
def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite"
def coolingValue = location.temperatureScale == "C"? convertCtoF(targetCoolingSetpoint) : targetCoolingSetpoint
def heatingValue = location.temperatureScale == "C"? convertCtoF(targetHeatingSetpoint) : targetHeatingSetpoint
if (parent.setHold(heatingValue, coolingValue, deviceId, sendHoldType)) {
sendEvent("name": "thermostatSetpoint", "value": temp.value, displayed: false)
sendEvent("name": "heatingSetpoint", "value": targetHeatingSetpoint, "unit": location.temperatureScale)
sendEvent("name": "coolingSetpoint", "value": targetCoolingSetpoint, "unit": location.temperatureScale)
log.debug "alterSetpoint in mode $mode succeed change setpoint to= ${temp.value}"
} else {
log.error "Error alterSetpoint()"
if (mode == "heat" || mode == "auxHeatOnly"){
sendEvent("name": "thermostatSetpoint", "value": heatingSetpoint.toString(), displayed: false)
} else if (mode == "cool") {
sendEvent("name": "thermostatSetpoint", "value": coolingSetpoint.toString(), displayed: false)
}
}
generateStatusEvent()
} }
def generateStatusEvent() { def generateStatusEvent() {

View File

@@ -57,7 +57,7 @@ metadata {
} }
void installed() { void installed() {
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan", hubHardwareId: device.hub.hardwareID], displayed: false) sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan"], displayed: false)
} }
// parse events into attributes // parse events into attributes

View File

@@ -7,11 +7,9 @@
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") {
capability "Health Check"
attribute "networkAddress", "string" attribute "networkAddress", "string"
// Used to indicate if bridge is reachable or not, i.e. is the bridge connected to the network // Used to indicate if bridge is reachable or not, i.e. is the bridge connected to the network
// Possible values "Online" or "Offline" // Possible values "Online" or "Offline"
attribute "status", "string" attribute "status", "string"
// Id is the number on the back of the hub, Hue uses last six digits of Mac address // 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 // This is also used in the Hue application as ID
@@ -44,10 +42,6 @@ metadata {
} }
} }
void installed() {
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan"], displayed: false)
}
// parse events into attributes // parse events into attributes
def parse(description) { def parse(description) {
log.debug "Parsing '${description}'" log.debug "Parsing '${description}'"
@@ -76,8 +70,13 @@ def parse(description) {
def bulbs = new groovy.json.JsonSlurper().parseText(msg.body) def bulbs = new groovy.json.JsonSlurper().parseText(msg.body)
if (bulbs.state) { if (bulbs.state) {
log.info "Bridge response: $msg.body" log.info "Bridge response: $msg.body"
} else {
// Sending Bulbs List to parent"
if (parent.isInBulbDiscovery())
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)
} }
@@ -86,7 +85,3 @@ def parse(description) {
} }
results results
} }
def ping() {
log.debug "${parent.ping(this)}"
}

View File

@@ -66,7 +66,7 @@ metadata {
} }
void installed() { void installed() {
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan", hubHardwareId: device.hub.hardwareID], displayed: false) sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan"], displayed: false)
} }
// parse events into attributes // parse events into attributes
@@ -174,7 +174,7 @@ void setColorTemperature(value) {
void refresh() { void refresh() {
log.debug "Executing 'refresh'" log.debug "Executing 'refresh'"
parent?.manualRefresh() parent.manualRefresh()
} }
def verifyPercent(percent) { def verifyPercent(percent) {

View File

@@ -50,7 +50,7 @@ metadata {
} }
void installed() { void installed() {
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan", hubHardwareId: device.hub.hardwareID], displayed: false) sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan"], displayed: false)
} }
// parse events into attributes // parse events into attributes

View File

@@ -55,7 +55,7 @@ metadata {
} }
void installed() { void installed() {
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan", hubHardwareId: device.hub.hardwareID], displayed: false) sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan"], displayed: false)
} }
// parse events into attributes // parse events into attributes

View File

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

View File

@@ -1,37 +0,0 @@
# Nyce Door/Window Sensor (Open/Close Sensor)
Works with:
* [NYCE Door/Window Sensor NCZ-3011](https://support.smartthings.com/hc/en-us/articles/204576764-NYCE-Door-Window-Sensor)
## Table of contents
* [Capabilities](#capabilities)
* [Health](#device-health)
* [Battery](#battery-specification)
* [Troubleshooting](#troubleshooting)
## Capabilities
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
* **Contact Sensor** - can detect contact (with possible values - open/closed)
* **Battery** - defines device uses a battery
* **Refresh** - _refresh()_ command for status updates
* **Health Check** - indicates ability to get device health notifications
## Device Health
A Category C2 Nyce Door/Window sensor that has 12min check-in interval
## Battery Specification
One 3V CR2032 battery 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 sensor from SmartThings can be found in the following link:
* [Nyce Door/Window Sensor](https://support.smartthings.com/hc/en-us/articles/204576764-NYCE-Door-Window-Sensor)

View File

@@ -19,26 +19,25 @@ import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
metadata { metadata {
definition (name: "NYCE Open/Closed Sensor", namespace: "smartthings", author: "NYCE") { definition (name: "NYCE Open/Closed Sensor", namespace: "smartthings", author: "NYCE") {
capability "Battery" capability "Battery"
capability "Configuration" capability "Configuration"
capability "Contact Sensor" capability "Contact Sensor"
capability "Refresh" capability "Refresh"
capability "Health Check"
command "enrollResponse"
command "enrollResponse"
fingerprint inClusters: "0000,0001,0003,0500,0020", manufacturer: "NYCE", model: "3010", deviceJoinName: "NYCE Door Hinge Sensor"
fingerprint inClusters: "0000,0001,0003,0500,0020", manufacturer: "NYCE", model: "3010", deviceJoinName: "NYCE Door Hinge Sensor"
fingerprint inClusters: "0000,0001,0003,0406,0500,0020", manufacturer: "NYCE", model: "3011", deviceJoinName: "NYCE Door/Window Sensor" fingerprint inClusters: "0000,0001,0003,0406,0500,0020", manufacturer: "NYCE", model: "3011", deviceJoinName: "NYCE Door/Window Sensor"
fingerprint inClusters: "0000,0001,0003,0500,0020", manufacturer: "NYCE", model: "3011", deviceJoinName: "NYCE Door/Window Sensor" fingerprint inClusters: "0000,0001,0003,0500,0020", manufacturer: "NYCE", model: "3011", deviceJoinName: "NYCE Door/Window Sensor"
fingerprint inClusters: "0000,0001,0003,0406,0500,0020", manufacturer: "NYCE", model: "3014", deviceJoinName: "NYCE Tilt Sensor" fingerprint inClusters: "0000,0001,0003,0406,0500,0020", manufacturer: "NYCE", model: "3014", deviceJoinName: "NYCE Tilt Sensor"
fingerprint inClusters: "0000,0001,0003,0500,0020", manufacturer: "NYCE", model: "3014", deviceJoinName: "NYCE Tilt Sensor" fingerprint inClusters: "0000,0001,0003,0500,0020", manufacturer: "NYCE", model: "3014", deviceJoinName: "NYCE Tilt Sensor"
} }
simulator { simulator {
} }
tiles { tiles {
standardTile("contact", "device.contact", width: 2, height: 2) { standardTile("contact", "device.contact", width: 2, height: 2) {
state("open", label:'${name}', icon:"st.contact.contact.open", backgroundColor:"#ffa81e") state("open", label:'${name}', icon:"st.contact.contact.open", backgroundColor:"#ffa81e")
@@ -274,28 +273,23 @@ private List parseIasMessage(String description) {
return resultListMap return resultListMap
} }
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
}
def configure() { def configure() {
// Device-Watch allows 2 check-in misses from device
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui) String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
def enrollCmds = [ def configCmds = [
//battery reporting and heartbeat
"zdo bind 0x${device.deviceNetworkId} 1 ${endpointId} 1 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 1 0x20 0x20 600 3600 {01}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 1500",
// Writes CIE attribute on end device to direct reports to the hub's EUID // Writes CIE attribute on end device to direct reports to the hub's EUID
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200", "zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 500", "send 0x${device.deviceNetworkId} 1 1", "delay 500",
] ]
log.debug "configure: Write IAS CIE" log.debug "configure: Write IAS CIE"
// battery minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity return configCmds
return enrollCmds + zigbee.batteryConfig(30, 300) + refresh() // send refresh cmds as part of config
} }
def enrollResponse() { def enrollResponse() {
@@ -340,8 +334,7 @@ Integer convertHexToInt(hex) {
def refresh() { def refresh() {
log.debug "Refreshing Battery" log.debug "Refreshing Battery"
def refreshCmds = [ [
"st rattr 0x${device.deviceNetworkId} ${endpointId} 1 0x20", "delay 200" "st rattr 0x${device.deviceNetworkId} ${endpointId} 1 0x20", "delay 200"
] ]
return refreshCmds + enrollResponse()
} }

View File

@@ -21,6 +21,9 @@ metadata {
attribute "colorName", "string" attribute "colorName", "string"
command "setAdjustedColor" command "setAdjustedColor"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Gardenspot RGB"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY Gardenspot RGB"
} }
// simulator metadata // simulator metadata

View File

@@ -133,7 +133,7 @@ def refresh() {
} }
def configure() { def configure() {
refresh() + onOffConfig() + levelConfig() + powerConfig() onOffConfig() + levelConfig() + powerConfig() + refresh()
} }

View File

@@ -47,21 +47,9 @@ def parse(String description) {
// Commands to device // Commands to device
def on() { def on() {
[ 'zcl on-off on'
'zcl on-off on',
'delay 200',
"send 0x${zigbee.deviceNetworkId} 0x01 0x${zigbee.endpointId}",
'delay 500'
]
} }
def off() { def off() {
[ 'zcl on-off off'
'zcl on-off off',
'delay 200',
"send 0x${zigbee.deviceNetworkId} 0x01 0x${zigbee.endpointId}",
'delay 500'
]
} }

View File

@@ -23,10 +23,10 @@ Works with:
## Device Health ## Device Health
A Category C1 smart power outlet with maxReportTime of 5 mins. A Category C1 smart power outlet with maxReportTime of 10 min.
Check-in interval is double the value of maxReportTime. 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. This gives the device twice the amount of time to respond before it is marked as offline.
Check-in interval = 12 mins Check-in interval = 2*10 = 20 min
## Troubleshooting ## Troubleshooting

View File

@@ -16,7 +16,7 @@
metadata { metadata {
// Automatically generated. Make future change here. // Automatically generated. Make future change here.
definition (name: "SmartPower Outlet", namespace: "smartthings", author: "SmartThings") { definition (name: "SmartPower Outlet", namespace: "smartthings", author: "SmartThings", category: "C1") {
capability "Actuator" capability "Actuator"
capability "Switch" capability "Switch"
capability "Power Meter" capability "Power Meter"
@@ -104,21 +104,8 @@ def parse(String description) {
} }
} }
else { else {
def cluster = zigbee.parse(description) log.warn "DID NOT PARSE MESSAGE for description : $description"
log.debug zigbee.parseDescriptionAsMap(description)
if (cluster && cluster.clusterId == 0x0006 && cluster.command == 0x07){
if (cluster.data[0] == 0x00) {
log.debug "ON/OFF REPORTING CONFIG RESPONSE: " + cluster
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
}
else {
log.warn "ON/OFF REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
}
}
else {
log.warn "DID NOT PARSE MESSAGE for description : $description"
log.debug "${cluster}"
}
} }
} }
@@ -141,12 +128,10 @@ def refresh() {
} }
def configure() { def configure() {
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time) // Device-Watch allows 2 check-in misses from device
// enrolls with default periodic reporting until newer 5 min interval is confirmed sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee"])
sendEvent(name: "checkInterval", value: 3 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity // OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
refresh() + zigbee.onOffConfig(0, 300) + powerConfig() zigbee.onOffConfig(0, 300) + powerConfig() + refresh()
} }
//power config for devices with min reporting interval as 1 seconds and reporting interval if no activity as 10min (600s) //power config for devices with min reporting interval as 1 seconds and reporting interval if no activity as 10min (600s)

View File

@@ -23,10 +23,10 @@ Works with:
## Device Health ## Device Health
A Category C2 moisture sensor with maxReportTime of 5 mins. A Category C2 moisture sensor with maxReportTime of 1 hr.
Check-in interval is double the value of maxReportTime. 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. This gives the device twice the amount of time to respond before it is marked as offline.
Check-in interval = 12 mins Check-in interval = 2*60 = 120 min
## Battery Specification ## Battery Specification

View File

@@ -17,7 +17,7 @@ import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
metadata { metadata {
definition (name: "SmartSense Moisture Sensor",namespace: "smartthings", author: "SmartThings") { definition (name: "SmartSense Moisture Sensor",namespace: "smartthings", author: "SmartThings", category: "C2") {
capability "Configuration" capability "Configuration"
capability "Battery" capability "Battery"
capability "Refresh" capability "Refresh"
@@ -118,28 +118,14 @@ private Map parseCatchAllMessage(String description) {
if (shouldProcessMessage(cluster)) { if (shouldProcessMessage(cluster)) {
switch(cluster.clusterId) { switch(cluster.clusterId) {
case 0x0001: case 0x0001:
// 0x07 - configure reporting resultMap = getBatteryResult(cluster.data.last())
if (cluster.command != 0x07) {
resultMap = getBatteryResult(cluster.data.last())
}
break break
case 0x0402: case 0x0402:
if (cluster.command == 0x07) { // temp is last 2 data values. reverse to swap endian
if (cluster.data[0] == 0x00){ String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
log.debug "TEMP REPORTING CONFIG RESPONSE" + cluster def value = getTemperature(temp)
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID]) resultMap = getTemperatureResult(value)
}
else {
log.warn "TEMP REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
}
}
else {
// temp is last 2 data values. reverse to swap endian
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
def value = getTemperature(temp)
resultMap = getTemperatureResult(value)
}
break break
} }
} }
@@ -149,8 +135,10 @@ private Map parseCatchAllMessage(String description) {
private boolean shouldProcessMessage(cluster) { private boolean shouldProcessMessage(cluster) {
// 0x0B is default response indicating message got through // 0x0B is default response indicating message got through
// 0x07 is bind message
boolean ignoredMessage = cluster.profileId != 0x0104 || boolean ignoredMessage = cluster.profileId != 0x0104 ||
cluster.command == 0x0B || cluster.command == 0x0B ||
cluster.command == 0x07 ||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e) (cluster.data.size() > 0 && cluster.data.first() == 0x3e)
return !ignoredMessage return !ignoredMessage
} }
@@ -304,13 +292,19 @@ def refresh() {
} }
def configure() { def configure() {
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time) // Device-Watch allows 2 check-in misses from device
// enrolls with default periodic reporting until newer 5 min interval is confirmed sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee"])
sendEvent(name: "checkInterval", value: 3 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
log.debug "Configuring Reporting, IAS CIE, and Bindings."
def enrollCmds = [
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
]
// temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity // temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity
// battery minReport 30 seconds, maxReportTime 6 hrs by default // battery minReport 30 seconds, maxReportTime 6 hrs by default
return refresh() + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) // send refresh cmds as part of config return enrollCmds + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) + refresh() // send refresh cmds as part of config
} }
def enrollResponse() { def enrollResponse() {

View File

@@ -22,10 +22,10 @@ Works with:
## Device Health ## Device Health
A Category C2 motion sensor with maxReportTime of 5 mins. A Category C2 motion sensor with maxReportTime of 1 hr.
Check-in interval is double the value of maxReportTime. 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. This gives the device twice the amount of time to respond before it is marked as offline.
Check-in interval = 12 mins Check-in interval = 2*60 = 120 min
## Battery Specification ## Battery Specification

View File

@@ -17,7 +17,7 @@ import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
metadata { metadata {
definition (name: "SmartSense Motion Sensor", namespace: "smartthings", author: "SmartThings") { definition (name: "SmartSense Motion Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") {
capability "Motion Sensor" capability "Motion Sensor"
capability "Configuration" capability "Configuration"
capability "Battery" capability "Battery"
@@ -122,37 +122,19 @@ private Map parseCatchAllMessage(String description) {
if (shouldProcessMessage(cluster)) { if (shouldProcessMessage(cluster)) {
switch(cluster.clusterId) { switch(cluster.clusterId) {
case 0x0001: case 0x0001:
// 0x07 - configure reporting resultMap = getBatteryResult(cluster.data.last())
if (cluster.command != 0x07) {
resultMap = getBatteryResult(cluster.data.last())
}
break break
case 0x0402: case 0x0402:
if (cluster.command == 0x07) { // temp is last 2 data values. reverse to swap endian
if (cluster.data[0] == 0x00) { String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
log.debug "TEMP REPORTING CONFIG RESPONSE" + cluster def value = getTemperature(temp)
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID]) resultMap = getTemperatureResult(value)
}
else {
log.warn "TEMP REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
}
}
else {
// temp is last 2 data values. reverse to swap endian
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
def value = getTemperature(temp)
resultMap = getTemperatureResult(value)
}
break break
case 0x0406: case 0x0406:
// 0x07 - configure reporting log.debug 'motion'
if (cluster.command != 0x07) { resultMap.name = 'motion'
log.debug 'motion'
resultMap.name = 'motion'
}
break break
} }
} }
@@ -162,8 +144,10 @@ private Map parseCatchAllMessage(String description) {
private boolean shouldProcessMessage(cluster) { private boolean shouldProcessMessage(cluster) {
// 0x0B is default response indicating message got through // 0x0B is default response indicating message got through
// 0x07 is bind message
boolean ignoredMessage = cluster.profileId != 0x0104 || boolean ignoredMessage = cluster.profileId != 0x0104 ||
cluster.command == 0x0B || cluster.command == 0x0B ||
cluster.command == 0x07 ||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e) (cluster.data.size() > 0 && cluster.data.first() == 0x3e)
return !ignoredMessage return !ignoredMessage
} }
@@ -319,13 +303,19 @@ def refresh() {
} }
def configure() { def configure() {
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time) // Device-Watch allows 2 check-in misses from device
// enrolls with default periodic reporting until newer 5 min interval is confirmed sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee"])
sendEvent(name: "checkInterval", value: 3 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
log.debug "Configuring Reporting, IAS CIE, and Bindings."
def enrollCmds = [
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
]
// temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity // temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity
// battery minReport 30 seconds, maxReportTime 6 hrs by default // battery minReport 30 seconds, maxReportTime 6 hrs by default
return refresh() + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) // send refresh cmds as part of config return enrollCmds + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) + refresh() // send refresh cmds as part of config
} }
def enrollResponse() { def enrollResponse() {

View File

@@ -255,9 +255,13 @@ def refresh() {
} }
def configure() { def configure() {
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
log.debug "Configuring Reporting, IAS CIE, and Bindings." log.debug "Configuring Reporting, IAS CIE, and Bindings."
def configCmds = [ def configCmds = [
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 200", "zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", //checkin time 6 hrs "zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", //checkin time 6 hrs
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500", "send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
@@ -266,7 +270,7 @@ def configure() {
"zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}", "zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500" "send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500"
] ]
return refresh() + configCmds // send refresh cmds as part of config return configCmds + refresh() // send refresh cmds as part of config
} }
def enrollResponse() { def enrollResponse() {

View File

@@ -26,10 +26,10 @@ Works with:
## Device Health ## Device Health
A Category C2 multi sensor with maxReportTime of 5 mins. A Category C2 multi sensor with maxReportTime of 1 hr.
Check-in interval is double the value of maxReportTime. 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. This gives the device twice the amount of time to respond before it is marked as offline.
Check-in interval = 12 mins Check-in interval = 2*60 = 120 min
## Battery Specification ## Battery Specification

View File

@@ -16,7 +16,7 @@
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
metadata { metadata {
definition (name: "SmartSense Multi Sensor", namespace: "smartthings", author: "SmartThings") { definition (name: "SmartSense Multi Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") {
capability "Three Axis" capability "Three Axis"
capability "Battery" capability "Battery"
@@ -147,33 +147,20 @@ private Map parseCatchAllMessage(String description) {
if (shouldProcessMessage(cluster)) { if (shouldProcessMessage(cluster)) {
switch(cluster.clusterId) { switch(cluster.clusterId) {
case 0x0001: case 0x0001:
// 0x07 - configure reporting resultMap = getBatteryResult(cluster.data.last())
if (cluster.command != 0x07) {
resultMap = getBatteryResult(cluster.data.last())
}
break break
case 0xFC02: case 0xFC02:
log.debug 'ACCELERATION' log.debug 'ACCELERATION'
break break
case 0x0402: case 0x0402:
if (cluster.command == 0x07) { log.debug 'TEMP'
if(cluster.data[0] == 0x00) { // temp is last 2 data values. reverse to swap endian
log.debug "TEMP REPORTING CONFIG RESPONSE" + cluster String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID]) def value = getTemperature(temp)
} resultMap = getTemperatureResult(value)
else { break
log.warn "TEMP REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
}
}
else {
// temp is last 2 data values. reverse to swap endian
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
def value = getTemperature(temp)
resultMap = getTemperatureResult(value)
}
break
} }
} }
@@ -182,8 +169,10 @@ private Map parseCatchAllMessage(String description) {
private boolean shouldProcessMessage(cluster) { private boolean shouldProcessMessage(cluster) {
// 0x0B is default response indicating message got through // 0x0B is default response indicating message got through
// 0x07 is bind message
boolean ignoredMessage = cluster.profileId != 0x0104 || boolean ignoredMessage = cluster.profileId != 0x0104 ||
cluster.command == 0x0B || cluster.command == 0x0B ||
cluster.command == 0x07 ||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e) (cluster.data.size() > 0 && cluster.data.first() == 0x3e)
return !ignoredMessage return !ignoredMessage
} }
@@ -412,22 +401,22 @@ def refresh() {
} }
def configure() { def configure() {
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time) // Device-Watch allows 2 check-in misses from device
// enrolls with default periodic reporting until newer 5 min interval is confirmed sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee"])
sendEvent(name: "checkInterval", value: 3 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
log.debug "Configuring Reporting" log.debug "Configuring Reporting"
// temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity // temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity
// battery minReport 30 seconds, maxReportTime 6 hrs by default // battery minReport 30 seconds, maxReportTime 6 hrs by default
def configCmds = zigbee.batteryConfig() + def configCmds = enrollResponse() +
zigbee.batteryConfig() +
zigbee.temperatureConfig(30, 300) + zigbee.temperatureConfig(30, 300) +
zigbee.configureReporting(0xFC02, 0x0010, 0x18, 10, 3600, 0x01, [mfgCode: manufacturerCode]) + zigbee.configureReporting(0xFC02, 0x0010, 0x18, 10, 3600, 0x01, [mfgCode: manufacturerCode]) +
zigbee.configureReporting(0xFC02, 0x0012, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode]) + zigbee.configureReporting(0xFC02, 0x0012, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode]) +
zigbee.configureReporting(0xFC02, 0x0013, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode]) + zigbee.configureReporting(0xFC02, 0x0013, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode]) +
zigbee.configureReporting(0xFC02, 0x0014, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode]) zigbee.configureReporting(0xFC02, 0x0014, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode])
return refresh() + configCmds return configCmds + refresh()
} }
private getEndpointId() { private getEndpointId() {

View File

@@ -277,8 +277,12 @@ def getTemperature(value) {
def configure() { def configure() {
sendEvent(name: "checkInterval", value: 7200, displayed: false) sendEvent(name: "checkInterval", value: 7200, displayed: false)
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
log.debug "Configuring Reporting, IAS CIE, and Bindings." log.debug "Configuring Reporting, IAS CIE, and Bindings."
def configCmds = [ def configCmds = [
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 200", "zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", //checkin time 6 hrs "zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", //checkin time 6 hrs
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500", "send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
@@ -291,7 +295,7 @@ def configure() {
"zcl global send-me-a-report 0xFC02 2 0x18 30 3600 {01}", "zcl global send-me-a-report 0xFC02 2 0x18 30 3600 {01}",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500" "send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500"
] ]
return refresh() + configCmds // send refresh cmds as part of config return configCmds + refresh() // send refresh cmds as part of config
} }
def enrollResponse() { def enrollResponse() {

View File

@@ -24,10 +24,10 @@ Works with:
## Device Health ## Device Health
A Category C2 open/closed sensor with maxReportTime of 5 mins. A Category C2 open/closed sensor with maxReportTime of 1 hr.
Check-in interval is double the value of maxReportTime. 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. This gives the device twice the amount of time to respond before it is marked as offline.
Check-in interval = 12 mins Check-in interval = 2*60 = 120 min
## Battery Specification ## Battery Specification

View File

@@ -16,7 +16,7 @@
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
metadata { metadata {
definition (name: "SmartSense Open/Closed Sensor", namespace: "smartthings", author: "SmartThings") { definition (name: "SmartSense Open/Closed Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") {
capability "Battery" capability "Battery"
capability "Configuration" capability "Configuration"
capability "Contact Sensor" capability "Contact Sensor"
@@ -109,28 +109,15 @@ private Map parseCatchAllMessage(String description) {
if (shouldProcessMessage(cluster)) { if (shouldProcessMessage(cluster)) {
switch(cluster.clusterId) { switch(cluster.clusterId) {
case 0x0001: case 0x0001:
// 0x07 - configure reporting resultMap = getBatteryResult(cluster.data.last())
if (cluster.command != 0x07) {
resultMap = getBatteryResult(cluster.data.last())
}
break break
case 0x0402: case 0x0402:
if (cluster.command == 0x07){ log.debug 'TEMP'
if (cluster.data[0] == 0x00) { // temp is last 2 data values. reverse to swap endian
log.debug "TEMP REPORTING CONFIG RESPONSE" + cluster String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID]) def value = getTemperature(temp)
} resultMap = getTemperatureResult(value)
else {
log.warn "TEMP REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
}
}
else {
// temp is last 2 data values. reverse to swap endian
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
def value = getTemperature(temp)
resultMap = getTemperatureResult(value)
}
break break
} }
} }
@@ -140,8 +127,10 @@ private Map parseCatchAllMessage(String description) {
private boolean shouldProcessMessage(cluster) { private boolean shouldProcessMessage(cluster) {
// 0x0B is default response indicating message got through // 0x0B is default response indicating message got through
// 0x07 is bind message
boolean ignoredMessage = cluster.profileId != 0x0104 || boolean ignoredMessage = cluster.profileId != 0x0104 ||
cluster.command == 0x0B || cluster.command == 0x0B ||
cluster.command == 0x07 ||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e) (cluster.data.size() > 0 && cluster.data.first() == 0x3e)
return !ignoredMessage return !ignoredMessage
} }
@@ -266,15 +255,19 @@ def refresh() {
} }
def configure() { def configure() {
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time) // Device-Watch allows 2 check-in misses from device
// enrolls with default periodic reporting until newer 5 min interval is confirmed sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee"])
sendEvent(name: "checkInterval", value: 3 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
log.debug "Configuring Reporting, IAS CIE, and Bindings." log.debug "Configuring Reporting, IAS CIE, and Bindings."
def enrollCmds = [
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
]
// temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity // temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity
// battery minReport 30 seconds, maxReportTime 6 hrs by default // battery minReport 30 seconds, maxReportTime 6 hrs by default
return refresh() + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) // send refresh cmds as part of config return enrollCmds + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) + refresh() // send refresh cmds as part of config
} }
def enrollResponse() { def enrollResponse() {

View File

@@ -24,10 +24,10 @@ Works with:
## Device Health ## Device Health
A Category C2 SmartSense Temp/Humidity Sensor with maxReportTime of 5 mins. A Category C2 SmartSense Temp/Humidity Sensor with maxReportTime of 1 hr.
Check-in interval is double the value of maxReportTime. 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. This gives the device twice the amount of time to respond before it is marked as offline.
Check-in interval = 12 mins Check-in interval = 2*60 = 120 min
## Battery Specification ## Battery Specification

View File

@@ -14,7 +14,7 @@
* *
*/ */
metadata { metadata {
definition (name: "SmartSense Temp/Humidity Sensor",namespace: "smartthings", author: "SmartThings") { definition (name: "SmartSense Temp/Humidity Sensor",namespace: "smartthings", author: "SmartThings", category: "C2") {
capability "Configuration" capability "Configuration"
capability "Battery" capability "Battery"
capability "Refresh" capability "Refresh"
@@ -93,37 +93,20 @@ private Map parseCatchAllMessage(String description) {
if (shouldProcessMessage(cluster)) { if (shouldProcessMessage(cluster)) {
switch(cluster.clusterId) { switch(cluster.clusterId) {
case 0x0001: case 0x0001:
// 0x07 - configure reporting resultMap = getBatteryResult(cluster.data.last())
if (cluster.command != 0x07) {
resultMap = getBatteryResult(cluster.data.last())
}
break break
case 0x0402: case 0x0402:
if (cluster.command == 0x07) { // temp is last 2 data values. reverse to swap endian
if (cluster.data[0] == 0x00){ String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
log.debug "TEMP REPORTING CONFIG RESPONSE" + cluster def value = getTemperature(temp)
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID]) resultMap = getTemperatureResult(value)
} break
else {
log.warn "TEMP REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
}
}
else {
// temp is last 2 data values. reverse to swap endian
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
def value = getTemperature(temp)
resultMap = getTemperatureResult(value)
}
break
case 0xFC45: case 0xFC45:
// 0x07 - configure reporting String pctStr = cluster.data[-1, -2].collect { Integer.toHexString(it) }.join('')
if (cluster.command != 0x07) { String display = Math.round(Integer.valueOf(pctStr, 16) / 100)
String pctStr = cluster.data[-1, -2].collect { Integer.toHexString(it) }.join('') resultMap = getHumidityResult(display)
String display = Math.round(Integer.valueOf(pctStr, 16) / 100)
resultMap = getHumidityResult(display)
}
break break
} }
} }
@@ -133,8 +116,10 @@ private Map parseCatchAllMessage(String description) {
private boolean shouldProcessMessage(cluster) { private boolean shouldProcessMessage(cluster) {
// 0x0B is default response indicating message got through // 0x0B is default response indicating message got through
// 0x07 is bind message
boolean ignoredMessage = cluster.profileId != 0x0104 || boolean ignoredMessage = cluster.profileId != 0x0104 ||
cluster.command == 0x0B || cluster.command == 0x0B ||
cluster.command == 0x07 ||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e) (cluster.data.size() > 0 && cluster.data.first() == 0x3e)
return !ignoredMessage return !ignoredMessage
} }
@@ -250,7 +235,11 @@ private Map getTemperatureResult(value) {
private Map getHumidityResult(value) { private Map getHumidityResult(value) {
log.debug 'Humidity' log.debug 'Humidity'
return value ? [name: 'humidity', value: value, unit: '%'] : [:] return [
name: 'humidity',
value: value,
unit: '%'
]
} }
/** /**
@@ -263,16 +252,20 @@ def ping() {
def refresh() def refresh()
{ {
log.debug "refresh temperature, humidity, and battery" log.debug "refresh temperature, humidity, and battery"
return zigbee.readAttribute(0xFC45, 0x0000, ["mfgCode": 0xC2DF]) + // Original firmware [
zigbee.readAttribute(0xFC45, 0x0000, ["mfgCode": 0x104E]) + // New firmware
zigbee.readAttribute(0x0402, 0x0000) + "zcl mfg-code 0xC2DF", "delay 1000",
zigbee.readAttribute(0x0001, 0x0020) "zcl global read 0xFC45 0", "delay 1000",
"send 0x${device.deviceNetworkId} 1 1", "delay 1000",
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
"st rattr 0x${device.deviceNetworkId} 1 1 0x20"
]
} }
def configure() { def configure() {
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time) // Device-Watch allows 2 check-in misses from device
// enrolls with default periodic reporting until newer 5 min interval is confirmed sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee"])
sendEvent(name: "checkInterval", value: 3 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
log.debug "Configuring Reporting and Bindings." log.debug "Configuring Reporting and Bindings."
def humidityConfigCmds = [ def humidityConfigCmds = [
@@ -283,7 +276,7 @@ def configure() {
// temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity // temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity
// battery minReport 30 seconds, maxReportTime 6 hrs by default // battery minReport 30 seconds, maxReportTime 6 hrs by default
return refresh() + humidityConfigCmds + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) // send refresh cmds as part of config return humidityConfigCmds + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) + refresh() // send refresh cmds as part of config
} }
private hex(value) { private hex(value) {

View File

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

View File

@@ -1,42 +0,0 @@
# Tyco Door Window Sensor
Works with:
* [Tyco Door Window Sensor](https://support.smartthings.com/hc/en-us/articles/204834100-Tyco-Door-Window-Sensor)
## Table of contents
* [Capabilities](#capabilities)
* [Health](#device-health)
* [Battery](#battery-specification)
## Capabilities
* **Battery** - defines device uses a battery
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
* **Contact Sensor** - can detect contact (open/close)
* **Refresh** - _refresh()_ command for status updates
* **Temperature Measurement** - can measure the device temperature
* **Health Check** - indicates ability to get device health notifications
## Device Health
Contact sensor with maxReportTime of 5 mins.
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 = 12 min
## Battery Specification
3V CR2032 battery is required.
## Troubleshooting
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that either the sensor needs to be reseted or the sensor is out of range.
Reset needs to be done by inserting the battery in the sensor and then quickly pressing the adjacent black button 10 times. Pairing should be tried again now.
It may happen that sensor is out of range, then 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:
* [Tyco Door Window Sensor (MCT-340)](https://support.smartthings.com/hc/en-us/articles/204834100-Tyco-Door-Window-Sensor)

View File

@@ -22,7 +22,6 @@ metadata {
capability "Contact Sensor" capability "Contact Sensor"
capability "Refresh" capability "Refresh"
capability "Temperature Measurement" capability "Temperature Measurement"
capability "Health Check"
command "enrollResponse" command "enrollResponse"
@@ -230,42 +229,44 @@ private Map getContactResult(value) {
] ]
} }
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
return zigbee.readAttribute(0x0402, 0x0000) // Read the Temperature Cluster
}
def refresh() def refresh()
{ {
log.debug "Refreshing Temperature and Battery" log.debug "Refreshing Temperature and Battery"
def refreshCmds = [ [
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200", "st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
"st rattr 0x${device.deviceNetworkId} 1 1 0x20" "st rattr 0x${device.deviceNetworkId} 1 1 0x20"
] ]
return refreshCmds + enrollResponse()
} }
def configure() { def configure() {
// Device-Watch allows 2 check-in misses from device
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
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."
def enrollCmds = [ def configCmds = [
"delay 1000", "delay 1000",
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200", "zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 1500", "send 0x${device.deviceNetworkId} 1 1", "delay 1500",
"zcl global send-me-a-report 1 0x20 0x20 600 3600 {01}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 1500",
"zcl global send-me-a-report 0x402 0 0x29 300 3600 {6400}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 1500",
//"raw 0x500 {01 23 00 00 00}", "delay 200", //"raw 0x500 {01 23 00 00 00}", "delay 200",
//"send 0x${device.deviceNetworkId} 1 1", "delay 1500", //"send 0x${device.deviceNetworkId} 1 1", "delay 1500",
"zdo bind 0x${device.deviceNetworkId} 1 1 0x402 {${device.zigbeeId}} {}", "delay 500",
"zdo bind 0x${device.deviceNetworkId} 1 1 1 {${device.zigbeeId}} {}",
"delay 500"
] ]
return enrollCmds + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) + refresh() // send refresh cmds as part of config return configCmds + enrollResponse() + refresh() // send refresh cmds as part of config
} }
def enrollResponse() { def enrollResponse() {

View File

@@ -89,8 +89,14 @@ def parse(String description) {
} }
private Map parseIasButtonMessage(String description) { private Map parseIasButtonMessage(String description) {
def zs = zigbee.parseZoneStatus(description) int zoneInt = Integer.parseInt((description - "zone status 0x"), 16)
return zs.isAlarm2Set() ? getButtonResult("press") : getButtonResult("release") if (zoneInt & 0x02) {
resultMap = getButtonResult('press')
} else {
resultMap = getButtonResult('release')
}
return resultMap
} }
private Map getBatteryResult(rawValue) { private Map getBatteryResult(rawValue) {

View File

@@ -93,5 +93,5 @@ def refresh() {
def configure() { def configure() {
log.debug "Configuring Reporting and Bindings." log.debug "Configuring Reporting and Bindings."
refresh() zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.simpleMeteringPowerConfig() + zigbee.electricMeasurementPowerConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.simpleMeteringPowerRefresh() + zigbee.electricMeasurementPowerRefresh()
} }

View File

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

View File

@@ -1,36 +0,0 @@
# OSRAM Lightify LED On/Off/Dim
Works with:
* [OSRAM Lightify LED On/Off/Dim](https://shop.smartthings.com/#!/products/osram-led-smart-bulb-on-off-dim)
## Table of contents
* [Capabilities](#capabilities)
* [Health](#device-health)
* [Battery](#battery-specification)
## Capabilities
* **Actuator** - represents that a Device has commands
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
* **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 C1 Zigbee dimmer with maxReportTime of 5 mins.
Check-in interval is double the value of maxReportTime.
This gives the device twice the amount of time to respond before it is marked as offline.
Check-in interval = 12 mins
## 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.
Other troubleshooting tips are listed as follows:
* [Troubleshooting:](https://support.smartthings.com/hc/en-us/articles/207191763-OSRAM-LIGHTIFY-LED-Smart-Connected-Light-A19-On-Off-Dim)

View File

@@ -60,21 +60,8 @@ def parse(String description) {
} }
} }
else { else {
def cluster = zigbee.parse(description) log.warn "DID NOT PARSE MESSAGE for description : $description"
log.debug zigbee.parseDescriptionAsMap(description)
if (cluster && cluster.clusterId == 0x0006 && cluster.command == 0x07) {
if (cluster.data[0] == 0x00) {
log.debug "ON/OFF REPORTING CONFIG RESPONSE: " + cluster
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
}
else {
log.warn "ON/OFF REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
}
}
else {
log.warn "DID NOT PARSE MESSAGE for description : $description"
log.debug "${cluster}"
}
} }
} }
@@ -97,15 +84,13 @@ def ping() {
} }
def refresh() { def refresh() {
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig(0, 300) + zigbee.levelConfig() zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig() + zigbee.levelConfig()
} }
def configure() { def configure() {
log.debug "Configuring Reporting and Bindings." log.debug "Configuring Reporting and Bindings."
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time) // Device-Watch allows 2 check-in misses from device
// enrolls with default periodic reporting until newer 5 min interval is confirmed sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee"])
sendEvent(name: "checkInterval", value: 3 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity // OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig(0, 300) + zigbee.levelConfig() zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh()
} }

View File

@@ -31,8 +31,7 @@
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: "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: "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" 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"
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019", manufacturer: "Yale", model: "YRD226/246 TSDB", deviceJoinName: "Yale Touch Screen Deadbolt Lock" }
}
tiles(scale: 2) { tiles(scale: 2) {
multiAttributeTile(name:"toggle", type:"generic", width:6, height:4){ multiAttributeTile(name:"toggle", type:"generic", width:6, height:4){
@@ -90,7 +89,7 @@ def configure() {
zigbee.configureReporting(CLUSTER_POWER, POWER_ATTR_BATTERY_PERCENTAGE_REMAINING, zigbee.configureReporting(CLUSTER_POWER, POWER_ATTR_BATTERY_PERCENTAGE_REMAINING,
TYPE_U8, 600, 21600, 0x01) TYPE_U8, 600, 21600, 0x01)
log.info "configure() --- cmds: $cmds" log.info "configure() --- cmds: $cmds"
return refresh() + cmds // send refresh cmds as part of config return cmds + refresh() // send refresh cmds as part of config
} }
def refresh() { def refresh() {

View File

@@ -1,154 +0,0 @@
/**
* Copyright 2016 SmartThings
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
* Author: SmartThings
* Date: 2016-01-19
*
* This DTH should serve as the generic DTH to handle RGB ZigBee HA devices (For color bulbs with no color temperature)
*/
metadata {
definition (name: "ZigBee RGB Bulb", namespace: "smartthings", author: "SmartThings") {
capability "Actuator"
capability "Color Control"
capability "Configuration"
capability "Polling"
capability "Refresh"
capability "Switch"
capability "Switch Level"
capability "Health Check"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Gardenspot RGB"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY Gardenspot RGB"
}
// 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.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
}
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
attributeState "level", action:"switch level.setLevel"
}
tileAttribute ("device.color", key: "COLOR_CONTROL") {
attributeState "color", action:"color control.setColor"
}
}
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
main(["switch"])
details(["switch", "refresh"])
}
}
//Globals
private getATTRIBUTE_HUE() { 0x0000 }
private getATTRIBUTE_SATURATION() { 0x0001 }
private getHUE_COMMAND() { 0x00 }
private getSATURATION_COMMAND() { 0x03 }
private getCOLOR_CONTROL_CLUSTER() { 0x0300 }
// Parse incoming device messages to generate events
def parse(String description) {
log.debug "description is $description"
def event = zigbee.getEvent(description)
if (event) {
log.debug event
if (event.name=="level" && event.value==0) {}
else {
sendEvent(event)
}
}
else {
def zigbeeMap = zigbee.parseDescriptionAsMap(description)
def cluster = zigbee.parse(description)
if (zigbeeMap?.clusterInt == COLOR_CONTROL_CLUSTER) {
if(zigbeeMap.attrInt == ATTRIBUTE_HUE){ //Hue Attribute
def hueValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 255 * 100)
sendEvent(name: "hue", value: hueValue, descriptionText: "Color has changed")
}
else if(zigbeeMap.attrInt == ATTRIBUTE_SATURATION){ //Saturation Attribute
def saturationValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 255 * 100)
sendEvent(name: "saturation", value: saturationValue, descriptionText: "Color has changed", displayed: false)
}
}
else if (cluster && cluster.clusterId == 0x0006 && cluster.command == 0x07) {
if (cluster.data[0] == 0x00){
log.debug "ON/OFF REPORTING CONFIG RESPONSE: $cluster"
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
}
else {
log.warn "ON/OFF REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
}
}
else {
log.info "DID NOT PARSE MESSAGE for description : $description"
log.debug zigbeeMap
}
}
}
def on() {
zigbee.on()
}
def off() {
zigbee.off()
}
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
def ping() {
return zigbee.onOffRefresh()
}
def refresh() {
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION) + zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE, 0x20, 1, 3600, 0x01) + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION, 0x20, 1, 3600, 0x01)
}
def configure() {
log.debug "Configuring Reporting and Bindings."
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
// enrolls with default periodic reporting until newer 5 min interval is confirmed
sendEvent(name: "checkInterval", value: 3 * 10 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE, 0x20, 1, 3600, 0x01) + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION, 0x20, 1, 3600, 0x01) + zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION)
}
def setLevel(value) {
zigbee.setLevel(value)
}
def setColor(value){
log.trace "setColor($value)"
zigbee.on() + setHue(value.hue) + "delay 500" + setSaturation(value.saturation)
}
def setHue(value) {
def scaledHueValue = zigbee.convertToHexString(Math.round(value * 0xfe / 100.0), 2)
zigbee.command(COLOR_CONTROL_CLUSTER, HUE_COMMAND, scaledHueValue, "00", "0500") //payload-> hue value, direction (00-> shortest distance), transition time (1/10th second) (0500 in U16 reads 5)
}
def setSaturation(value) {
def scaledSatValue = zigbee.convertToHexString(Math.round(value * 0xfe / 100.0), 2)
zigbee.command(COLOR_CONTROL_CLUSTER, SATURATION_COMMAND, scaledSatValue, "0500") + "delay 1000" + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION)
}

View File

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

View File

@@ -1,42 +0,0 @@
# OSRAM LIGHTIFY LED RGBW Bulb
Works with:
* [OSRAM LIGHTIFY LED RGBW Bulb](https://support.smartthings.com/hc/en-us/articles/207728173-OSRAM-LIGHTIFY-LED-Smart-Connected-Light-A19-RGBW)
## Table of contents
* [Capabilities](#capabilities)
* [Health](#device-health)
* [Battery](#battery-specification)
## Capabilities
* **Actuator** - It represents that a device has commands.
* **Color Control** - It represents that the color attributes of a device can be controlled (hue, saturation, color value).
* **Color Temperature** - It represents color temperature capability measured in degree Kelvin.
* **Polling** - It represents that a device can be polled.
* **Switch** - can detect state (possible values: on/off)
* **Switch Level** - can detect current light level (0-100 in percent)
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
* **Refresh** - _refresh()_ command for status updates
* **Health Check** - indicates ability to get device health notifications
## Device Health
A Category C6 OSRAM LIGHTIFY LED RGBW Bulb with maxReportTime of 5 mins.
Check-in interval is double the value of maxReportTime.
This gives the device twice the amount of time to respond before it is marked as offline.
Check-in interval = 12 mins
## 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.
It may also happen that you need to reset the device.
Instructions related to pairing, resetting and removing the device from SmartThings can be found in the following link:
* [Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/207728173-OSRAM-LIGHTIFY-LED-Smart-Connected-Light-A19-RGBW)

View File

@@ -95,7 +95,7 @@ def parse(String description) {
} }
else { else {
def zigbeeMap = zigbee.parseDescriptionAsMap(description) def zigbeeMap = zigbee.parseDescriptionAsMap(description)
def cluster = zigbee.parse(description) log.trace "zigbeeMap : $zigbeeMap"
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
@@ -107,18 +107,8 @@ def parse(String description) {
sendEvent(name: "saturation", value: saturationValue, descriptionText: "Color has changed", displayed: false) sendEvent(name: "saturation", value: saturationValue, descriptionText: "Color has changed", displayed: false)
} }
} }
else if (cluster && cluster.clusterId == 0x0006 && cluster.command == 0x07) {
if (cluster.data[0] == 0x00){
log.debug "ON/OFF REPORTING CONFIG RESPONSE: " + cluster
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
}
else {
log.warn "ON/OFF REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
}
}
else { else {
log.info "DID NOT PARSE MESSAGE for description : $description" log.info "DID NOT PARSE MESSAGE for description : $description"
log.debug zigbeeMap
} }
} }
} }
@@ -138,17 +128,15 @@ def ping() {
} }
def refresh() { def refresh() {
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_COLOR_TEMPERATURE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION) + zigbee.onOffConfig(0, 300) + 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.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)
} }
def configure() { def configure() {
log.debug "Configuring Reporting and Bindings." log.debug "Configuring Reporting and Bindings."
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time) // Device-Watch allows 2 check-in misses from device
// enrolls with default periodic reporting until newer 5 min interval is confirmed sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee"])
sendEvent(name: "checkInterval", value: 3 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity // OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
refresh() zigbee.onOffConfig(0, 300) + 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) {
@@ -189,5 +177,5 @@ def setHue(value) {
def setSaturation(value) { def setSaturation(value) {
def scaledSatValue = zigbee.convertToHexString(Math.round(value * 0xfe / 100.0), 2) def scaledSatValue = zigbee.convertToHexString(Math.round(value * 0xfe / 100.0), 2)
zigbee.command(COLOR_CONTROL_CLUSTER, SATURATION_COMMAND, scaledSatValue, "0500") + "delay 1000" + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION) zigbee.command(COLOR_CONTROL_CLUSTER, SATURATION_COMMAND, scaledSatValue, "0500") //payload-> sat value, transition time
} }

View File

@@ -82,5 +82,5 @@ def refresh() {
def configure() { def configure() {
log.debug "Configuring Reporting and Bindings." log.debug "Configuring Reporting and Bindings."
refresh() zigbee.onOffConfig() + zigbee.simpleMeteringPowerConfig() + zigbee.electricMeasurementPowerConfig() + zigbee.onOffRefresh() + zigbee.simpleMeteringPowerRefresh() + zigbee.electricMeasurementPowerRefresh()
} }

View File

@@ -78,5 +78,5 @@ def refresh() {
def configure() { def configure() {
log.debug "Configuring Reporting and Bindings." log.debug "Configuring Reporting and Bindings."
zigbee.onOffRefresh() + zigbee.onOffConfig() zigbee.onOffConfig() + zigbee.onOffRefresh()
} }

View File

@@ -134,5 +134,10 @@ def refresh() {
def configure() { def configure() {
log.debug "Configuring Reporting and Bindings." log.debug "Configuring Reporting and Bindings."
refresh() zigbee.onOffConfig() +
zigbee.configureReporting(CLUSTER_POWER, POWER_ATTR_BATTERY_PERCENTAGE_REMAINING, TYPE_U8, 600, 21600, 1) +
zigbee.configureReporting(CLUSTER_BASIC, BASIC_ATTR_POWER_SOURCE, TYPE_ENUM8, 5, 21600, 1) +
zigbee.onOffRefresh() +
zigbee.readAttribute(CLUSTER_BASIC, BASIC_ATTR_POWER_SOURCE) +
zigbee.readAttribute(CLUSTER_POWER, POWER_ATTR_BATTERY_PERCENTAGE_REMAINING)
} }

View File

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

View File

@@ -1,37 +0,0 @@
# OSRAM Lightify Tunable 60 White
Works with:
* [OSRAM Lightify Tunable 60 White](http://www.osram.com/osram_com/tools-and-services/tools/lightify---smart-connected-light/lightify-for-home---what-is-light-to-you/lightify-products/lightify-classic-a60-tunable-white/index.jsp)
## Table of contents
* [Capabilities](#capabilities)
* [Health](#device-health)
* [Battery](#battery-specification)
## Capabilities
* **Actuator** - represents that a Device has commands
* **Color Temperature** - represents color temperature, measured in degrees Kelvin.
* **Configuration** - _configure()_ command called when device is installed or device preferences updated.
* **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 C1 OSRAM Lightify Tunable 60 White with maxReportTime of 5 mins.
Check-in interval is double the value of maxReportTime.
This gives the device twice the amount of time to respond before it is marked as offline.
Check-in interval = 12 mins
## 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.
Other troubleshooting tips are listed as follows:
* [Troubleshooting:](https://support.smartthings.com/hc/en-us/articles/204576454-OSRAM-LIGHTIFY-Tunable-White-60-Bulb)

View File

@@ -83,21 +83,8 @@ def parse(String description) {
} }
} }
else { else {
def cluster = zigbee.parse(description) log.warn "DID NOT PARSE MESSAGE for description : $description"
log.debug zigbee.parseDescriptionAsMap(description)
if (cluster && cluster.clusterId == 0x0006 && cluster.command == 0x07) {
if (cluster.data[0] == 0x00) {
log.debug "ON/OFF REPORTING CONFIG RESPONSE: " + cluster
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
}
else {
log.warn "ON/OFF REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
}
}
else {
log.warn "DID NOT PARSE MESSAGE for description : $description"
log.debug "${cluster}"
}
} }
} }
@@ -121,17 +108,15 @@ def ping() {
} }
def refresh() { def refresh() {
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh() + zigbee.onOffConfig(0, 300) + 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."
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time) // Device-Watch allows 2 check-in misses from device
// enrolls with default periodic reporting until newer 5 min interval is confirmed sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee"])
sendEvent(name: "checkInterval", value: 3 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity // OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
refresh() zigbee.onOffConfig(0, 300) + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh()
} }
def setColorTemperature(value) { def setColorTemperature(value) {

View File

@@ -1,134 +0,0 @@
/**
* Copyright 2016 SmartThings
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
*/
metadata {
definition (name: "ZLL RGB Bulb", namespace: "smartthings", author: "SmartThings") {
capability "Actuator"
capability "Color Control"
capability "Configuration"
capability "Polling"
capability "Refresh"
capability "Switch"
capability "Switch Level"
}
// 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.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
}
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
attributeState "level", action:"switch level.setLevel"
}
tileAttribute ("device.color", key: "COLOR_CONTROL") {
attributeState "color", action:"color control.setColor"
}
}
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
main(["switch"])
details(["switch", "refresh"])
}
}
//Globals
private getATTRIBUTE_HUE() { 0x0000 }
private getATTRIBUTE_SATURATION() { 0x0001 }
private getHUE_COMMAND() { 0x00 }
private getSATURATION_COMMAND() { 0x03 }
private getCOLOR_CONTROL_CLUSTER() { 0x0300 }
// Parse incoming device messages to generate events
def parse(String description) {
log.debug "description is $description"
def finalResult = zigbee.getEvent(description)
if (finalResult) {
log.debug finalResult
sendEvent(finalResult)
}
else {
def zigbeeMap = zigbee.parseDescriptionAsMap(description)
log.trace "zigbeeMap : $zigbeeMap"
if (zigbeeMap?.clusterInt == COLOR_CONTROL_CLUSTER) {
if(zigbeeMap.attrInt == ATTRIBUTE_HUE){ //Hue Attribute
def hueValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 255 * 360)
sendEvent(name: "hue", value: hueValue, displayed:false)
}
else if(zigbeeMap.attrInt == ATTRIBUTE_SATURATION){ //Saturation Attribute
def saturationValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 255 * 100)
sendEvent(name: "saturation", value: saturationValue, displayed:false)
}
}
else {
log.info "DID NOT PARSE MESSAGE for description : $description"
}
}
}
def on() {
zigbee.on() + ["delay 1500"] + zigbee.onOffRefresh()
}
def off() {
zigbee.off() + ["delay 1500"] + zigbee.onOffRefresh()
}
def refresh() {
refreshAttributes() + configureAttributes()
}
def poll() {
refreshAttributes()
}
def configure() {
log.debug "Configuring Reporting and Bindings."
configureAttributes() + refreshAttributes()
}
def configureAttributes() {
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE, 0x20, 1, 3600, 0x01) + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION, 0x20, 1, 3600, 0x01)
}
def refreshAttributes() {
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION)
}
def setLevel(value) {
zigbee.setLevel(value) + ["delay 1500"] + zigbee.levelRefresh() //adding refresh because of ZLL bulb not conforming to send-me-a-report
}
def setColor(value){
log.trace "setColor($value)"
zigbee.on() + setHue(value.hue) + ["delay 300"] + setSaturation(value.saturation) + ["delay 2000"] + refreshAttributes()
}
def setHue(value) {
def scaledHueValue = zigbee.convertToHexString(Math.round(value * 0xfe / 100.0), 2)
zigbee.command(COLOR_CONTROL_CLUSTER, HUE_COMMAND, scaledHueValue, "00", "0500") + ["delay 1500"] + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) //payload-> hue value, direction (00-> shortest distance), transition time (1/10th second) (0500 in U16 reads 5)
}
def setSaturation(value) {
def scaledSatValue = zigbee.convertToHexString(Math.round(value * 0xfe / 100.0), 2)
zigbee.command(COLOR_CONTROL_CLUSTER, SATURATION_COMMAND, scaledSatValue, "0500") + ["delay 1500"] + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION) //payload-> sat value, transition time
}

View File

@@ -123,7 +123,7 @@ def configureAttributes() {
} }
def refreshAttributes() { def refreshAttributes() {
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh() + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION) zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh() + zigbee.readAttribute(0x0300, 0x00) + zigbee.readAttribute(0x0300, ATTRIBUTE_HUE) + zigbee.readAttribute(0x0300, ATTRIBUTE_SATURATION)
} }
def setColorTemperature(value) { def setColorTemperature(value) {
@@ -141,10 +141,10 @@ def setColor(value){
def setHue(value) { def setHue(value) {
def scaledHueValue = zigbee.convertToHexString(Math.round(value * 0xfe / 100.0), 2) def scaledHueValue = zigbee.convertToHexString(Math.round(value * 0xfe / 100.0), 2)
zigbee.command(COLOR_CONTROL_CLUSTER, HUE_COMMAND, scaledHueValue, "00", "0500") + ["delay 1500"] + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) //payload-> hue value, direction (00-> shortest distance), transition time (1/10th second) (0500 in U16 reads 5) zigbee.command(COLOR_CONTROL_CLUSTER, HUE_COMMAND, scaledHueValue, "00", "0500") //payload-> hue value, direction (00-> shortest distance), transition time (1/10th second) (0500 in U16 reads 5)
} }
def setSaturation(value) { def setSaturation(value) {
def scaledSatValue = zigbee.convertToHexString(Math.round(value * 0xfe / 100.0), 2) def scaledSatValue = zigbee.convertToHexString(Math.round(value * 0xfe / 100.0), 2)
zigbee.command(COLOR_CONTROL_CLUSTER, SATURATION_COMMAND, scaledSatValue, "0500") + ["delay 1500"] + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION) //payload-> sat value, transition time zigbee.command(COLOR_CONTROL_CLUSTER, SATURATION_COMMAND, scaledSatValue, "0500") //payload-> sat value, transition time
} }

View File

@@ -387,18 +387,18 @@ def getDeviceList() {
state.deviceDetail = [:] state.deviceDetail = [:]
state.deviceState = [:] state.deviceState = [:]
apiGet("/api/getstationsdata") { response -> apiGet("/api/devicelist") { response ->
response.data.body.devices.each { value -> response.data.body.devices.each { value ->
def key = value._id def key = value._id
deviceList[key] = "${value.station_name}: ${value.module_name}" deviceList[key] = "${value.station_name}: ${value.module_name}"
state.deviceDetail[key] = value state.deviceDetail[key] = value
state.deviceState[key] = value.dashboard_data state.deviceState[key] = value.dashboard_data
value.modules.each { value2 -> }
def key2 = value2._id response.data.body.modules.each { value ->
deviceList[key2] = "${value.station_name}: ${value2.module_name}" def key = value._id
state.deviceDetail[key2] = value2 deviceList[key] = "${state.deviceDetail[value.main_device].station_name}: ${value.module_name}"
state.deviceState[key2] = value2.dashboard_data state.deviceDetail[key] = value
} state.deviceState[key] = value.dashboard_data
} }
} }

View File

@@ -1,7 +1,7 @@
/** /**
* Smart Windows * Smart Windows
* Compares two temperatures indoor vs outdoor, for example then sends an alert if windows are open (or closed!). * Compares two temperatures indoor vs outdoor, for example then sends an alert if windows are open (or closed!).
* *
* Copyright 2014 Eric Gideon * Copyright 2014 Eric Gideon
* *
* Based in part on the "When it's going to rain" SmartApp by the SmartThings team, * Based in part on the "When it's going to rain" SmartApp by the SmartThings team,
@@ -21,18 +21,13 @@ definition(
name: "Smart Windows", name: "Smart Windows",
namespace: "egid", namespace: "egid",
author: "Eric Gideon", author: "Eric Gideon",
description: "Compares two temperatures indoor vs outdoor, for example then sends an alert if windows are open (or closed!). If you don't use an external temperature device, your location will be used instead.", description: "Compares two temperatures indoor vs outdoor, for example then sends an alert if windows are open (or closed!). If you don't use an external temperature device, your zipcode will be used instead.",
iconUrl: "https://s3.amazonaws.com/smartthings-device-icons/Home/home9-icn.png", iconUrl: "https://s3.amazonaws.com/smartthings-device-icons/Home/home9-icn.png",
iconX2Url: "https://s3.amazonaws.com/smartthings-device-icons/Home/home9-icn@2x.png" iconX2Url: "https://s3.amazonaws.com/smartthings-device-icons/Home/home9-icn@2x.png"
) )
preferences { preferences {
if (!(location.zipCode || ( location.latitude && location.longitude )) && location.channelName == 'samsungtv') {
section { paragraph title: "Note:", "Location is required for this SmartApp. Go to 'Location Name' settings to setup your correct location." }
}
section( "Set the temperature range for your comfort zone..." ) { section( "Set the temperature range for your comfort zone..." ) {
input "minTemp", "number", title: "Minimum temperature" input "minTemp", "number", title: "Minimum temperature"
input "maxTemp", "number", title: "Maximum temperature" input "maxTemp", "number", title: "Maximum temperature"
@@ -44,11 +39,9 @@ preferences {
input "inTemp", "capability.temperatureMeasurement", title: "Indoor" input "inTemp", "capability.temperatureMeasurement", title: "Indoor"
input "outTemp", "capability.temperatureMeasurement", title: "Outdoor (optional)", required: false input "outTemp", "capability.temperatureMeasurement", title: "Outdoor (optional)", required: false
} }
section( "Set your location" ) {
if (location.channelName != 'samsungtv') { input "zipCode", "text", title: "Zip code"
section( "Set your location" ) { input "zipCode", "text", title: "Zip code" } }
}
section( "Notifications" ) { section( "Notifications" ) {
input "sendPushMessage", "enum", title: "Send a push notification?", metadata:[values:["Yes","No"]], required:false input "sendPushMessage", "enum", title: "Send a push notification?", metadata:[values:["Yes","No"]], required:false
input "retryPeriod", "number", title: "Minutes between notifications:" input "retryPeriod", "number", title: "Minutes between notifications:"
@@ -79,7 +72,7 @@ def temperatureHandler(evt) {
def currentInTemp = evt.doubleValue def currentInTemp = evt.doubleValue
def openWindows = sensors.findAll { it?.latestValue("contact") == 'open' } def openWindows = sensors.findAll { it?.latestValue("contact") == 'open' }
log.trace "Temp event: $evt" log.trace "Temp event: $evt"
log.info "In: $currentInTemp; Out: $currentOutTemp" log.info "In: $currentInTemp; Out: $currentOutTemp"
@@ -105,7 +98,7 @@ def temperatureHandler(evt) {
if ( currentOutTemp < maxTemp && !openWindows ) { if ( currentOutTemp < maxTemp && !openWindows ) {
send( "Open some windows to cool down the house! Currently ${currentInTemp}°F inside and ${currentOutTemp}°F outside." ) send( "Open some windows to cool down the house! Currently ${currentInTemp}°F inside and ${currentOutTemp}°F outside." )
} else if ( currentOutTemp > maxTemp && openWindows ) { } else if ( currentOutTemp > maxTemp && openWindows ) {
send( "It's gotten warmer outside! You should close these windows: ${openWindows.join(', ')}. Currently ${currentInTemp}°F inside and ${currentOutTemp}°F outside." ) send( "It's gotten warmer outside! You should close these windows: ${openWindows.join(', ')}. Currently ${currentInTemp}°F inside and ${currentOutTemp}°F outside." )
} else { } else {
log.debug "No notifications sent. Everything is in the right place." log.debug "No notifications sent. Everything is in the right place."
} }
@@ -132,11 +125,7 @@ def temperatureHandler(evt) {
} }
def weatherCheck() { def weatherCheck() {
def json def json = getWeatherFeature("conditions", zipCode)
if (location.channelName != 'samsungtv')
json = getWeatherFeature("conditions", zipCode)
else
json = getWeatherFeature("conditions")
def currentTemp = json?.current_observation?.temp_f def currentTemp = json?.current_observation?.temp_f
if ( currentTemp ) { if ( currentTemp ) {
@@ -161,4 +150,4 @@ private send(msg) {
} }
log.info msg log.info msg
} }

View File

@@ -1,253 +0,0 @@
/**
* Gideon
*
* Copyright 2016 Nicola Russo
*
* 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: "Gideon",
namespace: "gideon.api",
author: "Braindrain Solutions",
description: "Gideon AI Smart app allows you to connect and control all of your SmartThings devices through the Gideon AI app, making your SmartThings devices even smarter.",
category: "Family",
iconUrl: "http://s33.postimg.org/t77u7y7v3/logo.png",
iconX2Url: "http://s33.postimg.org/t77u7y7v3/logo.png",
iconX3Url: "http://s33.postimg.org/t77u7y7v3/logo.png",
oauth: [displayName: "Gideon AI API", displayLink: "gideon.ai"])
preferences {
section("Control these switches...") {
input "switches", "capability.switch", multiple:true
}
section("Control these motion sensors...") {
input "motions", "capability.motionSensor", multiple:true
}
section("Control these presence sensors...") {
input "presence_sensors", "capability.presenceSensor", multiple:true
}
section("Control these outlets...") {
input "outlets", "capability.switch", multiple:true
}
section("Control these locks...") {
input "locks", "capability.lock", multiple:true
}
section("Control these locks...") {
input "temperature_sensors", "capability.temperatureMeasurement"
}
}
def installed() {
log.debug "Installed with settings: ${settings}"
initialize()
}
def updated() {
log.debug "Updated with settings: ${settings}"
unsubscribe()
initialize()
}
def initialize() {
// TODO: subscribe to attributes, devices, locations, etc.
subscribe(outlet, "energy", outletHandler)
subscribe(outlet, "switch", outletHandler)
}
// TODO: implement event handlers
def outletHandler(evt) {
log.debug "$outlet.currentEnergy"
//TODO call G API
}
private device(it, type) {
it ? [id: it.id, label: it.label, type: type] : null
}
//API Mapping
mappings {
path("/getalldevices") {
action: [
GET: "getAllDevices"
]
}
path("/doorlocks/:id/:command") {
action: [
GET: "updateDoorLock"
]
}
path("/doorlocks/:id") {
action: [
GET: "getDoorLockStatus"
]
}
path("/tempsensors/:id") {
action: [
GET: "getTempSensorsStatus"
]
}
path("/presences/:id") {
action: [
GET: "getPresenceStatus"
]
}
path("/motions/:id") {
action: [
GET: "getMotionStatus"
]
}
path("/outlets/:id") {
action: [
GET: "getOutletStatus"
]
}
path("/outlets/:id/:command") {
action: [
GET: "updateOutlet"
]
}
path("/switches/:command") {
action: [
PUT: "updateSwitch"
]
}
}
//API Methods
def getAllDevices() {
def locks_list = locks.collect{device(it,"Lock")}
def presences_list = presence_sensors.collect{device(it,"Presence")}
def motions_list = motions.collect{device(it,"Motion")}
def outlets_list = outlets.collect{device(it,"Outlet")}
def switches_list = switches.collect{device(it,"Switch")}
def temp_list = temperature_sensors.collect{device(it,"Temperature")}
return [Locks: locks_list, Presences: presences_list, Motions: motions_list, Outlets: outlets_list, Switches: switches_list, Temperatures: temp_list]
}
//LOCKS
def getDoorLockStatus() {
def device = locks.find { it.id == params.id }
if (!device) {
httpError(404, "Device not found")
} else {
return [Device_state: device.currentValue('lock')]
}
}
def updateDoorLock() {
def command = params.command
def device = locks.find { it.id == params.id }
if (command){
if (!device) {
httpError(404, "Device not found")
} else {
if(command == "toggle")
{
if(device.currentValue('lock') == "locked")
device.unlock();
else
device.lock();
return [Device_id: params.id, result_action: "200"]
}
}
}
}
//PRESENCE
def getPresenceStatus() {
def device = presence_sensors.find { it.id == params.id }
if (!device) {
httpError(404, "Device not found")
} else {
return [Device_state: device.currentValue('presence')]
}
}
//MOTION
def getMotionStatus() {
def device = motions.find { it.id == params.id }
if (!device) {
httpError(404, "Device not found")
} else {
return [Device_state: device.currentValue('motion')]
}
}
//OUTLET
def getOutletStatus() {
def device = outlets.find { it.id == params.id }
if (!device) {
httpError(404, "Device not found")
} else {
return [Device_state: device.currentSwitch, Current_watt: device.currentValue("energy")]
}
}
def updateOutlet() {
def command = params.command
def device = outlets.find { it.id == params.id }
if (command){
if (!device) {
httpError(404, "Device not found")
} else {
if(command == "toggle")
{
if(device.currentSwitch == "on")
device.off();
else
device.on();
return [Device_id: params.id, result_action: "200"]
}
}
}
}
//SWITCH
def updateSwitch() {
def command = params.command
def device = switches.find { it.id == params.id }
if (command){
if (!device) {
httpError(404, "Device not found")
} else {
if(command == "toggle")
{
if(device.currentSwitch == "on")
device.off();
else
device.on();
return [Device_id: params.id, result_action: "200"]
}
}
}
}
//TEMPERATURE
def getTempSensorsStatus() {
def device = temperature_sensors.find { it.id == params.id }
if (!device) {
httpError(404, "Device not found")
} else {
return [Device_state: device.currentValue('temperature')]
}
}

View File

@@ -18,13 +18,8 @@ definition(
) )
preferences { preferences {
section("Zip code?") {
if (!(location.zipCode || ( location.latitude && location.longitude )) && location.channelName == 'samsungtv') { input "zipcode", "text", title: "Zipcode?"
section { paragraph title: "Note:", "Location is required for this SmartApp. Go to 'Location Name' settings to setup your correct location." }
}
if (location.channelName != 'samsungtv') {
section( "Set your location" ) { input "zipCode", "text", title: "Zip code" }
} }
section("Things to check?") { section("Things to check?") {
@@ -65,11 +60,7 @@ def scheduleCheck(evt) {
// Only need to poll if we haven't checked in a while - and if something is left open. // Only need to poll if we haven't checked in a while - and if something is left open.
if((now() - (30 * 60 * 1000) > state.lastCheck["time"]) && open) { if((now() - (30 * 60 * 1000) > state.lastCheck["time"]) && open) {
log.info("Something's open - let's check the weather.") log.info("Something's open - let's check the weather.")
def response def response = getWeatherFeature("forecast", zipcode)
if (location.channelName != 'samsungtv')
response = getWeatherFeature("forecast", zipCode)
else
response = getWeatherFeature("forecast")
def weather = isStormy(response) def weather = isStormy(response)
if(weather) { if(weather) {

View File

@@ -4,9 +4,6 @@
* Author: Juan Risso * Author: Juan Risso
* Date: 2013-12-19 * Date: 2013-12-19
*/ */
include 'asynchttp_v1'
definition( definition(
name: "Jawbone UP (Connect)", name: "Jawbone UP (Connect)",
namespace: "juano2310", namespace: "juano2310",
@@ -306,8 +303,7 @@ def setup() {
def childDevice = addChildDevice('juano2310', "Jawbone User", "${app.id}.${member.xid}",null,[name:"Jawbone UP - " + member.first, completedSetup: true]) def childDevice = addChildDevice('juano2310', "Jawbone User", "${app.id}.${member.xid}",null,[name:"Jawbone UP - " + member.first, completedSetup: true])
if (childDevice) { if (childDevice) {
log.debug "Child Device Successfully Created" log.debug "Child Device Successfully Created"
childDevice?.generateSleepingEvent(false) generateInitialEvent (member, childDevice)
pollChild(childDevice)
} }
} }
} }
@@ -353,67 +349,67 @@ def uninstalled() {
} }
def pollChild(childDevice) { def pollChild(childDevice) {
def childMap = [ value: "$childDevice.device.deviceNetworkId}"] def member = state.member
generatePollingEvents (member, childDevice)
def params = [
uri: 'https://jawbone.com',
path: '/nudge/api/users/@me/goals',
headers: ["Authorization": "Bearer ${state.JawboneAccessToken}" ],
contentType: 'application/json'
]
asynchttp_v1.get('responseGoals', params, childMap)
def params2 = [
uri: 'https://jawbone.com',
path: '/nudge/api/users/@me/moves',
headers: ["Authorization": "Bearer ${state.JawboneAccessToken}" ],
contentType: 'application/json'
]
asynchttp_v1.get('responseMoves', params2, childMap)
} }
def responseGoals(response, dni) { def generatePollingEvents (member, childDevice) {
if (response.hasError()) { // lets figure out if the member is currently "home" (At the place)
log.error "response has error: $response.errorMessage" def urlgoals = "https://jawbone.com/nudge/api/users/@me/goals"
} else { def urlmoves = "https://jawbone.com/nudge/api/users/@me/moves"
def goals def urlsleeps = "https://jawbone.com/nudge/api/users/@me/sleeps"
try { def goals = null
// json response already parsed into JSONElement object def moves = null
goals = response.json.data def sleeps = null
} catch (e) { httpGet(uri: urlgoals, headers: ["Authorization": "Bearer ${state.JawboneAccessToken}" ]) {response ->
log.error "error parsing json from response: $e" goals = response.data.data
} }
if (goals) { httpGet(uri: urlmoves, headers: ["Authorization": "Bearer ${state.JawboneAccessToken}" ]) {response ->
def childDevice = getChildDevice(dni.value) moves = response.data.data.items[0]
log.debug "Goal = ${goals.move_steps} Steps" }
childDevice?.sendEvent(name:"goal", value: goals.move_steps)
} else { try { // we are going to just ignore any errors
log.debug "did not get json results from response body: $response.data" log.debug "Member = ${member.first}"
} log.debug "Moves Goal = ${goals.move_steps} Steps"
} log.debug "Moves = ${moves.details.steps} Steps"
childDevice?.sendEvent(name:"steps", value: moves.details.steps)
childDevice?.sendEvent(name:"goal", value: goals.move_steps)
//setColor(moves.details.steps,goals.move_steps,childDevice)
}
catch (e) {
// eat it
}
} }
def responseMoves(response, dni) { def generateInitialEvent (member, childDevice) {
if (response.hasError()) { // lets figure out if the member is currently "home" (At the place)
log.error "response has error: $response.errorMessage" def urlgoals = "https://jawbone.com/nudge/api/users/@me/goals"
} else { def urlmoves = "https://jawbone.com/nudge/api/users/@me/moves"
def moves def urlsleeps = "https://jawbone.com/nudge/api/users/@me/sleeps"
try { def goals = null
// json response already parsed into JSONElement object def moves = null
moves = response.json.data.items[0] def sleeps = null
} catch (e) { httpGet(uri: urlgoals, headers: ["Authorization": "Bearer ${state.JawboneAccessToken}" ]) {response ->
log.error "error parsing json from response: $e" goals = response.data.data
} }
if (moves) { httpGet(uri: urlmoves, headers: ["Authorization": "Bearer ${state.JawboneAccessToken}" ]) {response ->
def childDevice = getChildDevice(dni.value) moves = response.data.data.items[0]
log.debug "Moves = ${moves.details.steps} Steps" }
childDevice?.sendEvent(name:"steps", value: moves.details.steps)
} else { try { // we are going to just ignore any errors
log.debug "did not get json results from response body: $response.data" log.debug "Member = ${member.first}"
} log.debug "Moves Goal = ${goals.move_steps} Steps"
} log.debug "Moves = ${moves.details.steps} Steps"
log.debug "Sleeping state = false"
childDevice?.generateSleepingEvent(false)
childDevice?.sendEvent(name:"steps", value: moves.details.steps)
childDevice?.sendEvent(name:"goal", value: goals.move_steps)
//setColor(moves.details.steps,goals.move_steps,childDevice)
}
catch (e) {
// eat it
}
} }
def setColor (steps,goal,childDevice) { def setColor (steps,goal,childDevice) {
@@ -437,7 +433,7 @@ def hookEventHandler() {
// get some stuff we need // get some stuff we need
def userId = json.events.user_xid[0] def userId = json.events.user_xid[0]
def json_type = json.events.type[0] def json_type = json.events.type[0]
def json_action = json.events.action[0] def json_action = json.events.action[0]
//log.debug json //log.debug json
log.debug "Userid = ${userId}" log.debug "Userid = ${userId}"

View File

@@ -454,23 +454,17 @@ def sendStopEvent(source) {
eventData.value += "cancelled" eventData.value += "cancelled"
} }
// send 100% completion event
sendTimeRemainingEvent(100)
// send a non-displayed 0% completion to reset tiles
sendTimeRemainingEvent(0, false)
// send sessionStatus event last so the event feed is ordered properly
sendControllerEvent(eventData) sendControllerEvent(eventData)
sendTimeRemainingEvent(0)
} }
def sendTimeRemainingEvent(percentComplete, displayed = true) { def sendTimeRemainingEvent(percentComplete) {
log.trace "sendTimeRemainingEvent(${percentComplete})" log.trace "sendTimeRemainingEvent(${percentComplete})"
def percentCompleteEventData = [ def percentCompleteEventData = [
name: "percentComplete", name: "percentComplete",
value: percentComplete as int, value: percentComplete as int,
displayed: displayed, displayed: true,
isStateChange: true isStateChange: true
] ]
sendControllerEvent(percentCompleteEventData) sendControllerEvent(percentCompleteEventData)
@@ -480,7 +474,7 @@ def sendTimeRemainingEvent(percentComplete, displayed = true) {
def timeRemainingEventData = [ def timeRemainingEventData = [
name: "timeRemaining", name: "timeRemaining",
value: displayableTime(timeRemaining), value: displayableTime(timeRemaining),
displayed: displayed, displayed: true,
isStateChange: true isStateChange: true
] ]
sendControllerEvent(timeRemainingEventData) sendControllerEvent(timeRemainingEventData)
@@ -614,6 +608,8 @@ private completion() {
handleCompletionMessaging() handleCompletionMessaging()
handleCompletionModesAndPhrases() handleCompletionModesAndPhrases()
sendTimeRemainingEvent(100)
} }
private handleCompletionSwitches() { private handleCompletionSwitches() {

View File

@@ -131,7 +131,10 @@ def bulbDiscovery() {
def refreshInterval = 3 def refreshInterval = 3
state.inBulbDiscovery = true state.inBulbDiscovery = true
def bridge = null def bridge = null
if (selectedHue) {
bridge = getChildDevice(selectedHue)
subscribe(bridge, "bulbList", bulbListData)
}
state.bridgeRefreshCount = 0 state.bridgeRefreshCount = 0
def allLightsFound = bulbsDiscovered() ?: [:] def allLightsFound = bulbsDiscovered() ?: [:]
@@ -256,6 +259,10 @@ Map bulbsDiscovered() {
return bulbmap return bulbmap
} }
def bulbListData(evt) {
state.bulbs = evt.jsonData
}
Map getHueBulbs() { Map getHueBulbs() {
state.bulbs = state.bulbs ?: [:] state.bulbs = state.bulbs ?: [:]
} }
@@ -309,6 +316,29 @@ def uninstalled(){
state.username = null state.username = null
} }
// Handles events to add new bulbs
def bulbListHandler(hub, data = "") {
def msg = "Bulbs list not processed. Only while in settings menu."
def bulbs = [:]
if (state.inBulbDiscovery) {
def logg = ""
log.trace "Adding bulbs to state..."
state.bridgeProcessedLightList = true
def object = new groovy.json.JsonSlurper().parseText(data)
object.each { k,v ->
if (v instanceof Map)
bulbs[k] = [id: k, name: v.name, type: v.type, modelid: v.modelid, hub:hub, online: v.state?.reachable]
}
}
def bridge = null
if (selectedHue) {
bridge = getChildDevice(selectedHue)
bridge?.sendEvent(name: "bulbList", value: hub, data: bulbs, isStateChange: true, displayed: false)
}
msg = "${bulbs.size()} bulbs found. ${bulbs}"
return msg
}
private upgradeDeviceType(device, newHueType) { private upgradeDeviceType(device, newHueType) {
def deviceType = getDeviceType(newHueType) def deviceType = getDeviceType(newHueType)
@@ -540,8 +570,11 @@ void lightsHandler(physicalgraph.device.HubResponse hubResponse) {
if (isValidSource(hubResponse.mac)) { if (isValidSource(hubResponse.mac)) {
def body = hubResponse.json def body = hubResponse.json
if (!body?.state?.on) { //check if first time poll made it here by mistake if (!body?.state?.on) { //check if first time poll made it here by mistake
def bulbs = getHueBulbs()
log.debug "Adding bulbs to state!" log.debug "Adding bulbs to state!"
updateBulbState(body, hubResponse.hubId) body.each { k, v ->
bulbs[k] = [id: k, name: v.name, type: v.type, modelid: v.modelid, hub: hubResponse.hubId]
}
} }
} }
} }
@@ -657,8 +690,11 @@ def locationHandler(evt) {
} else { } else {
//GET /api/${state.username}/lights response (application/json) //GET /api/${state.username}/lights response (application/json)
if (!body?.state?.on) { //check if first time poll made it here by mistake if (!body?.state?.on) { //check if first time poll made it here by mistake
def bulbs = getHueBulbs()
log.debug "Adding bulbs to state!" log.debug "Adding bulbs to state!"
updateBulbState(body, parsedEvent.hub) body.each { k,v ->
bulbs[k] = [id: k, name: v.name, type: v.type, modelid: v.modelid, hub:parsedEvent.hub]
}
} }
} }
} }
@@ -718,7 +754,7 @@ private void checkBridgeStatus() {
} }
if (it.value.lastActivity < time) { // it.value.lastActivity != null && if (it.value.lastActivity < time) { // it.value.lastActivity != null &&
log.warn "Bridge $it.value.idNumber is Offline" log.warn "Bridge $it.key is Offline"
d.sendEvent(name: "status", value: "Offline") d.sendEvent(name: "status", value: "Offline")
state.bulbs?.each { state.bulbs?.each {
@@ -743,31 +779,6 @@ def isInBulbDiscovery() {
return state.inBulbDiscovery return state.inBulbDiscovery
} }
private updateBulbState(messageBody, hub) {
def bulbs = getHueBulbs()
// Copy of bulbs used to locate old lights in state that are no longer on bridge
def toRemove = [:]
toRemove << bulbs
messageBody.each { k,v ->
if (v instanceof Map) {
if (bulbs[k] == null) {
bulbs[k] = [:]
}
bulbs[k] << [id: k, name: v.name, type: v.type, modelid: v.modelid, hub:hub, remove: false]
toRemove.remove(k)
}
}
// Remove bulbs from state that are no longer discovered
toRemove.each { k,v ->
log.warn "${bulbs[k].name} no longer exists on bridge, removing"
bulbs.remove(k)
}
}
///////////////////////////////////// /////////////////////////////////////
//CHILD DEVICE METHODS //CHILD DEVICE METHODS
///////////////////////////////////// /////////////////////////////////////
@@ -1162,14 +1173,7 @@ def setColor(childDevice, huesettings) {
} }
def ping(childDevice) { def ping(childDevice) {
if (childDevice.device?.deviceNetworkId?.equalsIgnoreCase(selectedHue)) { if (isOnline(getId(childDevice))) {
if (childDevice.device?.currentValue("status")?.equalsIgnoreCase("Online")) {
childDevice.sendEvent(name: "deviceWatch-ping", value: "ONLINE", description: "Hue Bridge is reachable", displayed: false, isStateChange: true)
return "Bridge is Online"
} else {
return "Bridge is Offline"
}
} else if (isOnline(getId(childDevice))) {
childDevice.sendEvent(name: "deviceWatch-ping", value: "ONLINE", description: "Hue Light is reachable", displayed: false, isStateChange: true) childDevice.sendEvent(name: "deviceWatch-ping", value: "ONLINE", description: "Hue Light is reachable", displayed: false, isStateChange: true)
return "Device is Online" return "Device is Online"
} else { } else {
@@ -1194,7 +1198,7 @@ private poll() {
} }
private isOnline(id) { private isOnline(id) {
return (state.bulbs[id]?.online != null && state.bulbs[id]?.online) || state.bulbs[id]?.online == null return (state.bulbs[id].online != null && state.bulbs[id].online) || state.bulbs[id].online == null
} }
private put(path, body) { private put(path, body) {
@@ -1261,7 +1265,7 @@ def convertBulbListToMap() {
try { try {
if (state.bulbs instanceof java.util.List) { if (state.bulbs instanceof java.util.List) {
def map = [:] def map = [:]
state.bulbs?.unique {it.id}.each { bulb -> state.bulbs.unique {it.id}.each { bulb ->
map << ["${bulb.id}":["id":bulb.id, "name":bulb.name, "type": bulb.type, "modelid": bulb.modelid, "hub":bulb.hub, "online": bulb.online]] map << ["${bulb.id}":["id":bulb.id, "name":bulb.name, "type": bulb.type, "modelid": bulb.modelid, "hub":bulb.hub, "online": bulb.online]]
} }
state.bulbs = map state.bulbs = map

View File

@@ -34,7 +34,6 @@
* locks | lock | lock, unlock | locked, unlocked * locks | lock | lock, unlock | locked, unlocked
* ---------------------+-------------------+-----------------------------+------------------------------------ * ---------------------+-------------------+-----------------------------+------------------------------------
*/ */
include 'asynchttp_v1'
definition( definition(
name: "Logitech Harmony (Connect)", name: "Logitech Harmony (Connect)",
@@ -97,7 +96,7 @@ def authPage() {
def description = null def description = null
if (!state.HarmonyAccessToken) { if (!state.HarmonyAccessToken) {
if (!state.accessToken) { if (!state.accessToken) {
log.debug "Harmony - About to create access token" log.debug "About to create access token"
createAccessToken() createAccessToken()
} }
description = "Click to enter Harmony Credentials" description = "Click to enter Harmony Credentials"
@@ -110,28 +109,26 @@ def authPage() {
//device discovery request every 5 //25 seconds //device discovery request every 5 //25 seconds
int deviceRefreshCount = !state.deviceRefreshCount ? 0 : state.deviceRefreshCount as int int deviceRefreshCount = !state.deviceRefreshCount ? 0 : state.deviceRefreshCount as int
state.deviceRefreshCount = deviceRefreshCount + 1 state.deviceRefreshCount = deviceRefreshCount + 1
def refreshInterval = 5 def refreshInterval = 3
def huboptions = state.HarmonyHubs ?: [] def huboptions = state.HarmonyHubs ?: []
def actoptions = state.HarmonyActivities ?: [] def actoptions = state.HarmonyActivities ?: []
def numFoundHub = huboptions.size() ?: 0 def numFoundHub = huboptions.size() ?: 0
def numFoundAct = actoptions.size() ?: 0 def numFoundAct = actoptions.size() ?: 0
if((deviceRefreshCount % 5) == 0) { if((deviceRefreshCount % 5) == 0) {
discoverDevices() discoverDevices()
} }
return dynamicPage(name:"Credentials", title:"Discovery Started!", nextPage:"", refreshInterval:refreshInterval, install:true, uninstall: true) { return dynamicPage(name:"Credentials", title:"Discovery Started!", nextPage:"", refreshInterval:refreshInterval, install:true, uninstall: true) {
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.") { 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, submitOnChange: true, options:huboptions input "selectedhubs", "enum", required:false, title:"Select Harmony Hubs (${numFoundHub} found)", multiple:true, options:huboptions
} }
// Virtual activity flag // Virtual activity flag
if (numFoundHub > 0 && numFoundAct > 0 && true) if (numFoundHub > 0 && numFoundAct > 0 && true)
section("You can also add activities as virtual switches for other convenient integrations") { 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, submitOnChange: true, options:actoptions input "selectedactivities", "enum", required:false, title:"Select Harmony Activities (${numFoundAct} found)", multiple:true, options:actoptions
} }
if (state.resethub) 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,13 +138,13 @@ def callback() {
def redirectUrl = null def redirectUrl = null
if (params.authQueryString) { if (params.authQueryString) {
redirectUrl = URLDecoder.decode(params.authQueryString.replaceAll(".+&redirect_url=", "")) redirectUrl = URLDecoder.decode(params.authQueryString.replaceAll(".+&redirect_url=", ""))
log.debug "Harmony - redirectUrl: ${redirectUrl}" log.debug "redirectUrl: ${redirectUrl}"
} else { } else {
log.warn "Harmony - No authQueryString" log.warn "No authQueryString"
} }
if (state.HarmonyAccessToken) { if (state.HarmonyAccessToken) {
log.debug "Harmony - Access token already exists" log.debug "Access token already exists"
discovery() discovery()
success() success()
} else { } else {
@@ -155,27 +152,27 @@ def callback() {
if (code) { if (code) {
if (code.size() > 6) { if (code.size() > 6) {
// Harmony code // Harmony code
log.debug "Harmony - Exchanging code for access token" log.debug "Exchanging code for access token"
receiveToken(redirectUrl) receiveToken(redirectUrl)
} else { } else {
// Initiate the Harmony OAuth flow. // Initiate the Harmony OAuth flow.
init() init()
} }
} else { } else {
log.debug "Harmony - This code should be unreachable" log.debug "This code should be unreachable"
success() success()
} }
} }
} }
def init() { def init() {
log.debug "Harmony - Requesting Code" log.debug "Requesting Code"
def oauthParams = [client_id: "${appSettings.clientId}", scope: "remote", response_type: "code", redirect_uri: "${servercallbackUrl}" ] def oauthParams = [client_id: "${appSettings.clientId}", scope: "remote", response_type: "code", redirect_uri: "${servercallbackUrl}" ]
redirect(location: "https://home.myharmony.com/oauth2/authorize?${toQueryString(oauthParams)}") redirect(location: "https://home.myharmony.com/oauth2/authorize?${toQueryString(oauthParams)}")
} }
def receiveToken(redirectUrl = null) { def receiveToken(redirectUrl = null) {
log.debug "Harmony - receiveToken" log.debug "receiveToken"
def oauthParams = [ client_id: "${appSettings.clientId}", client_secret: "${appSettings.clientSecret}", grant_type: "authorization_code", code: params.code ] def oauthParams = [ client_id: "${appSettings.clientId}", client_secret: "${appSettings.clientSecret}", grant_type: "authorization_code", code: params.code ]
def params = [ def params = [
uri: "https://home.myharmony.com/oauth2/token?${toQueryString(oauthParams)}", uri: "https://home.myharmony.com/oauth2/token?${toQueryString(oauthParams)}",
@@ -186,7 +183,7 @@ def receiveToken(redirectUrl = null) {
} }
} catch (java.util.concurrent.TimeoutException e) { } catch (java.util.concurrent.TimeoutException e) {
fail(e) fail(e)
log.warn "Harmony - Connection timed out, please try again later." log.warn "Connection timed out, please try again later."
} }
discovery() discovery()
if (state.HarmonyAccessToken) { if (state.HarmonyAccessToken) {
@@ -310,7 +307,7 @@ def buildRedirectUrl(page) {
def installed() { def installed() {
if (!state.accessToken) { if (!state.accessToken) {
log.debug "Harmony - About to create access token" log.debug "About to create access token"
createAccessToken() createAccessToken()
} else { } else {
initialize() initialize()
@@ -319,7 +316,7 @@ def installed() {
def updated() { def updated() {
if (!state.accessToken) { if (!state.accessToken) {
log.debug "Harmony - About to create access token" log.debug "About to create access token"
createAccessToken() createAccessToken()
} else { } else {
initialize() initialize()
@@ -330,9 +327,9 @@ def uninstalled() {
if (state.HarmonyAccessToken) { if (state.HarmonyAccessToken) {
try { try {
state.HarmonyAccessToken = "" state.HarmonyAccessToken = ""
log.debug "Harmony - Success disconnecting Harmony from SmartThings" log.debug "Success disconnecting Harmony from SmartThings"
} catch (groovyx.net.http.HttpResponseException e) { } catch (groovyx.net.http.HttpResponseException e) {
log.error "Harmony - Error disconnecting Harmony from SmartThings: ${e.statusCode}" log.error "Error disconnecting Harmony from SmartThings: ${e.statusCode}"
} }
} }
} }
@@ -341,8 +338,7 @@ def initialize() {
state.aux = 0 state.aux = 0
if (selectedhubs || selectedactivities) { if (selectedhubs || selectedactivities) {
addDevice() addDevice()
runEvery5Minutes("poll") runEvery5Minutes("poll")
getActivityList()
} }
} }
@@ -351,7 +347,7 @@ def getHarmonydevices() {
} }
Map discoverDevices() { Map discoverDevices() {
log.trace "Harmony - Discovering devices..." log.trace "Discovering devices..."
discovery() discovery()
if (getHarmonydevices() != []) { if (getHarmonydevices() != []) {
def devices = state.Harmonydevices.hubs def devices = state.Harmonydevices.hubs
@@ -363,7 +359,7 @@ Map discoverDevices() {
def hubname = getHubName(it.key) def hubname = getHubName(it.key)
def hubvalue = "${hubname}" def hubvalue = "${hubname}"
hubs["harmony-${hubkey}"] = hubvalue hubs["harmony-${hubkey}"] = hubvalue
it.value.response.data.activities.each { it.value.response.data.activities.each {
def value = "${it.value.name}" def value = "${it.value.name}"
def key = "harmony-${hubkey}-${it.key}" def key = "harmony-${hubkey}-${it.key}"
activities["${key}"] = value activities["${key}"] = value
@@ -381,177 +377,164 @@ def discovery() {
try { try {
httpGet(uri: url, headers: ["Accept": "application/json"]) {response -> httpGet(uri: url, headers: ["Accept": "application/json"]) {response ->
if (response.status == 200) { if (response.status == 200) {
log.debug "Harmony - valid Token" log.debug "valid Token"
state.Harmonydevices = response.data state.Harmonydevices = response.data
state.resethub = false state.resethub = false
getActivityList()
poll()
} else { } else {
log.debug "Harmony - Error: $response.status" log.debug "Error: $response.status"
} }
} }
} catch (groovyx.net.http.HttpResponseException e) { } catch (groovyx.net.http.HttpResponseException e) {
if (e.statusCode == 401) { // token is expired if (e.statusCode == 401) { // token is expired
state.remove("HarmonyAccessToken") state.remove("HarmonyAccessToken")
log.warn "Harmony - Harmony Access token has expired" log.warn "Harmony Access token has expired"
} }
} catch (java.net.SocketTimeoutException e) { } catch (java.net.SocketTimeoutException e) {
log.warn "Harmony - Connection to the hub timed out. Please restart the hub and try again." log.warn "Connection to the hub timed out. Please restart the hub and try again."
state.resethub = true state.resethub = true
} catch (e) { } catch (e) {
log.info "Harmony - Error: $e" log.info "Logitech Harmony - Error: $e"
} }
return null return null
} }
def addDevice() { def addDevice() {
log.trace "Harmony - Adding Hubs" log.trace "Adding Hubs"
selectedhubs.each { dni -> selectedhubs.each { dni ->
def d = getChildDevice(dni) def d = getChildDevice(dni)
if(!d) { if(!d) {
def newAction = state.HarmonyHubs.find { it.key == dni } def newAction = state.HarmonyHubs.find { it.key == dni }
d = addChildDevice("smartthings", "Logitech Harmony Hub C2C", dni, null, [label:"${newAction.value}"]) d = addChildDevice("smartthings", "Logitech Harmony Hub C2C", dni, null, [label:"${newAction.value}"])
log.trace "Harmony - Created ${d.displayName} with id $dni" log.trace "created ${d.displayName} with id $dni"
poll() poll()
} else { } else {
log.trace "Harmony - Found ${d.displayName} with id $dni already exists" log.trace "found ${d.displayName} with id $dni already exists"
} }
} }
log.trace "Harmony - Adding Activities" log.trace "Adding Activities"
selectedactivities.each { dni -> selectedactivities.each { dni ->
def d = getChildDevice(dni) def d = getChildDevice(dni)
if(!d) { if(!d) {
def newAction = state.HarmonyActivities.find { it.key == dni } def newAction = state.HarmonyActivities.find { it.key == dni }
if (newAction) { if (newAction) {
d = addChildDevice("smartthings", "Harmony Activity", dni, null, [label:"${newAction.value} [Harmony Activity]"]) d = addChildDevice("smartthings", "Harmony Activity", dni, null, [label:"${newAction.value} [Harmony Activity]"])
log.trace "Harmony - Created ${d.displayName} with id $dni" log.trace "created ${d.displayName} with id $dni"
poll() poll()
} }
} else { } else {
log.trace "Harmony - Found ${d.displayName} with id $dni already exists" log.trace "found ${d.displayName} with id $dni already exists"
} }
} }
} }
def activity(dni,mode) { def activity(dni,mode) {
def tokenParam = [auth: state.HarmonyAccessToken] def Params = [auth: state.HarmonyAccessToken]
def url def msg = "Command failed"
def url = ''
if (dni == "all") { if (dni == "all") {
url = "https://home.myharmony.com/cloudapi/activity/off?${toQueryString(tokenParam)}" url = "https://home.myharmony.com/cloudapi/activity/off?${toQueryString(Params)}"
} else { } else {
def aux = dni.split('-') def aux = dni.split('-')
def hubId = aux[1] def hubId = aux[1]
if (mode == "hub" || (aux.size() <= 2) || (aux[2] == "off")){ if (mode == "hub" || (aux.size() <= 2) || (aux[2] == "off")){
url = "https://home.myharmony.com/cloudapi/hub/${hubId}/activity/off?${toQueryString(tokenParam)}" url = "https://home.myharmony.com/cloudapi/hub/${hubId}/activity/off?${toQueryString(Params)}"
} else { } else {
def activityId = aux[2] def activityId = aux[2]
url = "https://home.myharmony.com/cloudapi/hub/${hubId}/activity/${activityId}/${mode}?${toQueryString(tokenParam)}" url = "https://home.myharmony.com/cloudapi/hub/${hubId}/activity/${activityId}/${mode}?${toQueryString(Params)}"
} }
} }
def params = [ try {
uri: url, httpPostJson(uri: url) { response ->
contentType: 'application/json' if (response.data.code == 200 || dni == "all") {
] msg = "Command sent succesfully"
asynchttp_v1.post('activityResponse', params) state.aux = 0
return "Command Sent" } else {
} msg = "Command failed. Error: $response.data.code"
}
def activityResponse(response, data) { }
if (response.hasError()) { } catch (groovyx.net.http.HttpResponseException ex) {
log.error "Harmony - response has error: $response.errorMessage" log.error ex
if (response.status == 401) { // token is expired if (state.aux == 0) {
state.remove("HarmonyAccessToken") state.aux = 1
log.warn "Harmony - Access token has expired" activity(dni,mode)
} else {
msg = ex
state.aux = 0
}
} catch(Exception ex) {
msg = ex
} }
} else { runIn(10, "poll", [overwrite: true])
if (response.status == 200) { return msg
log.trace "Harmony - Command sent succesfully"
poll()
} else {
log.trace "Harmony - Command failed. Error: $response.status"
}
}
} }
def poll() { def poll() {
// GET THE LIST OF ACTIVITIES // GET THE LIST OF ACTIVITIES
if (state.HarmonyAccessToken) { if (state.HarmonyAccessToken) {
def tokenParam = [auth: state.HarmonyAccessToken] getActivityList()
def params = [ def Params = [auth: state.HarmonyAccessToken]
uri: "https://home.myharmony.com/cloudapi/state?${toQueryString(tokenParam)}", def url = "https://home.myharmony.com/cloudapi/state?${toQueryString(Params)}"
headers: ["Accept": "application/json"], try {
contentType: 'application/json' httpGet(uri: url, headers: ["Accept": "application/json"]) {response ->
] def map = [:]
asynchttp_v1.get('pollResponse', params) response.data.hubs.each {
} else { if (it.value.message == "OK") {
log.warn "Harmony - Access token has expired" map["${it.key}"] = "${it.value.response.data.currentAvActivity},${it.value.response.data.activityStatus}"
} def hub = getChildDevice("harmony-${it.key}")
} if (hub) {
if (it.value.response.data.currentAvActivity == "-1") {
def pollResponse(response, data) { hub.sendEvent(name: "currentActivity", value: "--", descriptionText: "There isn't any activity running", display: false)
if (response.hasError()) { } else {
log.error "Harmony - response has error: $response.errorMessage" def currentActivity = getActivityName(it.value.response.data.currentAvActivity,it.key)
if (response.status == 401) { // token is expired hub.sendEvent(name: "currentActivity", value: currentActivity, descriptionText: "Current activity is ${currentActivity}", display: false)
state.remove("HarmonyAccessToken") }
log.warn "Harmony - Access token has expired" }
} } else {
} else { log.trace it.value.message
def ResponseValues }
try { }
// json response already parsed into JSONElement object def activities = getChildDevices()
ResponseValues = response.json def activitynotrunning = true
} catch (e) { activities.each { activity ->
log.error "Harmony - error parsing json from response: $e" def act = activity.deviceNetworkId.split('-')
} if (act.size() > 2) {
if (ResponseValues) { def aux = map.find { it.key == act[1] }
def map = [:] if (aux) {
ResponseValues.hubs.each { def aux2 = aux.value.split(',')
if (it.value.message == "OK") { def childDevice = getChildDevice(activity.deviceNetworkId)
map["${it.key}"] = "${it.value.response.data.currentAvActivity},${it.value.response.data.activityStatus}" if ((act[2] == aux2[0]) && (aux2[1] == "1" || aux2[1] == "2")) {
def hub = getChildDevice("harmony-${it.key}") childDevice?.sendEvent(name: "switch", value: "on")
if (hub) { if (aux2[1] == "1")
if (it.value.response.data.currentAvActivity == "-1") { runIn(5, "poll", [overwrite: true])
hub.sendEvent(name: "currentActivity", value: "--", descriptionText: "There isn't any activity running", display: false) } else {
} else { childDevice?.sendEvent(name: "switch", value: "off")
def currentActivity if (aux2[1] == "3")
def activityDTH = getChildDevice("harmony-${it.key}-${it.value.response.data.currentAvActivity}") runIn(5, "poll", [overwrite: true])
if (activityDTH) }
currentActivity = activityDTH.device.displayName }
else
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 "Harmony - error response: $it.value.message"
}
}
def activities = getChildDevices()
def activitynotrunning = true
activities.each { activity ->
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)
if ((act[2] == aux2[0]) && (aux2[1] == "1" || aux2[1] == "2")) {
childDevice?.sendEvent(name: "switch", value: "on")
if (aux2[1] == "1")
runIn(5, "poll", [overwrite: true])
} else {
childDevice?.sendEvent(name: "switch", value: "off")
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")
log.warn "Harmony Access token has expired"
}
} catch (java.net.SocketTimeoutException e) {
log.warn "Connection to the hub timed out. Please restart the hub and try again."
state.resethub = true
} catch (e) {
log.info "Logitech Harmony - Error: $e"
} }
} else { }
log.debug "Harmony - did not get json results from response body: $response.data"
}
}
} }
def getActivityList() { def getActivityList() {
// GET ACTIVITY'S NAME
if (state.HarmonyAccessToken) { if (state.HarmonyAccessToken) {
def Params = [auth: state.HarmonyAccessToken] def Params = [auth: state.HarmonyAccessToken]
def url = "https://home.myharmony.com/cloudapi/activity/all?${toQueryString(Params)}" def url = "https://home.myharmony.com/cloudapi/activity/all?${toQueryString(Params)}"
@@ -568,19 +551,21 @@ def getActivityList() {
[id: it.key, name: it.value['name'], type: it.value['type']] [id: it.key, name: it.value['name'], type: it.value['type']]
} }
activities += [id: "off", name: "Activity OFF", type: "0"] 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) { } catch (groovyx.net.http.HttpResponseException e) {
log.trace e log.trace e
} catch (java.net.SocketTimeoutException e) { } catch (java.net.SocketTimeoutException e) {
log.trace e log.trace e
} catch(Exception e) { } catch(Exception e) {
log.trace e log.trace e
} }
} }
return activity
} }
def getActivityName(activity,hubId) { def getActivityName(activity,hubId) {
@@ -643,7 +628,7 @@ def sendNotification(msg) {
def hookEventHandler() { def hookEventHandler() {
// log.debug "In hookEventHandler method." // log.debug "In hookEventHandler method."
log.debug "Harmony - request = ${request}" log.debug "request = ${request}"
def json = request.JSON def json = request.JSON
@@ -652,14 +637,14 @@ def hookEventHandler() {
} }
def listDevices() { def listDevices() {
log.debug "Harmony - getDevices(), params: ${params}" log.debug "getDevices, params: ${params}"
allDevices.collect { allDevices.collect {
deviceItem(it) deviceItem(it)
} }
} }
def getDevice() { def getDevice() {
log.debug "Harmony - getDevice(), params: ${params}" log.debug "getDevice, params: ${params}"
def device = allDevices.find { it.id == params.id } def device = allDevices.find { it.id == params.id }
if (!device) { if (!device) {
render status: 404, data: '{"msg": "Device not found"}' render status: 404, data: '{"msg": "Device not found"}'
@@ -672,7 +657,7 @@ def updateDevice() {
def data = request.JSON def data = request.JSON
def command = data.command def command = data.command
def arguments = data.arguments def arguments = data.arguments
log.debug "Harmony - updateDevice(), params: ${params}, request: ${data}" log.debug "updateDevice, params: ${params}, request: ${data}"
if (!command) { if (!command) {
render status: 400, data: '{"msg": "command is required"}' render status: 400, data: '{"msg": "command is required"}'
} else { } else {
@@ -740,7 +725,7 @@ def getDeviceCapabilityCommands(deviceCapabilities) {
} }
def listSubscriptions() { def listSubscriptions() {
log.debug "Harmony - listSubscriptions()" log.debug "listSubscriptions()"
app.subscriptions?.findAll { it.device?.device && it.device.id }?.collect { app.subscriptions?.findAll { it.device?.device && it.device.id }?.collect {
def deviceInfo = state[it.device.id] def deviceInfo = state[it.device.id]
def response = [ def response = [
@@ -761,17 +746,17 @@ def addSubscription() {
def attribute = data.attributeName def attribute = data.attributeName
def callbackUrl = data.callbackUrl def callbackUrl = data.callbackUrl
log.debug "Harmony - addSubscription, params: ${params}, request: ${data}" log.debug "addSubscription, params: ${params}, request: ${data}"
if (!attribute) { if (!attribute) {
render status: 400, data: '{"msg": "attributeName is required"}' render status: 400, data: '{"msg": "attributeName is required"}'
} else { } else {
def device = allDevices.find { it.id == data.deviceId } def device = allDevices.find { it.id == data.deviceId }
if (device) { if (device) {
if (!state.harmonyHubs) { if (!state.harmonyHubs) {
log.debug "Harmony - Adding callbackUrl: $callbackUrl" log.debug "Adding callbackUrl: $callbackUrl"
state[device.id] = [callbackUrl: callbackUrl] state[device.id] = [callbackUrl: callbackUrl]
} }
log.debug "Harmony - Adding subscription" log.debug "Adding subscription"
def subscription = subscribe(device, attribute, deviceHandler) def subscription = subscribe(device, attribute, deviceHandler)
if (!subscription || !subscription.eventSubscription) { if (!subscription || !subscription.eventSubscription) {
subscription = app.subscriptions?.find { it.device?.device && it.device.id == data.deviceId && it.data == attribute && it.handler == 'deviceHandler' } subscription = app.subscriptions?.find { it.device?.device && it.device.id == data.deviceId && it.data == attribute && it.handler == 'deviceHandler' }
@@ -799,7 +784,7 @@ def removeSubscription() {
log.debug "removeSubscription, params: ${params}, subscription: ${subscription}, device: ${device}" log.debug "removeSubscription, params: ${params}, subscription: ${subscription}, device: ${device}"
if (device) { if (device) {
log.debug "Harmony - Removing subscription for device: ${device.id}" log.debug "Removing subscription for device: ${device.id}"
state.remove(device.id) state.remove(device.id)
unsubscribe(device) unsubscribe(device)
} }
@@ -823,17 +808,16 @@ def deviceHandler(evt) {
def deviceInfo = state[evt.deviceId] def deviceInfo = state[evt.deviceId]
if (state.harmonyHubs) { if (state.harmonyHubs) {
state.harmonyHubs.each { harmonyHub -> state.harmonyHubs.each { harmonyHub ->
log.trace "Harmony - Sending data to $harmonyHub.name"
sendToHarmony(evt, harmonyHub.callbackUrl) sendToHarmony(evt, harmonyHub.callbackUrl)
} }
} else if (deviceInfo) { } else if (deviceInfo) {
if (deviceInfo.callbackUrl) { if (deviceInfo.callbackUrl) {
sendToHarmony(evt, deviceInfo.callbackUrl) sendToHarmony(evt, deviceInfo.callbackUrl)
} else { } else {
log.warn "Harmony - No callbackUrl set for device: ${evt.deviceId}" log.warn "No callbackUrl set for device: ${evt.deviceId}"
} }
} else { } else {
log.warn "Harmony - No subscribed device found for device: ${evt.deviceId}" log.warn "No subscribed device found for device: ${evt.deviceId}"
} }
} }
@@ -857,12 +841,12 @@ def sendToHarmony(evt, String callbackUrl) {
body: [evt: [deviceId: evt.deviceId, name: evt.name, value: evt.value]] body: [evt: [deviceId: evt.deviceId, name: evt.name, value: evt.value]]
] ]
try { try {
log.debug "Harmony - Sending data to Harmony Cloud: $params" log.debug "Sending data to Harmony Cloud: $params"
httpPostJson(params) { resp -> httpPostJson(params) { resp ->
log.debug "Harmony - Cloud Response: ${resp.status}" log.debug "Harmony Cloud - Response: ${resp.status}"
} }
} catch (e) { } catch (e) {
log.error "Harmony - Cloud Something went wrong: $e" log.error "Harmony Cloud - Something went wrong: $e"
} }
} }
} }
@@ -887,10 +871,10 @@ def activityCallback() {
if (data.errorCode == "200") { if (data.errorCode == "200") {
device.setCurrentActivity(data.currentActivityId) device.setCurrentActivity(data.currentActivityId)
} else { } else {
log.warn "Harmony - Activity callback error: ${data}" log.warn "Activity callback error: ${data}"
} }
} else { } else {
log.warn "Harmony - Activity callback sent to non-existant dni: ${params.dni}" log.warn "Activity callback sent to non-existant dni: ${params.dni}"
} }
render status: 200, data: '{"msg": "Successfully received callbackUrl"}' render status: 200, data: '{"msg": "Successfully received callbackUrl"}'
} }
@@ -924,13 +908,13 @@ def harmony() {
} }
def deleteHarmony() { def deleteHarmony() {
log.debug "Harmony - Trying to delete Harmony hub with mac: ${params.mac}" log.debug "Trying to delete Harmony hub with mac: ${params.mac}"
def harmonyHub = state.harmonyHubs?.find { it.mac == params.mac } def harmonyHub = state.harmonyHubs?.find { it.mac == params.mac }
if (harmonyHub) { if (harmonyHub) {
log.debug "Harmony - Deleting Harmony hub with mac: ${params.mac}" log.debug "Deleting Harmony hub with mac: ${params.mac}"
state.harmonyHubs.remove(harmonyHub) state.harmonyHubs.remove(harmonyHub)
} else { } else {
log.debug "Harmony - Couldn't find Harmony hub with mac: ${params.mac}" log.debug "Couldn't find Harmony hub with mac: ${params.mac}"
} }
render status: 204, data: "{}" render status: 204, data: "{}"
} }

View File

@@ -26,22 +26,17 @@ definition(
) )
preferences { preferences {
section ("In addition to push notifications, send text alerts to...") {
if (!(location.zipCode || ( location.latitude && location.longitude )) && location.channelName == 'samsungtv') { input("recipients", "contact", title: "Send notifications to") {
section { paragraph title: "Note:", "Location is required for this SmartApp. Go to 'Location Name' settings to setup your correct location." } input "phone1", "phone", title: "Phone Number 1", required: false
input "phone2", "phone", title: "Phone Number 2", required: false
input "phone3", "phone", title: "Phone Number 3", required: false
}
} }
if (location.channelName != 'samsungtv') { section ("Zip code (optional, defaults to location coordinates)...") {
section( "Set your location" ) { input "zipCode", "text", title: "Zip code" } input "zipcode", "text", title: "Zip Code", required: false
} }
section ("In addition to push notifications, send text alerts to...") {
input("recipients", "contact", title: "Send notifications to") {
input "phone1", "phone", title: "Phone Number 1", required: false
input "phone2", "phone", title: "Phone Number 2", required: false
input "phone3", "phone", title: "Phone Number 3", required: false
}
}
} }
def installed() { def installed() {
@@ -66,7 +61,7 @@ def checkForSevereWeather() {
def alerts def alerts
if(locationIsDefined()) { if(locationIsDefined()) {
if(zipcodeIsValid()) { if(zipcodeIsValid()) {
alerts = getWeatherFeature("alerts", zipCode)?.alerts alerts = getWeatherFeature("alerts", zipcode)?.alerts
} else { } else {
log.warn "Severe Weather Alert: Invalid zipcode entered, defaulting to location's zipcode" log.warn "Severe Weather Alert: Invalid zipcode entered, defaulting to location's zipcode"
alerts = getWeatherFeature("alerts")?.alerts alerts = getWeatherFeature("alerts")?.alerts