ZLight2 working


(Serge Sozonoff) #1

Hi All,

I now have the ZLight2 working with ST from the TI Developers Kit
http://processors.wiki.ti.com/index.php/ZStack-Lighting_Kit

Took me a little while for what turned out to be a silly thing.

Can anyone point me to where the 3 ZigBee message type syntax’s are clearly documented ?
I read this ZigBee Primer but it did not help, also looked at several examples…

My issue was simple. sending an “st cmd …” for the endpointId 0B

st cmd 0x${device.deviceNetworkId} 0B 6 1 {}

The above did not work and it turned out to work with 0x0B, I wish the documentation would mention when a leading “0x” is needed and when its not?

Anyway works now.

Second question I have is what exactly ${endpointId} (in a DeviceType script) resolves to ? Its used in numerous examples but unclear what happens when a device has multiple endpoints and how this variable actually gets populated.

Thanks,
Serge


(Serge Sozonoff) #2

And here is the code for this

/*
* ZLight2 does not support attribute reporting so the only way to get attribute value updates
* is the refresh button and polling.
* ZLL spec does not support configure reporting on the Lights attributes
*
*/
metadata {
// Automatically generated. Make future change here.
definition (name: “ZLight2”, namespace: “sozonoff”, author: “Sozonoff”) {
capability "Switch Level"
capability "Color Control"
capability "Switch"
capability "Polling"
capability "Refresh"
capability “Sensor”

        fingerprint endpointId: "0B", profileId: "0104", deviceId: "0102", inClusters: "0000 0003 0004 0005 0006 0008 0300"
        fingerprint endpointId: "0D", profileId: "C05E", inClusters: "1000", outClusters: "1000"
        fingerprint endpointId: "0C", profileId: "0104", deviceId: "0200"
	}

	simulator {

	}

	// UI tile definitions
	tiles {
		standardTile("switch", "device.switch", width: 1, height: 1, canChangeIcon: true) {
			state "off", label: '${name}', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff"
			state "on", label: '${name}', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#79b821"
		}
		standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
			state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
		}
		controlTile("rgbSelector", "device.color", "color", height: 3, width: 3, inactiveLabel: false) {
			state "color", action:"color control.setColor"
		}
		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", width: 1, height: 1) {
			state "level", label: 'Level ${currentValue}%'
		}
		valueTile("saturation", "device.saturation", inactiveLabel: false, decoration: "flat", width: 1, height: 1) {
			state "saturation", label: 'Sat ${currentValue}    '
		}
		main(["switch"])
		details(["switch", "levelSliderControl", "rgbSelector", "level", "saturation", "refresh"])
	}
}


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

// Parse incoming device messages to generate events
def parse(String description) {
	log.trace("desc: ${description}")
    
    // for some reason the on/off read comes in a catchall....
	if (description?.startsWith("catchall:")) {
		def msg = zigbee.parse(description)
                
        if(msg.clusterId == 0x0006 && msg.command == 0x01) {
	        log.trace("got on/off switch status ${msg.data}")
            def v = msg.data[4] == 1 ? "on" : "off"
            sendEvent(name: "switch", value: v)
        }       
	}
    
    // handle read attr responses
	if (description?.startsWith("read attr")) {
        def respMap = parseDescriptionAsMap(description)
        
        switch(respMap.cluster) {
        	case "0006" :
            break
            
            case "0008" :
            	if (respMap.attrId == "0000") {
                    def i = Math.round(convertHexToInt(respMap.value) / 256 * 100 )
                    log.trace("hue value: ${i}")
                    sendEvent( name: "level", value: i )
                }
            break
        }        
    }
}

def on() {
	log.debug "on()"
	sendEvent(name: "switch", value: "on")    
    def cmds = []
    
    // restore to the last level recorded
    if (device.latestValue("level"))
    	cmds << setLevel(device.latestValue("level"))
    
	cmds << "st cmd 0x${device.deviceNetworkId} 0X0B 6 1 {}"        
    return cmds
}

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

	
def setHue(value) {
	// Hue is provided by the ST colour wheel as a percentage, needs to be converted to a range between 0-254
	log.trace "setHue($value)"
	sendEvent(name: "hue", value: value)
	def scaledValue = convertHueFromPercentage(value)
	def cmd = "st cmd 0x${device.deviceNetworkId} 0X0B 0x300 0x00 {${hex(scaledValue)} 00 0000}"
	cmd
}

private convertHueFromPercentage(value) {
	def max = 0xfe
    return Math.round(value * max / 100.0)
}

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

	sendEvent(name: "hue", value: value.hue)
	sendEvent(name: "saturation", value: value.saturation)
	def scaledHueValue = convertHueFromPercentage(value.hue)
	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} 0X0B 6 1 {}"
		cmd << "delay 150"
	}

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

    // restore to the last level recorded
    if (device.latestValue("level")) {   		
    	cmd << "delay 150"
    	cmd << setLevel(device.latestValue("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} 0X0B 0x300 0x03 {${hex(scaledValue)} 0000}"
	cmd
}

def refresh() {
	log.trace("refreshing")
	def cmd = []    
	cmd << ["st rattr 0x${device.deviceNetworkId} 0x0B 0x0006 0x00", "delay 1000"]
    cmd << "st rattr 0x${device.deviceNetworkId} 0x0B 0x0008 0x00"
    return cmd
}

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

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

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

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

	//log.debug cmds
	cmds
}

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

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