Migrating app question

Awesome! Thank you and it works flawlessly!!

Is this an ESP chip? I wonder if my lights that work with Magic Home would work with this. I've been searching everywhere to find something that doesn't require a server man in the middle approach as somebody on the ST forums was doing.

Yes but it's running firmware written by Eric.

Oh so I'd have to crack it open, solder headers and get a usb adapter? I've seen those guides. Seems to be the trend in cheap wifi devices.

@cuboy29

Do you have the programs and such working? I use programs quite a bit in ST and they don't seem to work in HE. Thoughts?

I never really use program even in ST so never reallly look into it. Maybe if you are getting some error, post it in the forum and maybe the community will jump in and help sort it out.

I got basic control working. I feel like something is missing because I thought it had custom patterns you could command it to do from an App.

Do you do anything surrounding cycling through colors?

It seems the XMLParsing isn't supported on Hubitat so the SmartApp that loads custom color cycles doesn't work. I reached out to Eric and let him know. I'm sure he's busy. @chuck.schwer confirmed it here:

But that was a while ago, so who knows if it's coming sooner or later.

I just set it up in SmartThings and all of that custom light sequencing is there.

Looks like Eric is working on porting the driver and the connect app :slight_smile: should be a matter of time now.

Did you get this working in hubitat? I am getting a constant error in the logs:

:errorjava.lang.ArrayIndexOutOfBoundsException: 1 on line 755 (parse)
I used Eric's device and controller codes.

I still get those errors.
It's not working that great at all.
I've been using the web UI to turn them on every night.
Which doesn't always load...

@adamkempenich made a MagicHome driver that I want to try out but I have to switch back to the stock controller which won't happen until I return from business travel.

Hubitat support told me that these errors will cause the hub to slow down over time. I thought that this was ready for hubitat because there was the link to the hubitat versions. My lights do work with the Simple Lighting app. I have them go on and off at certain times and that seems to be working. But I don't like seeing the errors. Wish I knew more about the Groovy language.

1 Like

I've asked @ericm about it but I know he's busy.
Let me invoke him again.

That would be great if he could look at it again. Seems to be just the one error on line 755.

1 Like

Replace that parseDescriptionAsMap method with this and that error should go away.

def parseDescriptionAsMap(description) {
	description.split(",").inject([:]) { map, param ->
		def nameAndValue = param.split(":")
        
        if (nameAndValue.length == 2) map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
        else map += [(nameAndValue[0].trim()):""]
	}
}
2 Likes

That worked for that error but now it is giving this error:

errorjava.lang.IllegalArgumentException: Text must not be null or empty on line 264 (parse)

Here is a version that is working for me. Give it a shot

/**
 *  Copyright 2016 Eric Maycock
 *
 *  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.
 *
 *  SmartLife RGBW Controller
 *
 *  Author: Eric Maycock (erocm123) and updated by cjcharles
 *  Date: 2018-05-03
 */

import groovy.json.JsonSlurper
import groovy.util.XmlSlurper

metadata {
	definition (name: "SmartLife RGBW Controller", namespace: "erocm123", author: "Eric Maycock") {
		capability "Switch Level"
		capability "Actuator"
        capability "Color Control"
		capability "Switch"
		capability "Refresh"
		capability "Sensor"
        capability "Configuration"
        capability "Health Check"
        
        (1..6).each { n ->
			attribute "switch$n", "enum", ["on", "off"]
			command "on$n"
			command "off$n"
        }

        command "on"
        command "off"
        
        command "reset"
        command "setProgram"
        command "setWhiteLevel"
        
        command "redOn"
        command "redOff"
        command "greenOn"
        command "greenOff"
        command "blueOn"
        command "blueOff"
        command "white1On"
        command "white1Off"
        command "white2On"
        command "white2Off"
        
 		command "setRedLevel"
        command "setGreenLevel"
        command "setBlueLevel"
        command "setWhite1Level"
        command "setWhite2Level"
	}

	simulator {
	}
    
    preferences {
        input description: "Once you change values on this page, the corner of the \"configuration\" icon will change orange until all configuration parameters are updated.", title: "Settings", displayDuringSetup: false, type: "paragraph", element: "paragraph"
		generate_preferences(configuration_model())
	}

	tiles (scale: 2){      
		multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
			tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
				attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
                attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#00a0dc", nextState:"turningOff"
				attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#00a0dc", nextState:"turningOff"
				attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
			}
			tileAttribute ("device.level", key: "SLIDER_CONTROL") {
				attributeState "level", action:"switch level.setLevel"
			}
			tileAttribute ("device.color", key: "COLOR_CONTROL") {
				attributeState "color", action:"setColor"
			}
        }

		standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
			state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
		}
       standardTile("configure", "device.needUpdate", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
            state "NO" , label:'', action:"configuration.configure", icon:"http://cdn.device-icons.smartthings.com/secondary/configure@2x.png"
            state "YES", label:'', action:"configuration.configure", icon:"https://github.com/erocm123/SmartThingsPublic/raw/master/devicetypes/erocm123/qubino-flush-1d-relay.src/configure@2x.png"
        }
        
        standardTile("red", "device.red", height: 1, width: 1, inactiveLabel: false, decoration: "flat", canChangeIcon: false) {
            state "off", label:"R", action:"redOn", icon:"st.illuminance.illuminance.dark", backgroundColor:"#cccccc"
            state "on", label:"R", action:"redOff", icon:"st.illuminance.illuminance.bright", backgroundColor:"#FF0000"
        }
        controlTile("redSliderControl", "device.redLevel", "slider", height: 1, width: 4, inactiveLabel: false) {
			state "redLevel", action:"setRedLevel"
		}
        valueTile("redValueTile", "device.redLevel", height: 1, width: 1) {
        	state "redLevel", label:'${currentValue}%'
        }     
        
        standardTile("green", "device.green", height: 1, width: 1, inactiveLabel: false, decoration: "flat", canChangeIcon: false) {
            state "off", label:"G", action:"greenOn", icon:"st.illuminance.illuminance.dark", backgroundColor:"#cccccc"
            state "on", label:"G", action:"greenOff", icon:"st.illuminance.illuminance.bright", backgroundColor:"#00FF00"
        }
        controlTile("greenSliderControl", "device.greenLevel", "slider", height: 1, width: 4, inactiveLabel: false) {
			state "greenLevel", action:"setGreenLevel"
		}
        valueTile("greenValueTile", "device.greenLevel", height: 1, width: 1) {
        	state "greenLevel", label:'${currentValue}%'
        }    
        
        standardTile("blue", "device.blue", height: 1, width:1, inactiveLabel: false, decoration: "flat", canChangeIcon: false) {
            state "off", label:"B", action:"blueOn", icon:"st.illuminance.illuminance.dark", backgroundColor:"#cccccc"
            state "on", label:"B", action:"blueOff", icon:"st.illuminance.illuminance.bright", backgroundColor:"#0000FF"
        }
        controlTile("blueSliderControl", "device.blueLevel", "slider", height: 1, width: 4, inactiveLabel: false) {
			state "blueLevel", action:"setBlueLevel"
		}
        valueTile("blueValueTile", "device.blueLevel", height: 1, width: 1) {
        	state "blueLevel", label:'${currentValue}%'
        }  
        
        standardTile("white1", "device.white1", height: 1, width: 1, inactiveLabel: false, decoration: "flat", canChangeIcon: false) {
            state "off", label:"W1", action:"white1On", icon:"st.illuminance.illuminance.dark", backgroundColor:"#cccccc"
            state "on", label:"W1", action:"white1Off", icon:"st.illuminance.illuminance.bright", backgroundColor:"#FFFFFF"
        }
        controlTile("white1SliderControl", "device.white1Level", "slider", height: 1, width: 4, inactiveLabel: false) {
			state "white1Level", action:"setWhite1Level"
		}
        valueTile("white1ValueTile", "device.white1Level", height: 1, width: 1) {
        	state "white1Level", label:'${currentValue}%'
        } 
        standardTile("white2", "device.white2", height: 1, width: 1, inactiveLabel: false, decoration: "flat", canChangeIcon: false) {
            state "off", label:"W2", action:"white2On", icon:"st.illuminance.illuminance.dark", backgroundColor:"#cccccc"
            state "on", label:"W2", action:"white2Off", icon:"st.illuminance.illuminance.bright", backgroundColor:"#FFFFFF"
        }
        controlTile("white2SliderControl", "device.white2Level", "slider", height: 1, width: 4, inactiveLabel: false) {
			state "white2Level", action:"setWhite2Level"
		}
        valueTile("white2ValueTile", "device.white2Level", height: 1, width: 1) {
        	state "white2Level", label:'${currentValue}%'
        } 
        valueTile("ip", "ip", width: 2, height: 1) {
    		state "ip", label:'IP Address\r\n${currentValue}'
		}
        valueTile("firmware", "firmware", width: 2, height: 1) {
    		state "firmware", label:'Firmware ${currentValue}'
		}
        
        
        (1..6).each { n ->
			standardTile("switch$n", "switch$n", canChangeIcon: true, width: 2, height: 2, decoration: "flat") {
				state "off", label: "Program\n$n", action: "on$n", icon: "st.switches.switch.off", backgroundColor: "#cccccc"
                state "on", label: "Program\n$n", action: "off$n", icon: "st.switches.switch.on", backgroundColor: "#00a0dc"
			}
		}
    }

	main(["switch"])
	details(["switch", "levelSliderControl",
             "red", "redSliderControl", "redValueTile", 
             "green", "greenSliderControl", "greenValueTile",
             "blue", "blueSliderControl", "blueValueTile",
             "white1", "white1SliderControl", "white1ValueTile",
             "white2", "white2SliderControl", "white2ValueTile",
             "switch1", "switch2", "switch3",
             "switch4", "switch5", "switch6",
             "refresh", "configure", "ip", "firmware" ])
}

def installed() {
	log.debug "installed()"
	configure()
}

def configure() {
    logging("configure()", 1)
    def cmds = []
    cmds = update_needed_settings()
    cmds << getAction("/info")
    if (cmds != []) cmds
}

def updated()
{
    logging("updated()", 1)
    def cmds = [] 
    cmds = update_needed_settings()
    sendEvent(name: "checkInterval", value: 12 * 60 * 2, data: [protocol: "lan", hubHardwareId: device.hub.hardwareID], displayed: false)
    sendEvent(name:"needUpdate", value: device.currentValue("needUpdate"), displayed:false, isStateChange: true)
    if (cmds != []) response(cmds)
}

private def logging(message, level) {
    if (logLevel != "0"){
    switch (logLevel) {
       case "1":
          if (level > 1)
             log.debug "$message"
       break
       case "99":
          log.debug "$message"
       break
    }
    }
}

def getDefault(){
    if(settings.dcolor == "Previous") {
        return "Previous"
    } else if(settings.dcolor == "Random") {
        return "${transition == "false"? "d~" : "f~"}${getHexColor(settings.dcolor)}"
    } else if(settings.dcolor == "Custom" && settings.custom.length() == 10) {
    	//Here we have a 10 char value, i.e. exact colour choice
        return "${settings.custom}"
    } else if(settings.dcolor == "Custom" && settings.dcolor.length() != 10) {
        return "${transition == "false"? "d~" : "f~"}${settings.custom}"
    } else if(settings.dcolor == "Soft White" || settings.dcolor == "Warm White") {
        if (settings.level == null || settings.level == "0") {
            return "${transition == "false"? "x~" : "w~"}${getDimmedColor(getHexColor(settings.dcolor), "100")}"
        } else {
            return "${transition == "false"? "x~" : "w~"}${getDimmedColor(getHexColor(settings.dcolor), settings.level)}"
        }
    } else if(settings.dcolor == "W1") {
        if (settings.level == null || settings.level == "0") {
            return "${transition == "false"? "x~" : "w~"}${getDimmedColor(getHexColor(settings.dcolor), "100")}"
        } else {
            return "${transition == "false"? "x~" : "w~"}${getDimmedColor(getHexColor(settings.dcolor), settings.level)}"
        }
    } else if(settings.dcolor == "W2") {
        if (settings.level == null || settings.level == "0") {
            return "${transition == "false"? "z~" : "y~"}${getDimmedColor(getHexColor(settings.dcolor), "100")}"
        } else {
            return "${transition == "false"? "z~" : "y~"}${getDimmedColor(getHexColor(settings.dcolor), settings.level)}"
        }
    } else {
        if (settings.level == null || settings.dcolor == null){
			//Colour and level are null so default to previous
           return "Previous"
        } else if (settings.level == null || settings.level == "0") {
			//Level is null or 0, so default to previous
            return "${transition == "false"? "d~" : "f~"}${getDimmedColor(getHexColor(settings.dcolor), "100")}"
        } else {
			//Use custom Colour setting
            return "${getDimmedColor(getHexColor(settings.dcolor), settings.level)}"
        }
    }
}

def parse(description) {
    def map = [:]
    def events = []
    def cmds = []
    
    if(description == "updated") return
    def descMap = parseDescriptionAsMap(description)

    if (descMap["mac"] != null && (!state.mac || state.mac != descMap["mac"])) {
		log.debug "Mac address of device found ${descMap["mac"]}"
        updateDataValue("mac", descMap["mac"])
        state.mac = descMap["mac"]
	}
    
    if (state.mac != null && state.dni != state.mac) state.dni = setDeviceNetworkId(state.mac)     
    if (descMap["body"]) body = new String(descMap["body"].decodeBase64())

    if (body && body != "") {
     
    if(body.startsWith("{") || body.startsWith("[")) {
    def slurper = new JsonSlurper()
    def result = slurper.parseText(body)
    
    if (result.containsKey("type")) {
        if (result.type == "configuration")
            events << update_current_properties(result)
    }
    if (result.containsKey("power")) {
        events << createEvent(name: "switch", value: result.power)
        toggleTiles("all")
    }
    if (result.containsKey("rgb")) {
       events << createEvent(name:"color", value:"#$result.rgb")
       def rgb = hexToRgb("#$result.rgb") 
       def hsv = rgbwToHSV(rgb)
       events << createEvent(name:"hue", value:hsv.hue)
       events << createEvent(name:"saturation", value:hsv.saturation)

       // only store the previous value if the response did not come from a power-off command
       if (result.power != "off")
         state.previousRGB = result.rgb
    }
    if (result.containsKey("r")) {
       events << createEvent(name:"redLevel", value: Integer.parseInt(result.r,16)/255 * 100 as Integer, displayed: false)
       if ((Integer.parseInt(result.r,16)/255 * 100 as Integer) > 0 ) {
          events << createEvent(name:"red", value: "on", displayed: false)
       } else {
    	  events << createEvent(name:"red", value: "off", displayed: false)
       }
    }
    if (result.containsKey("g")) {
       events << createEvent(name:"greenLevel", value: Integer.parseInt(result.g,16)/255 * 100 as Integer, displayed: false)
       if ((Integer.parseInt(result.g,16)/255 * 100 as Integer) > 0 ) {
          events << createEvent(name:"green", value: "on", displayed: false)
       } else {
    	  events << createEvent(name:"green", value: "off", displayed: false)
       }
    }
    if (result.containsKey("b")) {
       events << createEvent(name:"blueLevel", value: Integer.parseInt(result.b,16)/255 * 100 as Integer, displayed: false)
       if ((Integer.parseInt(result.b,16)/255 * 100 as Integer) > 0 ) {
          events << createEvent(name:"blue", value: "on", displayed: false)
       } else {
    	  events << createEvent(name:"blue", value: "off", displayed: false)
       }
    }
    if (result.containsKey("w1")) {
       events << createEvent(name:"white1Level", value: Integer.parseInt(result.w1,16)/255 * 100 as Integer, displayed: false)
       if ((Integer.parseInt(result.w1,16)/255 * 100 as Integer) > 0 ) {
          events << createEvent(name:"white1", value: "on", displayed: false)
       } else {
    	  events << createEvent(name:"white1", value: "off", displayed: false)
       }

       // only store the previous value if the response did not come from a power-off command
       if (result.power != "off")
          state.previousW1 = result.w1
    }
    if (result.containsKey("w2")) {
       events << createEvent(name:"white2Level", value: Integer.parseInt(result.w2,16)/255 * 100 as Integer, displayed: false)
       if ((Integer.parseInt(result.w2,16)/255 * 100 as Integer) > 0 ) {
          events << createEvent(name:"white2", value: "on", displayed: false)
       } else {
    	  events << createEvent(name:"white2", value: "off", displayed: false)
       }

       // only store the previous value if the response did not come from a power-off command
       if (result.power != "off")
          state.previousW2 = result.w2
    }
    if (result.containsKey("version")) {
       events << createEvent(name:"firmware", value: result.version + "\r\n" + result.date, displayed: false)
    }

    if (result.containsKey("success")) {
       if (result.success == "true") state.configSuccess = "true" else state.configSuccess = "false" 
    }
    if (result.containsKey("program")) {
        if (result.running == "false") {
            toggleTiles("all")
        }
    }else {
            toggleTiles("switch$result.program")
            events << createEvent(name:"switch$result.program", value: "on")
        }
    }
    }
    if (!device.currentValue("ip") || (device.currentValue("ip") != getDataValue("ip"))) events << createEvent(name: 'ip', value: getDataValue("ip"))

    return events
}

private toggleTiles(value) {
   def tiles = ["switch1", "switch2", "switch3", "switch4", "switch5", "switch6"]
   tiles.each {tile ->
      if (tile != value) sendEvent(name: tile, value: "off")
   }
}

private getScaledColor(color) {
   def rgb = color.findAll(/[0-9a-fA-F]{2}/).collect { Integer.parseInt(it, 16) }
   def maxNumber = 1
   for (int i = 0; i < 3; i++){
     if (rgb[i] > maxNumber) {
	    maxNumber = rgb[i]
     }
   }
   def scale = 255/maxNumber
   for (int i = 0; i < 3; i++){
     rgb[i] = rgb[i] * scale
   }
   def myred = rgb[0]
   def mygreen = rgb[1]
   def myblue = rgb[2]
   return rgbToHex([r:myred, g:mygreen, b:myblue])
}

def on() {
	log.debug "on()"
    getAction("/on?transition=$transition")
}

def off() {
	log.debug "off()"
    getAction("/off?transition=$transition")
}

def setLevel(level) {
	setLevel(level, 1)
}

def setLevel(level, duration) {
	log.debug "setLevel() level = ${level}"
    if(level > 100) level = 100
    if (level == 0) { off() }
    else if (device.latestValue("switch") == "off") { on() }
	sendEvent(name: "level", value: level)
    sendEvent(name: "setLevel", value: level, displayed: false)
	setColor(aLevel: level)
}
def setSaturation(percent) {
	log.debug "setSaturation($percent)"
	setColor(saturation: percent)
}
def setHue(value) {
	log.debug "setHue($value)"
	setColor(hue: value)
}
def getWhite(value) {
	log.debug "getWhite($value)"
	def level = Math.min(value as Integer, 99)    
    level = 255 * level/99 as Integer
	log.debug "level: ${level}"
	return hex(level)
}
def setColor(value) {
    log.debug "setColor being called with ${value}"
    def uri
    def validValue = true
    
    if ((value.saturation != null) && (value.hue != null)) {
        def hue = (value.hue != null) ? value.hue : 13
		def saturation = (value.saturation != null) ? value.saturation : 13
		def rgb = huesatToRGB(hue as Integer, saturation as Integer)
        value.hex = rgbToHex([r:rgb[0], g:rgb[1], b:rgb[2]])
    } 
    
    if (value.hue == 5 && value.saturation == 4) {
       log.debug "setting color Soft White - Default"
       def whiteLevel = getWhite(value.level)
       uri = "/w1?value=${whiteLevel}"
       state.previousColor = "${whiteLevel}"
    }
    /* Letting White - Concentrate adjust RGB values
    else if (value.hue == 63 && value.saturation == 28) {
       log.debug "setting color White - Concentrate"
       def whiteLevel = getWhite(value.level)
       uri = "/w1?value=${whiteLevel}"
       state.previousColor = "${whiteLevel}"
    } */
    else if (value.hue == 63 && value.saturation == 43) {
       log.debug "setting color Daylight - Energize"
       def whiteLevel = getWhite(value.level)
       uri = "/w2?value=${whiteLevel}"
       state.previousColor = "${whiteLevel}"
    }
    else if (value.hue == 79 && value.saturation == 7) {
       log.debug "setting color Warm White - Relax"
       def whiteLevel = getWhite(value.level)
       uri = "/w1?value=${whiteLevel}"
       state.previousColor = "${whiteLevel}"
    } 
    else if (value.colorTemperature) {
       log.debug "setting color with color temperature"
       def whiteLevel = getWhite(value.level)
       uri = "/w1?value=${whiteLevel}"
       state.previousColor = "${whiteLevel}"
    }
	else if (value.hex) {
       log.debug "setting color with hex"
       if (!value.hex ==~ /^\#([A-Fa-f0-9]){6}$/) {
           log.debug "$value.hex is not valid"
           validValue = false
       } else {
           def rgb = value.hex.findAll(/[0-9a-fA-F]{2}/).collect { Integer.parseInt(it, 16) }
           def myred = rgb[0] < 40 ? 0 : rgb[0]
           def mygreen = rgb[1] < 40 ? 0 : rgb[1]
           def myblue = rgb[2] < 40 ? 0 : rgb[2]
           def dimmedColor = getDimmedColor(rgbToHex([r:myred, g:mygreen, b:myblue]))
           uri = "/rgb?value=${dimmedColor}"
       }
    }
    else if (value.white) {
       uri = "/w1?value=${value.white}"
    }
    else if (value.aLevel) {
    	def actions = []
        if (channels == "true") {
           def skipColor = false
           // Handle white channel dimmers if they're on or were not previously off (excluding power-off command)
           if (device.currentValue("white1") == "on" || state.previousW1 != "00") {
              actions.push(setWhite1Level(value.aLevel))
              skipColor = true
           }
           if (device.currentValue("white2") == "on" || state.previousW2 != "00") {
              actions.push(setWhite2Level(value.aLevel))
              skipColor = true
           }
        if (skipColor == false) {
        log.debug state.previousRGB
           // if the device is currently on, scale the current RGB values; otherwise scale the previous setting
           uri = "/rgb?value=${getDimmedColor(device.latestValue("switch") == "on" ? device.currentValue("color").substring(1) : state.previousRGB)}"
           actions.push(getAction("$uri&channels=$channels&transition=$transition"))
        }
        } else {
           // Handle white channel dimmers if they're on or were not previously off (excluding power-off command)
           if (device.currentValue("white1") == "on" || state.previousW1 != "00")
              actions.push(setWhite1Level(value.aLevel))
           if (device.currentValue("white2") == "on" || state.previousW2 != "00")
              actions.push(setWhite2Level(value.aLevel))
        
           // if the device is currently on, scale the current RGB values; otherwise scale the previous setting
           uri = "/rgb?value=${getDimmedColor(device.latestValue("switch") == "on" ? device.currentValue("color").substring(1) : state.previousRGB)}"
           actions.push(getAction("$uri&channels=$channels&transition=$transition"))
        }
        return actions
    }
    else {
       // A valid color was not chosen. Setting to white
       uri = "/w1?value=ff"
    }

    if (uri != null && validValue != false) getAction("$uri&channels=$channels&transition=$transition")

}

private getDimmedColor(color, level) {
   if(color.size() > 2){
      def scaledColor = getScaledColor(color)
      def rgb = scaledColor.findAll(/[0-9a-fA-F]{2}/).collect { Integer.parseInt(it, 16) }
    
      def r = hex(rgb[0] * (level.toInteger()/100))
      def g = hex(rgb[1] * (level.toInteger()/100))
      def b = hex(rgb[2] * (level.toInteger()/100))

      return "${r + g + b}"
   }else{
      color = Integer.parseInt(color, 16)
      return hex(color * (level.toInteger()/100))
   }
}

private getDimmedColor(color) {
   if (device.latestValue("level")) {
      getDimmedColor(color, device.latestValue("level"))
   } else {
      return color.replaceAll("#","")
   }
}

def reset() {
	log.debug "reset()"
	setColor(white: "ff")
}

def refresh() {
	log.debug "refresh()"
    getAction("/status")
}

def ping() {
    log.debug "ping()"
    refresh()
}

def setWhiteLevel(value) {
	log.debug "setwhiteLevel: ${value}"
    def level = Math.min(value as Integer, 99)    
    level = 255 * level/99 as Integer
	log.debug "level: ${level}"
	if ( value > 0 ) {
    	if (device.latestValue("switch") == "off") { on() }
        sendEvent(name: "white", value: "on")
    } else {
    	sendEvent(name: "white", value: "off")
    }
	def whiteLevel = hex(level)
    setColor(white: whiteLevel)
}

def hexToRgb(colorHex) {
	def rrInt = Integer.parseInt(colorHex.substring(1,3),16)
    def ggInt = Integer.parseInt(colorHex.substring(3,5),16)
    def bbInt = Integer.parseInt(colorHex.substring(5,7),16)
    
    def colorData = [:]
    colorData = [r: rrInt, g: ggInt, b: bbInt]
    colorData
}

// huesatToRGB Changed method provided by daved314
def huesatToRGB(float hue, float sat) {
	if (hue <= 100) {
		hue = hue * 3.6
    }
    sat = sat / 100
    float v = 1.0
    float c = v * sat
    float x = c * (1 - Math.abs(((hue/60)%2) - 1))
    float m = v - c
    int mod_h = (int)(hue / 60)
    int cm = Math.round((c+m) * 255)
    int xm = Math.round((x+m) * 255)
    int zm = Math.round((0+m) * 255)
    switch(mod_h) {
    	case 0: return [cm, xm, zm]
       	case 1: return [xm, cm, zm]
        case 2: return [zm, cm, xm]
        case 3: return [zm, xm, cm]
        case 4: return [xm, zm, cm]
        case 5: return [cm, zm, xm]
	}   	
}

private rgbwToHSV(Map colorMap) {
    log.debug "rgbwToHSV(): colorMap: ${colorMap}"

    if (colorMap.containsKey("r") & colorMap.containsKey("g") & colorMap.containsKey("b")) { 

        float r = colorMap.r / 255f
        float g = colorMap.g / 255f
        float b = colorMap.b / 255f
        float w = (colorMap.white) ? colorMap.white / 255f : 0.0
        float max = [r, g, b].max()
        float min = [r, g, b].min()
        float delta = max - min

        float h,s,v = 0

        if (delta) {
            s = delta / max
            if (r == max) {
                h = ((g - b) / delta) / 6
            } else if (g == max) {
                h = (2 + (b - r) / delta) / 6
            } else {
                h = (4 + (r - g) / delta) / 6
            }
            while (h < 0) h += 1
            while (h >= 1) h -= 1
        }

        v = [max,w].max() 

        return colorMap << [ hue: h * 100, saturation: s * 100, level: Math.round(v * 100) ]
    }
    else {
        log.error "rgbwToHSV(): Cannot obtain color information from colorMap: ${colorMap}"
    }
}

private hex(value, width=2) {
	def s = new BigInteger(Math.round(value).toString()).toString(16)
	while (s.size() < width) {
		s = "0" + s
	}
	s
}
def rgbToHex(rgb) {
    def r = hex(rgb.r)
    def g = hex(rgb.g)
    def b = hex(rgb.b)
    def hexColor = "#${r}${g}${b}"
    
    hexColor
}

def sync(ip, port) {
    def existingIp = getDataValue("ip")
    def existingPort = getDataValue("port")
    if (ip && ip != existingIp) {
        updateDataValue("ip", ip)
        sendEvent(name: 'ip', value: ip)
    }
    if (port && port != existingPort) {
        updateDataValue("port", port)
    }
}

private encodeCredentials(username, password){
	def userpassascii = "${username}:${password}"
    def userpass = "Basic " + userpassascii.encodeAsBase64().toString()
    return userpass
}

private getAction(uri){ 
  updateDNI()
  def userpass
  log.debug uri
  if(password != null && password != "") 
    userpass = encodeCredentials("admin", password)
    
  def headers = getHeader(userpass)

  def hubAction = new hubitat.device.HubAction(
    method: "GET",
    path: uri,
    headers: headers
  )
  return hubAction    
}

private postAction(uri, data){ 
  updateDNI()
  
  def userpass
  
  if(password != null && password != "") 
    userpass = encodeCredentials("admin", password)
  
  def headers = getHeader(userpass)
  
  def hubAction = new hubitat.device.HubAction(
    method: "POST",
    path: uri,
    headers: headers,
    body: data
  )
  return hubAction    
}

private setDeviceNetworkId(ip, port = null){
    def myDNI
    if (port == null) {
        myDNI = ip
    } else {
  	    def iphex = convertIPtoHex(ip)
  	    def porthex = convertPortToHex(port)
        
        myDNI = "$iphex:$porthex"
    }
    log.debug "Device Network Id set to ${myDNI}"
    return myDNI
}

private updateDNI() { 
    if (state.dni != null && state.dni != "" && device.deviceNetworkId != state.dni) {
       device.deviceNetworkId = state.dni
    }
}

private getHostAddress() {
    if(getDeviceDataByName("ip") && getDeviceDataByName("port")){
        return "${getDeviceDataByName("ip")}:${getDeviceDataByName("port")}"
    }else{
	    return "${ip}:80"
    }
}

private String convertIPtoHex(ipAddress) { 
    String hex = ipAddress.tokenize( '.' ).collect {  String.format( '%02x', it.toInteger() ) }.join()
    return hex
}

private String convertPortToHex(port) {
	String hexport = port.toString().format( '%04x', port.toInteger() )
    return hexport
}
/*
def parseDescriptionAsMap(description) {
	description.split(",").inject([:]) { map, param ->
		def nameAndValue = param.split(":")
		map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
	}
} */

def parseDescriptionAsMap(description) {
	description.split(",").inject([:]) { map, param ->
		def nameAndValue = param.split(":")
        
        if (nameAndValue.length == 2) map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
        else map += [(nameAndValue[0].trim()):""]
	}
}

private getHeader(userpass = null){
    def headers = [:]
    headers.put("Host", getHostAddress())
    headers.put("Content-Type", "application/x-www-form-urlencoded")
    if (userpass != null)
       headers.put("Authorization", userpass)
    return headers
}

def toAscii(s){
        StringBuilder sb = new StringBuilder();
        String ascString = null;
        long asciiInt;
                for (int i = 0; i < s.length(); i++){
                    sb.append((int)s.charAt(i));
                    sb.append("|");
                    char c = s.charAt(i);
                }
                ascString = sb.toString();
                asciiInt = Long.parseLong(ascString);
                return asciiInt;
}

def on1() { onOffCmd(1, 1) }
def on2() { onOffCmd(1, 2) }
def on3() { onOffCmd(1, 3) }
def on4() { onOffCmd(1, 4) }
def on5() { onOffCmd(1, 5) }
def on6() { onOffCmd(1, 6) }

def off1(p=null) { onOffCmd((p == null ? 0 : p), 1) }
def off2(p=null) { onOffCmd((p == null ? 0 : p), 2) }
def off3(p=null) { onOffCmd((p == null ? 0 : p), 3) }
def off4(p=null) { onOffCmd((p == null ? 0 : p), 4) }
def off5(p=null) { onOffCmd((p == null ? 0 : p), 5) }
def off6(p=null) { onOffCmd((p == null ? 0 : p), 6) }

def onOffCmd(value, program) {
    log.debug "onOffCmd($value, $program)"
    def uri
    if (value == 1){
       if(state."program${program}" != null) {
          uri = "/program?value=${state."program${program}"}&number=$program"
       }    
    } else if(value == 0){
       uri = "/stop"
    } else {
       uri = "/off"
    }
	log.debug uri
    if (uri != null) return getAction(uri)
}

def setProgram(value, program){
   state."program${program}" = value
}

def hex2int(value){
   return Integer.parseInt(value, 10)
}

def redOn() {
	log.debug "redOn()"
    getAction("/r?value=ff&channels=$channels&transition=$transition")
}
def redOff() {
	log.debug "redOff()"
    getAction("/r?value=00&channels=$channels&transition=$transition")
}

def setRedLevel(value) {
	log.debug "setRedLevel: ${value}"
    def level = Math.min(value as Integer, 99)    
    level = 255 * level/99 as Integer
	log.debug "level: ${level}"
	level = hex(level)
    getAction("/r?value=$level&channels=$channels&transition=$transition")
}
def greenOn() {
	log.debug "greenOn()"
    getAction("/g?value=ff&channels=$channels&transition=$transition")
}
def greenOff() {
	log.debug "greenOff()"
    getAction("/g?value=00&channels=$channels&transition=$transition")
}

def setGreenLevel(value) {
	log.debug "setGreenLevel: ${value}"
    def level = Math.min(value as Integer, 99)    
    level = 255 * level/99 as Integer
	log.debug "level: ${level}"
	level = hex(level)
    getAction("/g?value=$level&channels=$channels&transition=$transition")
}
def blueOn() {
	log.debug "blueOn()"
    getAction("/b?value=ff&channels=$channels&transition=$transition")
}
def blueOff() {
	log.debug "blueOff()"
    getAction("/b?value=00&channels=$channels&transition=$transition")
}

def setBlueLevel(value) {
	log.debug "setBlueLevel: ${value}"
    def level = Math.min(value as Integer, 99)    
    level = 255 * level/99 as Integer
	log.debug "level: ${level}"
	level = hex(level)
    getAction("/b?value=$level&channels=$channels&transition=$transition")
}
def white1On() {
	log.debug "white1On()"
    getAction("/w1?value=ff&channels=$channels&transition=$transition")
}
def white1Off() {
	log.debug "white1Off()"
    getAction("/w1?value=00&channels=$channels&transition=$transition")
}

def setWhite1Level(value) {
	log.debug "setwhite1Level: ${value}"
    def level = Math.min(value as Integer, 99)    
    level = 255 * level/99 as Integer
	log.debug "level: ${level}"
	def whiteLevel = hex(level)
    getAction("/w1?value=$whiteLevel&channels=$channels&transition=$transition")
}
def white2On() {
	log.debug "white2On()"
    getAction("/w2?value=ff&channels=$channels&transition=$transition")
}
def white2Off() {
	log.debug "white2Off()"
    getAction("/w2?value=00&channels=$channels&transition=$transition")
}

def setWhite2Level(value) {
	log.debug "setwhite2Level: ${value}"
    def level = Math.min(value as Integer, 99)    
    level = 255 * level/99 as Integer
	log.debug "level: ${level}"
	def whiteLevel = hex(level)
    getAction("/w2?value=$whiteLevel&channels=$channels&transition=$transition")
}

private getHexColor(value){
def color = ""
  switch(value){
    case "Previous":
    color = "Previous"
    break;
    case "White":
    color = "ffffff"
    break;
    case "Daylight":
    color = "ffffff"
    break;
    case "Soft White":
    color = "ff"
    break;
    case "Warm White":
    color = "ff"
    break;
    case "W1":
    color = "ff"
    break;
    case "W2":
    color = "ff"
    break;
    case "Blue":
    color = "0000ff"
    break;
    case "Green":
    color = "00ff00"
    break;
    case "Yellow":
    color = "ffff00"
    break;
    case "Orange":
    color = "ff5a00"
    break;
    case "Purple":
    color = "5a00ff"
    break;
    case "Pink":
    color = "ff00ff"
    break;
    case "Cyan":
    color = "00ffff"
    break;
    case "Red":
    color = "ff0000"
    break;
    case "Off":
    color = "000000"
    break;
    case "Random":
    color = "xxxxxx"
    break;
}
   return color
}

def generate_preferences(configuration_model)
{
    def configuration = new XmlSlurper().parseText(configuration_model)
   
    configuration.Value.each
    {
        if(it.@hidden != "true" && it.@disabled != "true"){
        switch(it.@type)
        {   
            case ["number"]:
                input "${it.@index}", "number",
                    title:"${it.@label}\n" + "${it.Help}",
                    range: "${it.@min}..${it.@max}",
                    defaultValue: "${it.@value}",
                    displayDuringSetup: "${it.@displayDuringSetup}"
            break
            case "list":
                def items = []
                it.Item.each { items << ["${it.@value}":"${it.@label}"] }
                input "${it.@index}", "enum",
                    title:"${it.@label}\n" + "${it.Help}",
                    defaultValue: "${it.@value}",
                    displayDuringSetup: "${it.@displayDuringSetup}",
                    options: items
            break
            case ["password"]:
                input "${it.@index}", "password",
                    title:"${it.@label}\n" + "${it.Help}",
                    displayDuringSetup: "${it.@displayDuringSetup}"
            break
            case "decimal":
               input "${it.@index}", "decimal",
                    title:"${it.@label}\n" + "${it.Help}",
                    range: "${it.@min}..${it.@max}",
                    defaultValue: "${it.@value}",
                    displayDuringSetup: "${it.@displayDuringSetup}"
            break
            case "boolean":
               input "${it.@index}", "boolean",
                    title:"${it.@label}\n" + "${it.Help}",
                    defaultValue: "${it.@value}",
                    displayDuringSetup: "${it.@displayDuringSetup}"
            break
            case "text":
               input "${it.@index}", "text",
                    title:"${it.@label}\n" + "${it.Help}",
                    defaultValue: "${it.@value}",
                    displayDuringSetup: "${it.@displayDuringSetup}"
            break
        }
        }
    }
}

 /*  Code has elements from other community source @CyrilPeponnet (Z-Wave Parameter Sync). */

def update_current_properties(cmd)
{
    def currentProperties = state.currentProperties ?: [:]
    currentProperties."${cmd.name}" = cmd.value

    if (settings."${cmd.name}" != null)
    {
        if (convertParam("${cmd.name}", settings."${cmd.name}").toString() == cmd.value)
        {
            sendEvent(name:"needUpdate", value:"NO", displayed:false, isStateChange: true)
        }
        else
        {
            sendEvent(name:"needUpdate", value:"YES", displayed:false, isStateChange: true)
        }
    }
    state.currentProperties = currentProperties
}


def update_needed_settings()
{
    def cmds = []
    def currentProperties = state.currentProperties ?: [:]
     
     def configuration = new XmlSlurper().parseText(configuration_model())
    def isUpdateNeeded = "NO"
    
    cmds << getAction("/configSet?name=haip&value=${device.hub.getDataValue("localIP")}")
    cmds << getAction("/configSet?name=haport&value=${device.hub.getDataValue("localSrvPortTCP")}")
    
    configuration.Value.each
    {     
        if ("${it.@setting_type}" == "lan" && it.@disabled != "true"){
            if (currentProperties."${it.@index}" == null)
            {
               if (it.@setonly == "true"){
                  logging("Setting ${it.@index} will be updated to ${convertParam("${it.@index}", it.@value)}", 2)
                  cmds << getAction("/configSet?name=${it.@index}&value=${convertParam("${it.@index}", it.@value)}")
               } else {
                  isUpdateNeeded = "YES"
                  logging("Current value of setting ${it.@index} is unknown", 2)
                  cmds << getAction("/configGet?name=${it.@index}")
               }
            }
            else if ((settings."${it.@index}" != null || it.@hidden == "true") && currentProperties."${it.@index}" != (settings."${it.@index}"? convertParam("${it.@index}", settings."${it.@index}".toString()) : convertParam("${it.@index}", "${it.@value}")))
            { 
                isUpdateNeeded = "YES"
                logging("Setting ${it.@index} will be updated to ${convertParam("${it.@index}", settings."${it.@index}")}", 2)
                cmds << getAction("/configSet?name=${it.@index}&value=${convertParam("${it.@index}", settings."${it.@index}")}")
            } 
        }
    }
    
    sendEvent(name:"needUpdate", value: isUpdateNeeded, displayed:false, isStateChange: true)
    return cmds
}

def convertParam(name, value) {
	switch (name){
        case "dcolor":
            getDefault()
        break
        default:
        	value
        break
    }
}

def configuration_model()
{
'''
<configuration>
<Value type="password" byteSize="1" index="password" label="Password" min="" max="" value="" setting_type="preference" fw="">
<Help>
</Help>
</Value>
<Value type="list" byteSize="1" index="pos" label="Boot Up State" min="0" max="2" value="0" setting_type="lan" fw="">
<Help>
Default: Off
</Help>
    <Item label="Off" value="0" />
    <Item label="On" value="1" />
</Value>
<Value type="list" byteSize="1" index="transition" label="Default Transition" min="0" max="1" value="0" setting_type="preference" fw="">
<Help>
Default: Fade
</Help>
    <Item label="Fade" value="true" />
    <Item label="Flash" value="false" />
</Value>
<Value type="list" byteSize="1" index="dcolor" label="Default Color" min="" max="" value="" setting_type="lan" fw="">
<Help>
Default: Previous
</Help>
    <Item label="Previous" value="Previous" />
    <Item label="Soft White - Default" value="Soft White" />
    <Item label="White - Concentrate" value="White" />
    <Item label="Daylight - Energize" value="Daylight" />
    <Item label="Warm White - Relax" value="Warm White" />
    <Item label="Red" value="Red" />
    <Item label="Green" value="Green" />
    <Item label="Blue" value="Blue" />
    <Item label="Yellow" value="Yellow" />
    <Item label="Orange" value="Orange" />
    <Item label="Purple" value="Purple" />
    <Item label="Pink" value="Pink" />
    <Item label="Cyan" value="Random" />
    <Item label="W1" value="W1" />
    <Item label="W2" value="W2" />
    <Item label="Custom" value="Custom" />
</Value>
<Value type="text" byteSize="1" index="custom" label="Custom Color in Hex - RRGGBBW1W2" min="" max="" value="" setting_type="preference" fw="">
<Help>
 e.g. FF00008080 would be Red at 100% and W1+W2 at 50%
If \"Custom\" is chosen above as the default color. Default level does not apply if custom hex value is chosen.
</Help>
</Value>
<Value type="number" byteSize="1" index="level" label="Default Level" min="1" max="100" value="" setting_type="preference" fw="">
<Help>
</Help>
</Value>
<Value type="boolean" byteSize="1" index="channels" label="Mutually Exclusive RGB / White.\nOnly allow one or the other" min="" max="" value="false" setting_type="preference" fw="">
<Help>
</Help>
</Value>
<Value type="list" byteSize="1" index="transitionspeed" label="Transition Speed" min="1" max="3" value="1" setting_type="lan" fw="">
<Help>
Default: Slow
</Help>
    <Item label="Slow" value="1" />
    <Item label="Medium" value="2" />
    <Item label="Fast" value="3" />
</Value>
<Value type="number" byteSize="1" index="autooff" label="Auto Off" min="0" max="65536" value="0" setting_type="lan" fw="" disabled="true">
<Help>
Automatically turn the switch off after this many seconds.
Range: 0 to 65536
Default: 0 (Disabled)
</Help>
</Value>
<Value type="list" index="logLevel" label="Debug Logging Level?" value="0" setting_type="preference" fw="">
<Help>
</Help>
    <Item label="None" value="0" />
    <Item label="Reports" value="1" />
    <Item label="All" value="99" />
</Value>
</configuration>
'''
}
1 Like

Thanks a lot @cuboy29 .
Evaluating now.