OSRAM E27 Edison Screw A60 ES 10W LED Lightify RGB Light Bulb

Hi all,

First post here…

I thought I’d share customised code for these excellent bulbs which are around £20 on Amazon at the moment, the standard ZigBee Hue Bulb device type works fine, but for smooth dimmer transition whilst retaining RGBW colour capability the following code will allow you to control both. I did try using the RGBW Flex code, but found the colour selector stopped working.

If anyone can improve on the code (e.g. enable smooth colour transitions, and smooth on/off) that would be great :smiley:

**EDIT 18-10-15 - @Sticks18’s code found here supports all the features of the OSRAM bulb such as smooth on/off, colour selection and dim transition (the code below will be left for archival purposes:

ChangeLog:

16-10-15 18:32 - Included smooth brightness transitions and colour transitions.
16-10-15 20:38 - Added status of colour and brightness to refresh command.
17-10-15 15:11 - Added smooth off (dims to minimal then off).
18-10-15 16:59 - Removed erroneous line in on command causing brightness to be set to 0.

      /**
 *  Copyright 2015 SmartThings
 *
 *  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.
 *
 */
/* OSRAM Lightify RGBW Bulb

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

metadata {
	definition (name: "OSRAM Lightify Zigbee RGBW Bulb", namespace: "guy_mayhew", author: "Guy Mayhew, Scott Gibson") {
		capability "Switch Level"
		capability "Actuator"
		capability "Color Control"
		capability "Switch"
		capability "Configuration"
		capability "Polling"
		capability "Refresh"
		capability "Sensor"

		command "setAdjustedColor"

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

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

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

	// UI tile definitions
	tiles {
		standardTile("switch", "device.switch", width: 1, height: 1, canChangeIcon: true) {
			state "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
			state "off", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
			state "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
			state "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
		}
		standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
			state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
		}
		controlTile("rgbSelector", "device.color", "color", height: 3, width: 3, inactiveLabel: false) {
			state "color", action:"setAdjustedColor"
		}
		controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false) {
			state "level", action:"switch level.setLevel"
		}
		valueTile("level", "device.level", inactiveLabel: false, decoration: "flat") {
			state "level", label: 'Level ${currentValue}%'
		}
		controlTile("saturationSliderControl", "device.saturation", "slider", height: 1, width: 2, inactiveLabel: false) {
			state "saturation", action:"color control.setSaturation"
		}
		valueTile("saturation", "device.saturation", inactiveLabel: false, decoration: "flat") {
			state "saturation", label: 'Sat ${currentValue}    '
		}
		controlTile("hueSliderControl", "device.hue", "slider", height: 1, width: 2, inactiveLabel: false) {
			state "hue", action:"color control.setHue"
		}

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

// Parse incoming device messages to generate events
def parse(String description) {
    log.debug description
    if (description?.startsWith("catchall:")) {
        if(description?.endsWith("0100") ||description?.endsWith("1001") || description?.matches("on/off\\s*:\\s*1"))
        {
            def result = createEvent(name: "switch", value: "on")
            log.debug "Parse returned ${result?.descriptionText}"
            return result
        }
        else if(description?.endsWith("0000") || description?.endsWith("1000") || description?.matches("on/off\\s*:\\s*0"))
        {
            if(!(description?.startsWith("catchall: 0104 0300"))){
                def result = createEvent(name: "switch", value: "off")
                log.debug "Parse returned ${result?.descriptionText}"
                return result
            }
        }
    }
    else if (description?.startsWith("read attr -")) {
        def descMap = parseDescriptionAsMap(description)
        log.trace "descMap : $descMap"

        if (descMap.cluster == "0300") {
            if(descMap.attrId == "0000"){  //Hue Attribute
                def hueValue = Math.round(convertHexToInt(descMap.value) / 255 * 360)
                log.debug "Hue value returned is $hueValue"
                sendEvent(name: "hue", value: hueValue, displayed:false)
            }
            else if(descMap.attrId == "0001"){ //Saturation Attribute
                def saturationValue = Math.round(convertHexToInt(descMap.value) / 255 * 100)
                log.debug "Saturation from refresh is $saturationValue"
                sendEvent(name: "saturation", value: saturationValue, displayed:false)
            }
        }
        else if(descMap.cluster == "0008"){
            def dimmerValue = Math.round(convertHexToInt(descMap.value) * 100 / 255)
            log.debug "dimmer value is $dimmerValue"
            sendEvent(name: "level", value: dimmerValue)
        }
    }
    else {
        def name = description?.startsWith("on/off: ") ? "switch" : null
        def value = name == "switch" ? (description?.endsWith(" 1") ? "on" : "off") : null
        def result = createEvent(name: name, value: value)
        log.debug "Parse returned ${result?.descriptionText}"
        return result
    }


}

def parseDescriptionAsMap(description) {
    (description - "read attr - ").split(",").inject([:]) { map, param ->
        def nameAndValue = param.split(":")
        map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
    }
}
def on() {
	// just assume it works for now
	log.debug "on()"
	sendEvent(name: "switch", value: "on")
	"st cmd 0x${device.deviceNetworkId} ${endpointId} 6 1 {}"
}

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

def setHue(value) {
	def max = 0xfe
	log.trace "setHue($value)"
	sendEvent(name: "hue", value: value)
	def scaledValue = Math.round(value * max / 100.0)
	def cmd = "st cmd 0x${device.deviceNetworkId} ${endpointId} 0x300 0x00 {${hex(scaledValue)} 00 2000}"
	//log.info cmd
	cmd
}

def setAdjustedColor(value) {
	log.debug "setAdjustedColor: ${value}"
	def adjusted = value + [:]
	adjusted.hue = adjustOutgoingHue(value.hue)
	adjusted.level = null // needed because color picker always sends 100
	setColor(adjusted)
}

def setColor(value){
	log.trace "setColor($value)"
	def max = 0xfe

	sendEvent(name: "hue", value: value.hue)
	sendEvent(name: "saturation", value: value.saturation)
	def scaledHueValue = Math.round(value.hue * max / 100.0)
	def scaledSatValue = Math.round(value.saturation * max / 100.0)

	def cmd = []
	if (value.switch != "off" && device.latestValue("switch") == "off") {
		cmd << "st cmd 0x${device.deviceNetworkId} ${endpointId} 6 1 {}"
		cmd << "delay 150"
	}

	cmd << "st cmd 0x${device.deviceNetworkId} ${endpointId} 0x300 0x00 {${hex(scaledHueValue)} 00 1500}"
	cmd << "delay 150"
	cmd << "st cmd 0x${device.deviceNetworkId} ${endpointId} 0x300 0x03 {${hex(scaledSatValue)} 1500}"

	if (value.level != null) {
		cmd << "delay 150"
		cmd.addAll(setLevel(value.level))
	}

	if (value.switch == "off") {
		cmd << "delay 150"
		cmd << off()
	}
	log.info cmd
	cmd
}

def setSaturation(value) {
	def max = 0xfe
	log.trace "setSaturation($value)"
	sendEvent(name: "saturation", value: value)
	def scaledValue = Math.round(value * max / 100.0)
	def cmd = "st cmd 0x${device.deviceNetworkId} ${endpointId} 0x300 0x03 {${hex(scaledValue)} 1500 }"
	//log.info cmd
	cmd
}

def configure() {
    state.levelValue = 100
    log.debug "Configuring Reporting and Bindings."
    def configCmds = [

            //Switch Reporting
            "zcl global send-me-a-report 6 0 0x10 0 3600 {01}", "delay 500",
            "send 0x${device.deviceNetworkId} ${endpointId} 1", "delay 1000",

            //Level Control Reporting
            "zcl global send-me-a-report 8 0 0x20 5 3600 {0010}", "delay 200",
            "send 0x${device.deviceNetworkId} ${endpointId} 1", "delay 1500",

            "zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 6 {${device.zigbeeId}} {}", "delay 1000",
            "zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 8 {${device.zigbeeId}} {}", "delay 500",
            "zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x0300 {${device.zigbeeId}} {}", "delay 500"
    ]
    return configCmds + refresh() // send refresh cmds as part of config
}

def refresh() {
			[	
            
            "st rattr 0x${device.deviceNetworkId} ${endpointId} 6 0", "delay 500",
            "st rattr 0x${device.deviceNetworkId} ${endpointId} 8 0", "delay 500",
            "st rattr 0x${device.deviceNetworkId} ${endpointId} 0x0300 0", "delay 500",
            "st rattr 0x${device.deviceNetworkId} ${endpointId} 0x0300 1", "delay 500",
            "st wattr 0x${device.deviceNetworkId} ${endpointId} 8 0x10 0x21 {0015}"
            
            ]
}

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

def zigbeeSetLevel(level) {
    "st cmd 0x${device.deviceNetworkId} ${endpointId} 8 4 {${level} 1500}"
}

def setLevel(value) {
	state.levelValue = (value==null) ? 100 : value
    log.trace "setLevel($value)"
    def cmds = []

    if (value == 0) {
        sendEvent(name: "switch", value: "off")
        cmds << zigbeeOff()
    }
    else if (device.latestValue("switch") == "off") {
        sendEvent(name: "switch", value: "on")
    }

    sendEvent(name: "level", value: state.levelValue)
    def level = hex(state.levelValue * 255 / 100)
    cmds << zigbeeSetLevel(level)

    //log.debug cmds
    cmds
}

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

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

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

private adjustOutgoingHue(percent) {
	def adjusted = percent
	if (percent > 31) {
		if (percent < 63.0) {
			adjusted = percent + (7 * (percent -30 ) / 32)
		}
		else if (percent < 73.0) {
			adjusted = 69 + (5 * (percent - 62) / 10)
		}
		else {
			adjusted = percent + (2 * (100 - percent) / 28)
		}
	}
	log.info "percent: $percent, adjusted: $adjusted"
	adjusted
}
2 Likes

how do you mean SMOOTH ON/OFF . . . is it currently clunky ?

ive heard alot of people say these LIGHTFY bulbs are abit touch and go with smartthings, is this resolves as i have to admit i like the price point and the fact they do GU10s too cheap :slight_smile:

Hi Kyle,

They seem pretty good at the moment (only got them yesterday). One thing I found was that the bulb had to be in very close proximity to pair with the hub (then it could be placed wherever it needed to be), not sure if that is a standard ZigBee thing?

Will test over the next few days and let you know how I get on with them. For smooth on/off I mean that when I switch it on it dims gradually from off to whatever level (say 40%) rather than going from off to on instantly and then when turned off it dims from whatever level the bulb is at gradually down to 0%.

Similar with colour change if it could cycle slowly to the colour, kind of like how the “Please Wait” screen on Windows 8 did:
YouTube Link

Will grab a quick video showing the bulbs in action and post it up here.

Video as promised, unfortunately couldn’t show colour change as my phone (with SmartThings installed) was the camera:

1 Like

i think i prefer the nice dim . . . reminds me of nice hotels :slight_smile:

Hi @Guy_Mayhew

I also have these bulbs and am loving them. There was some great work from @Sticks18 this week on both the Colour temp feature (inc button to return from colour to usable white) + some other UI changes. You can grab his code here as you may want to merge a few features. Scott has also added smooth colour transitions for a different lightify device in this thread so suspect you can see how thats been achieved and modify as required Osram/Sylvania Lightify (it works)

Hope this helps

2 Likes

Thanks for the shout Ant! In general the transition time is the last 4 digit parameter in the zigbee command payload for things like setLevel, setHue, setSaturation and setColorTemp. @Guy_Mayhew added the setLevel from one of the other bulbs.

At the end is 1500 which is the transition time. If you look at the other “st cmd” for setting Hue and Saturation, it’s “0000” which is instantaneous (or at least as fast as the bulb can change). It’s little endian and in tenths of a second, so the 1500 means 1.5 seconds from full bright to 0. For color transitions, you might want to try 2000, which is what I used for color temp when I added it. Feel free to pick what works for you.

You might want to replace the refresh section with the below. Right now the refresh is only asking the bulb for on/off info. This adds a check for dim level, hue and saturation. The last line will give you those same smooth transitions for on/off commands if it’s supported by the bulb (the US Tunable White did not initially support that feature).

            "st rattr 0x${device.deviceNetworkId} ${endpointId} 6 0", "delay 500",
            "st rattr 0x${device.deviceNetworkId} ${endpointId} 8 0", "delay 500",
            "st rattr 0x${device.deviceNetworkId} ${endpointId} 0x0300 0", "delay 500",
            "st rattr 0x${device.deviceNetworkId} ${endpointId} 0x0300 1", "delay 500",
            "st wattr 0x${device.deviceNetworkId} 1 8 0x10 0x21 {0015}"
1 Like

Hi @Sticks18 great, thanks for that - will update the code and test in Dev tonight and let you know how it works. Will keep the code at the top of the page updated with edits so people landing on this page can see the current version.

Thanks again :slight_smile:

Hi Sticks18,

Have integrated smooth transitions for colour using your suggestions and included these on the code at the top of thread, thanks for that - really helpful.

I have tried adding the code to replace the refresh section which currently shows:

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

With:

def refresh() {
 			
            "st rattr 0x${device.deviceNetworkId} ${endpointId} 6 0", "delay 500",
            "st rattr 0x${device.deviceNetworkId} ${endpointId} 8 0", "delay 500",
            "st rattr 0x${device.deviceNetworkId} ${endpointId} 0x0300 0", "delay 500",
            "st rattr 0x${device.deviceNetworkId} ${endpointId} 0x0300 1", "delay 500",
            "st wattr 0x${device.deviceNetworkId} 1 8 0x10 0x21 {0015}"
}

But am getting this when trying to commit:

org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
script1445017454894934734980.groovy: 240: expecting ‘}’, found ‘,’ @ line 240, column 69.
eNetworkId} ${endpointId} 6 0", "delay 5
^

1 error

Line 240 reads:

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

I’m not particular knowledgeable in debugging in this language, and I cant see any obvious mistakes. Any ideas please?

Thanks again for your help so far, you have helped bring another accessory to SmartThings.

Cheers,
Guy

You just need to wrap those commands in brackets. It was fine before because it was just one command.

def refresh() {
 	[	
            "st rattr 0x${device.deviceNetworkId} ${endpointId} 6 0", "delay 500",
            "st rattr 0x${device.deviceNetworkId} ${endpointId} 8 0", "delay 500",
            "st rattr 0x${device.deviceNetworkId} ${endpointId} 0x0300 0", "delay 500",
            "st rattr 0x${device.deviceNetworkId} ${endpointId} 0x0300 1", "delay 500",
            "st wattr 0x${device.deviceNetworkId} 1 8 0x10 0x21 {0015}"
        ]
}

You da’ man @Sticks18 :slight_smile:

I don’t think the bulb supports soft on/off as it seems to come on instantly, will have a look at the on sequence to see if I can at least get it to start on say 10% dimmer then rise to whichever lux level the command needs.

I know my Tunable White bulb here in the US doesn’t support that attribute for slow on/off, but the GE Link does, which is nice. It wouldn’t go into effect until after you hit the refresh button to send that command.

And if you really wanted to, you could check by uncommenting out the first line in the parse() section, and hit refresh() while live logging, then post the output here. Towards the end, you probably get a message that ends in 86 for the unsupported attribute.

Hi Sticks18,

This is my output when hitting refresh:

37936b7c-8928-490a-a30e-9bd99e86dbfb ‎21‎:‎06‎:‎47: error groovy.lang.MissingMethodException: No signature of method: script1445024599878679568561.parseDescriptionAsMap() is applicable for argument types: (java.lang.String) values: [read attr - raw: 5F460303000A01000020CB, dni: 5F46, endpoint: 03, cluster: 0300, size: 0A, attrId: 0001, result: success, encoding: 20, value: cb] @ line 113

37936b7c-8928-490a-a30e-9bd99e86dbfb ‎21‎:‎06‎:‎47: error groovy.lang.MissingMethodException: No signature of method: script1445024599878679568561.parseDescriptionAsMap() is applicable for argument types: (java.lang.String) values: [read attr - raw: 5F460303000A0000002033, dni: 5F46, endpoint: 03, cluster: 0300, size: 0A, attrId: 0000, result: success, encoding: 20, value: 33] @ line 113

37936b7c-8928-490a-a30e-9bd99e86dbfb ‎21‎:‎06‎:‎47: error groovy.lang.MissingMethodException: No signature of method: script1445024599878679568561.parseDescriptionAsMap() is applicable for argument types: (java.lang.String) values: [read attr - raw: 5F460303000A01000020CB, dni: 5F46, endpoint: 03, cluster: 0300, size: 0A, attrId: 0001, result: success, encoding: 20, value: cb] @ line 113

37936b7c-8928-490a-a30e-9bd99e86dbfb ‎21‎:‎06‎:‎46: debug Parse returned Living Room Lamp 2 switch is on

37936b7c-8928-490a-a30e-9bd99e86dbfb ‎21‎:‎06‎:‎46: error groovy.lang.MissingMethodException: No signature of method: script1445024599878679568561.parseDescriptionAsMap() is applicable for argument types: (java.lang.String) values: [read attr - raw: 5F460303000A0000002033, dni: 5F46, endpoint: 03, cluster: 0300, size: 0A, attrId: 0000, result: success, encoding: 20, value: 33] @ line 113

37936b7c-8928-490a-a30e-9bd99e86dbfb ‎21‎:‎06‎:‎46: error groovy.lang.MissingMethodException: No signature of method: script1445024599878679568561.parseDescriptionAsMap() is applicable for argument types: (java.lang.String) values: [read attr - raw: 5F460303000A01000020CB, dni: 5F46, endpoint: 03, cluster: 0300, size: 0A, attrId: 0001, result: success, encoding: 20, value: cb] @ line 113

37936b7c-8928-490a-a30e-9bd99e86dbfb ‎21‎:‎06‎:‎45: error groovy.lang.MissingMethodException: No signature of method: script1445024599878679568561.parseDescriptionAsMap() is applicable for argument types: (java.lang.String) values: [read attr - raw: 5F460303000A0000002033, dni: 5F46, endpoint: 03, cluster: 0300, size: 0A, attrId: 0000, result: success, encoding: 20, value: 33] @ line 113

Looks like you need to copy over this chunk of code from where you got the parse section. Just copy this after the last } of the parse section, before the def on() { line. That should eliminate the errors. In order to see whether the on/off write worked, change the //log.info "description is $description" line right after def parse… to just log.trace description

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

Can someone tell me a bit more about these bulbs please? They look really interesting and at that price point may be worth the investment.

However just 2 questions,

  • can I just buy the bulb and it will work? Or do I need to buy the additional hub too?

  • are these bright enough to be used as main room lights rather that corner/table top lights?

Thanks

I bought a couple of these earlier in the week. They are about as bright as I expected them to be from the specs - similar to a 15W CFL.

Although they can work directly with the SmartThings hub, there is currently no way to update the bulb firmware from ST. I bought a Lightify gateway and as soon as I paired the bulbs with it was offered a firmware update, so this is definitely something to be aware of.

J-P

1 Like

So would you say that they are bright enough to be used as main lights in a room.

Sorry I know you may have answered this in your previous post but I’m not too sure from wattage factors, is there information on the lumens it puts out?

I need to have the SAF sorted here :slight_smile:

Hi Deano12,

I’ll post up a photo tonight to show you how bright they are as the only lights in a living room.

Cheers
Guy

I plan to use them as the main lights in several rooms but will be using multiple bulbs in some. It really depends how bright you want your rooms to be, e.g. in my study, which is 4x4 m I’ll be using three, hallways just one.

Hi Scott,

I get the following when polling on refresh:

37936b7c-8928-490a-a30e-9bd99e86dbfb ‎13‎:‎04‎:‎48: error groovy.lang.MissingMethodException: No signature of method: script1445026128929679568561.parseDescriptionAsMap() is applicable for argument types: (java.lang.String) values: [read attr - raw: 5F460303000A0100002000, dni: 5F46, endpoint: 03, cluster: 0300, size: 0A, attrId: 0001, result: success, encoding: 20, value: 00] @ line 113

37936b7c-8928-490a-a30e-9bd99e86dbfb ‎13‎:‎04‎:‎48: error groovy.lang.MissingMethodException: No signature of method: script1445026128929679568561.parseDescriptionAsMap() is applicable for argument types: (java.lang.String) values: [read attr - raw: 5F460303000A0000002000, dni: 5F46, endpoint: 03, cluster: 0300, size: 0A, attrId: 0000, result: success, encoding: 20, value: 00] @ line 113

37936b7c-8928-490a-a30e-9bd99e86dbfb ‎13‎:‎04‎:‎48: error groovy.lang.MissingMethodException: No signature of method: script1445026128929679568561.parseDescriptionAsMap() is applicable for argument types: (java.lang.String) values: [read attr - raw: 5F460303000A0100002000, dni: 5F46, endpoint: 03, cluster: 0300, size: 0A, attrId: 0001, result: success, encoding: 20, value: 00] @ line 113

37936b7c-8928-490a-a30e-9bd99e86dbfb ‎13‎:‎04‎:‎47: error groovy.lang.MissingMethodException: No signature of method: script1445026128929679568561.parseDescriptionAsMap() is applicable for argument types: (java.lang.String) values: [read attr - raw: 5F460300080A0000002093, dni: 5F46, endpoint: 03, cluster: 0008, size: 0A, attrId: 0000, result: success, encoding: 20, value: 93] @ line 113

37936b7c-8928-490a-a30e-9bd99e86dbfb ‎13‎:‎04‎:‎47: error groovy.lang.MissingMethodException: No signature of method: script1445026128929679568561.parseDescriptionAsMap() is applicable for argument types: (java.lang.String) values: [read attr - raw: 5F460303000A0000002000, dni: 5F46, endpoint: 03, cluster: 0300, size: 0A, attrId: 0000, result: success, encoding: 20, value: 00] @ line 113

37936b7c-8928-490a-a30e-9bd99e86dbfb ‎13‎:‎04‎:‎47: error groovy.lang.MissingMethodException: No signature of method: script1445026128929679568561.parseDescriptionAsMap() is applicable for argument types: (java.lang.String) values: [read attr - raw: 5F460303000A0100002000, dni: 5F46, endpoint: 03, cluster: 0300, size: 0A, attrId: 0001, result: success, encoding: 20, value: 00] @ line 113

37936b7c-8928-490a-a30e-9bd99e86dbfb ‎13‎:‎04‎:‎47: error groovy.lang.MissingMethodException: No signature of method: script1445026128929679568561.parseDescriptionAsMap() is applicable for argument types: (java.lang.String) values: [read attr - raw: 5F460300080A0000002093, dni: 5F46, endpoint: 03, cluster: 0008, size: 0A, attrId: 0000, result: success, encoding: 20, value: 93] @ line 113

37936b7c-8928-490a-a30e-9bd99e86dbfb ‎13‎:‎04‎:‎47: error groovy.lang.MissingMethodException: No signature of method: script1445026128929679568561.parseDescriptionAsMap() is applicable for argument types: (java.lang.String) values: [read attr - raw: 5F460303000A0000002000, dni: 5F46, endpoint: 03, cluster: 0300, size: 0A, attrId: 0000, result: success, encoding: 20, value: 00] @ line 113

37936b7c-8928-490a-a30e-9bd99e86dbfb ‎13‎:‎04‎:‎46: error groovy.lang.MissingMethodException: No signature of method: script1445026128929679568561.parseDescriptionAsMap() is applicable for argument types: (java.lang.String) values: [read attr - raw: 5F460300080A0000002093, dni: 5F46, endpoint: 03, cluster: 0008, size: 0A, attrId: 0000, result: success, encoding: 20, value: 93] @ line 113

37936b7c-8928-490a-a30e-9bd99e86dbfb ‎13‎:‎04‎:‎45: debug Parse returned Living Room Lamp 2 switch is off

The code looks like:

/**
 *  Copyright 2015 SmartThings
 *
 *  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.
 *
 */
/* OSRAM Lightify RGBW Bulb

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

metadata {
	definition (name: "OSRAM Lightify Zigbee RGBW Bulb", namespace: "guy_mayhew", author: "Guy Mayhew") {
		capability "Switch Level"
		capability "Actuator"
		capability "Color Control"
		capability "Switch"
		capability "Configuration"
		capability "Polling"
		capability "Refresh"
		capability "Sensor"

		command "setAdjustedColor"

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

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

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

	// UI tile definitions
	tiles {
		standardTile("switch", "device.switch", width: 1, height: 1, canChangeIcon: true) {
			state "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
			state "off", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
			state "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
			state "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
		}
		standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
			state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
		}
		controlTile("rgbSelector", "device.color", "color", height: 3, width: 3, inactiveLabel: false) {
			state "color", action:"setAdjustedColor"
		}
		controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false) {
			state "level", action:"switch level.setLevel"
		}
		valueTile("level", "device.level", inactiveLabel: false, decoration: "flat") {
			state "level", label: 'Level ${currentValue}%'
		}
		controlTile("saturationSliderControl", "device.saturation", "slider", height: 1, width: 2, inactiveLabel: false) {
			state "saturation", action:"color control.setSaturation"
		}
		valueTile("saturation", "device.saturation", inactiveLabel: false, decoration: "flat") {
			state "saturation", label: 'Sat ${currentValue}    '
		}
		controlTile("hueSliderControl", "device.hue", "slider", height: 1, width: 2, inactiveLabel: false) {
			state "hue", action:"color control.setHue"
		}

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

// Parse incoming device messages to generate events
def parse(String description) {
    log.trace description
    if (description?.startsWith("catchall:")) {
        if(description?.endsWith("0100") ||description?.endsWith("1001") || description?.matches("on/off\\s*:\\s*1"))
        {
            def result = createEvent(name: "switch", value: "on")
            log.debug "Parse returned ${result?.descriptionText}"
            return result
        }
        else if(description?.endsWith("0000") || description?.endsWith("1000") || description?.matches("on/off\\s*:\\s*0"))
        {
            if(!(description?.startsWith("catchall: 0104 0300"))){
                def result = createEvent(name: "switch", value: "off")
                log.debug "Parse returned ${result?.descriptionText}"
                return result
            }
        }
    }
    else if (description?.startsWith("read attr -")) {
        def descMap = parseDescriptionAsMap(description)
        log.trace "descMap : $descMap"

        if (descMap.cluster == "0300") {
            if(descMap.attrId == "0000"){  //Hue Attribute
                def hueValue = Math.round(convertHexToInt(descMap.value) / 255 * 360)
                log.debug "Hue value returned is $hueValue"
                sendEvent(name: "hue", value: hueValue, displayed:false)
            }
            else if(descMap.attrId == "0001"){ //Saturation Attribute
                def saturationValue = Math.round(convertHexToInt(descMap.value) / 255 * 100)
                log.debug "Saturation from refresh is $saturationValue"
                sendEvent(name: "saturation", value: saturationValue, displayed:false)
            }
        }
        else if(descMap.cluster == "0008"){
            def dimmerValue = Math.round(convertHexToInt(descMap.value) * 100 / 255)
            log.debug "dimmer value is $dimmerValue"
            sendEvent(name: "level", value: dimmerValue)
        }
    }
    else {
        def name = description?.startsWith("on/off: ") ? "switch" : null
        def value = name == "switch" ? (description?.endsWith(" 1") ? "on" : "off") : null
        def result = createEvent(name: name, value: value)
        log.debug "Parse returned ${result?.descriptionText}"
        return result
    }

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

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

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

def setHue(value) {
	def max = 0xfe
	log.trace "setHue($value)"
	sendEvent(name: "hue", value: value)
	def scaledValue = Math.round(value * max / 100.0)
	def cmd = "st cmd 0x${device.deviceNetworkId} ${endpointId} 0x300 0x00 {${hex(scaledValue)} 00 2000}"
	//log.info cmd
	cmd
}

def setAdjustedColor(value) {
	log.debug "setAdjustedColor: ${value}"
	def adjusted = value + [:]
	adjusted.hue = adjustOutgoingHue(value.hue)
	adjusted.level = null // needed because color picker always sends 100
	setColor(adjusted)
}

def setColor(value){
	log.trace "setColor($value)"
	def max = 0xfe

	sendEvent(name: "hue", value: value.hue)
	sendEvent(name: "saturation", value: value.saturation)
	def scaledHueValue = Math.round(value.hue * max / 100.0)
	def scaledSatValue = Math.round(value.saturation * max / 100.0)

	def cmd = []
	if (value.switch != "off" && device.latestValue("switch") == "off") {
		cmd << "st cmd 0x${device.deviceNetworkId} ${endpointId} 6 1 {}"
		cmd << "delay 150"
	}

	cmd << "st cmd 0x${device.deviceNetworkId} ${endpointId} 0x300 0x00 {${hex(scaledHueValue)} 00 1500}"
	cmd << "delay 150"
	cmd << "st cmd 0x${device.deviceNetworkId} ${endpointId} 0x300 0x03 {${hex(scaledSatValue)} 1500}"

	if (value.level != null) {
		cmd << "delay 150"
		cmd.addAll(setLevel(value.level))
	}

	if (value.switch == "off") {
		cmd << "delay 150"
		cmd << off()
	}
	log.info cmd
	cmd
}

def setSaturation(value) {
	def max = 0xfe
	log.trace "setSaturation($value)"
	sendEvent(name: "saturation", value: value)
	def scaledValue = Math.round(value * max / 100.0)
	def cmd = "st cmd 0x${device.deviceNetworkId} ${endpointId} 0x300 0x03 {${hex(scaledValue)} 1500 }"
	//log.info cmd
	cmd
}

def configure() {
    state.levelValue = 100
    log.debug "Configuring Reporting and Bindings."
    def configCmds = [

            //Switch Reporting
            "zcl global send-me-a-report 6 0 0x10 0 3600 {01}", "delay 500",
            "send 0x${device.deviceNetworkId} ${endpointId} 1", "delay 1000",

            //Level Control Reporting
            "zcl global send-me-a-report 8 0 0x20 5 3600 {0010}", "delay 200",
            "send 0x${device.deviceNetworkId} ${endpointId} 1", "delay 1500",

            "zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 6 {${device.zigbeeId}} {}", "delay 1000",
            "zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 8 {${device.zigbeeId}} {}", "delay 500",
            "zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x0300 {${device.zigbeeId}} {}", "delay 500"
    ]
    return configCmds + refresh() // send refresh cmds as part of config
}

def refresh() {
 	[	
            "st rattr 0x${device.deviceNetworkId} ${endpointId} 6 0", "delay 500",
            "st rattr 0x${device.deviceNetworkId} ${endpointId} 8 0", "delay 500",
            "st rattr 0x${device.deviceNetworkId} ${endpointId} 0x0300 0", "delay 500",
            "st rattr 0x${device.deviceNetworkId} ${endpointId} 0x0300 1", "delay 500",
            "st wattr 0x${device.deviceNetworkId} 1 8 0x10 0x21 {0015}"
        ]
}

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

def zigbeeSetLevel(level) {
    "st cmd 0x${device.deviceNetworkId} ${endpointId} 8 4 {${level} 1500}"
}

def setLevel(value) {
	state.levelValue = (value==null) ? 100 : value
    log.trace "setLevel($value)"
    def cmds = []

    if (value == 0) {
        sendEvent(name: "switch", value: "off")
        cmds << zigbeeOff()
    }
    else if (device.latestValue("switch") == "off") {
        sendEvent(name: "switch", value: "on")
    }

    sendEvent(name: "level", value: state.levelValue)
    def level = hex(state.levelValue * 255 / 100)
    cmds << zigbeeSetLevel(level)

    //log.debug cmds
    cmds
}

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

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

private adjustOutgoingHue(percent) {
	def adjusted = percent
	if (percent > 31) {
		if (percent < 63.0) {
			adjusted = percent + (7 * (percent -30 ) / 32)
		}
		else if (percent < 73.0) {
			adjusted = 69 + (5 * (percent - 62) / 10)
		}
		else {
			adjusted = percent + (2 * (100 - percent) / 28)
		}
	}
	log.info "percent: $percent, adjusted: $adjusted"
	adjusted
}