Philips Hue Lux Device?

Hi all,

I’ve bought a couple of Hue Lux bulbs - not to be confused with Hue as Hue Lux are just white, but still dimmable.

After support kindly updated my Zigbee firmware I was able to pair a bulb with SmartThings, however it showed up as Unknown. The reason for this (I assume) is that I did not have the “Hue Lux Bulb” added to My Device Types (I had added “Zigbee Hue Bulb” instead but it looks like this is specifically for the color changing one).

So I created a Device Type for “Hue Lux Bulb” from the template and updated the Unknown device so that it’s type is now “Hue Lux Bulb” instead of Unknown.

This seemed to work fine and the device showed up in the Things from the app Dashboard.

However whenever I try to switch it on or off the (Windows Phone) app crashed and the following line appears in Live Logging:
error java.lang.NullPointerException: Cannot invoke method off() on null object @ line 69

And at line 69 we have “parent.off(this)”, so it looks like parent is null when it shouldn’t be.

Looking at the code now it doesn’t even look like a Zigbee device. Have I added a Device Type for the Philips Hue Hub controlled Hue Lux bulb?

If it’s of any interest, this is what appeared in Raw Description for the device:
0B C05E 0100 02 07 0000 0003 0004 0005 0006 0008 1000 01 0019

What have I done wrong?
Is there a working Zigbee Device Type for this bulb somewhere?

If not I guess I’ll have to roll up my sleeves, and try to implement my own by going though the docs - any pointers appreciated.

You did add the Device Type for a bulb paired with the Hue Hub. There is a device type for Zigbee Hue Bulb that you could adapt for the Hue Lux by removing the color changing portions. To start, you could just remove the 0300 from the inClusters list and the on/off and dim commands should all work out of the gate.

Then you would start refining it from there. It should be very similar to the GE Link device type for comparison, but the profileId in the fingerprint for the Hue Lux should be “C05E” instead of “0104” for the GE Link.

In the IDE there is a sample device type for the LUX bulb. Try that.

Many thanks @Sticks18, I’ll try adapting one of the other Zigbee light devices as you suggest.

@tslagle13 - is that the one I already tried which looks like it it’s a hue hub one rather than a direct Zigbee one? Please let me know if you’ve found a Zigbee one for Hue Lux!

Okay, got it working more or less. I got an error about ambiguous use of BigInteger and read elsewhere that I would have to delete the Device and re-pair it - that was great because it actually matched it to the Device Type correctly.

I have noticed a couple of things. If powered off then back on, it comes back full, and clicking refresh doesn’t change the level to 100% in the app - would be nice if powering it up again automatically did this.

The code is below. Any pointers on how to improve it greatly received:

/* Philips Hue Lux (via Zigbee)

Capabilities:
  Actuator
  Configuration
  Polling
  Refresh
  Sensor
  Switch
  Switch Level
  
Custom Commands:
  setAdjustedColor
    
*/

metadata {
	definition (name: "Zigbee Hue Lux Bulb", namespace: "smartthings", author: "SmartThings") {
		capability "Switch Level"
		capability "Actuator"
		capability "Switch"
		capability "Configuration"
		capability "Polling"
		capability "Refresh"
		capability "Sensor"

		fingerprint profileId: "C05E", inClusters: "0000,0003,0004,0005,0006,0008,1000", outClusters: "0019"
	}

	// simulator metadata
	simulator {
		// status messages
		status "on": "on/off: 1"
		status "off": "on/off: 0"

		// reply messages
		reply "zcl on-off on": "on/off: 1"
		reply "zcl on-off off": "on/off: 0"
	}

	// UI tile definitions
	tiles {
		standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
			state "off", label: '${name}', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff"
			state "on", label: '${name}', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#79b821"
		}
		standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
			state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
		}
		controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 3, inactiveLabel: false) {
			state "level", action:"switch level.setLevel"
		}
		valueTile("level", "device.level", inactiveLabel: false, decoration: "flat") {
			state "level", label: 'Level ${currentValue}%'
		}

		main(["switch"])
		details(["switch", "levelSliderControl", "refresh"])
	}
}

// Parse incoming device messages to generate events
def parse(String description) {
	//log.trace description
	if (description?.startsWith("catchall:")) {
		def msg = zigbee.parse(description)
		//log.trace msg
		//log.trace "data: $msg.data"
	}
	else {
		def name = description?.startsWith("on/off: ") ? "switch" : null
		def value = name == "switch" ? (description?.endsWith(" 1") ? "on" : "off") : null
		def result = createEvent(name: name, value: value)
		log.debug "Parse returned ${result?.descriptionText}"
		return result
	}
}

def on() {
	// just assume it works for now
	log.debug "on()"
	sendEvent(name: "switch", value: "on")
	"st cmd 0x${device.deviceNetworkId} ${endpointId} 6 1 {}"
}

def off() {
	// just assume it works for now
	log.debug "off()"
	sendEvent(name: "switch", value: "off")
	"st cmd 0x${device.deviceNetworkId} ${endpointId} 6 0 {}"
}


def refresh() {
	"st rattr 0x${device.deviceNetworkId} ${endpointId} 6 0"
}

def poll(){
	log.debug "Poll is calling refresh"
	refresh()
}

def setLevel(value) {
	log.trace "setLevel($value)"
	def cmds = []

	if (value == 0) {
		sendEvent(name: "switch", value: "off")
		cmds << "st cmd 0x${device.deviceNetworkId} ${endpointId} 6 0 {}"
	}
	else if (device.latestValue("switch") == "off") {
		sendEvent(name: "switch", value: "on")
	}

	sendEvent(name: "level", value: value)
	def level = new BigInteger(Math.round(value * 255 / 100).toString()).toString(16)
	cmds << "st cmd 0x${device.deviceNetworkId} ${endpointId} 8 4 {${level} 0000}"

	//log.debug cmds
	cmds
}

private getEndpointId() {
	new BigInteger(device.endpointId, 16).toString()
}

private hex(value, width=2) {
	def s = new BigInteger(Math.round(value).toString()).toString(16)
	while (s.size() < width) {
		s = "0" + s
	}
	s
}

For refresh() try this:

"st rattr 0x${device.deviceNetworkId} 1 6 0", "delay 500",
"st rattr 0x${device.deviceNetworkId} 1 8 0"

And also add this to your parse()

if (description?.startsWith("read attr")) {
            def i = Math.round(convertHexToInt(description[-2..-1]) / 256 * 100 )
            sendEvent( name: "level", value: i )
            sendEvent( name: "switch.setLevel", value: i) //added to help subscribers
}

And finally, this method at the end of your code:

private Integer convertHexToInt(hex) {
    Integer.parseInt(hex,16)
}

The addition to refresh() polls the device for the dim level. The addition to parse should read the dim level returned by the bulb and pass the hex value into the new convertHexToInt() call to bring back a value, which is then converted into a % for the app.

Many thanks @Sticks18, it works perfectly (well perhaps a little slower than I would like!)

I wonder if you could help me to understand the Parse method a little better. Mine currently looks like this:

// Parse incoming device messages to generate events
def parse(String description) {
	log.trace description
	if (description?.startsWith("catchall:")) {
		def msg = zigbee.parse(description)
		//log.trace msg
		//log.trace "data: $msg.data"
	}
    else if (description?.startsWith("read attr")) {
		def i = Math.round(convertHexToInt(description[-2..-1]) / 256 * 100 )
        log.trace "Parse level {$i}" 
		sendEvent( name: "level", value: i )
        sendEvent( name: "switch.setLevel", value: i) //added to help subscribers
	}
	else {
		def name = description?.startsWith("on/off: ") ? "switch" : null
		def value = name == "switch" ? (description?.endsWith(" 1") ? "on" : "off") : null
		def result = createEvent(name: name, value: value)
		log.debug "Parse returned ${result?.descriptionText}"
		return result
	}
}

It seems a bit messy so I want to tidy it up but to do so I need to understand a bit more about what it is doing. I have the following questions:

  1. Is there any point to the zigbee.parse being called when the message is “catchall”? And what is the “catchall” message for?
  2. What is the difference between sendEvent and createEvent?
  3. What should be returned by Parse

I understand that Parse parses messages both from the device and to the device. This is probably the cause for some of my confusion. I would much rather there were two methods, one for each direction. Is there a way of determining the direction instead of working it out from the message contents?

Many thanks for all your help!

1 Like

Parse only reads incoming messages from the device, and creates events based on those messages.

  1. catchall is just a format of some zigbee messages. Basic default responses and messages coming from on/off cluster are catchall format, so if you turned on the logging, you’d see “catchall: C05E 0006 …” which is a on/off cluster message. If you look at the GE Link device type, it has examples of using those messages to determine on/off status. The zigbee.parse isn’t really needed in this example.

  2. createEvent is used specifically in parse, but must be returned to generate an event. It allows for adding additional commands/events into a map and returning the whole map from parse. sendEvent to my knowledge just fires off an event. You can find some more info in the developers documentation.

  3. Events, log messages and sometimes responses back to a device are the typical items returned from parse, but it could be anything.

Hope this helps. The code I gave you was intended to give you a quick way to get basic functionality, but take a look at the device type code in the below thread for GE Link. The Hue Lux should work well with that, you’d just need to keep the fingerprint in your current code.

Thanks again @Sticks18. A couple more questions if you can bare it!

In the code you provided you raised two events:

sendEvent( name: "level", value: i )
sendEvent( name: "switch.setLevel", value: i) //added to help subscribers

Why is the second event necessary? Any subscribers to a level change will be notified so why do we need one for switch.setLevel.

In refresh() we do two "st rattr"s one for switch and one for level, so presumably the results of both of these will be sent to parse(). However in parse() it looks like we are assuming that the “read attr” is for level only. Should we also be handling the one for switch?

Thanks again for helping me through the learning curve on this!

[quote=“JasonBSteele, post:9, topic:9623”]
Why is the second event necessary? Any subscribers to a level change will be notified so why do we need one for switch.setLevel.[/quote]

We added the second one because some SmartApps subscribe to the setLevel event, so this covers both possibilities. In particular, the Dim with Me SmartApp was using the setLevel event. From what I can tell, the level event is mainly used to update the level for this device in the ST app (the slider); whereas setLevel is for SmartApps subscribed. This is probably a gap in the documentation and/or a symptom of an active coding community of various skill levels (I’m only a very basic coder myself, and have little formal education or experience. Mostly just adapt known structures and use any documentation I can find.)

We probably should, but you might be capturing on/off status already via the startsWith("on/off: ") portion of your parse. That’s reading incoming status messages from the bulb and ST’s zigbee implementation is pre-formatting for us. This is where I mentioned that the on/off responses come back with a “catchall” format in my previous post.

For some reason with the way ST handles zigbee bulbs, the level cluster gets configured such that it reports back to us on changes and the messages from that cluster always have the “read attr” prefix. We get two types of messages from the level cluster: 1) size: 0A - is the response when we send a “st rattr” command and is considered a Report Attributes message. 2) size: 08 - is the response when the level changes due to some command and is a Read Reporting Configuration message (you might notice that the level bounces around when you turn on/off. This is because the bulb actually internally adjusts it’s “level” as part of those commands and is sending a report.)

The on/off cluster sends a generic Read Attributes Response after the “st rattr” command, which gets a catchall format. It would look like the below, except you would see C05E instead of 0104 because that’s the profile. Hue uses Light Link, GE uses Home Automation. If you focus on the last grouping of digits, that’s the actual information from the response. The rest is info about the device addressing, cluster, type of message, endpoints, etc… The last group would be separated into 0000 (on/off attribute being read), 00 (successful read), 10 (attribute is an unsigned 8 bit integer format), 01 (attribute value, in this case “on”).
catchall: 0104 0006 01 01 0000 00 E68D 00 00 0000 01 01 0000001001

Here’s a link to the zigbee library info that I’ve been using to learn from and how I broke down that catchall message. Sorry if I gave too much info.

Not at all! Thank you so much for taking the time to share what you have learnt. The link to the GE Bulb and the Zigbee doc are very useful. I should now be armed with enough information to pore over the Live Logging and work out what’s going on!

Hi @Sticks18, in the GE Links implementation at http://community.smartthings.com/t/updated-ge-link-bulbs-finally-getting-on-status-after-manually-turning-on the title implies that you were able to get the ST app to show that the bulb is ON when it has been switched on by a physical switch (rather than an app button or triggered by a SmartApp).

Is this the case?

When I have logging switched on and manually turn the light on I’m not seeing any calls to parse() or configure() so it doesn’t look like I have a chance to update the state of the device.

Yes and no. When the GE is physically switched on it sometimes sends an ‘on/off = on’ message and a level message, so we look for the ‘on’ message. It’s not consistent though, so the main way to update the status is via polling. With polling every minute, it picks up newly switched on bulbs pretty fast.

Sounds like polling is the only option for the hue lux based on your comment. Good luck!

@JasonBSteele, do you have an updated consolidation of your Lux device type code? Just bought some Lux bulbus with the Amazon sale and looking to directly connect them over zigbee. Thanks!

Hi Chris,

This is my currently live one. It still doesn’t handle refreshes that well - let me know if you manage to tweak it any further!

/**

  • Zigbee Hue Lux Bulb
  • Copyright 2015 Jason Steele
  • 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.
  • Thanks all the contributors to the GE Link bulb device handler at
  • http://community.smartthings.com/t/updated-ge-link-bulbs-finally-getting-on-status-after-manually-turning-on
  • and special thanks to to @Sticks18 for all his help with this
    */

metadata {
definition (name: “Zigbee Hue Lux Bulb”, namespace: “JasonBSteele”, author: “Jason Steele”) {
capability “Switch Level”
capability “Actuator”
capability “Switch”
capability “Configuration”
capability “Polling”
capability “Refresh”
capability “Sensor”

  fingerprint profileId: "C05E", inClusters: "0000,0003,0004,0005,0006,0008,1000", outClusters: "0019"

}

// simulator metadata
simulator {
// status messages
status “on”: “on/off: 1”
status “off”: “on/off: 0”

  // reply messages
  reply "zcl on-off on": "on/off: 1"
  reply "zcl on-off off": "on/off: 0"

}

// UI tile definitions
tiles {
standardTile(“switch”, “device.switch”, width: 2, height: 2, canChangeIcon: true) {
state “off”, label: ‘${name}’, action: “switch.on”, icon: “st.switches.light.off”, backgroundColor: “#ffffff
state “on”, label: ‘${name}’, action: “switch.off”, icon: “st.switches.light.on”, backgroundColor: “#79b821
}
standardTile(“refresh”, “device.switch”, inactiveLabel: false, decoration: “flat”) {
state “default”, label:“”, action:“refresh.refresh”, icon:“st.secondary.refresh”
}
controlTile(“levelSliderControl”, “device.level”, “slider”, height: 1, width: 3, inactiveLabel: false) {
state “level”, action:“switch level.setLevel”
}
valueTile(“level”, “device.level”, inactiveLabel: false, decoration: “flat”) {
state “level”, label: ‘Level ${currentValue}%’
}

  main(["switch"])
  details(["switch", "levelSliderControl", "refresh"])

}
}

// Parse incoming device messages to generate events
def parse(String description) {
log.trace “parse(${description})”

def result = null
//Refresh causes catchall: 0104 0006 0B 01 0100 00 5C14 00 00 0000 01 01 0000001001 to be sent if On 
//(and 1000 if Off) and "read attr" for level 
//0000 and 0100 are returned by the On/Off being tapped in the app so we are already raising an event for this
//(however these only get returned when successful so it could be used to only raise the event when bulb is actually powered)
if (description?.startsWith("catchall:")) {
  def x = description[-4..-1]
    switch (x) 
    {
        case "1000":
        	result = createEvent(name: "switch", value: "off")
            break
        case "1001":
        	result = createEvent(name: "switch", value: "on")
            break
        //case "0000":
        //	result = createEvent(name: "switch", value: "off")
        //  break
        //case "0100":
        //	result = createEvent(name: "switch", value: "on")
        //  break
    }
}
else if (description?.startsWith("read attr")) {
  def i = Math.round(convertHexToInt(description[-2..-1]) / 256 * 100 )
    //log.debug "Parse level {$i}" 
  result = createEvent( name: "level", value: i )
    sendEvent( name: "switch.setLevel", value: i) //added to help subscribers

}

else if (description?.startsWith(“on/off:”)) {
//Does the same as sendEvent(name: “switch”, value: “on”) but why?
//Could be more for the benefit of the simulation
//According to Sticks18 this often gets sent when a GE bulb is manually turned on
//This never seems `to happen for the Hue Lux
def value = description?.endsWith(" 1") ? “on” : “off”
result = createEvent(name: “switch”, value: value)
}

//log.debug "parse returned ${result?.descriptionText}"
return result

}

def on() {
// Raise an event to let subscribers know and then tell the device
log.trace “on()”
sendEvent(name: “switch”, value: “on”)
“st cmd 0x${device.deviceNetworkId} ${endpointId} 6 1 {}”
}

def off() {
// Raise an event to let subscribers know and then tell the device
log.trace “off()”
sendEvent(name: “switch”, value: “off”)
“st cmd 0x${device.deviceNetworkId} ${endpointId} 6 0 {}”
}

def refresh() {
//Ask the device to send values for the switch and level
log.trace “refresh()”
[
“st rattr 0x${device.deviceNetworkId} ${endpointId} 6 0”,
“delay 500”,
“st rattr 0x${device.deviceNetworkId} ${endpointId} 8 0”
]
}

def poll(){
log.trace “poll()”
refresh()
}

def setLevel(value) {
log.trace “setLevel($value)”
def cmds =

// If level set to 0 then raise a switch=off event and tell the device
if (value == 0) {
sendEvent(name: “switch”, value: “off”)
cmds << “st cmd 0x${device.deviceNetworkId} ${endpointId} 6 0 {}”
}

// If the level is greater than 0 and the switch is off then raise a switch=on event 

else if (device.latestValue(“switch”) == “off”) {
sendEvent(name: “switch”, value: “on”)
}

// Raise a level=value event and tell the device
sendEvent(name: “level”, value: value)
sendEvent( name: “switch.setLevel”, value: value) //added to help subscribers
def level = new BigInteger(Math.round(value * 255 / 100).toString()).toString(16)
cmds << “st cmd 0x${device.deviceNetworkId} ${endpointId} 8 4 {${level} 0000}”

//log.debug cmds
cmds
}

/*
//Doesn’t seem to be called and doesn’t appear to be necessary
def configure() {
log.trace “configure()”

log.debug “Confuguring Reporting and Bindings.”
def configCmds = [

    //Switch Reporting
    "zcl global send-me-a-report 6 0 0x10 0 3600 {01}", "delay 500",
    "send 0x${device.deviceNetworkId} 1 1", "delay 1000",
    //Level Control Reporting
    "zcl global send-me-a-report 8 0 0x20 5 3600 {0010}", "delay 200",
    "send 0x${device.deviceNetworkId} 1 1", "delay 1500",
    "zdo bind 0x${device.deviceNetworkId} 1 1 6 {${device.zigbeeId}} {}", "delay 1000",
  "zdo bind 0x${device.deviceNetworkId} 1 1 8 {${device.zigbeeId}} {}", "delay 500",

]
return configCmds + refresh() // send refresh cmds as part of config
}
*/

private getEndpointId() {
new BigInteger(device.endpointId, 16).toString()
}

private hex(value, width=2) {
def s = new BigInteger(Math.round(value).toString()).toString(16)
while (s.size() < width) {
s = “0” + s
}
s
}

private Integer convertHexToInt(hex) {
Integer.parseInt(hex,16)
}

2 Likes

Awesome. Thanks man. I ended getting some Cree bulbs too, which have a much more enjoyable color temp and were a hell of a lot easier to join, so the Luxes might be headed back to the store.

Just got a V2 hub and tried this quickly with a Hue Lux (Direct - no Hub) - got errors with device.endpointId conversion - hard coding to 255 (broadcast seemed to work)
Pretty new to Smartthings - Any ideas how to get the actual endpoints ?

@JasonBSteele Where is the IDE you are adding this located? I’m a brand new Smartthings owner, so I’m not familiar yet with where I can write my own scripts like this.

“My Device Types” link on /graph.api.smartthings.com. Is that where I add your script?

This should help. It includes the step-by-step publishing directions for a custom device type.