Milight / easy bulbs

Hey folks, thanks for the effort that's gone into this.

I can change the colour from the MiLightHub, but not from HE, any ideas why?

At the moment, I've got the device type set to rgbw in HE, which matches the value in MiLightHub, but when I select a new colour from the picker and hit the "Set Colour" button, nothing happens.

On/Off/Night Mode work fine.

If it helps at all, then this is the output when I subscribe to the MQTT topic and select the colour:

{"state":"ON","bulb_mode":"white","color":{"r":255,"g":255,"b":255},"effect":"white_mode","device_id":4369,"group_id":0,"device_type":"rgbw"}

Hmmm, is there anything in the HE logs?

@worbyb did the colour bulb code, perhaps they can help out troubleshooting?

Hmmm, looks like the colour parameters aren't being set:

[dev:18](http://192.168.1.174/logs#dev18)2020-03-28 06:47:57.441 [debug](http://192.168.1.174/device/edit/18)[success:true]

[dev:18](http://192.168.1.174/logs#dev18)2020-03-28 06:47:57.364 [debug](http://192.168.1.174/device/edit/18)postParams [uri:http://192.168.1.7/gateways/0x1111/rgbw/0, contentType:application/json, requestContentType:application/json, body:[status:on, color:[r:0, g:0, b:0]]]

[dev:18](http://192.168.1.174/logs#dev18)2020-03-28 06:47:57.360 [debug](http://192.168.1.174/device/edit/18)http://192.168.1.7/gateways/0x1111/rgbw/0

[dev:18](http://192.168.1.174/logs#dev18)2020-03-28 06:47:57.357 [debug](http://192.168.1.174/device/edit/18)hsvToRGB [red:0, green:0, blue:0]

[dev:18](http://192.168.1.174/logs#dev18)2020-03-28 06:47:57.342 [warn](http://192.168.1.174/device/edit/18)setColor

OK, more logging when trying to control the device from the device list entry:

[dev:18](http://192.168.1.174/logs#dev18)2020-03-28 12:41:42.430 [error](http://192.168.1.174/device/edit/18)groovy.lang.MissingMethodException: No signature of method: user_driver_community_MiLight_Light_323.setSaturation() is applicable for argument types: (java.math.BigDecimal) values: [91] (setSaturation)
[dev:18](http://192.168.1.174/logs#dev18)2020-03-28 12:41:26.018 [error](http://192.168.1.174/device/edit/18)groovy.lang.MissingMethodException: No signature of method: user_driver_community_MiLight_Light_323.setHue() is applicable for argument types: (java.math.BigDecimal) values: [65] Possible solutions: setLevel(java.lang.Object), getAt(java.lang.String) (setHue)
[dev:18](http://192.168.1.174/logs#dev18)2020-03-28 12:41:09.312 [debug](http://192.168.1.174/device/edit/18)[success:true]
[dev:18](http://192.168.1.174/logs#dev18)2020-03-28 12:41:09.172 [debug](http://192.168.1.174/device/edit/18)postParams [uri:http://192.168.1.7/gateways/0x1111/rgbw/0, contentType:application/json, requestContentType:application/json, body:[status:on, color:[r:0, g:0, b:0]]]
[dev:18](http://192.168.1.174/logs#dev18)2020-03-28 12:41:09.168 [debug](http://192.168.1.174/device/edit/18)http://192.168.1.7/gateways/0x1111/rgbw/0
[dev:18](http://192.168.1.174/logs#dev18)2020-03-28 12:41:09.165 [debug](http://192.168.1.174/device/edit/18)hsvToRGB [red:0, green:0, blue:0]
[dev:18](http://192.168.1.174/logs#dev18)2020-03-28 12:41:09.161 [warn](http://192.168.1.174/device/edit/18)setColor
[dev:18](http://192.168.1.174/logs#dev18)2020-03-28 12:41:06.755 [debug](http://192.168.1.174/device/edit/18)[success:true]
[dev:18](http://192.168.1.174/logs#dev18)2020-03-28 12:41:06.619 [debug](http://192.168.1.174/device/edit/18)http://192.168.1.7/gateways/0x1111/rgbw/0

The On/Off switch resets the colour to white, then the errors occur when I try to set the hue/saturation directly.

The values from the "SetColour" box don't seem to get passed at all.

Found it - there was a bug on line 40 where the code tried to use a variable called hueMax, but that variable wasn't set to anything.

This meant that the Hue always defaulted to zero, so the maths didn't work.

If you "force" that value to 100 like all the other values, it works.

I've patched and tested locally, and put in a PR at hueMax was undefined, therefore the hue level was always 0 by proffalken · Pull Request #2 · cometfish/hubitat_driver_esp8266milighthub · GitHub to get this into the main code.

Thanks for the fix & PR - I've merged it in :smiley:

1 Like

Nice one, thanks!

I haven’t purchased any lights or anything yet, but looking for a hard light to find, and miLights has the answer.
Looking at doing the ESP8266 solution.
Is there full support for the RGB CCT? And what do you use on a dashboard, or do you just use the miLight app to change the colors and do on/off on a tile? I quick set up a color bulb tile with this driver, and didn’t look like it had all the options that should have.

And finally I believe I read you can still use remotes, like the 8266 impersonates the remotes, so you just pair to the remote and then impersonate? And multiple remotes?

Can you use the miLights phone app with the 8266 solution? Or is that ONLY for their WiFi boxes? I saw that the 8266 code duplicated the original UDP code so that original things would work.

Thanks for all the help before I leap in on a couple hundred bucks in lights.

I've never used the miLights app, so I don't know if it works with the ESP too.

Yes you can still use remotes, multiple ones too.

Honestly if you haven't already got miLights, I'd say go with Zigbee lights instead, which work natively with Hubitat, no extra hub required.

Hello @cometfish, I don't know if you still frequent the forums or if somebody else might have an answer. Sorry for dredging up an old thread. It had the most related information I could find.

I setup an ESP controller and am able to connect a MiLight to Hubitat. The web interface works great:

I'm able to control both RGB and CCT functions. When I setup the device in Hubitat there isn't a box for setting the color temperature; and the hue saturation controls don't work. I can set color using the color map control, also night mode, on, off, and level all work.

In the logs I see an error when I try to set the Hue.

Clicking on color temperature sends some commands but it is confused as there isn't an input field.

Any suggestions on getting this to work?

It looks like the driver is out of date. I know nothing about writing drivers for HE but I have managed to hack the existing driver a bit so that I can set it from the device or as a custom action in rule machine. It isn't pretty but the basics are there. Now to pair 29 more lights.

RM with hacked driver:

image

Not exciting but it proves the point.

Hi @lairdknox , I didn't write the colour code part - if you scroll up a few posts there were a couple of others who contributed it.
Anyway glad you got it fixed - if you'd like to send me your changes (github pull request, or PM) I can merge them in :slightly_smiling_face:

Right now it is a hack. I don't know what I'm doing with drivers. If I delete some of the parameters from the command section it shows the text box and allows you to enter a number.

//command "setColorTemperature", [[name: "ColorTemperature *", type: "number", description: "3000 for Warm white, up to 6500 for Cool white", constraints:[]]]
		
command "setColorTemperature", [[type: "number"]]

Right now I don't understand where the hue and saturation controls are coming from so I don't know why they are failing.

The set color temperature also doesn't show up in rule machine. You have to use the custom attribute action to access it. So there's definitely not anything to add to the project yet.

I'll let you know if I figure anything out but I need to dive into drivers in general to understand what's going on. Right now even though it is kind of hacky I can work with it. It just boils down to how much time I want to spend on it. I have 30 GU10 MiLights in the living room that have been off for about two years. :wink:

OK, so removing the command and adding capability "ColorTemperature" creates a working control on the device screen. It allows you to change the temperature but it doesn't handle the level and duration fields. That should just be a matter of creating the correct def.

This also allows the device to show up in the normal RM set color temperature action. It fails to execute but that's the same issue as above. So now to see if I can sort out the function.

OK, that wasn't as bad as I expected. If you remove the color temperature command and add it as a capability instead you will get a working control. Then I extended the function to handle the three parameters.

At the end you can see it call the level function. I didn't bother to combine them.

def setColorTemperature(temp, level = null, tt = null) {
    if (logEnable) log.debug "Sending color temperature request"

    //limitlessled bulbs range from 3000-6500K, 100 to 0
	if (temp<3000)
        temp = 3000
    else if (temp>6500)
	    temp = 6500
    lltemp = 100 - Math.round(((temp - 3000)/(6500-3000))* 100)
	if (logEnable) log.debug lltemp
    try {
		def url = "http://" + settings.ipAddress + "/gateways/" + settings.hubID + "/"+settings.lightType+"/" + settings.lightID
		if (logEnable) log.debug url
        def postParams = [
            uri: url,
			contentType: "application/json",
			requestContentType: "application/json",
            body : ["temperature": lltemp]
        ]
    
        httpPost(postParams) { resp ->
		    if (resp.success) {
                sendEvent(name: "colorTemperature", value: temp, isStateChange: true)
				
            }
            if (logEnable)
                if (resp.data) log.debug "${resp.data}"
        }
    } catch (Exception e) {
        log.warn "Call to colorTemperature failed: ${e.message} ${temp} "
    }
	if (level != null) {
		setLevel(level)
	}
}

Now to tackle the color code.

Well good grief, what a difference a good night's sleep makes. Looking at another driver I got it sorted out. Everything is working except transition time. I don't believe the MiLight bulbs support it. In any case you can enter a time and it won't crash the driver, it will just disregard it.

I'm not fami

/*
 * ESP8266_MiLight_Hub driver
 *
 * Controls MiLight bulbs via the ESP8266 MiLight Hub by sidoh (https://github.com/sidoh/esp8266_milight_hub)
 * 
 */
metadata {
    definition(name: "MiLight Light", namespace: "community", author: "cometfish", importUrl: "https://raw.githubusercontent.com/cometfish/hubitat_driver_esp8266milighthub/master/lightbulbdriver.groovy") {
        capability "Actuator"
		capability "Bulb"
        capability "Switch"
		capability "SwitchLevel"
        capability "Light"
		capability "ColorControl"
		capability "ColorTemperature"
		
		attribute "nightMode", "enum", ["on", "off"]
		attribute "level", "number"
		attribute "colorTemperature", "number" 
						
		command "nightMode"
    }
}

preferences {
    section("URIs") {
        input "ipAddress", "text", title: "ESP8266 Hub IP Address", required: true
	    input "hubID", "text", title: "Hub ID (eg. 0xFFFF)", required: true
        input "lightID", "text", title: "Light Group ID", required: true
        input "lightType", "text", title: "Light Type", required: true
        input name: "logEnable", type: "bool", title: "Enable debug logging", defaultValue: true
    }
}

def hsvToRGB(float conversionHue = 0, float conversionSaturation = 100, float conversionValue = 100){
    // Accepts conversionHue (0-100), conversionSaturation (0-100), and conversionValue (0-100)
    // Returns RGB map ([ red: 0-255, green: 0-255, blue: 0-255 ])
    
    // Check HSV limits
    conversionHue > 100 ? ( conversionHue = 1 ) : ( conversionHue < 0 ? ( conversionHue = 0 ) : ( conversionHue /= 100 ) )
    conversionSaturation > 100 ? ( conversionSaturation = 1 ) : ( conversionSaturation < 0 ? ( conversionSaturation = 0 ) : ( conversionSaturation /= 100 ) )
    conversionValue > 100 ? ( conversionValue = 1 ) : ( conversionValue < 0 ? ( conversionValue = 0 ) : ( conversionValue /= 100 ) ) 
        
    int h = (int)(conversionHue * 6);
    float f = conversionHue * 6 - h;
    float p = conversionValue * (1 - conversionSaturation);
    float q = conversionValue * (1 - f * conversionSaturation);
    float t = conversionValue * (1 - (1 - f) * conversionSaturation);
    
    conversionValue *= 255
    f *= 255
    p *= 255
    q *= 255
    t *= 255
            
    if      (h==0) { rgbMap = [red: conversionValue, green: t, blue: p] }
    else if (h==1) { rgbMap = [red: q, green: conversionValue, blue: p] }
    else if (h==2) { rgbMap = [red: p, green: conversionValue, blue: t] }
    else if (h==3) { rgbMap = [red: p, green: q, blue: conversionValue] }
    else if (h==4) { rgbMap = [red: t, green: p, blue: conversionValue] }
    else if (h==5) { rgbMap = [red: conversionValue, green: p,blue: q]  }
    else           { rgbMap = [red: 0, green: 0, blue: 0] }

    return rgbMap
}

void setHue(value) {
	if (logEnable) log.debug "setHue($value)"
	setColor([hue: value, saturation: 100, level: device.currentValue("level")])
}

void setSaturation(percent) {
	if (logEnable) log.debug "setSaturation($percent)"
	setColor([saturation: percent, hue: device.currentValue("hue"), level: device.currentValue("level")])
}

def setColor(value){
    if (value.hue == null || value.saturation == null)
        return

    if (logEnable)
        log.warn "setColor"
    
	rgbColors = hsvToRGB(value.hue, value.saturation, value.level)
	
    if (logEnable)
        log.debug "hsvToRGB ${rgbColors}"

    try {
		def url = "http://" + settings.ipAddress + "/gateways/" + settings.hubID + "/" + settings.lightType + "/" + settings.lightID
		if (logEnable) log.debug url
        def postParams = [
            uri: url,
			contentType: "application/json",
			requestContentType: "application/json",
			body : ["status": "on","color":["r":rgbColors['red'],"g":rgbColors['green'],"b":rgbColors['blue']]]
        ]

        if (logEnable)
		    log.debug "postParams ${postParams}"
    
        httpPost(postParams) { resp ->
		    if (resp.success) {
                sendEvent(name: "hue", value: value.hue, isStateChange: true)
				sendEvent(name: "saturation", value: value.saturation, isStateChange: true)
                if (value.level)
                    sendEvent(name: "level", value: value.level, isStateChange: true)
            }
            if (logEnable)
                if (resp.data) log.debug "${resp.data}"
        }
    } catch (Exception e) {
        log.warn "Call to setColor failed: ${e.message}"
    }  
}

def updated() {
    log.info "updated..."
    log.warn "debug logging is: ${logEnable == true}"
}

def parse(String description) {
    if (logEnable) log.debug(description)
}

def on() {
    if (logEnable) log.debug "Sending on request"

    try {
		def url = "http://" + settings.ipAddress + "/gateways/" + settings.hubID + "/"+settings.lightType+"/" + settings.lightID
		if (logEnable) log.debug url
        def postParams = [
            uri: url,
			contentType: "application/json",
			requestContentType: "application/json",
			body : ["status": "on"]
        ]
    
        httpPost(postParams) { resp ->
		    if (resp.success) {
                sendEvent(name: "switch", value: "on", isStateChange: true)
				sendEvent(name: "nightMode", value: "off", isStateChange: true)
            }
            if (logEnable)
                if (resp.data) log.debug "${resp.data}"
        }
    } catch (Exception e) {
        log.warn "Call to on failed: ${e.message}"
    }
}

def off() {
    if (logEnable) log.debug "Sending off request"

    try {
		def url = "http://" + settings.ipAddress + "/gateways/" + settings.hubID + "/"+settings.lightType+"/" + settings.lightID
		if (logEnable) log.debug url
        def postParams = [
            uri: url,
			contentType: "application/json",
			requestContentType: "application/json",
            body : ["status": "off"]
        ]
    
        httpPost(postParams) { resp ->
		    if (resp.success) {
                sendEvent(name: "switch", value: "off", isStateChange: true)
				sendEvent(name: "nightMode", value: "off", isStateChange: true)
            }
            if (logEnable)
                if (resp.data) log.debug "${resp.data}"
        }
    } catch (Exception e) {
        log.warn "Call to off failed: ${e.message}"
    }
}

def nightMode() {
    if (logEnable) log.debug "Sending night mode request"

    try {
		def url = "http://" + settings.ipAddress + "/gateways/" + settings.hubID + "/"+settings.lightType+"/" + settings.lightID
		if (logEnable) log.debug url
        def postParams = [
            uri: url,
			contentType: "application/json",
			requestContentType: "application/json",
            body : ["command": "night_mode" ]
        ]
		
        httpPost(postParams) { resp ->
		    if (resp.success) {
                sendEvent(name: "switch", value: "off", isStateChange: true)
				sendEvent(name: "nightMode", value: "on", isStateChange: true)
            }
            if (logEnable)
                if (resp.data) log.debug "${resp.data}"
        }
    } catch (Exception e) {
        log.warn "Call to nightmode failed: ${e.message}"
    }
}


def setLevel(level, tt = null) {
    if (logEnable) log.debug "Sending level request"

    try {
		def url = "http://" + settings.ipAddress + "/gateways/" + settings.hubID + "/"+settings.lightType+"/" + settings.lightID
		if (logEnable) log.debug url
        def postParams = [
            uri: url,
			contentType: "application/json",
			requestContentType: "application/json",
            body : ["level": level]
        ]
    
        httpPost(postParams) { resp ->
		    if (resp.success) {
                sendEvent(name: "level", value: level, isStateChange: true)
				
            }
            if (logEnable)
                if (resp.data) log.debug "${resp.data}"
        }
    } catch (Exception e) {
        log.warn "Call to level failed: ${e.message} ${level} "
    }
}

def setColorTemperature(temp, level = null, tt = null) {
    if (logEnable) log.debug "Sending color temperature request"

    //limitlessled bulbs range from 3000-6500K, 100 to 0
	if (temp<3000)
        temp = 3000
    else if (temp>6500)
	    temp = 6500
    lltemp = 100 - Math.round(((temp - 3000)/(6500-3000))* 100)
	if (logEnable) log.debug lltemp
    try {
		def url = "http://" + settings.ipAddress + "/gateways/" + settings.hubID + "/"+settings.lightType+"/" + settings.lightID
		if (logEnable) log.debug url
        def postParams = [
            uri: url,
			contentType: "application/json",
			requestContentType: "application/json",
            body : ["temperature": lltemp]
        ]
    
        httpPost(postParams) { resp ->
		    if (resp.success) {
                sendEvent(name: "colorTemperature", value: temp, isStateChange: true)
				
            }
            if (logEnable)
                if (resp.data) log.debug "${resp.data}"
        }
    } catch (Exception e) {
        log.warn "Call to colorTemperature failed: ${e.message} ${temp} "
    }
	if (level != null) {
		setLevel(level)
	}
}
/*
 * ESP8266_MiLight_Hub driver
 *
 * Controls MiLight bulbs via the ESP8266 MiLight Hub by sidoh (https://github.com/sidoh/esp8266_milight_hub)
 * 
 */
metadata {
    definition(name: "MiLight Light", namespace: "community", author: "cometfish", importUrl: "https://raw.githubusercontent.com/cometfish/hubitat_driver_esp8266milighthub/master/lightbulbdriver.groovy") {
        capability "Actuator"
		capability "Bulb"
        capability "Switch"
		capability "SwitchLevel"
        capability "Light"
		capability "ColorControl"
		capability "ColorTemperature"
		
		attribute "nightMode", "enum", ["on", "off"]
		attribute "level", "number"
		attribute "colorTemperature", "number" 
						
		command "nightMode"
    }
}

preferences {
    section("URIs") {
        input "ipAddress", "text", title: "ESP8266 Hub IP Address", required: true
	    input "hubID", "text", title: "Hub ID (eg. 0xFFFF)", required: true
        input "lightID", "text", title: "Light Group ID", required: true
        input "lightType", "text", title: "Light Type", required: true
        input name: "logEnable", type: "bool", title: "Enable debug logging", defaultValue: true
    }
}

def hsvToRGB(float conversionHue = 0, float conversionSaturation = 100, float conversionValue = 100){
    // Accepts conversionHue (0-100), conversionSaturation (0-100), and conversionValue (0-100)
    // Returns RGB map ([ red: 0-255, green: 0-255, blue: 0-255 ])
    
    // Check HSV limits
    conversionHue > 100 ? ( conversionHue = 1 ) : ( conversionHue < 0 ? ( conversionHue = 0 ) : ( conversionHue /= 100 ) )
    conversionSaturation > 100 ? ( conversionSaturation = 1 ) : ( conversionSaturation < 0 ? ( conversionSaturation = 0 ) : ( conversionSaturation /= 100 ) )
    conversionValue > 100 ? ( conversionValue = 1 ) : ( conversionValue < 0 ? ( conversionValue = 0 ) : ( conversionValue /= 100 ) ) 
        
    int h = (int)(conversionHue * 6);
    float f = conversionHue * 6 - h;
    float p = conversionValue * (1 - conversionSaturation);
    float q = conversionValue * (1 - f * conversionSaturation);
    float t = conversionValue * (1 - (1 - f) * conversionSaturation);
    
    conversionValue *= 255
    f *= 255
    p *= 255
    q *= 255
    t *= 255
            
    if      (h==0) { rgbMap = [red: conversionValue, green: t, blue: p] }
    else if (h==1) { rgbMap = [red: q, green: conversionValue, blue: p] }
    else if (h==2) { rgbMap = [red: p, green: conversionValue, blue: t] }
    else if (h==3) { rgbMap = [red: p, green: q, blue: conversionValue] }
    else if (h==4) { rgbMap = [red: t, green: p, blue: conversionValue] }
    else if (h==5) { rgbMap = [red: conversionValue, green: p,blue: q]  }
    else           { rgbMap = [red: 0, green: 0, blue: 0] }

    return rgbMap
}

void setHue(value) {
	if (logEnable) log.debug "setHue($value)"
	setColor([hue: value, saturation: 100, level: device.currentValue("level")])
}

void setSaturation(percent) {
	if (logEnable) log.debug "setSaturation($percent)"
	setColor([saturation: percent, hue: device.currentValue("hue"), level: device.currentValue("level")])
}

def setColor(value){
    if (value.hue == null || value.saturation == null)
        return

    if (logEnable)
        log.warn "setColor"
    
	rgbColors = hsvToRGB(value.hue, value.saturation, value.level)
	
    if (logEnable)
        log.debug "hsvToRGB ${rgbColors}"

    try {
		def url = "http://" + settings.ipAddress + "/gateways/" + settings.hubID + "/" + settings.lightType + "/" + settings.lightID
		if (logEnable) log.debug url
        def postParams = [
            uri: url,
			contentType: "application/json",
			requestContentType: "application/json",
			body : ["status": "on","color":["r":rgbColors['red'],"g":rgbColors['green'],"b":rgbColors['blue']]]
        ]

        if (logEnable)
		    log.debug "postParams ${postParams}"
    
        httpPost(postParams) { resp ->
		    if (resp.success) {
                sendEvent(name: "hue", value: value.hue, isStateChange: true)
				sendEvent(name: "saturation", value: value.saturation, isStateChange: true)
                if (value.level)
                    sendEvent(name: "level", value: value.level, isStateChange: true)
            }
            if (logEnable)
                if (resp.data) log.debug "${resp.data}"
        }
    } catch (Exception e) {
        log.warn "Call to setColor failed: ${e.message}"
    }  
}

def updated() {
    log.info "updated..."
    log.warn "debug logging is: ${logEnable == true}"
}

def parse(String description) {
    if (logEnable) log.debug(description)
}

def on() {
    if (logEnable) log.debug "Sending on request"

    try {
		def url = "http://" + settings.ipAddress + "/gateways/" + settings.hubID + "/"+settings.lightType+"/" + settings.lightID
		if (logEnable) log.debug url
        def postParams = [
            uri: url,
			contentType: "application/json",
			requestContentType: "application/json",
			body : ["status": "on"]
        ]
    
        httpPost(postParams) { resp ->
		    if (resp.success) {
                sendEvent(name: "switch", value: "on", isStateChange: true)
				sendEvent(name: "nightMode", value: "off", isStateChange: true)
            }
            if (logEnable)
                if (resp.data) log.debug "${resp.data}"
        }
    } catch (Exception e) {
        log.warn "Call to on failed: ${e.message}"
    }
}

def off() {
    if (logEnable) log.debug "Sending off request"

    try {
		def url = "http://" + settings.ipAddress + "/gateways/" + settings.hubID + "/"+settings.lightType+"/" + settings.lightID
		if (logEnable) log.debug url
        def postParams = [
            uri: url,
			contentType: "application/json",
			requestContentType: "application/json",
            body : ["status": "off"]
        ]
    
        httpPost(postParams) { resp ->
		    if (resp.success) {
                sendEvent(name: "switch", value: "off", isStateChange: true)
				sendEvent(name: "nightMode", value: "off", isStateChange: true)
            }
            if (logEnable)
                if (resp.data) log.debug "${resp.data}"
        }
    } catch (Exception e) {
        log.warn "Call to off failed: ${e.message}"
    }
}

def nightMode() {
    if (logEnable) log.debug "Sending night mode request"

    try {
		def url = "http://" + settings.ipAddress + "/gateways/" + settings.hubID + "/"+settings.lightType+"/" + settings.lightID
		if (logEnable) log.debug url
        def postParams = [
            uri: url,
			contentType: "application/json",
			requestContentType: "application/json",
            body : ["command": "night_mode" ]
        ]
		
        httpPost(postParams) { resp ->
		    if (resp.success) {
                sendEvent(name: "switch", value: "off", isStateChange: true)
				sendEvent(name: "nightMode", value: "on", isStateChange: true)
            }
            if (logEnable)
                if (resp.data) log.debug "${resp.data}"
        }
    } catch (Exception e) {
        log.warn "Call to nightmode failed: ${e.message}"
    }
}


def setLevel(level, tt = null) {
    if (logEnable) log.debug "Sending level request"

    try {
		def url = "http://" + settings.ipAddress + "/gateways/" + settings.hubID + "/"+settings.lightType+"/" + settings.lightID
		if (logEnable) log.debug url
        def postParams = [
            uri: url,
			contentType: "application/json",
			requestContentType: "application/json",
            body : ["level": level]
        ]
    
        httpPost(postParams) { resp ->
		    if (resp.success) {
                sendEvent(name: "level", value: level, isStateChange: true)
				
            }
            if (logEnable)
                if (resp.data) log.debug "${resp.data}"
        }
    } catch (Exception e) {
        log.warn "Call to level failed: ${e.message} ${level} "
    }
}

def setColorTemperature(temp, level = null, tt = null) {
    if (logEnable) log.debug "Sending color temperature request"

    //limitlessled bulbs range from 3000-6500K, 100 to 0
	if (temp<3000)
        temp = 3000
    else if (temp>6500)
	    temp = 6500
    lltemp = 100 - Math.round(((temp - 3000)/(6500-3000))* 100)
	if (logEnable) log.debug lltemp
    try {
		def url = "http://" + settings.ipAddress + "/gateways/" + settings.hubID + "/"+settings.lightType+"/" + settings.lightID
		if (logEnable) log.debug url
        def postParams = [
            uri: url,
			contentType: "application/json",
			requestContentType: "application/json",
            body : ["temperature": lltemp]
        ]
    
        httpPost(postParams) { resp ->
		    if (resp.success) {
                sendEvent(name: "colorTemperature", value: temp, isStateChange: true)
				
            }
            if (logEnable)
                if (resp.data) log.debug "${resp.data}"
        }
    } catch (Exception e) {
        log.warn "Call to colorTemperature failed: ${e.message} ${temp} "
    }
	if (level != null) {
		setLevel(level)
	}
}

I just setup git and forked the repo but I'm not sure where to proceed to checkout and edit the files. I have some other priorities today so you may use the above code or I'll work on doing a proper pull later.

Added on() and off() calls at the top and bottom of the setLevel function.

If the lights are off then the set level will not turn them on and they won't respond. This makes sure they are on before setting the level.

If the level is set to 0 the lights do not completely turn off. The addition of the off() at the end makes sure they go out. It is at the end to allow the lights to fade down rather than just blink off. You can still use a discrete off command to turn them off without fading.

I discovered this behavior while testing the first ten lights with my automation. They do have a popcorn effect when you are controlling multiple lights but I can live with that.

/*
 * ESP8266_MiLight_Hub driver
 *
 * Controls MiLight bulbs via the ESP8266 MiLight Hub by sidoh (https://github.com/sidoh/esp8266_milight_hub)
 * 
 */
metadata {
    definition(name: "MiLight Light", namespace: "community", author: "cometfish", importUrl: "https://raw.githubusercontent.com/cometfish/hubitat_driver_esp8266milighthub/master/lightbulbdriver.groovy") {
        capability "Actuator"
		capability "Bulb"
        capability "Switch"
		capability "SwitchLevel"
        capability "Light"
		capability "ColorControl"
		capability "ColorTemperature"
		
		attribute "nightMode", "enum", ["on", "off"]
		attribute "level", "number"
		attribute "colorTemperature", "number" 
						
		command "nightMode"
    }
}

preferences {
    section("URIs") {
        input "ipAddress", "text", title: "ESP8266 Hub IP Address", required: true
	    input "hubID", "text", title: "Hub ID (eg. 0xFFFF)", required: true
        input "lightID", "text", title: "Light Group ID", required: true
        input "lightType", "text", title: "Light Type", required: true
        input name: "logEnable", type: "bool", title: "Enable debug logging", defaultValue: true
    }
}

def hsvToRGB(float conversionHue = 0, float conversionSaturation = 100, float conversionValue = 100){
    // Accepts conversionHue (0-100), conversionSaturation (0-100), and conversionValue (0-100)
    // Returns RGB map ([ red: 0-255, green: 0-255, blue: 0-255 ])
    
    // Check HSV limits
    conversionHue > 100 ? ( conversionHue = 1 ) : ( conversionHue < 0 ? ( conversionHue = 0 ) : ( conversionHue /= 100 ) )
    conversionSaturation > 100 ? ( conversionSaturation = 1 ) : ( conversionSaturation < 0 ? ( conversionSaturation = 0 ) : ( conversionSaturation /= 100 ) )
    conversionValue > 100 ? ( conversionValue = 1 ) : ( conversionValue < 0 ? ( conversionValue = 0 ) : ( conversionValue /= 100 ) ) 
        
    int h = (int)(conversionHue * 6);
    float f = conversionHue * 6 - h;
    float p = conversionValue * (1 - conversionSaturation);
    float q = conversionValue * (1 - f * conversionSaturation);
    float t = conversionValue * (1 - (1 - f) * conversionSaturation);
    
    conversionValue *= 255
    f *= 255
    p *= 255
    q *= 255
    t *= 255
            
    if      (h==0) { rgbMap = [red: conversionValue, green: t, blue: p] }
    else if (h==1) { rgbMap = [red: q, green: conversionValue, blue: p] }
    else if (h==2) { rgbMap = [red: p, green: conversionValue, blue: t] }
    else if (h==3) { rgbMap = [red: p, green: q, blue: conversionValue] }
    else if (h==4) { rgbMap = [red: t, green: p, blue: conversionValue] }
    else if (h==5) { rgbMap = [red: conversionValue, green: p,blue: q]  }
    else           { rgbMap = [red: 0, green: 0, blue: 0] }

    return rgbMap
}

void setHue(value) {
	if (logEnable) log.debug "setHue($value)"
	setColor([hue: value, saturation: 100, level: device.currentValue("level")])
}

void setSaturation(percent) {
	if (logEnable) log.debug "setSaturation($percent)"
	setColor([saturation: percent, hue: device.currentValue("hue"), level: device.currentValue("level")])
}

def setColor(value){
    if (value.hue == null || value.saturation == null)
        return

    if (logEnable)
        log.warn "setColor"
    
	rgbColors = hsvToRGB(value.hue, value.saturation, value.level)
	
    if (logEnable)
        log.debug "hsvToRGB ${rgbColors}"

    try {
		def url = "http://" + settings.ipAddress + "/gateways/" + settings.hubID + "/" + settings.lightType + "/" + settings.lightID
		if (logEnable) log.debug url
        def postParams = [
            uri: url,
			contentType: "application/json",
			requestContentType: "application/json",
			body : ["status": "on","color":["r":rgbColors['red'],"g":rgbColors['green'],"b":rgbColors['blue']]]
        ]

        if (logEnable)
		    log.debug "postParams ${postParams}"
    
        httpPost(postParams) { resp ->
		    if (resp.success) {
                sendEvent(name: "hue", value: value.hue, isStateChange: true)
				sendEvent(name: "saturation", value: value.saturation, isStateChange: true)
                if (value.level)
                    sendEvent(name: "level", value: value.level, isStateChange: true)
            }
            if (logEnable)
                if (resp.data) log.debug "${resp.data}"
        }
    } catch (Exception e) {
        log.warn "Call to setColor failed: ${e.message}"
    }  
}

def updated() {
    log.info "updated..."
    log.warn "debug logging is: ${logEnable == true}"
}

def parse(String description) {
    if (logEnable) log.debug(description)
}

def on() {
    if (logEnable) log.debug "Sending on request"

    try {
		def url = "http://" + settings.ipAddress + "/gateways/" + settings.hubID + "/"+settings.lightType+"/" + settings.lightID
		if (logEnable) log.debug url
        def postParams = [
            uri: url,
			contentType: "application/json",
			requestContentType: "application/json",
			body : ["status": "on"]
        ]
    
        httpPost(postParams) { resp ->
		    if (resp.success) {
                sendEvent(name: "switch", value: "on", isStateChange: true)
				sendEvent(name: "nightMode", value: "off", isStateChange: true)
            }
            if (logEnable)
                if (resp.data) log.debug "${resp.data}"
        }
    } catch (Exception e) {
        log.warn "Call to on failed: ${e.message}"
    }
}

def off() {
    if (logEnable) log.debug "Sending off request"

    try {
		def url = "http://" + settings.ipAddress + "/gateways/" + settings.hubID + "/"+settings.lightType+"/" + settings.lightID
		if (logEnable) log.debug url
        def postParams = [
            uri: url,
			contentType: "application/json",
			requestContentType: "application/json",
            body : ["status": "off"]
        ]
    
        httpPost(postParams) { resp ->
		    if (resp.success) {
                sendEvent(name: "switch", value: "off", isStateChange: true)
				sendEvent(name: "nightMode", value: "off", isStateChange: true)
            }
            if (logEnable)
                if (resp.data) log.debug "${resp.data}"
        }
    } catch (Exception e) {
        log.warn "Call to off failed: ${e.message}"
    }
}

def nightMode() {
    if (logEnable) log.debug "Sending night mode request"

    try {
		def url = "http://" + settings.ipAddress + "/gateways/" + settings.hubID + "/"+settings.lightType+"/" + settings.lightID
		if (logEnable) log.debug url
        def postParams = [
            uri: url,
			contentType: "application/json",
			requestContentType: "application/json",
            body : ["command": "night_mode" ]
        ]
		
        httpPost(postParams) { resp ->
		    if (resp.success) {
                sendEvent(name: "switch", value: "off", isStateChange: true)
				sendEvent(name: "nightMode", value: "on", isStateChange: true)
            }
            if (logEnable)
                if (resp.data) log.debug "${resp.data}"
        }
    } catch (Exception e) {
        log.warn "Call to nightmode failed: ${e.message}"
    }
}


def setLevel(level, tt = null) {
    if (logEnable) log.debug "Sending level request"
	
	if (level != 0) {
		on()
	}
    try {
		def url = "http://" + settings.ipAddress + "/gateways/" + settings.hubID + "/"+settings.lightType+"/" + settings.lightID
		if (logEnable) log.debug url
        def postParams = [
            uri: url,
			contentType: "application/json",
			requestContentType: "application/json",
            body : ["level": level]
        ]
    
        httpPost(postParams) { resp ->
		    if (resp.success) {
                sendEvent(name: "level", value: level, isStateChange: true)
				
            }
            if (logEnable)
                if (resp.data) log.debug "${resp.data}"
        }
    } catch (Exception e) {
        log.warn "Call to level failed: ${e.message} ${level} "
    }
	if (level == 0) {
		off()
	}
}

def setColorTemperature(temp, level = null, tt = null) {
    if (logEnable) log.debug "Sending color temperature request"

    //limitlessled bulbs range from 3000-6500K, 100 to 0
	if (temp<3000)
        temp = 3000
    else if (temp>6500)
	    temp = 6500
    lltemp = 100 - Math.round(((temp - 3000)/(6500-3000))* 100)
	if (logEnable) log.debug lltemp
    try {
		def url = "http://" + settings.ipAddress + "/gateways/" + settings.hubID + "/"+settings.lightType+"/" + settings.lightID
		if (logEnable) log.debug url
        def postParams = [
            uri: url,
			contentType: "application/json",
			requestContentType: "application/json",
            body : ["temperature": lltemp]
        ]
    
        httpPost(postParams) { resp ->
		    if (resp.success) {
                sendEvent(name: "colorTemperature", value: temp, isStateChange: true)
				
            }
            if (logEnable)
                if (resp.data) log.debug "${resp.data}"
        }
    } catch (Exception e) {
        log.warn "Call to colorTemperature failed: ${e.message} ${temp} "
    }
	if (level != null) {
		setLevel(level)
	}
}

23 of 30 lights added. That's a lot of configuration! :stuck_out_tongue: