KUDLED - UK 1/2/3 Gang Switch

You can get the Devolo to do anything in SmartThings, not just smart bulbs

Andy is correct, what I want is a switch that will allow me to switch on the dumb GU10 bulbs in my kitchen by switching on each load/circuit - wife friendly, you press the button it switches, run an automation it switches off for example - after 2 minutes of no motion for example.

Or rather for me, I can ask Echo to run kitchen lights on, but the wife can physically switch the lights off without affecting the “state” of the switch, currently she switches of the switch and kills the smart bulb.

1 Like

I’m getting mine from China direct via AliExpress. will probably have to pay import and customs duty on it too. When it eventually arrives.

Thank you, however I still can’t find it. Closest I’ve got is
http://www.aliexpress.com/item/New-Zigbee-1-3gang-1-way-null-and-live-wire-Intelligtnet-controlled-by-wifi-and-gate/32529673604.html?spm=2114.01010208.8.43.wGSGh0 But it says no longer available.

Yeah that’s what I ordered surprised it’s not available anymore…

/**
 *  KUDLED Wall Switch Binder
 *  This app allows you to bind 3 Virtual On/Off Tiles to the 3 switchable outlets.
 *
 *  Author: simic
 *  Date: 04/05/2016
 */
// Automatically generated. Make future change here.
definition(
    name: "KUDLED Wall Switch Binder 1.1",
    namespace: "",
    author: "simics@gmail.com",
    description: "This app allows you to bind 3 Virtual On/Off Tiles to the 3 switchable outlets.",
    category: "Convenience",
    iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
    iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience%402x.png")
preferences {
	section("Which KUDLED Wall Switch is used?"){
		input "strip", "capability.Switch"
	}
	section("Select a Virtual Switch to bind to Outlet 1"){
		input "switch1", "capability.Switch"
	}
    section("Select a Virtual Switch to bind to Outlet 2"){
		input "switch2", "capability.Switch"
	}
    section("Select a Virtual Switch to bind to Outlet 3"){
		input "switch3", "capability.Switch"
	}
}
def installed() {
	log.debug "Installed with settings: ${settings}"
    subscribe(strip, "switch.on", MainSwitchOnOneHandler)
    subscribe(strip, "switch.off", MainSwitchOffOneHandler)
    
    subscribe(strip, "switch2.on", MainSwitchOnTwoHandler)
    subscribe(strip, "switch2.off", MainSwitchOffTwoHandler)
    
    subscribe(strip, "switch3.on", MainSwitchOnThreeHandler)
    subscribe(strip, "switch3.off", MainSwitchOffThreeHandler)
    
	subscribe(switch1, "switch.on", switchOnOneHandler)
    subscribe(switch2, "switch.on", switchOnTwoHandler)
    subscribe(switch3, "switch.on", switchOnThreeHandler)
    subscribe(switch1, "switch.off", switchOffOneHandler)
    subscribe(switch2, "switch.off", switchOffTwoHandler)
    subscribe(switch3, "switch.off", switchOffThreeHandler)
}
def updated(settings) {
	log.debug "Updated with settings: ${settings}"
	unsubscribe()
	subscribe(strip, "switch.on", MainSwitchOnOneHandler)
    subscribe(strip, "switch.off", MainSwitchOffOneHandler)

    subscribe(strip, "switch2.on", MainSwitchOnTwoHandler)
    subscribe(strip, "switch2.off", MainSwitchOffTwoHandler)

	subscribe(strip, "switch3.on", MainSwitchOnThreeHandler)
    subscribe(strip, "switch3.off", MainSwitchOffThreeHandler)
    
    subscribe(switch1, "switch.on", switchOnOneHandler)
    subscribe(switch2, "switch.on", switchOnTwoHandler)
    subscribe(switch3, "switch.on", switchOnThreeHandler)
    subscribe(switch1, "switch.off", switchOffOneHandler)
    subscribe(switch2, "switch.off", switchOffTwoHandler)
    subscribe(switch3, "switch.off", switchOffThreeHandler)
   }


def MainSwitchOnOneHandler(evt) {
	log.debug "switch on"
	switch1.on()
}
def MainSwitchOffOneHandler(evt) {
	log.debug "switch off"
	switch1.off()
}

def MainSwitchOnTwoHandler(evt) {
	log.debug "switch on"
	switch2.on()
}
def MainSwitchOffTwoHandler(evt) {
	log.debug "switch off"
	switch2.off()
}

def MainSwitchOnThreeHandler(evt) {
	log.debug "switch on"
	switch3.on()
}
def MainSwitchOffThreeHandler(evt) {
	log.debug "switch off"
	switch3.off()
}



def switchOnOneHandler(evt) {
	log.debug "switch on1"
	strip.OneOn()
}
def switchOnTwoHandler(evt) {
	log.debug "switch on2"
	strip.TwoOn()
}
def switchOnThreeHandler(evt) {
	log.debug "switch on3"
	strip.ThreeOn()
}
def switchOffOneHandler(evt) {
	log.debug "switch off1"
	strip.OneOff()
}
def switchOffTwoHandler(evt) {
	log.debug "switch off2"
	strip.TwoOff()
}
def switchOffThreeHandler(evt) {
	log.debug "switch off3"
	strip.ThreeOff()
}
/**
 *  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.
 *
 *	    GE/Jasco ZigBee Switch
 *
 *	Author: SmartThings
 *	Date: 2015-07-01
 */

metadata {
	// Automatically generated. Make future change here.
        // flipped the on/off detection
	definition (name: "KUDLED ZigBee Wall Switch 1.1", namespace: "smartthings", author: "SmartThings") {
		capability "Switch"
		capability "Configuration"
		capability "Refresh"
		capability "Actuator"
		capability "Sensor"
		
		command "OneOn"
		command "OneOff"
		command "TwoOn"
		command "TwoOff"
		command "ThreeOn"
		command "ThreeOff"
		
        
        attribute "switch2","ENUM",["on","off"]
        attribute "switch3","ENUM",["on","off"]        
 
		
		fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B05,0702", outClusters: "0003, 000A,0019", manufacturer: "Jasco Products", model: "45853"
		fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B05,0702", outClusters: "000A,0019", manufacturer: "Jasco Products", model: "45856"
	}

	// 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:"OneOff", icon:"st.switches.switch.on", backgroundColor:"#79b821"
			state "off", label:'${name}', action:"OneOn", icon:"st.switches.switch.off", backgroundColor:"#ffffff"
		}
	
		standardTile("switch2", "device.switch2", width: 1, height: 1, canChangeIcon: true) {
			state "on", label:'${name}', action:"TwoOff", icon:"st.switches.switch.on", backgroundColor:"#79b821"
			state "off", label:'${name}', action:"TwoOn", icon:"st.switches.switch.off", backgroundColor:"#ffffff"
		}
		
		standardTile("switch3", "device.switch3", width: 1, height: 1, canChangeIcon: true) {
			state "on", label:'${name}', action:"ThreeOff", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
			state "off", label:'${name}', action:"ThreeOn", 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"
		}

		

		
		main (["switch", "switch2", "switch3"])
		details(["switch", "switch2", "switch3", "refresh"])
        
        
        
	}
}

// Parse incoming device messages to generate events
def parse(String description) {
	log.debug "description is $description"

	def finalResult = isKnownDescription(description)
	if (finalResult != "false") {
		log.info finalResult
		if (finalResult.type == "update") {
			log.info "$device updates: ${finalResult.value}"
		}
		else {
			sendEvent(name: finalResult.type, value: finalResult.value)
		}
	}
	else {
		log.warn "DID NOT PARSE MESSAGE for description : $description"
		log.debug parseDescriptionAsMap(description)
	}
}

// Commands to device
def zigbeeCommand(endp, cluster, attribute){
	"st cmd 0x${device.deviceNetworkId} ${endp} ${cluster} ${attribute} {}"
}

def off() {
	zigbeeCommand("6", "2")
}

def on() {
	zigbeeCommand("6", "2")
}

def OneOn() {
	zigbeeCommand("0x12","6", "1")
}

def OneOff() {
	zigbeeCommand("0x12","6", "0")
}

def TwoOn() {
	zigbeeCommand("0x11","6", "1")
}

def TwoOff() {
	zigbeeCommand("0x11","6", "0")
}

def ThreeOn() {
	zigbeeCommand("0x10","6", "1")
}

def ThreeOff() {
	zigbeeCommand("0x10","6", "0")
}

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

}

def configure() {
	onOffConfig() + refresh()
}


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 String swapEndianHex(String hex) {
	reverseArray(hex.decodeHex()).encodeHex()
}

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

//Need to reverse array of size 2
private byte[] reverseArray(byte[] array) {
	byte tmp;
	tmp = array[1];
	array[1] = array[0];
	array[0] = tmp;
	return array
}

def parseDescriptionAsMap(description) {
	if (description?.startsWith("read attr -")) {
		(description - "read attr - ").split(",").inject([:]) { map, param ->
			def nameAndValue = param.split(":")
			map += [(nameAndValue[0].trim()): nameAndValue[1].trim()]
		}
	}
	else if (description?.startsWith("catchall: ")) {
		def seg = (description - "catchall: ").split(" ")
		def zigbeeMap = [:]
		zigbeeMap += [raw: (description - "catchall: ")]
		zigbeeMap += [profileId: seg[0]]
		zigbeeMap += [clusterId: seg[1]]
		zigbeeMap += [sourceEndpoint: seg[2]]
		zigbeeMap += [destinationEndpoint: seg[3]]
		zigbeeMap += [options: seg[4]]
		zigbeeMap += [messageType: seg[5]]
		zigbeeMap += [dni: seg[6]]
		zigbeeMap += [isClusterSpecific: Short.valueOf(seg[7], 16) != 0]
		zigbeeMap += [isManufacturerSpecific: Short.valueOf(seg[8], 16) != 0]
		zigbeeMap += [manufacturerId: seg[9]]
		zigbeeMap += [command: seg[10]]
		zigbeeMap += [direction: seg[11]]
		zigbeeMap += [data: seg.size() > 12 ? seg[12].split("").findAll { it }.collate(2).collect {
			it.join('')
		} : []]

		zigbeeMap
	}
}

def isKnownDescription(description) {
	if ((description?.startsWith("catchall:")) || (description?.startsWith("read attr -"))) {
		def descMap = parseDescriptionAsMap(description)
		if (descMap.cluster == "0006" || descMap.clusterId == "0006") {
			isDescriptionOnOff(descMap)
		}
		else if (descMap.cluster == "0702" || descMap.clusterId == "0702"){
			isDescriptionPower(descMap)
		}
		else {
			return "false"
		}
	}
	else if(description?.startsWith("on/off:")) {
		def switchValue = description?.endsWith("1") ? "on" : "off"
	
    		if (descMap.sourceEndpoint == "12") {   
    			return	[type: "switch", value : switchValue]
        	}
        	else if (descMap.sourceEndpoint == "11") {   
         		return	[type: "switch2", value : switchValue]
        	}
        	else if (descMap.sourceEndpoint == "10") {   
     			return	[type: "switch3", value : switchValue]
        	}

    
    
	}
	else {
		return "false"
	}
}

def isDescriptionOnOff(descMap) {
	def switchValue = "undefined"
	if (descMap.cluster == "0006") {				//cluster info from read attr
		value = descMap.value
		if (value == "01"){
			switchValue = "on"
		}
		else if (value == "00"){
			switchValue = "off"
		}
	}
    
	else if (descMap.clusterId == "0006") {
		//cluster info from catch all
		//command 0B is Default response and the last two bytes are [on/off][success]. on/off=00, success=00
		//command 01 is Read attr response. the last two bytes are [datatype][value]. boolean datatype=10; on/off value = 01/00
		
         if ((descMap.command=="0B" && descMap.raw.endsWith("0100")) || (descMap.command=="01" && descMap.raw.endsWith("1001"))){
			
            if (descMap.sourceEndpoint == "12") {   
    			return	[type: "switch", value : "on"]
        	}
        	else if (descMap.sourceEndpoint == "11") {   
         		return	[type: "switch2", value : "on"]
        	}
        	else if (descMap.sourceEndpoint == "10") {   
     			return	[type: "switch3", value : "on"]
        	}

            
            

		}
		else if ((descMap.command=="0B" && descMap.raw.endsWith("0000")) || (descMap.command=="01" && descMap.raw.endsWith("1000"))){
			
		
    		if (descMap.sourceEndpoint == "12") {   
    			return	[type: "switch", value : "off"]
        	}
        	else if (descMap.sourceEndpoint == "11") {   
         		return	[type: "switch2", value : "off"]
        	}
        	else if (descMap.sourceEndpoint == "10") {   
     			return	[type: "switch3", value : "off"]
        	}
            
        }
       
        else if (descMap.command=="01" && descMap.raw.endsWith("2000")){
		
    		if (descMap.sourceEndpoint == "12") {   
    			return	[type: "switch", value : "off"]
        	}
        	else if (descMap.sourceEndpoint == "11") {   
         		return	[type: "switch2", value : "off"]
        	}
        	else if (descMap.sourceEndpoint == "10") {   
     			return	[type: "switch3", value : "off"]
        	}
        }
        else if (descMap.command=="01" && descMap.raw.endsWith("2001")){
		
    		if (descMap.sourceEndpoint == "12") {   
    			return	[type: "switch", value : "on"]
        	}
        	else if (descMap.sourceEndpoint == "11") {   
         		return	[type: "switch2", value : "on"]
        	}
        	else if (descMap.sourceEndpoint == "10") {   
     			return	[type: "switch3", value : "on"]
        	}

            
        
		}
        
     
		else if(descMap.command=="07"){
			return	[type: "update", value : "switch (0006) capability configured successfully"]
		}
	}

	if (switchValue != "undefined"){
		return	[type: "switch", value : switchValue]
	}
	else {
		return "false"
	}

}

def isDescriptionPower(descMap) {
	def powerValue = "undefined"
	if (descMap.cluster == "0702") {
		if (descMap.attrId == "0400") {
			powerValue = convertHexToInt(descMap.value)
		}
	}
	else if (descMap.clusterId == "0702") {
		if(descMap.command=="07"){
			return	[type: "update", value : "power (0702) capability configured successfully"]
		}
	}

	if (powerValue != "undefined"){
		return	[type: "power", value : powerValue]
	}
	else {
		return "false"
	}
}


def onOffConfig() {
	[
			"zdo bind 0x${device.deviceNetworkId} 1 ${endpointId} 6 {${device.zigbeeId}} {}", "delay 200",
			"zcl global send-me-a-report 6 0 0x10 0 600 {01}",
			"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 1500"
	]
}

String convertToHexString(value, width=2) {
	def s = new BigInteger(Math.round(value).toString()).toString(16)
	while (s.size() < width) {
		s = "0" + s
	}
	s
}
  1. Updated device handler as above.
    new device handler will sync states whether u toggle at physical button or press thru the smartphone app

  2. Smartapp binder as above

Updated to sync states on between real switch and virtual switches. Now, whichever you press, the states will stay coherent.

** The ON/OFF states could be wrong. I will update again when I have wired it up to my dumb bulbs.

Great progress Andy. Just need to find a way to buy the things!

I’m using this currently, a zwave touch switch. Not required neutral, see my earlier post:

They also come with single, double & tripper switches and the best things is you don’t have to worry about device type / driver. The built in smartthings device type able to identify & support the switch

I have further updated the code above. All should be well now. Ready to rock and roll for my actual install.

2 Likes

Cool thank you - still waiting for mine to arrive!

Real shame this requires such a large relay box as too big to go behind my switch and in my ceiling :frowning:

Ps: do they also work as dimmers ?

@Jamie I’ve taken a punt on the three gang one too, however 5 days in and I’m still waiting for it to be shipped :frowning:

I don’t think they work as dimmer

Mine arrived yesterday but I am in Dublin until Friday so a nice project for Saturday. Mine didn’t show as shipped for a few days.

I ordered it on the 7th. FYI

i still havent installed mine… pretty busy.

the on/off states could be swapped.

** reminder. U will require NEUTRAL wire to the kudled box. Most UK homes dont have neutral i the light switch boxes.

I will have to get an electrician to pull one for me.

i find difficulty to pair KUDLED devices to my hub nowadays. You have to be patient and keep retrying.

To put the switch in pair mode, press and hold the bottom most switch. The LED under the kudled logo will blink coninually when in pair mode.

You have to keep retrying it, unitil u see an unknown device in hub.

  1. assign it to the custom device handler above.

  2. create 3 virtual switches

  3. bind the 3 switches to the real device.

  4. then u can bind the virtual switches to smartapp actions.

let me know.

Thanks Andy, you are quite correct trying to get SmartThings to recognise the KUDLED as a “Thing” is a nightmare! I’ve not managed it yet - even though I know the switch has paired enough to get a solid LED but SmartThings won’t say it has found an unknown device.

This is the same as the Hue Bridge, but I expected Hue to restrict the switch being a more closed platform.

The switch works perfectly in all ways except the zigbee/ST front…

Jamie

Mine has eventually shipping, no silver ones left in stock, so I took a gold one (its under the stairs, I don’t care), however, from your above posts, it looks like I’ve fun and games ahead :slight_smile:

Wanted to get a few, but no stock. They are currently upgrading their switches to improve signal coverage and stability.