diff --git a/devicetypes/smartthings/ecobee-thermostat.src/ecobee-thermostat.groovy b/devicetypes/smartthings/ecobee-thermostat.src/ecobee-thermostat.groovy index 37cbb85..adf6433 100644 --- a/devicetypes/smartthings/ecobee-thermostat.src/ecobee-thermostat.groovy +++ b/devicetypes/smartthings/ecobee-thermostat.src/ecobee-thermostat.groovy @@ -148,14 +148,12 @@ def generateEvent(Map results) { handlerName: name] if (name=="temperature" || name=="heatingSetpoint" || name=="coolingSetpoint" ) { - def sendValue = convertTemperatureIfNeeded(value.toDouble(), "F", 1) //API return temperature value in F - sendValue = location.temperatureScale == "C"? roundC(sendValue) : sendValue + def sendValue = location.temperatureScale == "C"? roundC(convertFtoC(value.toDouble())) : value.toInteger() isChange = isTemperatureStateChange(device, name, value.toString()) isDisplayed = isChange event << [value: sendValue, unit: temperatureScale, isStateChange: isChange, displayed: isDisplayed] } else if (name=="maxCoolingSetpoint" || name=="minCoolingSetpoint" || name=="maxHeatingSetpoint" || name=="minHeatingSetpoint") { - def sendValue = convertTemperatureIfNeeded(value.toDouble(), "F", 1) //API return temperature value in F - sendValue = location.temperatureScale == "C"? roundC(sendValue) : sendValue + def sendValue = location.temperatureScale == "C"? roundC(convertFtoC(value.toDouble())) : value.toInteger() event << [value: sendValue, unit: temperatureScale, displayed: false] } else if (name=="heatMode" || name=="coolMode" || name=="autoMode" || name=="auxHeatMode"){ isChange = isStateChange(device, name, value.toString()) @@ -253,7 +251,6 @@ void setCoolingSetpoint(setpoint) { def maxCoolingSetpoint = device.currentValue("maxCoolingSetpoint") def minCoolingSetpoint = device.currentValue("minCoolingSetpoint") - if (coolingSetpoint > maxCoolingSetpoint) { coolingSetpoint = maxCoolingSetpoint } else if (coolingSetpoint < minCoolingSetpoint) { @@ -283,7 +280,6 @@ void setCoolingSetpoint(setpoint) { } void resumeProgram() { - log.debug "resumeProgram() is called" sendEvent("name":"thermostatStatus", "value":"resuming schedule", "description":statusText, displayed: false) def deviceId = device.deviceNetworkId.split(/\./).last() @@ -354,7 +350,6 @@ def switchFanMode() { } def switchToFanMode(nextMode) { - log.debug "switching to fan mode: $nextMode" def returnCommand @@ -520,63 +515,56 @@ def fanAuto() { } def generateSetpointEvent() { - log.debug "Generate SetPoint Event" def mode = device.currentValue("thermostatMode") - log.debug "Current Mode = ${mode}" def heatingSetpoint = device.currentValue("heatingSetpoint") - log.debug "Heating Setpoint = ${heatingSetpoint}" - def coolingSetpoint = device.currentValue("coolingSetpoint") - log.debug "Cooling Setpoint = ${coolingSetpoint}" - def maxHeatingSetpoint = device.currentValue("maxHeatingSetpoint") def maxCoolingSetpoint = device.currentValue("maxCoolingSetpoint") def minHeatingSetpoint = device.currentValue("minHeatingSetpoint") def minCoolingSetpoint = device.currentValue("minCoolingSetpoint") - if(location.temperatureScale == "C") - { - maxHeatingSetpoint = roundC(maxHeatingSetpoint) - maxCoolingSetpoint = roundC(maxCoolingSetpoint) - minHeatingSetpoint = roundC(minHeatingSetpoint) - minCoolingSetpoint = roundC(minCoolingSetpoint) - heatingSetpoint = roundC(heatingSetpoint) - coolingSetpoint = roundC(coolingSetpoint) + if(location.temperatureScale == "C") { + maxHeatingSetpoint = maxHeatingSetpoint > 40 ? roundC(convertFtoC(maxHeatingSetpoint)) : roundC(maxHeatingSetpoint) + maxCoolingSetpoint = maxCoolingSetpoint > 40 ? roundC(convertFtoC(maxCoolingSetpoint)) : roundC(maxCoolingSetpoint) + minHeatingSetpoint = minHeatingSetpoint > 40 ? roundC(convertFtoC(minHeatingSetpoint)) : roundC(minHeatingSetpoint) + minCoolingSetpoint = minCoolingSetpoint > 40 ? roundC(convertFtoC(minCoolingSetpoint)) : roundC(minCoolingSetpoint) + heatingSetpoint = heatingSetpoint > 40 ? roundC(convertFtoC(heatingSetpoint)) : roundC(heatingSetpoint) + coolingSetpoint = coolingSetpoint > 40 ? roundC(convertFtoC(coolingSetpoint)) : roundC(coolingSetpoint) + } else { + maxHeatingSetpoint = maxHeatingSetpoint < 40 ? roundC(convertCtoF(maxHeatingSetpoint)) : maxHeatingSetpoint + maxCoolingSetpoint = maxCoolingSetpoint < 40 ? roundC(convertCtoF(maxCoolingSetpoint)) : maxCoolingSetpoint + minHeatingSetpoint = minHeatingSetpoint < 40 ? roundC(convertCtoF(minHeatingSetpoint)) : minHeatingSetpoint + minCoolingSetpoint = minCoolingSetpoint < 40 ? roundC(convertCtoF(minCoolingSetpoint)) : minCoolingSetpoint + heatingSetpoint = heatingSetpoint < 40 ? roundC(convertCtoF(heatingSetpoint)) : heatingSetpoint + coolingSetpoint = coolingSetpoint < 40 ? roundC(convertCtoF(coolingSetpoint)) : coolingSetpoint } + log.debug "Current Mode = ${mode}" + log.debug "Heating Setpoint = ${heatingSetpoint}" + log.debug "Cooling Setpoint = ${coolingSetpoint}" sendEvent("name":"maxHeatingSetpoint", "value":maxHeatingSetpoint, "unit":location.temperatureScale) sendEvent("name":"maxCoolingSetpoint", "value":maxCoolingSetpoint, "unit":location.temperatureScale) sendEvent("name":"minHeatingSetpoint", "value":minHeatingSetpoint, "unit":location.temperatureScale) sendEvent("name":"minCoolingSetpoint", "value":minCoolingSetpoint, "unit":location.temperatureScale) - + sendEvent("name":"heatingSetpoint", "value":heatingSetpoint, "unit":location.temperatureScale) + sendEvent("name":"coolingSetpoint", "value":coolingSetpoint, "unit":location.temperatureScale) if (mode == "heat") { - sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint, "unit":location.temperatureScale) - } else if (mode == "cool") { - sendEvent("name":"thermostatSetpoint", "value":coolingSetpoint, "unit":location.temperatureScale) - } else if (mode == "auto") { - sendEvent("name":"thermostatSetpoint", "value":"Auto") - } else if (mode == "off") { - sendEvent("name":"thermostatSetpoint", "value":"Off") - } else if (mode == "auxHeatOnly") { - sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint, "unit":location.temperatureScale) - } - } void raiseSetpoint() { @@ -585,21 +573,31 @@ void raiseSetpoint() { def maxHeatingSetpoint = device.currentValue("maxHeatingSetpoint") def maxCoolingSetpoint = device.currentValue("maxCoolingSetpoint") - if (mode == "off" || mode == "auto") { log.warn "this mode: $mode does not allow raiseSetpoint" } else { + def heatingSetpoint = device.currentValue("heatingSetpoint") def coolingSetpoint = device.currentValue("coolingSetpoint") def thermostatSetpoint = device.currentValue("thermostatSetpoint") + + if (location.temperatureScale == "C") { + maxHeatingSetpoint = maxHeatingSetpoint > 40 ? convertFtoC(maxHeatingSetpoint) : maxHeatingSetpoint + maxCoolingSetpoint = maxCoolingSetpoint > 40 ? convertFtoC(maxCoolingSetpoint) : maxCoolingSetpoint + heatingSetpoint = heatingSetpoint > 40 ? convertFtoC(heatingSetpoint) : heatingSetpoint + coolingSetpoint = coolingSetpoint > 40 ? convertFtoC(coolingSetpoint) : coolingSetpoint + thermostatSetpoint = thermostatSetpoint > 40 ? convertFtoC(thermostatSetpoint) : thermostatSetpoint + } else { + maxHeatingSetpoint = maxHeatingSetpoint < 40 ? convertCtoF(maxHeatingSetpoint) : maxHeatingSetpoint + maxCoolingSetpoint = maxCoolingSetpoint < 40 ? convertCtoF(maxCoolingSetpoint) : maxCoolingSetpoint + heatingSetpoint = heatingSetpoint < 40 ? convertCtoF(heatingSetpoint) : heatingSetpoint + coolingSetpoint = coolingSetpoint < 40 ? convertCtoF(coolingSetpoint) : coolingSetpoint + thermostatSetpoint = thermostatSetpoint < 40 ? convertCtoF(thermostatSetpoint) : thermostatSetpoint + } + log.debug "raiseSetpoint() mode = ${mode}, heatingSetpoint: ${heatingSetpoint}, coolingSetpoint:${coolingSetpoint}, thermostatSetpoint:${thermostatSetpoint}" - if (device.latestState('thermostatSetpoint')) { - targetvalue = device.latestState('thermostatSetpoint').value - targetvalue = location.temperatureScale == "F"? targetvalue.toInteger() : targetvalue.toDouble() - } else { - targetvalue = 0 - } + targetvalue = thermostatSetpoint ? thermostatSetpoint : 0 targetvalue = location.temperatureScale == "F"? targetvalue + 1 : targetvalue + 0.5 if ((mode == "heat" || mode == "auxHeatOnly") && targetvalue > maxHeatingSetpoint) { @@ -622,20 +620,29 @@ void lowerSetpoint() { def minHeatingSetpoint = device.currentValue("minHeatingSetpoint") def minCoolingSetpoint = device.currentValue("minCoolingSetpoint") - if (mode == "off" || mode == "auto") { log.warn "this mode: $mode does not allow lowerSetpoint" } else { def heatingSetpoint = device.currentValue("heatingSetpoint") def coolingSetpoint = device.currentValue("coolingSetpoint") def thermostatSetpoint = device.currentValue("thermostatSetpoint") - log.debug "lowerSetpoint() mode = ${mode}, heatingSetpoint: ${heatingSetpoint}, coolingSetpoint:${coolingSetpoint}, thermostatSetpoint:${thermostatSetpoint}" - if (device.latestState('thermostatSetpoint')) { - targetvalue = device.latestState('thermostatSetpoint').value - targetvalue = location.temperatureScale == "F"? targetvalue.toInteger() : targetvalue.toDouble() + + if (location.temperatureScale == "C") { + minHeatingSetpoint = minHeatingSetpoint > 40 ? convertFtoC(minHeatingSetpoint) : minHeatingSetpoint + minCoolingSetpoint = minCoolingSetpoint > 40 ? convertFtoC(minCoolingSetpoint) : minCoolingSetpoint + heatingSetpoint = heatingSetpoint > 40 ? convertFtoC(heatingSetpoint) : heatingSetpoint + coolingSetpoint = coolingSetpoint > 40 ? convertFtoC(coolingSetpoint) : coolingSetpoint + thermostatSetpoint = thermostatSetpoint > 40 ? convertFtoC(thermostatSetpoint) : thermostatSetpoint } else { - targetvalue = 0 + minHeatingSetpoint = minHeatingSetpoint < 40 ? convertCtoF(minHeatingSetpoint) : minHeatingSetpoint + minCoolingSetpoint = minCoolingSetpoint < 40 ? convertCtoF(minCoolingSetpoint) : minCoolingSetpoint + heatingSetpoint = heatingSetpoint < 40 ? convertCtoF(heatingSetpoint) : heatingSetpoint + coolingSetpoint = coolingSetpoint < 40 ? convertCtoF(coolingSetpoint) : coolingSetpoint + thermostatSetpoint = thermostatSetpoint < 40 ? convertCtoF(thermostatSetpoint) : thermostatSetpoint } + log.debug "lowerSetpoint() mode = ${mode}, heatingSetpoint: ${heatingSetpoint}, coolingSetpoint:${coolingSetpoint}, thermostatSetpoint:${thermostatSetpoint}" + + targetvalue = thermostatSetpoint ? thermostatSetpoint : 0 targetvalue = location.temperatureScale == "F"? targetvalue - 1 : targetvalue - 0.5 if ((mode == "heat" || mode == "auxHeatOnly") && targetvalue < minHeatingSetpoint) { @@ -653,7 +660,6 @@ void lowerSetpoint() { //called by raiseSetpoint() and lowerSetpoint() void alterSetpoint(temp) { - def mode = device.currentValue("thermostatMode") if (mode == "off" || mode == "auto") { @@ -666,6 +672,18 @@ void alterSetpoint(temp) { def targetHeatingSetpoint def targetCoolingSetpoint + def temperatureScaleHasChanged = false + + if (location.temperatureScale == "C") { + if ( heatingSetpoint > 40.0 || coolingSetpoint > 40.0 ) { + temperatureScaleHasChanged = true + } + } else { + if ( heatingSetpoint < 40.0 || coolingSetpoint < 40.0 ) { + temperatureScaleHasChanged = true + } + } + //step1: check thermostatMode, enforce limits before sending request to cloud if (mode == "heat" || mode == "auxHeatOnly"){ if (temp.value > coolingSetpoint){ @@ -707,17 +725,18 @@ void alterSetpoint(temp) { sendEvent("name": "thermostatSetpoint", "value": coolingSetpoint.toString(), displayed: false) } } + + if ( temperatureScaleHasChanged ) + generateSetpointEvent() generateStatusEvent() } } def generateStatusEvent() { - def mode = device.currentValue("thermostatMode") def heatingSetpoint = device.currentValue("heatingSetpoint") def coolingSetpoint = device.currentValue("coolingSetpoint") def temperature = device.currentValue("temperature") - def statusText log.debug "Generate Status Event for Mode = ${mode}" @@ -727,36 +746,25 @@ def generateStatusEvent() { log.debug "HVAC Mode = ${mode}" if (mode == "heat") { - if (temperature >= heatingSetpoint) statusText = "Right Now: Idle" else statusText = "Heating to ${heatingSetpoint} ${location.temperatureScale}" - } else if (mode == "cool") { - if (temperature <= coolingSetpoint) statusText = "Right Now: Idle" else statusText = "Cooling to ${coolingSetpoint} ${location.temperatureScale}" - } else if (mode == "auto") { - statusText = "Right Now: Auto" - } else if (mode == "off") { - statusText = "Right Now: Off" - } else if (mode == "auxHeatOnly") { - statusText = "Emergency Heat" - } else { - statusText = "?" - } + log.debug "Generate Status Event = ${statusText}" sendEvent("name":"thermostatStatus", "value":statusText, "description":statusText, displayed: true) } @@ -770,7 +778,7 @@ def roundC (tempC) { } def convertFtoC (tempF) { - return String.format("%.1f", (Math.round(((tempF - 32)*(5/9)) * 2))/2) + return ((Math.round(((tempF - 32)*(5/9)) * 2))/2).toDouble() } def convertCtoF (tempC) { diff --git a/devicetypes/smartthings/hue-bloom.src/hue-bloom.groovy b/devicetypes/smartthings/hue-bloom.src/hue-bloom.groovy index e6a02ab..ae48f0d 100644 --- a/devicetypes/smartthings/hue-bloom.src/hue-bloom.groovy +++ b/devicetypes/smartthings/hue-bloom.src/hue-bloom.groovy @@ -57,7 +57,7 @@ metadata { } void installed() { - sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan", hubHardwareId: device.hub.hardwareID], displayed: false) + sendEvent(name: "DeviceWatch-Enroll", value: "{\"protocol\": \"LAN\", \"scheme\":\"untracked\", \"hubHardwareId\": \"${device.hub.hardwareID}\"}") } // parse events into attributes @@ -172,6 +172,3 @@ def verifyPercent(percent) { } } -def ping() { - log.debug "${parent.ping(this)}" -} diff --git a/devicetypes/smartthings/hue-bridge.src/hue-bridge.groovy b/devicetypes/smartthings/hue-bridge.src/hue-bridge.groovy index fee818f..e7fbce1 100644 --- a/devicetypes/smartthings/hue-bridge.src/hue-bridge.groovy +++ b/devicetypes/smartthings/hue-bridge.src/hue-bridge.groovy @@ -45,7 +45,7 @@ metadata { } void installed() { - sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan"], displayed: false) + sendEvent(name: "DeviceWatch-Enroll", value: "{\"protocol\": \"LAN\", \"scheme\":\"untracked\", \"hubHardwareId\": \"${device.hub.hardwareID}\"}") } // parse events into attributes @@ -87,6 +87,3 @@ def parse(description) { results } -def ping() { - log.debug "${parent.ping(this)}" -} diff --git a/devicetypes/smartthings/hue-bulb.src/hue-bulb.groovy b/devicetypes/smartthings/hue-bulb.src/hue-bulb.groovy index 4dbae74..143b0b3 100644 --- a/devicetypes/smartthings/hue-bulb.src/hue-bulb.groovy +++ b/devicetypes/smartthings/hue-bulb.src/hue-bulb.groovy @@ -66,7 +66,7 @@ metadata { } void installed() { - sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan", hubHardwareId: device.hub.hardwareID], displayed: false) + sendEvent(name: "DeviceWatch-Enroll", value: "{\"protocol\": \"LAN\", \"scheme\":\"untracked\", \"hubHardwareId\": \"${device.hub.hardwareID}\"}") } // parse events into attributes @@ -188,6 +188,3 @@ def verifyPercent(percent) { } } -def ping() { - log.trace "${parent.ping(this)}" -} diff --git a/devicetypes/smartthings/hue-lux-bulb.src/hue-lux-bulb.groovy b/devicetypes/smartthings/hue-lux-bulb.src/hue-lux-bulb.groovy index 158e11a..d855d38 100644 --- a/devicetypes/smartthings/hue-lux-bulb.src/hue-lux-bulb.groovy +++ b/devicetypes/smartthings/hue-lux-bulb.src/hue-lux-bulb.groovy @@ -50,7 +50,7 @@ metadata { } void installed() { - sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan", hubHardwareId: device.hub.hardwareID], displayed: false) + sendEvent(name: "DeviceWatch-Enroll", value: "{\"protocol\": \"LAN\", \"scheme\":\"untracked\", \"hubHardwareId\": \"${device.hub.hardwareID}\"}") } // parse events into attributes @@ -93,6 +93,3 @@ void refresh() { parent.manualRefresh() } -def ping() { - log.debug "${parent.ping(this)}" -} diff --git a/devicetypes/smartthings/hue-white-ambiance-bulb.src/hue-white-ambiance-bulb.groovy b/devicetypes/smartthings/hue-white-ambiance-bulb.src/hue-white-ambiance-bulb.groovy index 10315a5..718ed57 100644 --- a/devicetypes/smartthings/hue-white-ambiance-bulb.src/hue-white-ambiance-bulb.groovy +++ b/devicetypes/smartthings/hue-white-ambiance-bulb.src/hue-white-ambiance-bulb.groovy @@ -55,7 +55,7 @@ metadata { } void installed() { - sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan", hubHardwareId: device.hub.hardwareID], displayed: false) + sendEvent(name: "DeviceWatch-Enroll", value: "{\"protocol\": \"LAN\", \"scheme\":\"untracked\", \"hubHardwareId\": \"${device.hub.hardwareID}\"}") } // parse events into attributes @@ -107,6 +107,3 @@ void refresh() { parent.manualRefresh() } -def ping() { - log.debug "${parent.ping(this)}" -} diff --git a/devicetypes/smartthings/lifx-color-bulb.src/lifx-color-bulb.groovy b/devicetypes/smartthings/lifx-color-bulb.src/lifx-color-bulb.groovy index b93235e..d98e8d7 100644 --- a/devicetypes/smartthings/lifx-color-bulb.src/lifx-color-bulb.groovy +++ b/devicetypes/smartthings/lifx-color-bulb.src/lifx-color-bulb.groovy @@ -141,7 +141,6 @@ def setLevel(percentage) { percentage = 1 // clamp to 1% } if (percentage == 0) { - sendEvent(name: "level", value: 0) // Otherwise the level value tile does not update return off() // if the brightness is set to 0, just turn it off } parent.logErrors(logObject:log) { diff --git a/devicetypes/smartthings/lifx-white-bulb.src/lifx-white-bulb.groovy b/devicetypes/smartthings/lifx-white-bulb.src/lifx-white-bulb.groovy index 54a78df..d089f77 100644 --- a/devicetypes/smartthings/lifx-white-bulb.src/lifx-white-bulb.groovy +++ b/devicetypes/smartthings/lifx-white-bulb.src/lifx-white-bulb.groovy @@ -71,7 +71,6 @@ def setLevel(percentage) { percentage = 1 // clamp to 1% } if (percentage == 0) { - sendEvent(name: "level", value: 0) // Otherwise the level value tile does not update return off() // if the brightness is set to 0, just turn it off } parent.logErrors(logObject:log) { @@ -143,7 +142,7 @@ def poll() { sendEvent(name: "model", value: data.product.name) return [] -} + } def refresh() { log.debug "Executing 'refresh'" diff --git a/devicetypes/smartthings/osram-lightify-gardenspot-mini-rgb.src/osram-lightify-gardenspot-mini-rgb.groovy b/devicetypes/smartthings/osram-lightify-gardenspot-mini-rgb.src/osram-lightify-gardenspot-mini-rgb.groovy index 2dc1bf6..a5c98ac 100644 --- a/devicetypes/smartthings/osram-lightify-gardenspot-mini-rgb.src/osram-lightify-gardenspot-mini-rgb.groovy +++ b/devicetypes/smartthings/osram-lightify-gardenspot-mini-rgb.src/osram-lightify-gardenspot-mini-rgb.groovy @@ -21,9 +21,6 @@ metadata { attribute "colorName", "string" 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 diff --git a/devicetypes/smartthings/smartpower-dimming-outlet.src/smartpower-dimming-outlet.groovy b/devicetypes/smartthings/smartpower-dimming-outlet.src/smartpower-dimming-outlet.groovy index 51bde7b..27e9734 100644 --- a/devicetypes/smartthings/smartpower-dimming-outlet.src/smartpower-dimming-outlet.groovy +++ b/devicetypes/smartthings/smartpower-dimming-outlet.src/smartpower-dimming-outlet.groovy @@ -133,7 +133,7 @@ def refresh() { } def configure() { - onOffConfig() + levelConfig() + powerConfig() + refresh() + refresh() + onOffConfig() + levelConfig() + powerConfig() } diff --git a/devicetypes/smartthings/smartpower-outlet.src/smartpower-outlet.groovy b/devicetypes/smartthings/smartpower-outlet.src/smartpower-outlet.groovy index 2a46720..d96dd77 100644 --- a/devicetypes/smartthings/smartpower-outlet.src/smartpower-outlet.groovy +++ b/devicetypes/smartthings/smartpower-outlet.src/smartpower-outlet.groovy @@ -146,7 +146,7 @@ def configure() { 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) + powerConfig() + refresh() + refresh() + zigbee.onOffConfig(0, 300) + powerConfig() } //power config for devices with min reporting interval as 1 seconds and reporting interval if no activity as 10min (600s) diff --git a/devicetypes/smartthings/smartsense-moisture-sensor.src/smartsense-moisture-sensor.groovy b/devicetypes/smartthings/smartsense-moisture-sensor.src/smartsense-moisture-sensor.groovy index 22a8a61..f214cbd 100644 --- a/devicetypes/smartthings/smartsense-moisture-sensor.src/smartsense-moisture-sensor.groovy +++ b/devicetypes/smartthings/smartsense-moisture-sensor.src/smartsense-moisture-sensor.groovy @@ -308,16 +308,9 @@ def configure() { // enrolls with default periodic reporting until newer 5 min interval is confirmed 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 // battery minReport 30 seconds, maxReportTime 6 hrs by default - return enrollCmds + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) + refresh() // send refresh cmds as part of config + return refresh() + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) // send refresh cmds as part of config } def enrollResponse() { diff --git a/devicetypes/smartthings/smartsense-motion-sensor.src/smartsense-motion-sensor.groovy b/devicetypes/smartthings/smartsense-motion-sensor.src/smartsense-motion-sensor.groovy index d885613..d02d41d 100644 --- a/devicetypes/smartthings/smartsense-motion-sensor.src/smartsense-motion-sensor.groovy +++ b/devicetypes/smartthings/smartsense-motion-sensor.src/smartsense-motion-sensor.groovy @@ -323,16 +323,9 @@ def configure() { // enrolls with default periodic reporting until newer 5 min interval is confirmed 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 // battery minReport 30 seconds, maxReportTime 6 hrs by default - return enrollCmds + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) + refresh() // send refresh cmds as part of config + return refresh() + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) // send refresh cmds as part of config } def enrollResponse() { diff --git a/devicetypes/smartthings/smartsense-motion-temp-sensor.src/smartsense-motion-temp-sensor.groovy b/devicetypes/smartthings/smartsense-motion-temp-sensor.src/smartsense-motion-temp-sensor.groovy index dd0d801..6146673 100644 --- a/devicetypes/smartthings/smartsense-motion-temp-sensor.src/smartsense-motion-temp-sensor.groovy +++ b/devicetypes/smartthings/smartsense-motion-temp-sensor.src/smartsense-motion-temp-sensor.groovy @@ -255,13 +255,9 @@ def refresh() { } def configure() { - String zigbeeEui = swapEndianHex(device.hub.zigbeeEui) log.debug "Configuring Reporting, IAS CIE, and Bindings." 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", "zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", //checkin time 6 hrs "send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500", @@ -270,7 +266,7 @@ def configure() { "zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}", "send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500" ] - return configCmds + refresh() // send refresh cmds as part of config + return refresh() + configCmds // send refresh cmds as part of config } def enrollResponse() { diff --git a/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy b/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy index 9240ec5..94104b9 100644 --- a/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy +++ b/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy @@ -420,15 +420,14 @@ def configure() { // temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity // battery minReport 30 seconds, maxReportTime 6 hrs by default - def configCmds = enrollResponse() + - zigbee.batteryConfig() + + def configCmds = zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) + zigbee.configureReporting(0xFC02, 0x0010, 0x18, 10, 3600, 0x01, [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, 0x0014, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode]) - return configCmds + refresh() + return refresh() + configCmds } private getEndpointId() { diff --git a/devicetypes/smartthings/smartsense-open-closed-accelerometer-sensor.src/smartsense-open-closed-accelerometer-sensor.groovy b/devicetypes/smartthings/smartsense-open-closed-accelerometer-sensor.src/smartsense-open-closed-accelerometer-sensor.groovy index a5b0b6f..9af4f2d 100644 --- a/devicetypes/smartthings/smartsense-open-closed-accelerometer-sensor.src/smartsense-open-closed-accelerometer-sensor.groovy +++ b/devicetypes/smartthings/smartsense-open-closed-accelerometer-sensor.src/smartsense-open-closed-accelerometer-sensor.groovy @@ -277,12 +277,8 @@ def getTemperature(value) { def configure() { sendEvent(name: "checkInterval", value: 7200, displayed: false) - String zigbeeEui = swapEndianHex(device.hub.zigbeeEui) log.debug "Configuring Reporting, IAS CIE, and Bindings." 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", "zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", //checkin time 6 hrs "send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500", @@ -295,7 +291,7 @@ def configure() { "zcl global send-me-a-report 0xFC02 2 0x18 30 3600 {01}", "send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500" ] - return configCmds + refresh() // send refresh cmds as part of config + return refresh() + configCmds // send refresh cmds as part of config } def enrollResponse() { diff --git a/devicetypes/smartthings/smartsense-open-closed-sensor.src/smartsense-open-closed-sensor.groovy b/devicetypes/smartthings/smartsense-open-closed-sensor.src/smartsense-open-closed-sensor.groovy index bda54e6..7d411f5 100644 --- a/devicetypes/smartthings/smartsense-open-closed-sensor.src/smartsense-open-closed-sensor.groovy +++ b/devicetypes/smartthings/smartsense-open-closed-sensor.src/smartsense-open-closed-sensor.groovy @@ -270,16 +270,11 @@ def configure() { // enrolls with default periodic reporting until newer 5 min interval is confirmed 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 // battery minReport 30 seconds, maxReportTime 6 hrs by default - return enrollCmds + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) + refresh() // send refresh cmds as part of config + return refresh() + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) // send refresh cmds as part of config } def enrollResponse() { diff --git a/devicetypes/smartthings/smartsense-temp-humidity-sensor.src/smartsense-temp-humidity-sensor.groovy b/devicetypes/smartthings/smartsense-temp-humidity-sensor.src/smartsense-temp-humidity-sensor.groovy index ea94edb..98af1a4 100644 --- a/devicetypes/smartthings/smartsense-temp-humidity-sensor.src/smartsense-temp-humidity-sensor.groovy +++ b/devicetypes/smartthings/smartsense-temp-humidity-sensor.src/smartsense-temp-humidity-sensor.groovy @@ -250,11 +250,7 @@ private Map getTemperatureResult(value) { private Map getHumidityResult(value) { log.debug 'Humidity' - return [ - name: 'humidity', - value: value, - unit: '%' - ] + return value ? [name: 'humidity', value: value, unit: '%'] : [:] } /** @@ -267,15 +263,9 @@ def ping() { def refresh() { log.debug "refresh temperature, humidity, and battery" - [ - - "zcl mfg-code 0xC2DF", "delay 1000", - "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" - - ] + return zigbee.readAttribute(0xFC45, 0x0000, ["mfgCode": 0xC2DF]) + // Original firmware + zigbee.readAttribute(0x0402, 0x0000) + + zigbee.readAttribute(0x0001, 0x0020) } def configure() { @@ -292,7 +282,7 @@ def configure() { // temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity // battery minReport 30 seconds, maxReportTime 6 hrs by default - return humidityConfigCmds + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) + refresh() // send refresh cmds as part of config + return refresh() + humidityConfigCmds + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) // send refresh cmds as part of config } private hex(value) { diff --git a/devicetypes/smartthings/zigbee-button.src/zigbee-button.groovy b/devicetypes/smartthings/zigbee-button.src/zigbee-button.groovy index 12e06ea..6bd1858 100644 --- a/devicetypes/smartthings/zigbee-button.src/zigbee-button.groovy +++ b/devicetypes/smartthings/zigbee-button.src/zigbee-button.groovy @@ -89,14 +89,8 @@ def parse(String description) { } private Map parseIasButtonMessage(String description) { - int zoneInt = Integer.parseInt((description - "zone status 0x"), 16) - if (zoneInt & 0x02) { - resultMap = getButtonResult('press') - } else { - resultMap = getButtonResult('release') - } - - return resultMap + def zs = zigbee.parseZoneStatus(description) + return zs.isAlarm2Set() ? getButtonResult("press") : getButtonResult("release") } private Map getBatteryResult(rawValue) { diff --git a/devicetypes/smartthings/zigbee-dimmer-power.src/zigbee-dimmer-power.groovy b/devicetypes/smartthings/zigbee-dimmer-power.src/zigbee-dimmer-power.groovy index af8c227..6ffdf22 100644 --- a/devicetypes/smartthings/zigbee-dimmer-power.src/zigbee-dimmer-power.groovy +++ b/devicetypes/smartthings/zigbee-dimmer-power.src/zigbee-dimmer-power.groovy @@ -93,5 +93,5 @@ def refresh() { def configure() { log.debug "Configuring Reporting and Bindings." - zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.simpleMeteringPowerConfig() + zigbee.electricMeasurementPowerConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.simpleMeteringPowerRefresh() + zigbee.electricMeasurementPowerRefresh() + refresh() } diff --git a/devicetypes/smartthings/zigbee-dimmer.src/zigbee-dimmer.groovy b/devicetypes/smartthings/zigbee-dimmer.src/zigbee-dimmer.groovy index cb34038..ef17d94 100644 --- a/devicetypes/smartthings/zigbee-dimmer.src/zigbee-dimmer.groovy +++ b/devicetypes/smartthings/zigbee-dimmer.src/zigbee-dimmer.groovy @@ -107,5 +107,5 @@ def configure() { 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.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig(0, 300) + zigbee.levelConfig() } diff --git a/devicetypes/smartthings/zigbee-lock.src/zigbee-lock.groovy b/devicetypes/smartthings/zigbee-lock.src/zigbee-lock.groovy index b4edfd1..41de4e8 100644 --- a/devicetypes/smartthings/zigbee-lock.src/zigbee-lock.groovy +++ b/devicetypes/smartthings/zigbee-lock.src/zigbee-lock.groovy @@ -90,7 +90,7 @@ def configure() { zigbee.configureReporting(CLUSTER_POWER, POWER_ATTR_BATTERY_PERCENTAGE_REMAINING, TYPE_U8, 600, 21600, 0x01) log.info "configure() --- cmds: $cmds" - return cmds + refresh() // send refresh cmds as part of config + return refresh() + cmds // send refresh cmds as part of config } def refresh() { diff --git a/devicetypes/smartthings/zigbee-rgb-bulb.src/zigbee-rgb-bulb.groovy b/devicetypes/smartthings/zigbee-rgb-bulb.src/zigbee-rgb-bulb.groovy new file mode 100644 index 0000000..9bb7f20 --- /dev/null +++ b/devicetypes/smartthings/zigbee-rgb-bulb.src/zigbee-rgb-bulb.groovy @@ -0,0 +1,154 @@ +/** + * 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", deviceJoinName: "OSRAM LIGHTIFY Gardenspot mini RGB" + fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY Gardenspot RGB", deviceJoinName: "OSRAM LIGHTIFY Gardenspot mini 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) +} \ No newline at end of file diff --git a/devicetypes/smartthings/zigbee-rgbw-bulb.src/zigbee-rgbw-bulb.groovy b/devicetypes/smartthings/zigbee-rgbw-bulb.src/zigbee-rgbw-bulb.groovy index f0a15ad..aa4defb 100644 --- a/devicetypes/smartthings/zigbee-rgbw-bulb.src/zigbee-rgbw-bulb.groovy +++ b/devicetypes/smartthings/zigbee-rgbw-bulb.src/zigbee-rgbw-bulb.groovy @@ -138,7 +138,7 @@ def ping() { } def refresh() { - zigbee.onOffRefresh() + zigbee.readAttribute(0x0008, 0x00) + zigbee.readAttribute(0x0300, 0x00) + zigbee.readAttribute(0x0300, ATTRIBUTE_COLOR_TEMPERATURE) + zigbee.readAttribute(0x0300, ATTRIBUTE_HUE) + zigbee.readAttribute(0x0300, ATTRIBUTE_SATURATION) + zigbee.onOffConfig(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.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) } def configure() { @@ -148,7 +148,7 @@ def configure() { 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.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) + refresh() } def setColorTemperature(value) { diff --git a/devicetypes/smartthings/zigbee-switch-power.src/zigbee-switch-power.groovy b/devicetypes/smartthings/zigbee-switch-power.src/zigbee-switch-power.groovy index a5eb0ac..bb5f754 100644 --- a/devicetypes/smartthings/zigbee-switch-power.src/zigbee-switch-power.groovy +++ b/devicetypes/smartthings/zigbee-switch-power.src/zigbee-switch-power.groovy @@ -82,5 +82,5 @@ def refresh() { def configure() { log.debug "Configuring Reporting and Bindings." - zigbee.onOffConfig() + zigbee.simpleMeteringPowerConfig() + zigbee.electricMeasurementPowerConfig() + zigbee.onOffRefresh() + zigbee.simpleMeteringPowerRefresh() + zigbee.electricMeasurementPowerRefresh() + refresh() } diff --git a/devicetypes/smartthings/zigbee-switch.src/zigbee-switch.groovy b/devicetypes/smartthings/zigbee-switch.src/zigbee-switch.groovy index 7e243f4..fb1685b 100644 --- a/devicetypes/smartthings/zigbee-switch.src/zigbee-switch.groovy +++ b/devicetypes/smartthings/zigbee-switch.src/zigbee-switch.groovy @@ -78,5 +78,5 @@ def refresh() { def configure() { log.debug "Configuring Reporting and Bindings." - zigbee.onOffConfig() + zigbee.onOffRefresh() + zigbee.onOffRefresh() + zigbee.onOffConfig() } diff --git a/devicetypes/smartthings/zigbee-valve.src/zigbee-valve.groovy b/devicetypes/smartthings/zigbee-valve.src/zigbee-valve.groovy index 3d0e1e4..4b66fc6 100644 --- a/devicetypes/smartthings/zigbee-valve.src/zigbee-valve.groovy +++ b/devicetypes/smartthings/zigbee-valve.src/zigbee-valve.groovy @@ -134,10 +134,5 @@ def refresh() { def configure() { log.debug "Configuring Reporting and Bindings." - 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) + refresh() } diff --git a/devicetypes/smartthings/zigbee-white-color-temperature-bulb.src/zigbee-white-color-temperature-bulb.groovy b/devicetypes/smartthings/zigbee-white-color-temperature-bulb.src/zigbee-white-color-temperature-bulb.groovy index 01b6938..15a83f6 100644 --- a/devicetypes/smartthings/zigbee-white-color-temperature-bulb.src/zigbee-white-color-temperature-bulb.groovy +++ b/devicetypes/smartthings/zigbee-white-color-temperature-bulb.src/zigbee-white-color-temperature-bulb.groovy @@ -131,7 +131,7 @@ def configure() { 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.colorTemperatureConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh() + refresh() } def setColorTemperature(value) { diff --git a/devicetypes/smartthings/zll-rgb-bulb.src/zll-rgb-bulb.groovy b/devicetypes/smartthings/zll-rgb-bulb.src/zll-rgb-bulb.groovy new file mode 100644 index 0000000..5988334 --- /dev/null +++ b/devicetypes/smartthings/zll-rgb-bulb.src/zll-rgb-bulb.groovy @@ -0,0 +1,134 @@ +/** + * 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 +} diff --git a/devicetypes/smartthings/zll-rgbw-bulb.src/zll-rgbw-bulb.groovy b/devicetypes/smartthings/zll-rgbw-bulb.src/zll-rgbw-bulb.groovy index 9d9be84..e02d48d 100644 --- a/devicetypes/smartthings/zll-rgbw-bulb.src/zll-rgbw-bulb.groovy +++ b/devicetypes/smartthings/zll-rgbw-bulb.src/zll-rgbw-bulb.groovy @@ -123,7 +123,7 @@ def configureAttributes() { } def refreshAttributes() { - zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh() + zigbee.readAttribute(0x0300, 0x00) + zigbee.readAttribute(0x0300, ATTRIBUTE_HUE) + zigbee.readAttribute(0x0300, ATTRIBUTE_SATURATION) + zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh() + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION) } def setColorTemperature(value) { @@ -141,10 +141,10 @@ def setColor(value){ 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) + 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") //payload-> sat value, transition time + zigbee.command(COLOR_CONTROL_CLUSTER, SATURATION_COMMAND, scaledSatValue, "0500") + ["delay 1500"] + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION) //payload-> sat value, transition time } diff --git a/smartapps/smartthings/hue-connect.src/hue-connect.groovy b/smartapps/smartthings/hue-connect.src/hue-connect.groovy index d39d451..621d0d0 100644 --- a/smartapps/smartthings/hue-connect.src/hue-connect.groovy +++ b/smartapps/smartthings/hue-connect.src/hue-connect.groovy @@ -8,7 +8,7 @@ * 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 + * 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 @@ -24,7 +24,7 @@ definition( category: "SmartThings Labs", iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/hue.png", iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/hue@2x.png", - singleInstance: true + singleInstance: true ) preferences { @@ -110,7 +110,7 @@ def bridgeLinking() { if (state.refreshUsernameNeeded) { paragraphText = "The current Hue username is invalid.\n\nPlease press the button on your Hue Bridge to re-link. " } else { - paragraphText = "Press the button on your Hue Bridge to setup a link. " + paragraphText = "Press the button on your Hue Bridge to setup a link. " } } else { paragraphText = "You haven't selected a Hue Bridge, please Press \"Done\" and select one before clicking next." @@ -198,7 +198,7 @@ void ssdpSubscribe() { private sendDeveloperReq() { def token = app.id - def host = getBridgeIP() + def host = getBridgeIP() sendHubCommand(new physicalgraph.device.HubAction([ method: "POST", path: "/api", @@ -209,7 +209,7 @@ private sendDeveloperReq() { } private discoverHueBulbs() { - def host = getBridgeIP() + def host = getBridgeIP() sendHubCommand(new physicalgraph.device.HubAction([ method: "GET", path: "/api/${state.username}/lights", @@ -231,8 +231,8 @@ private verifyHueBridge(String deviceNetworkId, String host) { private verifyHueBridges() { def devices = getHueBridges().findAll { it?.value?.verified != true } devices.each { - def ip = convertHexToIP(it.value.networkAddress) - def port = convertHexToInt(it.value.deviceAddress) + def ip = convertHexToIP(it.value.networkAddress) + def port = convertHexToInt(it.value.deviceAddress) verifyHueBridge("${it.value.mac}", (ip + ":" + port)) } } @@ -261,7 +261,7 @@ Map bulbsDiscovered() { bulbs.each { def value = "${it.name}" def key = app.id +"/"+ it.id - logg += "$value - $key, " + logg += "$value - $key, " bulbmap["${key}"] = value } } @@ -288,22 +288,23 @@ def installed() { def updated() { log.trace "Updated with settings: ${settings}" unsubscribe() - unschedule() + unschedule() initialize() } def initialize() { log.debug "Initializing" - unsubscribe(bridge) - state.inBulbDiscovery = false - state.bridgeRefreshCount = 0 - state.bulbRefreshCount = 0 + unsubscribe(bridge) + state.inBulbDiscovery = false + state.bridgeRefreshCount = 0 + state.bulbRefreshCount = 0 state.updating = false + setupDeviceWatch() if (selectedHue) { - addBridge() - addBulbs() - doDeviceSync() - runEvery5Minutes("doDeviceSync") + addBridge() + addBulbs() + doDeviceSync() + runEvery5Minutes("doDeviceSync") } } @@ -321,6 +322,14 @@ def uninstalled(){ state.username = null } +private setupDeviceWatch() { + def hub = location.hubs[0] + // Make sure that all child devices are enrolled in device watch + getChildDevices().each { + it.sendEvent(name: "DeviceWatch-Enroll", value: "{\"protocol\": \"LAN\", \"scheme\":\"untracked\", \"hubHardwareId\": \"${hub?.hub?.hardwareID}\"}") + } +} + private upgradeDeviceType(device, newHueType) { def deviceType = getDeviceType(newHueType) @@ -369,7 +378,6 @@ def addBulbs() { if (d) { log.debug "created ${d.displayName} with id $dni" d.completedSetup = true - d.refresh() } } else { log.debug "$dni in not longer paired to the Hue Bridge or ID changed" @@ -399,23 +407,23 @@ def addBridge() { if(vbridge) { def d = getChildDevice(selectedHue) if(!d) { - // compatibility with old devices - def newbridge = true - childDevices.each { - if (it.getDeviceDataByName("mac")) { - def newDNI = "${it.getDeviceDataByName("mac")}" - if (newDNI != it.deviceNetworkId) { - def oldDNI = it.deviceNetworkId - log.debug "updating dni for device ${it} with $newDNI - previous DNI = ${it.deviceNetworkId}" - it.setDeviceNetworkId("${newDNI}") + // compatibility with old devices + def newbridge = true + childDevices.each { + if (it.getDeviceDataByName("mac")) { + def newDNI = "${it.getDeviceDataByName("mac")}" + if (newDNI != it.deviceNetworkId) { + def oldDNI = it.deviceNetworkId + log.debug "updating dni for device ${it} with $newDNI - previous DNI = ${it.deviceNetworkId}" + it.setDeviceNetworkId("${newDNI}") if (oldDNI == selectedHue) { app.updateSetting("selectedHue", newDNI) } - newbridge = false - } - } - } - if (newbridge) { + newbridge = false + } + } + } + if (newbridge) { // Hue uses last 6 digits of MAC address as ID number, this number is shown on the bottom of the bridge def idNumber = getBridgeIdNumber(selectedHue) d = addChildDevice("smartthings", "Hue Bridge", selectedHue, vbridge.value.hub, ["label": "Hue Bridge ($idNumber)"]) @@ -426,9 +434,11 @@ def addBridge() { d.completedSetup = true log.debug "created ${d.displayName} with id ${d.deviceNetworkId}" def childDevice = getChildDevice(d.deviceNetworkId) + childDevice?.sendEvent(name: "status", value: "Online") + childDevice?.sendEvent(name: "DeviceWatch-DeviceStatus", value: "online", displayed: false, isStateChange: true) updateBridgeStatus(childDevice) - childDevice.sendEvent(name: "idNumber", value: idNumber) + childDevice?.sendEvent(name: "idNumber", value: idNumber) if (vbridge.value.ip && vbridge.value.port) { if (vbridge.value.ip.contains(".")) { childDevice.sendEvent(name: "networkAddress", value: vbridge.value.ip + ":" + vbridge.value.port) @@ -580,47 +590,47 @@ void usernameHandler(physicalgraph.device.HubResponse hubResponse) { @Deprecated def locationHandler(evt) { def description = evt.description - log.trace "Location: $description" + log.trace "Location: $description" def hub = evt?.hubId def parsedEvent = parseLanMessage(description) parsedEvent << ["hub":hub] if (parsedEvent?.ssdpTerm?.contains("urn:schemas-upnp-org:device:basic:1")) { - //SSDP DISCOVERY EVENTS + //SSDP DISCOVERY EVENTS log.trace "SSDP DISCOVERY EVENTS" def bridges = getHueBridges() log.trace bridges.toString() if (!(bridges."${parsedEvent.ssdpUSN.toString()}")) { - //bridge does not exist + //bridge does not exist log.trace "Adding bridge ${parsedEvent.ssdpUSN}" bridges << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent] } else { // update the values - def ip = convertHexToIP(parsedEvent.networkAddress) - def port = convertHexToInt(parsedEvent.deviceAddress) - def host = ip + ":" + port + def ip = convertHexToIP(parsedEvent.networkAddress) + def port = convertHexToInt(parsedEvent.deviceAddress) + def host = ip + ":" + port log.debug "Device ($parsedEvent.mac) was already found in state with ip = $host." - def dstate = bridges."${parsedEvent.ssdpUSN.toString()}" + def dstate = bridges."${parsedEvent.ssdpUSN.toString()}" def dni = "${parsedEvent.mac}" - def d = getChildDevice(dni) - def networkAddress = null - if (!d) { - childDevices.each { - if (it.getDeviceDataByName("mac")) { - def newDNI = "${it.getDeviceDataByName("mac")}" + def d = getChildDevice(dni) + def networkAddress = null + if (!d) { + childDevices.each { + if (it.getDeviceDataByName("mac")) { + def newDNI = "${it.getDeviceDataByName("mac")}" d = it - if (newDNI != it.deviceNetworkId) { - def oldDNI = it.deviceNetworkId - log.debug "updating dni for device ${it} with $newDNI - previous DNI = ${it.deviceNetworkId}" - it.setDeviceNetworkId("${newDNI}") + if (newDNI != it.deviceNetworkId) { + def oldDNI = it.deviceNetworkId + log.debug "updating dni for device ${it} with $newDNI - previous DNI = ${it.deviceNetworkId}" + it.setDeviceNetworkId("${newDNI}") if (oldDNI == selectedHue) { app.updateSetting("selectedHue", newDNI) } - doDeviceSync() - } - } - } + doDeviceSync() + } + } + } } else { updateBridgeStatus(d) if (d.getDeviceDataByName("networkAddress")) { @@ -628,22 +638,22 @@ def locationHandler(evt) { } else { networkAddress = d.latestState('networkAddress').stringValue } - log.trace "Host: $host - $networkAddress" - if(host != networkAddress) { - log.debug "Device's port or ip changed for device $d..." - dstate.ip = ip - dstate.port = port - dstate.name = "Philips hue ($ip)" - d.sendEvent(name:"networkAddress", value: host) - d.updateDataValue("networkAddress", host) - } - } + log.trace "Host: $host - $networkAddress" + if(host != networkAddress) { + log.debug "Device's port or ip changed for device $d..." + dstate.ip = ip + dstate.port = port + dstate.name = "Philips hue ($ip)" + d.sendEvent(name:"networkAddress", value: host) + d.updateDataValue("networkAddress", host) + } + } } } else if (parsedEvent.headers && parsedEvent.body) { log.trace "HUE BRIDGE RESPONSES" def headerString = parsedEvent.headers.toString() if (headerString?.contains("xml")) { - log.trace "description.xml response (application/xml)" + log.trace "description.xml response (application/xml)" def body = new XmlSlurper().parseText(parsedEvent.body) if (body?.device?.modelName?.text().startsWith("Philips hue bridge")) { def bridges = getHueBridges() @@ -655,7 +665,7 @@ def locationHandler(evt) { } } } else if(headerString?.contains("json") && isValidSource(parsedEvent.mac)) { - log.trace "description.xml response (application/json)" + log.trace "description.xml response (application/json)" def body = new groovy.json.JsonSlurper().parseText(parsedEvent.body) if (body.success != null) { if (body.success[0] != null) { @@ -692,7 +702,7 @@ def doDeviceSync(){ poll() ssdpSubscribe() discoverBridges() - checkBridgeStatus() + checkBridgeStatus() } /** @@ -705,10 +715,14 @@ def doDeviceSync(){ private void updateBridgeStatus(childDevice) { // Update activity timestamp if child device is a valid bridge def vbridges = getVerifiedHueBridges() - def vbridge = vbridges.find {"${it.value.mac}".toUpperCase() == childDevice?.device?.deviceNetworkId?.toUpperCase()} + def vbridge = vbridges.find { + "${it.value.mac}".toUpperCase() == childDevice?.device?.deviceNetworkId?.toUpperCase() + } vbridge?.value?.lastActivity = now() - if(vbridge) { + if (vbridge && childDevice?.device?.currentValue("status") == "Offline") { + log.debug "$childDevice is back Online" childDevice?.sendEvent(name: "status", value: "Online") + childDevice?.sendEvent(name: "DeviceWatch-DeviceStatus", value: "online", displayed: false, isStateChange: true) } } @@ -717,29 +731,37 @@ private void updateBridgeStatus(childDevice) { * for the bridge and all connected lights. Also, set ID number on bridge if not done previously. */ private void checkBridgeStatus() { - def bridges = getHueBridges() - // Check if each bridge has been heard from within the last 11 minutes (2 poll intervals times 5 minutes plus buffer) - def time = now() - (1000 * 60 * 11) - bridges.each { - def d = getChildDevice(it.value.mac) - if(d) { - // Set id number on bridge if not done - if (it.value.idNumber == null) { - it.value.idNumber = getBridgeIdNumber(it.value.serialNumber) + def bridges = getHueBridges() + // Check if each bridge has been heard from within the last 11 minutes (2 poll intervals times 5 minutes plus buffer) + def time = now() - (1000 * 60 * 11) + bridges.each { + def d = getChildDevice(it.value.mac) + if (d) { + // Set id number on bridge if not done + if (it.value.idNumber == null) { + it.value.idNumber = getBridgeIdNumber(it.value.serialNumber) d.sendEvent(name: "idNumber", value: it.value.idNumber) - } + } if (it.value.lastActivity < time) { // it.value.lastActivity != null && - log.warn "Bridge $it.value.idNumber is Offline" - d.sendEvent(name: "status", value: "Offline") + if (d.currentStatus == "Online") { + log.warn "$d is Offline" + d.sendEvent(name: "status", value: "Offline") + d.sendEvent(name: "DeviceWatch-DeviceStatus", value: "offline", displayed: false, isStateChange: true) - state.bulbs?.each { - it.value.online = false + Calendar currentTime = Calendar.getInstance() + getChildDevices().each { + def id = getId(it) + if (state.bulbs[id]?.online == true) { + state.bulbs[id]?.online = false + state.bulbs[id]?.unreachableSince = currentTime.getTimeInMillis() + it.sendEvent(name: "DeviceWatch-DeviceStatus", value: "offline", displayed: false, isStateChange: true) + } + } } - getChildDevices().each { - it.sendEvent(name: "DeviceWatch-DeviceOffline", value: "offline", isStateChange: true, displayed: false) - } - } else { + } else if (d.currentStatus == "Offline") { + log.debug "$d is back Online" + d.sendEvent(name: "DeviceWatch-DeviceStatus", value: "online", displayed: false, isStateChange: true) d.sendEvent(name: "status", value: "Online")//setOnline(false) } } @@ -791,24 +813,24 @@ def parse(childDevice, description) { def parsedEvent = parseLanMessage(description) if (parsedEvent.headers && parsedEvent.body) { def headerString = parsedEvent.headers.toString() - def bodyString = parsedEvent.body.toString() + def bodyString = parsedEvent.body.toString() if (headerString?.contains("json")) { - def body - try { - body = new groovy.json.JsonSlurper().parseText(bodyString) - } catch (all) { - log.warn "Parsing Body failed - trying again..." - poll() - } - if (body instanceof java.util.Map) { - // get (poll) reponse - return handlePoll(body) + def body + try { + body = new groovy.json.JsonSlurper().parseText(bodyString) + } catch (all) { + log.warn "Parsing Body failed - trying again..." + poll() + } + if (body instanceof java.util.Map) { + // get (poll) reponse + return handlePoll(body) } else { //put response return handleCommandResponse(body) - } - } - } else { + } + } + } else { log.debug "parse - got something other than headers,body..." return [] } @@ -829,19 +851,19 @@ private sendColorEvents(device, xy, hue, sat, ct, colormode = null) { device.sendEvent([name: "colorTemperature", value: temp, descriptionText: "Color temperature has changed"]) // Return because color temperature change is not counted as a color change in SmartThings so no hex update necessary return - } + } if (hue != null) { // 0-65535 def value = Math.min(Math.round(hue * 100 / 65535), 65535) as int events["hue"] = [name: "hue", value: value, descriptionText: "Color has changed", displayed: false] - } + } if (sat != null) { // 0-254 def value = Math.round(sat * 100 / 254) as int - events["saturation"] = [name: "saturation", value: value, descriptionText: "Color has changed", displayed: false] - } + events["saturation"] = [name: "saturation", value: value, descriptionText: "Color has changed", displayed: false] + } // Following is used to decide what to base hex calculations on since it is preferred to return a colorchange in hex if (xy != null && colormode != "hs") { @@ -943,12 +965,9 @@ private handleCommandResponse(body) { updates.each { childDeviceNetworkId, params -> def device = getChildDevice(childDeviceNetworkId) def id = getId(device) - // If device is offline, then don't send events which will update device watch - if (isOnline(id)) { - sendBasicEvents(device, "on", params.on) - sendBasicEvents(device, "bri", params.bri) - sendColorEvents(device, params.xy, params.hue, params.sat, params.ct) - } + sendBasicEvents(device, "on", params.on) + sendBasicEvents(device, "bri", params.bri) + sendColorEvents(device, params.xy, params.hue, params.sat, params.ct) } return [] } @@ -981,39 +1000,38 @@ private handlePoll(body) { def bulbs = getChildDevices() for (bulb in body) { - def device = bulbs.find{it.deviceNetworkId == "${app.id}/${bulb.key}"} + def device = bulbs.find { it.deviceNetworkId == "${app.id}/${bulb.key}" } if (device) { if (bulb.value.state?.reachable) { - if (state.bulbs[bulb.key]?.online == false) { + if (state.bulbs[bulb.key]?.online == false || state.bulbs[bulb.key]?.online == null) { // light just came back online, notify device watch - def lastActivity = now() - device.sendEvent(name: "deviceWatch-status", value: "ONLINE", description: "Last Activity is on ${new Date((long) lastActivity)}", displayed: false, isStateChange: true) + device.sendEvent(name: "DeviceWatch-DeviceStatus", value: "online", displayed: false, isStateChange: true) log.debug "$device is Online" } // Mark light as "online" state.bulbs[bulb.key]?.unreachableSince = null state.bulbs[bulb.key]?.online = true - - // If user just executed commands, then do not send events to avoid confusing the turning on/off state - if (!state.updating) { - sendBasicEvents(device, "on", bulb.value?.state?.on) - sendBasicEvents(device, "bri", bulb.value?.state?.bri) - sendColorEvents(device, bulb.value?.state?.xy, bulb.value?.state?.hue, bulb.value?.state?.sat, bulb.value?.state?.ct, bulb.value?.state?.colormode) - } } else { if (state.bulbs[bulb.key]?.unreachableSince == null) { // Store the first time where device was reported as "unreachable" state.bulbs[bulb.key]?.unreachableSince = currentTime.getTimeInMillis() - } else if (state.bulbs[bulb.key]?.online) { + } + if (state.bulbs[bulb.key]?.online || state.bulbs[bulb.key]?.online == null) { // Check if device was "unreachable" for more than 11 minutes and mark "offline" if necessary - if (state.bulbs[bulb.key]?.unreachableSince < time11.getTimeInMillis()) { + if (state.bulbs[bulb.key]?.unreachableSince < time11.getTimeInMillis() || state.bulbs[bulb.key]?.online == null) { log.warn "$device went Offline" state.bulbs[bulb.key]?.online = false - device.sendEvent(name: "DeviceWatch-DeviceOffline", value: "offline", displayed: false, isStateChange: true) + device.sendEvent(name: "DeviceWatch-DeviceStatus", value: "offline", displayed: false, isStateChange: true) } } log.warn "$device may not reachable by Hue bridge" } + // If user just executed commands, then do not send events to avoid confusing the turning on/off state + if (!state.updating) { + sendBasicEvents(device, "on", bulb.value?.state?.on) + sendBasicEvents(device, "bri", bulb.value?.state?.bri) + sendColorEvents(device, bulb.value?.state?.xy, bulb.value?.state?.hue, bulb.value?.state?.sat, bulb.value?.state?.ct, bulb.value?.state?.colormode) + } } } return [] @@ -1032,16 +1050,16 @@ def updateHandler() { def hubVerification(bodytext) { log.trace "Bridge sent back description.xml for verification" - def body = new XmlSlurper().parseText(bodytext) - if (body?.device?.modelName?.text().startsWith("Philips hue bridge")) { - def bridges = getHueBridges() - def bridge = bridges.find {it?.key?.contains(body?.device?.UDN?.text())} - if (bridge) { - bridge.value << [name:body?.device?.friendlyName?.text(), serialNumber:body?.device?.serialNumber?.text(), verified: true] - } else { - log.error "/description.xml returned a bridge that didn't exist" - } - } + def body = new XmlSlurper().parseText(bodytext) + if (body?.device?.modelName?.text().startsWith("Philips hue bridge")) { + def bridges = getHueBridges() + def bridge = bridges.find {it?.key?.contains(body?.device?.UDN?.text())} + if (bridge) { + bridge.value << [name:body?.device?.friendlyName?.text(), serialNumber:body?.device?.serialNumber?.text(), verified: true] + } else { + log.error "/description.xml returned a bridge that didn't exist" + } + } } def on(childDevice) { @@ -1050,7 +1068,7 @@ def on(childDevice) { updateInProgress() createSwitchEvent(childDevice, "on") put("lights/$id/state", [on: true]) - return "Bulb is turning On" + return "Bulb is turning On" } def off(childDevice) { @@ -1059,15 +1077,15 @@ def off(childDevice) { updateInProgress() createSwitchEvent(childDevice, "off") put("lights/$id/state", [on: false]) - return "Bulb is turning Off" + return "Bulb is turning Off" } def setLevel(childDevice, percent) { log.debug "Executing 'setLevel'" def id = getId(childDevice) - updateInProgress() + updateInProgress() // 1 - 254 - def level + def level if (percent == 1) level = 1 else @@ -1101,7 +1119,7 @@ def setSaturation(childDevice, percent) { def setHue(childDevice, percent) { log.debug "Executing 'setHue($percent)'" def id = getId(childDevice) - updateInProgress() + updateInProgress() // 0 - 65535 def level = Math.min(Math.round(percent * 65535 / 100), 65535) // TODO should this be done by app only or should we default to on? @@ -1113,7 +1131,7 @@ def setHue(childDevice, percent) { def setColorTemperature(childDevice, huesettings) { log.debug "Executing 'setColorTemperature($huesettings)'" def id = getId(childDevice) - updateInProgress() + updateInProgress() // 153 (6500K) to 500 (2000K) def ct = hueSettings == 6500 ? 153 : Math.round(1000000/huesettings) createSwitchEvent(childDevice, "on") @@ -1122,14 +1140,14 @@ def setColorTemperature(childDevice, huesettings) { } def setColor(childDevice, huesettings) { - log.debug "Executing 'setColor($huesettings)'" + log.debug "Executing 'setColor($huesettings)'" def id = getId(childDevice) - updateInProgress() + updateInProgress() - def value = [:] - def hue = null - def sat = null - def xy = null + def value = [:] + def hue = null + def sat = null + def xy = null // Prefer hue/sat over hex to make sure it works with the majority of the smartapps if (huesettings.hue != null || huesettings.sat != null) { @@ -1154,48 +1172,32 @@ def setColor(childDevice, huesettings) { // value.xy = calculateXY(hex, model) // Once groups, or scenes are introduced it might be a good idea to use unique models again value.xy = calculateXY(hex) - } + } */ - // Default behavior is to turn light on - value.on = true + // Default behavior is to turn light on + value.on = true - if (huesettings.level != null) { - if (huesettings.level <= 0) - value.on = false - else if (huesettings.level == 1) - value.bri = 1 - else + if (huesettings.level != null) { + if (huesettings.level <= 0) + value.on = false + else if (huesettings.level == 1) + value.bri = 1 + else value.bri = Math.min(Math.round(huesettings.level * 254 / 100), 254) - } - value.alert = huesettings.alert ? huesettings.alert : "none" - value.transitiontime = huesettings.transitiontime ? huesettings.transitiontime : 4 + } + value.alert = huesettings.alert ? huesettings.alert : "none" + value.transitiontime = huesettings.transitiontime ? huesettings.transitiontime : 4 - // Make sure to turn off light if requested - if (huesettings.switch == "off") - value.on = false + // Make sure to turn off light if requested + if (huesettings.switch == "off") + value.on = false createSwitchEvent(childDevice, value.on ? "on" : "off") - put("lights/$id/state", value) + put("lights/$id/state", value) return "Setting color to $value" } -def ping(childDevice) { - if (childDevice.device?.deviceNetworkId?.equalsIgnoreCase(selectedHue)) { - 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) - return "Device is Online" - } else { - return "Device is Offline" - } -} - private getId(childDevice) { if (childDevice.device?.deviceNetworkId?.startsWith("HUE")) { return childDevice.device?.deviceNetworkId[3..-1] @@ -1246,30 +1248,30 @@ private getBridgeIdNumber(serialNumber) { private getBridgeIP() { def host = null if (selectedHue) { - def d = getChildDevice(selectedHue) - if (d) { - if (d.getDeviceDataByName("networkAddress")) - host = d.getDeviceDataByName("networkAddress") - else - host = d.latestState('networkAddress').stringValue - } - if (host == null || host == "") { - def serialNumber = selectedHue - def bridge = getHueBridges().find { it?.value?.serialNumber?.equalsIgnoreCase(serialNumber) }?.value - if (!bridge) { - bridge = getHueBridges().find { it?.value?.mac?.equalsIgnoreCase(serialNumber) }?.value - } - if (bridge?.ip && bridge?.port) { - if (bridge?.ip.contains(".")) - host = "${bridge?.ip}:${bridge?.port}" - else - host = "${convertHexToIP(bridge?.ip)}:${convertHexToInt(bridge?.port)}" - } else if (bridge?.networkAddress && bridge?.deviceAddress) - host = "${convertHexToIP(bridge?.networkAddress)}:${convertHexToInt(bridge?.deviceAddress)}" - } - log.trace "Bridge: $selectedHue - Host: $host" - } - return host + def d = getChildDevice(selectedHue) + if (d) { + if (d.getDeviceDataByName("networkAddress")) + host = d.getDeviceDataByName("networkAddress") + else + host = d.latestState('networkAddress').stringValue + } + if (host == null || host == "") { + def serialNumber = selectedHue + def bridge = getHueBridges().find { it?.value?.serialNumber?.equalsIgnoreCase(serialNumber) }?.value + if (!bridge) { + bridge = getHueBridges().find { it?.value?.mac?.equalsIgnoreCase(serialNumber) }?.value + } + if (bridge?.ip && bridge?.port) { + if (bridge?.ip.contains(".")) + host = "${bridge?.ip}:${bridge?.port}" + else + host = "${convertHexToIP(bridge?.ip)}:${convertHexToInt(bridge?.port)}" + } else if (bridge?.networkAddress && bridge?.deviceAddress) + host = "${convertHexToIP(bridge?.networkAddress)}:${convertHexToInt(bridge?.deviceAddress)}" + } + log.trace "Bridge: $selectedHue - Host: $host" + } + return host } private Integer convertHexToInt(hex) { @@ -1309,7 +1311,7 @@ private List getRealHubFirmwareVersions() { * @param childDevice device to send event for * @param setSwitch The new switch state, "on" or "off" * @param setLevel Optional, switchLevel between 0-100, used if you set level to 0 for example since - * that should generate "off" instead of level change + * that should generate "off" instead of level change */ private void createSwitchEvent(childDevice, setSwitch, setLevel = null) { @@ -1452,8 +1454,8 @@ private float[] calculateXY(colorStr, model = null) { xy[0] = closestPoint.x; xy[1] = closestPoint.y; } - // xy[0] = PHHueHelper.precision(4, xy[0]); - // xy[1] = PHHueHelper.precision(4, xy[1]); + // xy[0] = PHHueHelper.precision(4, xy[0]); + // xy[1] = PHHueHelper.precision(4, xy[1]); // TODO needed, assume it just sets number of decimals? @@ -1471,7 +1473,7 @@ private float[] calculateXY(colorStr, model = null) { * * @param points the float array contain x and the y value. [x,y] * @param model the model of the lamp, example: "LCT001" for hue bulb. Used to calculate the color gamut. - * If this value is empty the default gamut values are used. + * If this value is empty the default gamut values are used. * @return the color value in hex (#ff03d3). If xy is null OR xy is not an array of size 2, Color. BLACK will be returned */ private String colorFromXY(points, model ) { @@ -1785,3 +1787,4 @@ def hsvToHex(hue, sat, value = 100){ return "#$r1$g1$b1" } + diff --git a/smartapps/smartthings/lifx-connect.src/lifx-connect.groovy b/smartapps/smartthings/lifx-connect.src/lifx-connect.groovy index 7832b68..8988011 100644 --- a/smartapps/smartthings/lifx-connect.src/lifx-connect.groovy +++ b/smartapps/smartthings/lifx-connect.src/lifx-connect.groovy @@ -387,7 +387,7 @@ def updateDevices() { } else { childDevice = addChildDevice(app.namespace, "LIFX White Bulb", device.id, null, data) } - } + } } getChildDevices().findAll { !selectors.contains("${it.deviceNetworkId}") }.each { log.info("Deleting ${it.deviceNetworkId}")