KUDLED - UK 1/2/3 Gang Switch

improved the code.

pressing on the simulator is able to sync with pressing on the physical button now.

but still not working when i click in the smartphone all. WIP

1 Like

Iā€™ve just ordered a kudled 3 gang switch I thought it was worth a punt the for the kitchen lights. Thank you for developing the code Andy. Iā€™ll let you all know how I get on when it arrives.

Wow.

Pressure to get the code working :slight_smile:

Been busy at work. Will work on it again during the weekend.

1 Like

Works an absolute treat. It is z-wave and battery powered. Replaces your light switch and screws straight into the wall.

The device type and smartapp are here, and they provide single press for each of the 4 buttons, hold press, double click, giving you 12 different switches, in effect.

It passes the wife acceptance test, with flying colours.

1 Like

this devolo is like the aeon minimote?

i.e. it only works with exiting smart bulbs

whats so cool about the kudled 3 gang switch is that it hard wires into your non-smart light fixuture and make it smart. to be controllable by ST hub. Iā€™m trying to make my dumb fan and lights join the ST.

smart bulbs even those from KUDLED are Expensive!

@simic
Hello Andy, As hard as I try, I just canā€™t find where you bought your Kudled switch from.
Can you help with a link. I actually only want 1 & 2 gangs.

Any assistance would be gratefully received.

Best wishes.

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: