Tradfri LED RGB bulb

This past week Ikea had a "sale" on the Tradfri bulbs and I was able to pickup one for testing purposes. These Tradfris bulbs are very interesting and I have been reading the forums between HE and ST about them. I have been successful in pairing up the standard E26 RGB bulb and control it thanks to @mike.maxwell .Generic Zigbee CT Bulb (dev) driver. I am curious if any progress has been made for the RGB part of the bulb functionality?

2 Likes

try changing it to generic RGB bulb, hit configure, see what happens

I did try the generic zigbee RGB but nothing happens for color change.

Never see them on sale in Canada or I just missed it. First time I’ve noticed the plug for sale. Are they Zigbee?

not following, when you hit refresh in that driver does hue and saturation populate?, does on off and set level work?
with the bulb on, then doing setHue with a value of 90, doesn't turn the bulb purplish?

Yes Zigbee RGB Bulbs. They were $25 here in the US. Now $19.

1 Like

Is the plug Zigbee or WiFi?

Zigbee

1 Like

Hey @gavincampbel, can you pick up one of these outlets once they're available and see how it does with Xiaomi devices? I don’t have my Xbee yet, so can’t map my Zigbee network to test.

I am not sure these bulbs are like traditional RGB or RGBW Zigbee bulbs...

From the detailed description:

With the remote control you can switch between Cool white (4000 Kelvin), Warm white (2700 Kelvin), Warm glow (2200 Kelvin), Candlelight (1780 Kelvin), Warm amber, Dark peach, Pink, Light purple, and Light blue.
Add the gateway and app to also switch between Cold sky (6000 Kelvin), Cool daylight (5000 Kelvin), Sunrise (3000 Kelvin), Peach, Dark red, Light pink, Dark pink, Dark purple, Blue, Lime and Yellow.

Seems like a limited set of colors. Perhaps that is a limitation of their RemoteControl/Gateway, though - and not the bulb? Would seem strange to limit the range of colors like this.

There is a thread going on here about them. I can’t get the driver to port unfortunately. Issue with the include statement.

I had one of these before, in fact I just bought another tonight since their now finally selling them without that remote I don't want. Their pretty responsive and pair with Hue fine. Never tried it with Hubitat since I didn't own one at the time I had the previous bulb. Their full spectrum (ish). They strike out on some of the colors compared to Hue or Osram. For $26 CAD vs $60 for a Hue, you can forgive their smaller color gamut for a simple splash of color to play around with or accent.

They paired with Wink as well, but I think the color wasn't working at the time. Don't remember if I tried ST which I still owned. Nicest with Hue Bridge though.

When I had this same bulb paired with SmartThings it worked with color changing... with Hubitat, colors do not work. I’ve given up and just use it as dimmable bulb.

I didn’t know they are finally selling this without the dimmer attached to it. I’m not seeing it alone on the website yet,

Very easy to miss. Same look, price and description, but when you zoom in on one of them it lists “color and white spectrum opal white”.

I’m finding their packaging starkness a bit too hard to identify products like these on the store shelves as well.

@mike.maxwell I picked up one of these last night. It would not pair after two factory resets. Reset it again and it paired instantly with the Hue Bridge and had full control of it. Removed it and then I was able to pair with Hubitat after another reset. I saw that it pairs with the Generic Zigbee CT driver, and I switched it to Generic Zigbee RGB driver and then when that didn’t control color (I did use Configure), I switched to the Generic Zigbee RGBW driver, again using the Configure button. Both could dim and turn on/off, but cannot adjust the color. Here’s the fingerprint.

Manufacturer: IKEA of Sweden

Product Name: Generic Zigbee CT Bulb (dev)

Model Number: TRADFRI bulb E26 CWS opal 600lm

deviceTypeId: 231

more...

manufacturer:IKEA of Sweden
address64bit:90FD9FFFFEF372FF
address16bit:72CE
model:TRADFRI bulb E26 CWS opal 600lm
basicAttributesInitialized:true
application:11
endpoints.01.manufacturer:IKEA of Sweden
endpoints.01.idAsInt:1
endpoints.01.inClusters:0000,0003,0004,0005,0006,0008,0300,0B05,1000
endpoints.01.endpointId:01
endpoints.01.profileId:C05E
endpoints.01.application:11
endpoints.01.outClusters:0005,0019,0020,1000
endpoints.01.initialized:true
endpoints.01.model:TRADFRI bulb E26 CWS opal 600lm
endpoints.01.stage:4

So you might want to look at the ST DTH for the opal. It doesn’t look too complex and probably will work with the normal massaging. The RGB one is throwing errors because of the first import line of code.

/**

  • Copyright 2017 Pedro Garcia
  • 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.
  • IKEA TrĂĄdfri RGB Bulb
  • Color management is not trivial. IKEA bulbs are using CIE XY color scheme instead of Hue/Saturation. Also the
  • bulbs do not seem to be able to output "light cyan" color. Maybe it is my fault for not having being able to
  • identify the correct color scheme (currently assuming sRGB with gamma correction), but cannot either with the
  • IKEA remote pairing...
  • This handler is written so that it reports any change in the bulb state (on/off, brightness, color) as an event
  • immediately to be processed by other apps.
  • Author: Pedro Garcia
  • Date: 2017-09-17
  • Version: 1.1
  • TO DO:
    • Color presets
    • Enable debug logging on app settings
    • Remove custom code when ST correctly parses both all color attributes and multiple reporting in one message
    • Color event should send string with hue and saturation values instead of hex, as per API reference, but when
  •  doing so, the color picker does not get updated
    

**/

import physicalgraph.zigbee.zcl.DataType

metadata {
definition (name: "IKEA Tradfri RGB Light", namespace: "puzzle-star", author: "Pedro Garcia") {

// Hard Capabilities
capability "Light"
capability "Switch"
capability "Switch Level"
capability "Color Control"

// Soft Capabilities
capability "Actuator"
capability "Configuration"
capability "Refresh"
capability "Polling"
capability "Health Check"

// Capability Attributes
attribute "switch", "enum", ["on", "off"]
attribute "level", "number"
attribute "hue", "number"
attribute "saturation", "number"
attribute "color", "string"

// Custom Attributes
attribute "colorName", "string"
attribute "red", "number"
attribute "green", "number"
attribute "blue", "number"

// Custom Commands
command "setColorName"
command "setWhite"
command "nextColor"
command "setRed"
command "onRed"
command "offRed"
command "setGreen"
command "onGreen"
command "offGreen"
command "setBlue"
command "onBlue"
command "offBlue"

// TrĂĄdfri RGB bulb
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B05, 1000", outClusters: "0005, 0019, 0020, 1000", manufacturer: "IKEA of Sweden",  model: "TRADFRI bulb E27 CWS opal 600lm", deviceJoinName: "TRADFRI bulb E27 CWS opal 600lm"

}

preferences {
// input name: "debugEnabled", type: "bool", title: "Enable debug logging", defaultValue: false, displayDuringSetup: false, required: false
}

tiles(scale: 2) {
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#00a0dc", nextState:"turningOff"
attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#00a0dc", nextState:"turningOff"
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
}

  tileAttribute ("device.level", key: "SLIDER_CONTROL") {
    attributeState "level", action:"setLevel"
  }
  
  tileAttribute ("device.color", key: "COLOR_CONTROL") {
    attributeState "color", action:"setColor"
  }
}

controlTile("colorWheel", "device.color", "color", height: 4, width: 3, inactiveLabel: false) {
	state "color", label:"Color", action:"setColor"
}
    
valueTile("hue", "device.hue", inactiveLabel: false, decoration: "flat") {
    state "hue", label: 'Hue ${currentValue}%'
}

valueTile("saturation", "device.saturation", inactiveLabel: false, decoration: "flat") {
    state "saturation", label: 'Sat ${currentValue}%'
}

valueTile("value", "device.level", inactiveLabel: false, decoration: "flat") {
    state "level", label: 'Value ${currentValue}%'
}

standardTile("switchRed", "device.red", height: 1, width: 1, inactiveLabel: false, canChangeIcon: false, decoration:'flat') {
  state "on", label:'${currentValue}%', action:"offRed", icon:"st.illuminance.illuminance.bright", backgroundColor:"#FF6060"
  state "0", label:'Red', action:"onRed", icon:"st.illuminance.illuminance.dark", backgroundColor:"#FFD8D8"
}

standardTile("switchGreen", "device.green", height: 1, width: 1, inactiveLabel: false, canChangeIcon: false, decoration:'flat') {
  state "on", label:'${currentValue}%', action:"offGreen", icon:"st.illuminance.illuminance.bright", backgroundColor:"#60FF60"
  state "0", label:"Green", action:"onGreen", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8FFD8"
}

standardTile("switchBlue", "device.blue", height: 1, width: 1, inactiveLabel: false, canChangeIcon: false, decoration:'flat') {
  state "on", label:'${currentValue}%', action:"offBlue", icon:"st.illuminance.illuminance.bright", backgroundColor:"#6060FF"
  state "0", label:"Blue", action:"onBlue", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8FF"
}

valueTile("colorName", "device.colorName", inactiveLabel: false, decoration: "flat", width: 3, height: 1) {
  state "colorName", label: '${currentValue}'
}

standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 1, height: 1) {
  state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}

standardTile("setWhite", "device.default", inactiveLabel: false, decoration: "flat", width: 1, height: 1) {
  state "default", label:"W", action:"setWhite", icon:"st.switches.light.on", backgroundColor: "#FFFFFF"
}

standardTile("nextColor", "device.default", inactiveLabel: false, decoration: "flat", width: 1, height: 1) {
  state "default", label:"", action:"nextColor", icon:"https://puzzle-star.com/shared/images/color-wheel.png"
}

// TO DO: Color presets
standardTile("colorPreset1", "device.default", inactiveLabel: false, width: 1, height: 1) {
  state "default", label:"", action:"setColorPreset1", backgroundColor:"#ECCF73"
}

standardTile("colorPreset2", "device.default", inactiveLabel: false, width: 1, height: 1) {
  state "default", label:"", action:"setColorPreset2", backgroundColor:"#FBECCB"
}

standardTile("colorPreset3", "device.default", inactiveLabel: false, width: 1, height: 1) {
  state "default", label:"", action:"setColorPreset3", backgroundColor:"#F5FBFB"
}

standardTile("colorPreset4", "device.default", inactiveLabel: false, width: 1, height: 1) {
  state "default", label:"", action:"setColorPreset4", backgroundColor:"#ECCF73"
}

standardTile("colorPreset5", "device.default", inactiveLabel: false, width: 1, height: 1) {
  state "default", label:"", action:"setColorPreset5", backgroundColor:"#FBECCB"
}

standardTile("colorPreset6", "device.default", inactiveLabel: false, width: 1, height: 1) {
  state "default", label:"", action:"setColorPreset6", backgroundColor:"#F5FBFB"
}

main(["switch"])
details([
	"switch",
    "colorWheel",
    "hue", "saturation", "value",
	"switchRed", "switchGreen", "switchBlue",
    "refresh", "setWhite", "nextColor",
    "colorName", 
])

}
}

def getColorNames() {
[
[ name: "Red", color: "#FF0000", wheel: true ],
[ name: "Orange", color: "#FF7F00", wheel: true ],
[ name: "Yellow", color: "#FFFF00", wheel: true ],
[ name: "Green", color: "#00FF00", wheel: true ],
[ name: "Cyan", color: "#00FFFF", wheel: false ], // Not in Tradfri...
[ name: "Blue", color: "#0000FF", wheel: true ],
[ name: "Indigo", color: "#9300FF", wheel: true ],
[ name: "Fuchsia", color: "#FF00FF", wheel: true ],
[ name: "Bright Pink", color: "#FF007F", wheel: true ],
[ name: "White", color: "#FFFFFF", wheel: false ],
// Custom colors
[ name: "Pink", color: "#FF5F5F", wheel: false ],
[ name: "Light Pink", color: "#FF7F7F", wheel: false ],
[ name: "Light Blue", color: "#007FFF", wheel: false ],
[ name: "Violet", color: "#8F00FF", wheel: false ],
[ name: "Purple", color: "#7F00FF", wheel: false ],
]
}

def logDebug(msg) {
// log.debug msg
}

def logTrace(msg) {
// log.trace msg
}

def parseHex4le(hex) {
Integer.parseInt(hex.substring(2, 4) + hex.substring(0, 2), 16)
}

def parseColorAttribute(id, value) {
def parsed = false

if(id == 0x03) {
  // currentColorX
  value = parseHex4le(value)
  logTrace "Parsed ColorX: $value"
  value /= 65536
  parsed = true
  state.colorXReported = true;
  state.colorChanged |= value != colorX
  state.colorX = value
}
else if(id == 0x04) {
  // currentColorY
  value = parseHex4le(value)
  logTrace "Parsed ColorY: $value"
  value /= 65536
  parsed = true
  state.colorYReported = true;
  state.colorChanged |= value != colorY
  state.colorY = value
}
else {
  logDebug "Not parsing Color cluster attribute $id: $value"
}

parsed

}

def parseAttributeList(cluster, list) {
logTrace "Cluster: $cluster, AttrList: $list"
def parsed = true

while(list.length()) {
def attrId = parseHex4le(list.substring(0, 4))
def attrType = Integer.parseInt(list.substring(4, 6), 16)
def attrShift = 0

if(!attrType) {
  attrType = Integer.parseInt(list.substring(6, 8), 16)
  attrShift = 1
}

if(DataType.isVariableLength(attrType)) {
  logDebug "Not parsing variable length attribute: $list"
  parsed = false
  break
}

def attrLen = DataType.getLength(attrType)
def attrValue = list.substring(6 + 2*attrShift, 6 + 2*(attrShift+attrLen))

logTrace "Attr - Id: $attrId($attrLen), Type: $attrType, Value: $attrValue"

if(cluster == 0x300) {
  parsed &= parseColorAttribute(attrId, attrValue)
}
else {
  logDebug "Not parsing cluster $cluster attribute: $list"
  parsed = false;
}

list = list.substring(6 + 2*(attrShift+attrLen))

}

parsed
}

def parse(String description) {
logDebug "Parsing : $description"

def events = []
def event = zigbee.getEvent(description)
def parsed

if(event) {
parsed = true
events += event
}
else {
def cluster = zigbee.parse(description)

if(cluster) {
  logTrace "Cluster - $cluster"

  if (cluster.clusterId == 0x0006 && cluster.command == 0x07) {
    if (cluster.data[0] == 0x00) {
      events += createEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
      parsed = true
    }
    else {
      log.warn "ON/OFF REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
      parsed = true
    }
  }
  else {
    logDebug "Not parsing cluster message: $cluster"
  }
}
else {
  def map = description

  if (description instanceof String)  {
    map = stringToMap(description)
  }
  
  logTrace "Map - $map"
  def raw = map["read attr - raw"]

  if(raw) {
    def clusterId = Integer.parseInt(map.cluster, 16)
    def attrList = raw.substring(12)
  
    parsed = parseAttributeList(clusterId, attrList)

    if(state.colorChanged || (state.colorXReported && state.colorYReported)) {
      state.colorChanged = false;
      state.colorXReported = false;
      state.colorYReported = false;
      logTrace "Color Change: xy ($state.colorX, $state.colorY)"
      def rgb = colorXy2Rgb(state.colorX, state.colorY)
      logTrace "Color Change: RGB ($rgb.red, $rgb.green, $rgb.blue)"
      events += updateColor(rgb)
    }
  }
}

}

if(!parsed) {
log.warn "DID NOT PARSE MESSAGE for description : $description"
}

for(ev in events) {
logDebug "Event - $ev.name: $ev.value"
sendEvent(ev)
}
}

def updateColor(rgb) {
logTrace "updateColor: RGB ($rgb.red, $rgb.green, $rgb.blue)"
def events = []

def hsv = colorRgb2Hsv(rgb.red, rgb.green, rgb.blue)
hsv.hue = Math.round(hsv.hue * 100).intValue()
hsv.saturation = Math.round(hsv.saturation * 100).intValue()
hsv.level = Math.round(hsv.level * 100).intValue()
logTrace "updateColor: RGB ($hsv.hue, $hsv.saturation, $hsv.level)"

def colorMatch = getNearestMatch(rgb, false)
logTrace "updateColor: $colorMatch"

rgb.red = Math.round(rgb.red * 255).intValue()
rgb.green = Math.round(rgb.green * 255).intValue()
rgb.blue = Math.round(rgb.blue * 255).intValue()
logTrace "updateColor: RGB ($rgb.red, $rgb.green, $rgb.blue)"

def color = colorUtil.rgbToHex(rgb.red, rgb.green, rgb.blue)
logTrace "updateColor: $color"

events += createEvent(name: "color", value: color, data: [ hue: hsv.hue, saturation: hsv.saturation, red: rgb.red, green: rgb.green, blue: rgb.blue, hex: color])
events += createEvent(name: "hue", value: hsv.hue)
events += createEvent(name: "saturation", value: hsv.saturation)
events += createEvent(name: "red", value: Math.round(rgb.red * 100/255).intValue())
events += createEvent(name: "green", value: Math.round(rgb.green * 100/255).intValue())
events += createEvent(name: "blue", value: Math.round(rgb.blue * 100/255).intValue())

def colorName = colorMatch.name
logTrace "colorCheck: $colorMatch.color - $color"
if(colorMatch.color != color) colorName += "\n[$color]"
logTrace "colorName: $colorName"

events += createEvent(name: "colorName", value: colorName)

events
}

def off() {
zigbee.off()
}

def on() {
zigbee.on()
}

def setLevel(value) {
zigbee.setLevel(value)
}

def setColor(red, green, blue) {
logDebug "setColor: RGB ($red, $green, $blue)"

def colorName = colorUtil.rgbToHex(Math.round(red255).intValue(), Math.round(green255).intValue(), Math.round(blue*255).intValue())
setColorName("Setting Color\n[$colorName]");

def xy = colorRgb2Xy(red, green, blue);

logTrace "setColor: xy ($xy.x, $xy.y)"

def intX = Math.round(xy.x65536).intValue() // 0..65279
def intY = Math.round(xy.y
65536).intValue() // 0..65279

logTrace "setColor: xy ($intX, $intY)"

def strX = DataType.pack(intX, DataType.UINT16, 1);
def strY = DataType.pack(intY, DataType.UINT16, 1);

zigbee.command(0x0300, 0x07, strX, strY, "0a00")
}

def setColor(Map colorMap) {

logDebug "setColor: $colorMap"

def rgb

if(colorMap.containsKey("red") && colorMap.containsKey("green") && colorMap.containsKey("blue")) {
rgb = [ red : colorMap.red.intValue() / 255, green: colorMap.green.intValue() / 255, blue: colorMap.blue.intValue() / 255 ]
}
else if(colorMap.containsKey("hue") && colorMap.containsKey("saturation")) {
rgb = colorHsv2Rgb(colorMap.hue / 100, colorMap.saturation / 100)
}
else {
log.warn "Unable to set color $colorMap"
}

logTrace "setColor: RGB ($red, $green, $blue)"

setColor(rgb.red, rgb.green, rgb.blue)
}

def setHue(hue) {
logDebug "setHue: $hue"
setColor([ hue: hue, saturation: device.currentValue("saturation") ])
}

def setSaturation(saturation) {
logDebug "setSaturation: $saturation"
setColor([ hue: device.currentValue("hue"), saturation: saturation ])
}

def setColorName(name){
logDebug "Color Name: $name"
sendEvent(name: "colorName", value: name)
}

def setRed(level) {
def rgb = getCurrentRGB()
setColor(level/100, rgb.green, rgb.blue)
}

def setGreen(level) {
def rgb = getCurrentRGB()
setColor(rgb.red, level/100, rgb.blue)
}

def setBlue(level) {
def rgb = getCurrentRGB()
setColor(rgb.red, rgb.green, level/100)
}

def onRed() {
def rgb = getCurrentRGB()
if(state.lastRed) setColor(state.lastRed, rgb.green, rgb.blue)
}

def onGreen() {
def rgb = getCurrentRGB()
if(state.lastGreen) setColor(rgb.red, state.lastGreen, rgb.blue)
}

def onBlue() {
def rgb = getCurrentRGB()
if(state.lastBlue) setColor(rgb.red, rgb.green, state.lastBlue)
}

def offRed() {
def rgb = getCurrentRGB()
if(!rgb.green && !rgb.blue) return;
state.lastRed = rgb.red;
setColor(0, rgb.green, rgb.blue)
}

def offGreen() {
def rgb = getCurrentRGB()
if(!rgb.red && !rgb.blue) return;
state.lastGreen = rgb.green;
setColor(rgb.red, 0, rgb.blue)
}

def offBlue() {
def rgb = getCurrentRGB()
if(!rgb.red && !rgb.green) return;
state.lastBlue = rgb.blue;
setColor(rgb.red, rgb.green, 0)
}

def getCurrentRGB() {
def rgb = [ red: device.currentValue("red")/100, green: device.currentValue("green")/100, blue: device.currentValue("blue")/100 ]
logDebug "Current RGB: $rgb"

rgb
}

def getNearestMatch(rgb, wheelOnly) {
def colors = getColorNames()
def xy = colorRgb2Xy(rgb.red, rgb.green, rgb.blue)
logTrace "Match: RGB($rgb) - xy($xy)"

def matched = [index: -1, name: null, color: null, rgb: null, distance: 1]
def index = -1;

for(match in colors) {
index++
if(wheelOnly && !match.wheel) continue

def color = colorUtil.hexToRgb(match.color)
def red = color[0] / 255
def green = color[1] / 255
def blue = color[2] / 255
def xy2 = colorRgb2Xy(red, green, blue)
def distance = Math.sqrt(Math.pow(xy.x-xy2.x, 2) + Math.pow(xy.y-xy2.y, 2)) / 1.4142136
logTrace "Matching: $match.color xy($xy2) vs xy($xy) -> $distance"
if(distance <= matched.distance) {
  matched.index = index
  matched.distance = distance
  matched.name = match.name
  matched.color = match.color
  matched.rgb = [red: red, green: green, blue: blue]
}

}

logTrace "Match: $matched"

matched
}

def setWhite() {
setColor(1, 1, 1)
}

def nextColor() {
def rgb = getCurrentRGB()
logTrace "Current color: $rgb"

def match = getNearestMatch(rgb, true)
logDebug "Current color match: $match"

def colors = getColorNames()
def next = match.index

while (++next != match.index) {
if(next >= colors.size()) next = 0
if(colors[next].wheel) break
}

def color = colors[next].color
logDebug "Next color: $color"

rgb = colorUtil.hexToRgb(color)
setColor(rgb[0]/255, rgb[1]/255, rgb[2]/255)
}

def ping() {
return zigbee.onOffRefresh()
}

def colorControlRefresh() {
def commands = []
commands += zigbee.readAttribute(0x0300, 0x03) // currentColorX
commands += zigbee.readAttribute(0x0300, 0x04) // currentColorY
commands
}

def colorControlConfig(min, max, step) {
def commands = []
commands += zigbee.configureReporting(0x0300, 0x03, DataType.UINT16, min, max, step) // currentColorX
commands += zigbee.configureReporting(0x0300, 0x04, DataType.UINT16, min, max, step) // currentColorY
commands
}

def refresh() {
state.colorChanged = false
state.colorXReported = false
state.colorYReported = false
zigbee.onOffRefresh() + zigbee.levelRefresh() + colorControlRefresh() + zigbee.onOffConfig(0, 300) + zigbee.levelConfig(0, 300, 1) + colorControlConfig(0, 300, 1)
}

def poll() {
refresh()
}

def configure() {
sendEvent(name: "checkInterval", value: 2 * 10 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
refresh()
}

def installed() {
if ((device.currentState("level")?.value == null) || (device.currentState("level")?.value == 0)) {
sendEvent(name: "level", value: 100)
}
}

// Color Management functions

def min(first, ... rest) {
def min = first;
for(next in rest) {
if(next < min) min = next
}

min
}

def max(first, ... rest) {
def max = first;
for(next in rest) {
if(next > max) max = next
}

max
}

def colorGammaAdjust(component) {
return (component > 0.04045) ? Math.pow((component + 0.055) / (1.0 + 0.055), 2.4) : (component / 12.92)
}

def colorGammaRevert(component) {
return (component <= 0.0031308) ? 12.92 * component : (1.0 + 0.055) * Math.pow(component, (1.0 / 2.4)) - 0.055;
}

def colorXy2Rgb(x, y) {

logTrace "< Color xy: ($x, $y)"

def Y = 1;
def X = (Y / y) * x;
def Z = (Y / y) * (1.0 - x - y);

logTrace "< Color XYZ: ($X, $Y, $Z)"

// sRGB, Reference White D65
def M = [
[ 3.2410032, -1.5373990, -0.4986159 ],
[ -0.9692243, 1.8759300, 0.0415542 ],
[ 0.0556394, -0.2040112, 1.0571490 ]
]

def r = X * M[0][0] + Y * M[0][1] + Z * M[0][2]
def g = X * M[1][0] + Y * M[1][1] + Z * M[1][2]
def b = X * M[2][0] + Y * M[2][1] + Z * M[2][2]

def max = max(r, g, b)
r = colorGammaRevert(r / max)
g = colorGammaRevert(g / max)
b = colorGammaRevert(b / max)

logTrace "< Color RGB: ($r, $g, $b)"

[red: r, green: g, blue: b]
}

def colorRgb2Xy(r, g, b) {

logTrace "> Color RGB: ($r, $g, $b)"

r = colorGammaAdjust(r)
g = colorGammaAdjust(g)
b = colorGammaAdjust(b)

// sRGB, Reference White D65
// D65 0.31271 0.32902
// R 0.64000 0.33000
// G 0.30000 0.60000
// B 0.15000 0.06000
def M = [
[ 0.4123866, 0.3575915, 0.1804505 ],
[ 0.2126368, 0.7151830, 0.0721802 ],
[ 0.0193306, 0.1191972, 0.9503726 ]
]

def X = r * M[0][0] + g * M[0][1] + b * M[0][2]
def Y = r * M[1][0] + g * M[1][1] + b * M[1][2]
def Z = r * M[2][0] + g * M[2][1] + b * M[2][2]

logTrace "> Color XYZ: ($X, $Y, $Z)"

def x = X / (X + Y + Z)
def y = Y / (X + Y + Z)

logTrace "> Color xy: ($x, $y)"

[x: x, y: y]
}

def colorHsv2Rgb(h, s) {
logTrace "< Color HSV: ($h, $s, 1)"

def r
def g
def b

if (s == 0) {
    r = 1
    g = 1
    b = 1
}
else {
    def region = (6 * h).intValue()
    def remainder = 6 * h - region

    def p = 1 - s
    def q = 1 - s * remainder
    def t = 1 - s * (1 - remainder)

	if(region == 0) {
        r = 1
        g = t
        b = p
    }
    else if(region == 1) {
        r = q
        g = 1
        b = p
    }
    else if(region == 2) {
        r = p
        g = 1
        b = t
    }
    else if(region == 3) {
        r = p
        g = q
        b = 1
    }
    else if(region == 4) {
        r = t
        g = p
        b = 1
    }
    else {
        r = 1
        g = p
        b = q
    }
}

logTrace "< Color RGB: ($r, $g, $b)"

[red: r, green: g, blue: b]

}

def colorRgb2Hsv(r, g, b)
{
logTrace "> Color RGB: ($r, $g, $b)"

def min = min(r, g, b)
def max = max(r, g, b)
def delta = max - min

def h
def s
def v = max

if (delta == 0) {
	h = 0
    s = 0
}
else {
	s = delta / max
    if (r == max) h = ( g - b ) / delta			// between yellow & magenta
	else if(g == max) h = 2 + ( b - r ) / delta	// between cyan & yellow
	else h = 4 + ( r - g ) / delta				// between magenta & cyan
    h /= 6

	if(h < 0) h += 1
}

logTrace "> Color HSV: ($h, $s, $v)"

return [ hue: h, saturation: s, level: v ]

}

@mike.maxwell Any update on this? I'm having the same issue and worked with you briefly on this several months ago, but just gave up and have only been using the bulb as a dimming bulb. It would be nice to use RGBW features.

These bulbs need CIE XY color space for adjustment. The current driver won't do it. However, these do work on Hue, but again the driver would need modification. What would actually be quite nice would be way to speak to the CoAP interface on the TrĂĄdfri bridge. That would allow for low cost TrĂĄdfri lighting in your home, without the higher cost of a Hue Bridge. Although the $20 difference isn't much, and arguably worth it. Hue already works with HE, the Hue Bridge has a much better interface, and support for many great third-party apps that can run on the bridge and the bridge can still interface with Hubitat without issue.

[Edit] This looks like an interesting project. I bought a TrĂĄdfri bridge in the AS-IS section for $25 CAD. I may toy with this at some point. I'm sure it would run on a Pi Zero W, but wouldn't be practical to buy a Pi just to do this. It's more one of those "have a Pi laying around doing nothing" kind of projects.

This allows you to not have to buy a Hue bridge. I believe you already have a Pi running at your home too.

Ahh ok. I went ahead and bought a Hue starter kit from eBay (with the current 15% off coupon). This was just the excuse I needed. :slight_smile:

I’ll just pair the bulb to Hue and Hue hub to Hubitat when I get it next week.

1 Like