From 0c5840087b163507a7681860ffbbba6e7e7290ab Mon Sep 17 00:00:00 2001 From: Lars Finander Date: Mon, 28 Mar 2016 09:32:19 -0700 Subject: [PATCH] DVCSMP-1597 Philips HUE: Bloom and Strip lights need temperature control removed -Added new device type for Hue lights that have color control but no temperature control (Bloom/Strip) -Add missing event to setLevel --- .../hue-bloom.src/hue-bloom.groovy | 227 ++++++++++++++++++ .../smartthings/hue-bulb.src/hue-bulb.groovy | 6 +- .../hue-lux-bulb.src/hue-lux-bulb.groovy | 7 +- .../hue-connect.src/hue-connect.groovy | 53 +++- 4 files changed, 278 insertions(+), 15 deletions(-) create mode 100644 devicetypes/smartthings/hue-bloom.src/hue-bloom.groovy diff --git a/devicetypes/smartthings/hue-bloom.src/hue-bloom.groovy b/devicetypes/smartthings/hue-bloom.src/hue-bloom.groovy new file mode 100644 index 0000000..eedd244 --- /dev/null +++ b/devicetypes/smartthings/hue-bloom.src/hue-bloom.groovy @@ -0,0 +1,227 @@ +/** + * Hue Bloom + * + * Philips Hue Type "Color Light" + * + * Author: SmartThings + */ + +// for the UI +metadata { + // Automatically generated. Make future change here. + definition (name: "Hue Bloom", namespace: "smartthings", author: "SmartThings") { + capability "Switch Level" + capability "Actuator" + capability "Color Control" + capability "Switch" + capability "Refresh" + capability "Sensor" + + command "setAdjustedColor" + command "reset" + command "refresh" + } + + simulator { + // TODO: define status and reply messages here + } + + tiles (scale: 2){ + multiAttributeTile(name:"rich-control", 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:"#00A0DC", nextState:"turningOff" + attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#C6C7CC", nextState:"turningOn" + attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#00A0DC", nextState:"turningOff" + attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#C6C7CC", nextState:"turningOn" + } + tileAttribute ("device.level", key: "SLIDER_CONTROL") { + attributeState "level", action:"switch level.setLevel", range:"(0..100)" + } + tileAttribute ("device.level", key: "SECONDARY_CONTROL") { + attributeState "level", label: 'Level ${currentValue}%' + } + tileAttribute ("device.color", key: "COLOR_CONTROL") { + attributeState "color", action:"setAdjustedColor" + } + } + + standardTile("reset", "device.reset", height: 2, width: 2, inactiveLabel: false, decoration: "flat") { + state "default", label:"Reset Color", action:"reset", icon:"st.lights.philips.hue-single" + } + + standardTile("refresh", "device.refresh", height: 2, width: 2, inactiveLabel: false, decoration: "flat") { + state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh" + } + + main(["rich-control"]) + details(["rich-control", "colorTempSliderControl", "colorTemp", "reset", "refresh"]) + } +} + +// parse events into attributes +def parse(description) { + log.debug "parse() - $description" + def results = [] + + def map = description + if (description instanceof String) { + log.debug "Hue Bulb stringToMap - ${map}" + map = stringToMap(description) + } + + if (map?.name && map?.value) { + results << createEvent(name: "${map?.name}", value: "${map?.value}") + } + results +} + +// handle commands +void on() { + log.trace parent.on(this) + sendEvent(name: "switch", value: "on") +} + +void off() { + log.trace parent.off(this) + sendEvent(name: "switch", value: "off") +} + +void nextLevel() { + def level = device.latestValue("level") as Integer ?: 0 + if (level <= 100) { + level = Math.min(25 * (Math.round(level / 25) + 1), 100) as Integer + } + else { + level = 25 + } + setLevel(level) +} + +void setLevel(percent) { + log.debug "Executing 'setLevel'" + if (verifyPercent(percent)) { + parent.setLevel(this, percent) + sendEvent(name: "level", value: percent, descriptionText: "Level has changed to ${percent}%") + sendEvent(name: "switch", value: "on") + } +} + +void setSaturation(percent) { + log.debug "Executing 'setSaturation'" + if (verifyPercent(percent)) { + parent.setSaturation(this, percent) + sendEvent(name: "saturation", value: percent, displayed: false) + } +} + +void setHue(percent) { + log.debug "Executing 'setHue'" + if (verifyPercent(percent)) { + parent.setHue(this, percent) + sendEvent(name: "hue", value: percent, displayed: false) + } +} + +void setColor(value) { + log.debug "setColor: ${value}, $this" + def events = [] + def validValues = [:] + + if (verifyPercent(value.hue)) { + events << createEvent(name: "hue", value: value.hue, displayed: false) + validValues.hue = value.hue + } + if (verifyPercent(value.saturation)) { + events << createEvent(name: "saturation", value: value.saturation, displayed: false) + validValues.saturation = value.saturation + } + if (value.hex != null) { + if (value.hex ==~ /^\#([A-Fa-f0-9]){6}$/) { + events << createEvent(name: "color", value: value.hex) + validValues.hex = value.hex + } else { + log.warn "$value.hex is not a valid color" + } + } + if (verifyPercent(value.level)) { + events << createEvent(name: "level", value: value.level, descriptionText: "Level has changed to ${value.level}%") + validValues.level = value.level + } + if (value.switch == "off" || (value.level != null && value.level <= 0)) { + events << createEvent(name: "switch", value: "off") + validValues.switch = "off" + } else { + events << createEvent(name: "switch", value: "on") + validValues.switch = "on" + } + if (!events.isEmpty()) { + parent.setColor(this, validValues) + } + events.each { + sendEvent(it) + } +} + +void reset() { + log.debug "Executing 'reset'" + def value = [level:100, saturation:56, hue:23] + setAdjustedColor(value) + parent.poll() +} + +void setAdjustedColor(value) { + if (value) { + log.trace "setAdjustedColor: ${value}" + def adjusted = value + [:] + adjusted.hue = adjustOutgoingHue(value.hue) + // Needed because color picker always sends 100 + adjusted.level = null + setColor(adjusted) + } else { + log.warn "Invalid color input" + } +} + +void setColorTemperature(value) { + if (value) { + log.trace "setColorTemperature: ${value}k" + parent.setColorTemperature(this, value) + sendEvent(name: "colorTemperature", value: value) + sendEvent(name: "switch", value: "on") + } else { + log.warn "Invalid color temperature" + } +} + +void refresh() { + log.debug "Executing 'refresh'" + parent.manualRefresh() +} + +def adjustOutgoingHue(percent) { + def adjusted = percent + if (percent > 31) { + if (percent < 63.0) { + adjusted = percent + (7 * (percent -30 ) / 32) + } + else if (percent < 73.0) { + adjusted = 69 + (5 * (percent - 62) / 10) + } + else { + adjusted = percent + (2 * (100 - percent) / 28) + } + } + log.info "percent: $percent, adjusted: $adjusted" + adjusted +} + +def verifyPercent(percent) { + if (percent == null) + return false + else if (percent >= 0 && percent <= 100) { + return true + } else { + log.warn "$percent is not 0-100" + return false + } +} diff --git a/devicetypes/smartthings/hue-bulb.src/hue-bulb.groovy b/devicetypes/smartthings/hue-bulb.src/hue-bulb.groovy index 99b529b..93a0130 100644 --- a/devicetypes/smartthings/hue-bulb.src/hue-bulb.groovy +++ b/devicetypes/smartthings/hue-bulb.src/hue-bulb.groovy @@ -1,6 +1,8 @@ /** * Hue Bulb * + * Philips Hue Type "Extended Color Light" + * * Author: SmartThings */ @@ -69,11 +71,13 @@ metadata { def parse(description) { log.debug "parse() - $description" def results = [] + def map = description if (description instanceof String) { log.debug "Hue Bulb stringToMap - ${map}" map = stringToMap(description) } + if (map?.name && map?.value) { results << createEvent(name: "${map?.name}", value: "${map?.value}") } @@ -229,4 +233,4 @@ def verifyPercent(percent) { log.warn "$percent is not 0-100" return false } -} \ No newline at end of file +} 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 408c2e2..a1d7f33 100644 --- a/devicetypes/smartthings/hue-lux-bulb.src/hue-lux-bulb.groovy +++ b/devicetypes/smartthings/hue-lux-bulb.src/hue-lux-bulb.groovy @@ -1,6 +1,8 @@ /** * Hue Lux Bulb * + * Philips Hue Type "Dimmable Light" + * * Author: SmartThings */ // for the UI @@ -68,12 +70,12 @@ def parse(description) { // handle commands void on() { - parent.on(this) + log.trace parent.on(this) sendEvent(name: "switch", value: "on") } void off() { - parent.off(this) + log.trace parent.off(this) sendEvent(name: "switch", value: "off") } @@ -82,6 +84,7 @@ void setLevel(percent) { if (percent != null && percent >= 0 && percent <= 100) { parent.setLevel(this, percent) sendEvent(name: "level", value: percent) + sendEvent(name: "switch", value: "on") } else { log.warn "$percent is not 0-100" } diff --git a/smartapps/smartthings/hue-connect.src/hue-connect.groovy b/smartapps/smartthings/hue-connect.src/hue-connect.groovy index 0da5905..10980af 100644 --- a/smartapps/smartthings/hue-connect.src/hue-connect.groovy +++ b/smartapps/smartthings/hue-connect.src/hue-connect.groovy @@ -289,7 +289,7 @@ def bulbListHandler(hub, data = "") { 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, hub:hub] + bulbs[k] = [id: k, name: v.name, type: v.type, modelid: v.modelid, hub:hub] } } def bridge = null @@ -300,6 +300,40 @@ def bulbListHandler(hub, data = "") { return msg } +private upgradeDeviceType(device, newHueType) { + def deviceType = getDeviceType(newHueType) + + // Automatically change users Hue bulbs to correct device types + if (deviceType && !(device?.typeName?.equalsIgnoreCase(deviceType))) { + log.debug "Update device type: \"$device.label\" ${device?.typeName}->$deviceType" + device.setDeviceType(deviceType) + } +} + +private getDeviceType(hueType) { + // Determine ST device type based on Hue classification of light + if (hueType?.equalsIgnoreCase("Dimmable light")) + return "Hue Lux Bulb" + else if (hueType?.equalsIgnoreCase("Extended Color Light")) + return "Hue Bulb" + else if (hueType?.equalsIgnoreCase("Color Light")) + return "Hue Bloom" + else + return null +} + +private addChildBulb(dni, hueType, name, hub, update=false, device = null) { + def deviceType = getDeviceType(hueType) + + if (deviceType) { + return addChildDevice("smartthings", deviceType, dni, hub, ["label": name]) + } + else { + log.warn "Device type $hueType not supported" + return null + } +} + def addBulbs() { def bulbs = getHueBulbs() selectedBulbs?.each { dni -> @@ -309,11 +343,7 @@ def addBulbs() { if (bulbs instanceof java.util.Map) { newHueBulb = bulbs.find { (app.id + "/" + it.value.id) == dni } if (newHueBulb != null) { - if (newHueBulb?.value?.type?.equalsIgnoreCase("Dimmable light") ) { - d = addChildDevice("smartthings", "Hue Lux Bulb", dni, newHueBulb?.value.hub, ["label":newHueBulb?.value.name]) - } else { - d = addChildDevice("smartthings", "Hue Bulb", dni, newHueBulb?.value.hub, ["label":newHueBulb?.value.name]) - } + d = addChildBulb(dni, newHueBulb?.value?.type, newHueBulb?.value?.name, newHueBulb?.value?.hub) log.debug "created ${d.displayName} with id $dni" d.refresh() } else { @@ -322,16 +352,15 @@ def addBulbs() { } else { //backwards compatable newHueBulb = bulbs.find { (app.id + "/" + it.id) == dni } - d = addChildDevice("smartthings", "Hue Bulb", dni, newHueBulb?.hub, ["label":newHueBulb?.name]) + d = addChildBulb(dni, "Extended Color Light", newHueBulb?.value?.name, newHueBulb?.value?.hub) d.refresh() } } else { log.debug "found ${d.displayName} with id $dni already exists, type: '$d.typeName'" if (bulbs instanceof java.util.Map) { + // Update device type if incorrect def newHueBulb = bulbs.find { (app.id + "/" + it.value.id) == dni } - if (newHueBulb?.value?.type?.equalsIgnoreCase("Dimmable light") && d.typeName == "Hue Bulb") { - d.setDeviceType("Hue Lux Bulb") - } + upgradeDeviceType(d, newHueBulb?.value?.type) } } } @@ -473,7 +502,7 @@ def locationHandler(evt) { def bulbs = getHueBulbs() log.debug "Adding bulbs to state!" body.each { k,v -> - bulbs[k] = [id: k, name: v.name, type: v.type, hub:parsedEvent.hub] + bulbs[k] = [id: k, name: v.name, type: v.type, modelid: v.modelid, hub:parsedEvent.hub] } } } @@ -836,7 +865,7 @@ def convertBulbListToMap() { if (state.bulbs instanceof java.util.List) { def map = [:] state.bulbs.unique {it.id}.each { bulb -> - map << ["${bulb.id}":["id":bulb.id, "name":bulb.name, "hub":bulb.hub]] + map << ["${bulb.id}":["id":bulb.id, "name":bulb.name, "type": bulb.type, "modelid": bulb.modelid, "hub":bulb.hub]] } state.bulbs = map }