Philips Hue Groups Control App

So I’ve modified the Philips Hue (Connect) app to control groups instead of individual bulbs.

To create a group you have to use the Philiips Hue Debug Tool, go to http://developers.meethue.com/ and click on the Getting Started and go through the steps to learn to use the Debug Tool.

To create a group using the debug tool, in the URL put “/api/newdeveloper/groups” and in the Message Body put {“name”:“Name of Group”,“lights”:[“1”,“2”,“3”]} and click POST.

If you have any questions just ask.

4 Likes

Do I need to uninstall the “Hue (Connect)” app in order for this app to see the hue bridge?

Yes. This Hue app cannot see the bridge if the Hue (Connect) is installed.

Hey Robert-- followed your instructions and after associating my hub and adding the groups it gives me message saying “error installing your smart app.” not sure what the problem might be…any ideas? Thanks!

How far did you get? Would not install at all?
Did you make a device type for the Hue Bulb? That’s the only thing I can think of that might cause an error.

I got up to the point where you can select the groups. then after hitting done, it gives me the “couldnt install smartapp” error. I did not create a device type for the bulb…how do I do that?

or rather-- i see where i can create a new device type-- what do i specify to create a hue bulb?

Create a new device type and on the top right you will see Device Type Examples, chose the Hue Bulb, copy and paste and hit save.

Hey Rob- still running into an error when installing the smartapp. I’ve created the hue bulb device type and published it to the hub… but still get an error when i go over to my apps and try to run the hue(groups) app (after it finds the hub and stuff, it wont install). if i try to add hue bulb, it tries to use the regular hue (connect) app.

any thoughts?

What is the error saying? Can you install it through the developers page and tell me the error?
Did you also create a my device for the hue bridge (another my device type example)?

seems that the bridge device type was the missing link. thanks!

This is great - thanks so much for making this available! I finally have the ability to turn all the light in the room on / off at once.

One question - can individual lights be added as well? Or do I have to create a group for each light I want to control?

You will have to create a group for each individual light you want to control.

This works great. My Hue Lights work reliable now which was not the case with the standard Smartlabs app. One thing I couldn’t figure out. If you added more groups later, how do you make Hue (Groups) recognize them? If I run the discovery again it only finds my old groups, not new or changed groups. Any ideas?

Hey Rob,

I’m new to Smartthings and trying to get this App up and running.

I’ve created a Device Type for both the Hue Bridge and the Hue Bulb and I’ve created the new SmartApp using your code, but when I get to Bridge Connection point, the app never sees that I pressed the button on the Bridge. I just sits and sits (I waiting about 5 minutes before giving up).

Anything else I could be missing?

Thanks!

Edit: I got it working by taking the current state of the Code for the Hue (Connect) app and updating it to work properly with Groups… here is the new code:

/

**
 *  Hue Service Manager
 *
 *  Author: SmartThings
 */
definition(
	name: "Hue (Group)",
	namespace: "smartthings",
	author: "SmartThings",
	description: "Allows you to connect your Philips Hue lights with SmartThings and control them from your Things area or Dashboard in the SmartThings Mobile app. Adjust colors by going to the Thing detail screen for your Hue lights (tap the gear on Hue tiles).\n\nPlease update your Hue Bridge first, outside of the SmartThings app, using the Philips Hue app.",
	category: "SmartThings Labs",
	iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/hue.png",
	iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/hue@2x.png"
)

preferences {
	page(name:"mainPage", title:"Hue Device Setup", content:"mainPage", refreshTimeout:5)
	page(name:"bridgeDiscovery", title:"Hue Bridge Discovery", content:"bridgeDiscovery", refreshTimeout:5)
	page(name:"bridgeBtnPush", title:"Linking with your Hue", content:"bridgeLinking", refreshTimeout:5)
	page(name:"bulbDiscovery", title:"Hue Device Setup", content:"bulbDiscovery", refreshTimeout:5)
}

def mainPage() {
	if(canInstallLabs()) {
		def bridges = bridgesDiscovered()
		if (state.username && bridges) {
			return bulbDiscovery()
		} else {
			return bridgeDiscovery()
		}
	}
	else
	{
		def upgradeNeeded = """To use SmartThings Labs, your Hub should be completely up to date.

To update your Hub, access Location Settings in the Main Menu (tap the gear next to your location name), select your Hub, and choose "Update Hub"."""

		return dynamicPage(name:"bridgeDiscovery", title:"Upgrade needed!", nextPage:"", install:false, uninstall: true) {
			section("Upgrade") {
				paragraph "$upgradeNeeded"
			}
		}
	}
}

def bridgeDiscovery(params=[:])
{
	def bridges = bridgesDiscovered()
	int bridgeRefreshCount = !state.bridgeRefreshCount ? 0 : state.bridgeRefreshCount as int
	state.bridgeRefreshCount = bridgeRefreshCount + 1
	def refreshInterval = 3

	def options = bridges ?: []
	def numFound = options.size() ?: 0

	if(!state.subscribe) {
		subscribe(location, null, locationHandler, [filterEvents:false])
		state.subscribe = true
	}

	//bridge discovery request every 15 //25 seconds
	if((bridgeRefreshCount % 5) == 0) {
		discoverBridges()
	}

	//setup.xml request every 3 seconds except on discoveries
	if(((bridgeRefreshCount % 1) == 0) && ((bridgeRefreshCount % 5) != 0)) {
		verifyHueBridges()
	}

	return dynamicPage(name:"bridgeDiscovery", title:"Discovery Started!", nextPage:"bridgeBtnPush", refreshInterval:refreshInterval, uninstall: true) {
		section("Please wait while we discover your Hue Bridge. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") {
			input "selectedHue", "enum", required:false, title:"Select Hue Bridge (${numFound} found)", multiple:false, options:options
		}
	}
}

def bridgeLinking()
{
	int linkRefreshcount = !state.linkRefreshcount ? 0 : state.linkRefreshcount as int
	state.linkRefreshcount = linkRefreshcount + 1
	def refreshInterval = 3

	def nextPage = ""
	def title = "Linking with your Hue"
	def paragraphText = "Press the button on your Hue Bridge to setup a link."
	if (state.username) { //if discovery worked
		nextPage = "bulbDiscovery"
		title = "Success! - click 'Next'"
		paragraphText = "Linking to your hub was a success! Please click 'Next'!"
	}

	if((linkRefreshcount % 2) == 0 && !state.username) {
		sendDeveloperReq()
	}

	return dynamicPage(name:"bridgeBtnPush", title:title, nextPage:nextPage, refreshInterval:refreshInterval) {
		section("Button Press") {
			paragraph """${paragraphText}"""
		}
	}
}

def bulbDiscovery()
{
	int bulbRefreshCount = !state.bulbRefreshCount ? 0 : state.bulbRefreshCount as int
	state.bulbRefreshCount = bulbRefreshCount + 1
	def refreshInterval = 3

	def options = bulbsDiscovered() ?: []
	def numFound = options.size() ?: 0

	if((bulbRefreshCount % 3) == 0) {
		discoverHueBulbs()
	}

	return dynamicPage(name:"bulbDiscovery", title:"Group Discovery Started!", nextPage:"", refreshInterval:refreshInterval, install:true, uninstall: true) {
		section("Please wait while we discover your Hue Groups. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") {
			input "selectedBulbs", "enum", required:false, title:"Select Hue Groups (${numFound} found)", multiple:true, options:options
		}
		section {
			def title = bridgeDni ? "Hue bridge (${bridgeHostname})" : "Find bridges"
			href "bridgeDiscovery", title: title, description: "", state: selectedHue ? "complete" : "incomplete", params: [override: true]

		}
	}
}

private discoverBridges()
{
	sendHubCommand(new physicalgraph.device.HubAction("lan discovery urn:schemas-upnp-org:device:basic:1", physicalgraph.device.Protocol.LAN))
}

private sendDeveloperReq()
{
	def token = app.id
	sendHubCommand(new physicalgraph.device.HubAction([
		method: "POST",
		path: "/api",
		headers: [
			HOST: bridgeHostnameAndPort
		],
		body: [devicetype: "$token-0", username: "$token-0"]], bridgeDni))
}

private discoverHueBulbs()
{
	sendHubCommand(new physicalgraph.device.HubAction([
		method: "GET",
		path: "/api/${state.username}/groups",
		headers: [
			HOST: bridgeHostnameAndPort
		]], bridgeDni))
}

private verifyHueBridge(String deviceNetworkId) {
	log.trace "verifyHueBridge($deviceNetworkId)"
	sendHubCommand(new physicalgraph.device.HubAction([
		method: "GET",
		path: "/description.xml",
		headers: [
			HOST: ipAddressFromDni(deviceNetworkId)
		]], deviceNetworkId))
}

private verifyHueBridges() {
	def devices = getHueBridges().findAll { it?.value?.verified != true }
	//log.debug "UNVERIFIED BRIDGES!: $devices"
	devices.each {
		verifyHueBridge((it?.value?.ip + ":" + it?.value?.port))
	}
}

Map bridgesDiscovered() {
	def vbridges = getVerifiedHueBridges()
	def map = [:]
	vbridges.each {
		def value = "${it.value.name}"
		def key = it.value.ip + ":" + it.value.port
		map["${key}"] = value
	}
	map
}

Map bulbsDiscovered() {
	def bulbs =  getHueBulbs()
	def map = [:]
	if (bulbs instanceof java.util.Map) {
		bulbs.each {
			def value = "${it?.value?.name}"
			def key = app.id +"/"+ it?.value?.id
			map["${key}"] = value
		}
	} else { //backwards compatable
		bulbs.each {
			def value = "${it?.name}"
			def key = app.id +"/"+ it?.id
			map["${key}"] = value
		}
	}
	map
}

def getHueBulbs()
{
	state.bulbs = state.bulbs ?: [:]
}

def getHueBridges()
{
	state.bridges = state.bridges ?: [:]
}

def getVerifiedHueBridges()
{
	getHueBridges().findAll{ it?.value?.verified == true }
}

def installed() {
	log.trace "Installed with settings: ${settings}"
	initialize()

	runIn(900, "doDeviceSync" , [overwrite: false]) //setup ip:port syncing every 15 minutes
}

def updated() {
	log.trace "Updated with settings: ${settings}"
	unsubscribe()
	initialize()
}

def initialize() {
	// remove location subscription aftwards
	log.debug "INITIALIZE"
	state.subscribe = false
	state.bridgeSelectedOverride = false

	if (selectedHue) {
		addBridge()
	}
	if (selectedBulbs) {
		addBulbs()
	}

	if (selectedHue) {
		def bridge = getChildDevice(selectedHue)
		subscribe(bridge, "bulbList", bulbListHandler)
	}
}

// Handles events to add new bulbs
def bulbListHandler(evt) {
	def bulbs = [:]
	log.trace "Adding bulbs to state..."
	state.bridgeProcessedLightList = true
	evt.jsonData.each { k,v ->
		log.trace "$k: $v"
		if (v instanceof Map) {
			bulbs[k] = [id: k, name: v.name, type: v.type, hub:evt.value]
		}
	}
	state.bulbs = bulbs
	log.info "${bulbs.size()} bulbs found"
}

def addBulbs() {

	def bulbs = getHueBulbs()
	selectedBulbs.each { dni ->
		def d = getChildDevice(dni)
		if(!d) {
			def newHueBulb
			if (bulbs instanceof java.util.Map) {
				newHueBulb = bulbs.find { (app.id + "/" + it.value.id) == dni }
				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])
				}
			} else { 
            	//backwards compatable
				newHueBulb = bulbs.find { (app.id + "/" + it.id) == dni }
				d = addChildDevice("smartthings", "Hue Bulb", dni, newHueBulb?.hub, ["label":newHueBulb?.name])
			}

			log.debug "created ${d.displayName} with id $dni"
			d.refresh()
		} else {
			log.debug "found ${d.displayName} with id $dni already exists, type: '$d.typeName'"
			if (bulbs instanceof java.util.Map) {
            	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")
				}
			}
		}
	}
}

def addBridge() {
	def vbridges = getVerifiedHueBridges()
	def vbridge = vbridges.find {(it.value.ip + ":" + it.value.port) == selectedHue}

	if(vbridge) {
		def d = getChildDevice(selectedHue)
		if(!d) {
			d = addChildDevice("smartthings", "Hue Bridge", selectedHue, vbridge.value.hub, ["data":["mac": vbridge.value.mac]]) // ["preferences":["ip": vbridge.value.ip, "port":vbridge.value.port, "path":vbridge.value.ssdpPath, "term":vbridge.value.ssdpTerm]]

			log.debug "created ${d.displayName} with id ${d.deviceNetworkId}"

			sendEvent(d.deviceNetworkId, [name: "networkAddress", value: convertHexToIP(vbridge.value.ip) + ":" +  convertHexToInt(vbridge.value.port)])
			sendEvent(d.deviceNetworkId, [name: "serialNumber", value: vbridge.value.serialNumber])
		}
		else
		{
			log.debug "found ${d.displayName} with id $dni already exists"
		}
	}
}


def locationHandler(evt) {
	log.info "LOCATION HANDLER: $evt.description"
	def description = evt.description
	def hub = evt?.hubId

	def parsedEvent = parseEventMessage(description)
	parsedEvent << ["hub":hub]

	if (parsedEvent?.ssdpTerm?.contains("urn:schemas-upnp-org:device:basic:1"))
	{ //SSDP DISCOVERY EVENTS
		log.trace "SSDP DISCOVERY EVENTS"
		def bridges = getHueBridges()

		if (!(bridges."${parsedEvent.ssdpUSN.toString()}"))
		{ //bridge does not exist
			log.trace "Adding bridge ${parsedEvent.ssdpUSN}"
			bridges << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent]
		}
		else
		{ // update the values

			log.debug "Device was already found in state..."

			def d = bridges."${parsedEvent.ssdpUSN.toString()}"
			def host = parsedEvent.ip + ":" + parsedEvent.port
			if(d.ip != parsedEvent.ip || d.port != parsedEvent.port || host != state.hostname) {

				log.debug "Device's port or ip changed..."
				state.hostname = host
				d.ip = parsedEvent.ip
				d.port = parsedEvent.port
				d.name = "Philips hue ($bridgeHostname)"

				app.updateSetting("selectedHue", host)

				childDevices.each {
					if (it.getDeviceDataByName("mac") == parsedEvent.mac) {
						log.debug "updating dni for device ${it} with mac ${parsedEvent.mac}"
						it.setDeviceNetworkId((parsedEvent.ip + ":" + parsedEvent.port)) //could error if device with same dni already exists
					}
				}
			}
		}
	}
	else if (parsedEvent.headers && parsedEvent.body)
	{ // HUE BRIDGE RESPONSES
		log.trace "HUE BRIDGE RESPONSES"
		def headerString = new String(parsedEvent.headers.decodeBase64())
		def bodyString = new String(parsedEvent.body.decodeBase64())
		def type = (headerString =~ /Content-type:.*/) ? (headerString =~ /Content-type:.*/)[0] : null
		def body

		if (type?.contains("xml"))
		{ // description.xml response (application/xml)
			body = new XmlSlurper().parseText(bodyString)

			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"
				}
			}
		}
		else if(type?.contains("json"))
		{ //(application/json)
			body = new groovy.json.JsonSlurper().parseText(bodyString)

			if (body?.success != null)
			{ //POST /api response (application/json)
				if (body?.success?.username)
				{
					state.username = body.success.username[0]
					state.hostname = selectedHue
				}
			}
			else if (body.error != null)
			{
				//TODO: handle retries...
				log.error "ERROR: application/json ${body.error}"
			}
			else
			{ //GET /api/${state.username}/lights response (application/json)
				if (!body?.state?.on) { //check if first time poll made it here by mistake
					def bulbs = getHueBulbs()
					log.debug "Adding bulbs to state!"
					body.each { k,v ->
						bulbs[k] = [id: k, name: v.name, hub:parsedEvent.hub]
					}
				}
			}
		}
	}
	else {
		log.trace "NON-HUE EVENT $evt.description"
	}
}

private def parseEventMessage(Map event) {
	//handles bridge attribute events
	return event
}

private def parseEventMessage(String description) {
/*
	// TODO - replace with stringToMap() when it becomes available to smart apps
	if (description) {
		def pairs = description.split(",")
		def map = pairs.inject([:]) { data, pairString ->
			def pair = pairString.split(':')
			if(pair.size() > 1) {
				data[pair[0].trim()] = pair.size() > 2 ? pair[1..-1].join(':')?.trim() : pair[1]?.trim()
			} else {
				data[pair[0].trim()] = null
			}
			return data
		}
		return map
	}
	else {
		return [:]
	}
	*/
	def event = [:]
	def parts = description.split(',')
	parts.each { part ->
		part = part.trim()
		if (part.startsWith('devicetype:')) {
			def valueString = part.split(":")[1].trim()
			event.devicetype = valueString
		}
		else if (part.startsWith('mac:')) {
			def valueString = part.split(":")[1].trim()
			if (valueString) {
				event.mac = valueString
			}
		}
		else if (part.startsWith('networkAddress:')) {
			def valueString = part.split(":")[1].trim()
			if (valueString) {
				event.ip = valueString
			}
		}
		else if (part.startsWith('deviceAddress:')) {
			def valueString = part.split(":")[1].trim()
			if (valueString) {
				event.port = valueString
			}
		}
		else if (part.startsWith('ssdpPath:')) {
			def valueString = part.split(":")[1].trim()
			if (valueString) {
				event.ssdpPath = valueString
			}
		}
		else if (part.startsWith('ssdpUSN:')) {
			part -= "ssdpUSN:"
			def valueString = part.trim()
			if (valueString) {
				event.ssdpUSN = valueString
			}
		}
		else if (part.startsWith('ssdpTerm:')) {
			part -= "ssdpTerm:"
			def valueString = part.trim()
			if (valueString) {
				event.ssdpTerm = valueString
			}
		}
		else if (part.startsWith('headers')) {
			part -= "headers:"
			def valueString = part.trim()
			if (valueString) {
				event.headers = valueString
			}
		}
		else if (part.startsWith('body')) {
			part -= "body:"
			def valueString = part.trim()
			if (valueString) {
				event.body = valueString
			}
		}
	}

	event
}

def doDeviceSync(){
	log.debug "Doing Hue Device Sync!"
	runIn(900, "doDeviceSync" , [overwrite: false]) //schedule to run again in 15 minutes

	//shrink the large bulb lists
	convertBulbListToMap()

	if(!state.subscribe) {
		subscribe(location, null, locationHandler, [filterEvents:false])
		state.subscribe = true
	}

	discoverBridges()
}



////////////////////////////////////////////
//CHILD DEVICE METHODS
/////////////////////////////////////
def parse(childDevice, description) {
	def parsedEvent = parseEventMessage(description)

	if (parsedEvent.headers && parsedEvent.body) {
		def headerString = new String(parsedEvent.headers.decodeBase64())
		def bodyString = new String(parsedEvent.body.decodeBase64())
		log.debug "parse() - ${bodyString}"

		try {
			def body = new groovy.json.JsonSlurper().parseText(bodyString)

			if (body instanceof java.util.HashMap)
			{ //poll response
				def bulbs = getChildDevices()
				def d = bulbs.find{it.label == body.name}
				if (d) {
					if (body.state) {
						sendEvent(d.deviceNetworkId, [name: "switch", value: body.state.on ? "on" : "off"])
						sendEvent(d.deviceNetworkId, [name: "level", value: Math.round(body.state.bri * 100 / 255)])
						sendEvent(d.deviceNetworkId, [name: "saturation", value: Math.round(body.state.sat * 100 / 255)])
						sendEvent(d.deviceNetworkId, [name: "hue", value: Math.min(Math.round(body.state.hue * 100 / 65535), 65535)])
					}
					else {
						log.warn "BODY DOES NOT CONTAIN STATE PROPERTY"
					}
				}
			}
			else { //put response
				body.each { payload ->
					log.debug $payload
					if (payload?.success) {
						def childDeviceNetworkId = app.id + "/"
						def eventType
						body?.success[0].each { k, v ->
							childDeviceNetworkId += k.split("/")[2]
							eventType = k.split("/")[4]
							log.debug "eventType: $eventType"
							switch (eventType) {
								case "on":
									sendEvent(childDeviceNetworkId, [name: "switch", value: (v == true) ? "on" : "off"])
									break
								case "bri":
									sendEvent(childDeviceNetworkId, [name: "level", value: Math.round(v * 100 / 255)])
									break
								case "sat":
									sendEvent(childDeviceNetworkId, [name: "saturation", value: Math.round(v * 100 / 255)])
									break
								case "hue":
									sendEvent(childDeviceNetworkId, [name: "hue", value: Math.min(Math.round(v * 100 / 65535), 65535)])
									break
							}
						}

					} else if (payload.error) {
						log.debug "JSON error - ${body?.error}"
					}

				}
			}
		}
		catch (Exception ex) {
			log.error "Parse error $ex"
		}
	} else {
		log.debug "parse - got something other than headers,body..."
		return []
	}
}

def on(childDevice) {
	log.debug "Executing 'on'"
	put("groups/${getId(childDevice)}/action", [on: true])
}

def off(childDevice) {
	log.debug "Executing 'off'"
	put("groups/${getId(childDevice)}/action", [on: false])
}

def poll(childDevice) {
	log.debug "Executing 'poll'"
	get("groups/${getId(childDevice)}")
}

def setLevel(childDevice, percent) {
	log.debug "Executing 'setLevel'"
	def level = Math.min(Math.round(percent * 255 / 100), 255)
	put("groups/${getId(childDevice)}/action", [bri: level, on: percent > 0])
}

def setSaturation(childDevice, percent) {
	log.debug "Executing 'setSaturation($percent)'"
	def level = Math.min(Math.round(percent * 255 / 100), 255)
	put("groups/${getId(childDevice)}/action", [sat: level])
}

def setHue(childDevice, percent) {
	log.debug "Executing 'setHue($percent)'"
	def level =	Math.min(Math.round(percent * 65535 / 100), 65535)
	put("groups/${getId(childDevice)}/action", [hue: level])
}

def setColor(childDevice, color) {
	log.debug "Executing 'setColor($color)'"
	def hue =	Math.min(Math.round(color.hue * 65535 / 100), 65535)
	def sat = Math.min(Math.round(color.saturation * 255 / 100), 255)

	def value = [sat: sat, hue: hue]
	if (color.level != null) {
		value.bri = Math.min(Math.round(color.level * 255 / 100), 255)
		value.on = value.bri > 0
	}

	if (color.switch) {
		value.on = color.switch == "on"
	}

	log.debug "sending command $value"
	put("groups/${getId(childDevice)}/action", value)
}

def refresh() {
	log.debug "Executing 'refresh'"
	poll()
}

def 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)
}

/////////////////////////////////////
private getId(childDevice) {
	if (childDevice.device?.deviceNetworkId?.startsWith("HUE")) {
		return childDevice.device?.deviceNetworkId[3..-1]
	}
	else {
		return childDevice.device?.deviceNetworkId.split("/")[-1]
	}
}

private get(path) {
	log.trace "get($path)"
	def uri = "/api/${state.username}/$path"

	sendHubCommand(new physicalgraph.device.HubAction([
		method: "GET",
		path: uri,
		headers: [
			HOST: bridgeHostnameAndPort
		]], bridgeDni))
}

private put(path, body) {
	def uri = "/api/${state.username}/$path"
	def bodyJSON = new groovy.json.JsonBuilder(body).toString()
	def length = bodyJSON.getBytes().size().toString()

	log.debug "PUT:  $uri"
	log.debug "BODY: ${bodyJSON}"

	sendHubCommand(new physicalgraph.device.HubAction([
		method: "PUT",
		path: uri,
		headers: [
			HOST: bridgeHostnameAndPort
		],
		body: body], bridgeDni))
}

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

private String convertHexToIP(hex) {
	[convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
}

private Boolean canInstallLabs()
{
	return hasAllHubsOver("000.011.00603")
}

private Boolean hasAllHubsOver(String desiredFirmware)
{
	return realHubFirmwareVersions.every { fw -> fw >= desiredFirmware }
}

private List getRealHubFirmwareVersions()
{
	return location.hubs*.firmwareVersionString.findAll { it }
}

def convertBulbListToMap() {
	try {
		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]]
			}
			state.bulbs = map
		}
	}
	catch(Exception e) {
		log.error "Caught error attempting to convert bulb list to map: $e"
	}
}

def ipAddressFromDni(dni) {
	if (dni) {
		def segs = dni.split(":")
		convertHexToIP(segs[0]) + ":" +  convertHexToInt(segs[1])
	}
	else {
		null
	}
}

def getBridgeDni() {
	state.hostname
}

def getBridgeHostname() {
	def dni = state.hostname
	if (dni) {
		def segs = dni.split(":")
		convertHexToIP(segs[0])
	}
	else {
		null
	}
}

def getBridgeHostnameAndPort() {
	def result = null
	def dni = state.hostname
	if (dni) {
		def segs = dni.split(":")
		result = convertHexToIP(segs[0]) + ":" +  convertHexToInt(segs[1])
	}
	log.trace "result = $result"
	result
}
4 Likes

Is there any way this app can be made able to discover both groups AND individual bulbs at the same time? According to the API documentation Philips Hue Groups API (login required) the maximum number of groups is 16.

In my setup, I currently have over that number of Hue bulbs, not to mention the several amount of groups I already have. So if we have to create a separate group for an individual bulb, it will be all too easy to go past the limits.

If this app (or another) can detect both groups AND bulbs, then that problem will be solved and open up many more possibilities.

Thanks!

1 Like

Thank you for this! I implemented this tonight, and hope it will solve my reliability problems where automation doesnt turn on/off all the bulbs. By grouping them I hope to solve that issue.

Can someone help me add a Color Loop tile? If you don’t know what I’m talking about look at the “effect” argument in Set Group State: http://www.developers.meethue.com/documentation/groups-api

I don’t know much programing, but here’s a start:

def colorLoopOn(childDevice) {
    log.debug "Executing 'colorLoopOn'"
    put("groups/${getId(childDevice)}/action", [effect: colorloop])
}

def colorLoopOff(childDevice) {
    log.debug "Executing 'colorLoopOff'"
    put("groups/${getId(childDevice)}/action", [effect: none])
}
2 Likes

This is the Hue Groups and Lights that I have been working on. During setup it allows you to select both the groups you want and the bulbs you want to include. There is one small bug that I am working out, it adds each of the groups twice… but you can easily delete the copies. As I work on this I will keep updating my Github: https://github.com/zpriddy/SmartThings

This is the direct link to the Hue Groups and Lights (connect):
https://github.com/zpriddy/SmartThings/blob/master/zp_hue_groups_and_lights_connect.groovy

2 Likes

I finished up my Hue Groups and Lights (connect) app and I have included transition time into it as well.

In order to install this and take advantage of the new API calls you will have to also install my device types for these. They can all be found in my GitHub under the Hue Apps and Devices: https://github.com/zpriddy/SmartThings/tree/master/Hue%20SmartApps%20and%20Devices

I am working on getting the Lux and other Dimmable lights to add as Dimmable lights. But all other bugs have been worked out. If you find anything please let me know!

@Ashwin - I will see what I can do about adding in the color loop to the next version.

https://github.com/zpriddy/SmartThings/blob/master/Hue%20SmartApps%20and%20Devices/zp_hue_groups_and_lights_connect.groovy

5 Likes