Aqara QBKG03LM No Neutral Dual Button Light Switch

Received one of these a couple of days ago, I have tried a few drivers and this one from ST appears to work, however it doesn't register any presses from the switch but will control the buttons from the driver interface..any thoughts?

/**
 *
 *  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.
 *
 *  Modified from DTH by a4refillpad
 
 *  10/2017 first release
 */

metadata {
    definition (name: "Aqara 2 Button Wired Wall Switch", namespace: "simic", author: "simic") {
        capability "Actuator"
        capability "Configuration"
        capability "Refresh"
        capability "Switch"
        capability "Temperature Measurement"
        
        command "on2"
        command "off2"
        
        attribute "switch2","ENUM", ["on","off"]
        
        attribute "lastCheckin", "string"
    }

    // 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"
    }

    tiles(scale: 2) {
        multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
            tileAttribute ("device.switch", key: "PRIMARY_CONTROL") { 
                attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#00a0dc", nextState:"turningOff"
                attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
                attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#00a0dc", nextState:"turningOff"
                attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
            }
            
            
            
           	tileAttribute("device.lastCheckin", key: "SECONDARY_CONTROL") {
    			attributeState("default", label:'Last Update: ${currentValue}',icon: "st.Health & Wellness.health9")
		   	}
        }
        
        standardTile("switch2", "device.switch2", width: 2, height: 2, canChangeIcon: true) {
			state "off", label: '${name}', action: "on2", icon: "st.switches.switch.off", backgroundColor: "#ffffff"
			state "on", label: '${name}', action: "off2", icon: "st.switches.switch.on", backgroundColor: "#79b821"
		}

        valueTile("temperature", "device.temperature", width: 2, height: 2) {
			state("temperature", label:'${currentValue}°',
				backgroundColors:[
					[value: 31, color: "#153591"],
					[value: 44, color: "#1e9cbb"],
					[value: 59, color: "#90d2a7"],
					[value: 74, color: "#44b621"],
					[value: 84, color: "#f1d801"],
					[value: 95, color: "#d04e00"],
					[value: 96, color: "#bc2323"]
				]
			)
		}
        standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
            state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
        }
        main (["switch", "switch2", "temperature"])
        details(["switch", "switch2", "temperature", "refresh"])
    }
}

// Parse incoming device messages to generate events
def parse(String description) {
   log.debug "Parsing '${description}'"
   def value = zigbee.parse(description)?.text
   log.debug "Parse: $value"
   Map map = [:]
   
   if (description?.startsWith('catchall:')) {
		map = parseCatchAllMessage(description)
	}
	else if (description?.startsWith('read attr -')) {
		map = parseReportAttributeMessage(description)
	}
    else if (description?.startsWith('on/off: ')){
    	def resultMap = zigbee.getKnownDescription(description)
   		log.debug "${resultMap}"
        
        map = parseCustomMessage(description) 
    }

	log.debug "Parse returned $map"
    //  send event for heartbeat    
    def now = new Date()
    sendEvent(name: "lastCheckin", value: now)
    
	def results = map ? createEvent(map) : null
	return results;
}

private Map parseCatchAllMessage(String description) {
	Map resultMap = [:]
	def cluster = zigbee.parse(description)
	log.debug cluster
    
    if (cluster.clusterId == 0x0006 && cluster.command == 0x01){
    	def onoff = cluster.data[-1]
        if (onoff == 1)
        	resultMap = createEvent(name: "switch", value: "on")
        else if (onoff == 0)
            resultMap = createEvent(name: "switch", value: "off")
    }
    
	return resultMap
}

private Map parseReportAttributeMessage(String description) {
	Map descMap = (description - "read attr - ").split(",").inject([:]) { map, param ->
		def nameAndValue = param.split(":")
		map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
	}
	//log.debug "Desc Map: $descMap"
 
	Map resultMap = [:]

	if (descMap.cluster == "0001" && descMap.attrId == "0020") {
		resultMap = getBatteryResult(convertHexToInt(descMap.value / 2))
 	}
    if (descMap.cluster == "0002" && descMap.attrId == "0000") {
		resultMap = createEvent(name: "temperature", value: zigbee.parseHATemperatureValue("temperature: " + (convertHexToInt(descMap.value) / 2), "temperature: ", getTemperatureScale()), unit: getTemperatureScale())
		log.debug "Temperature Hex convert to ${resultMap.value}%"
    }
    else if (descMap.cluster == "0008" && descMap.attrId == "0000") {
    	resultMap = createEvent(name: "switch", value: "off")
    } 
	return resultMap
}

def off() {
    log.debug "off()"
	sendEvent(name: "switch", value: "off")
	"st cmd 0x${device.deviceNetworkId} 2 6 0 {}"
}

def on() {
   log.debug "on()"
	sendEvent(name: "switch", value: "on")
	"st cmd 0x${device.deviceNetworkId} 2 6 1 {}"
}

def off2() {
    log.debug "off2()"
	sendEvent(name: "switch2", value: "off")
	"st cmd 0x${device.deviceNetworkId} 3 6 0 {}"
}

def on2() {
   log.debug "on2()"
	sendEvent(name: "switch2", value: "on")
	"st cmd 0x${device.deviceNetworkId} 3 6 1 {}"
}

def refresh() {
	log.debug "refreshing"
    [
        "st rattr 0x${device.deviceNetworkId} 1 6 0", "delay 500",
        "st rattr 0x${device.deviceNetworkId} 1 6 0", "delay 250",
        "st rattr 0x${device.deviceNetworkId} 1 2 0", "delay 250",
        "st rattr 0x${device.deviceNetworkId} 1 1 0", "delay 250",
        "st rattr 0x${device.deviceNetworkId} 1 0 0"
    ]
}

private Map parseCustomMessage(String description) {
	def result
	if (description?.startsWith('on/off: ')) {
    	if (description == 'on/off: 0')
    		result = createEvent(name: "switch", value: "off")
    	else if (description == 'on/off: 1')
    		result = createEvent(name: "switch", value: "on")
	}
    
    return result
}

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

Friendly forum posting tip: When pasting your code into a post, please highlight all of the code, and then click the Preformatted Text menu button (looks like < / >) to make you code readable and copyable for others.

My apologies

```
/**
 *
 *  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.
 *
 *  Modified from DTH by a4refillpad
 
 *  10/2017 first release
 */

metadata {
    definition (name: "Aqara 2 Button Wired Wall Switch", namespace: "simic", author: "simic") {
        capability "Actuator"
        capability "Configuration"
        capability "Refresh"
        capability "Switch"
        capability "Temperature Measurement"
        
        command "on2"
        command "off2"
        
        attribute "switch2","ENUM", ["on","off"]
        
        attribute "lastCheckin", "string"
    }

    // 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"
    }

    tiles(scale: 2) {
        multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
            tileAttribute ("device.switch", key: "PRIMARY_CONTROL") { 
                attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#00a0dc", nextState:"turningOff"
                attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
                attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#00a0dc", nextState:"turningOff"
                attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
            }
            
            
            
           	tileAttribute("device.lastCheckin", key: "SECONDARY_CONTROL") {
    			attributeState("default", label:'Last Update: ${currentValue}',icon: "st.Health & Wellness.health9")
		   	}
        }
        
        standardTile("switch2", "device.switch2", width: 2, height: 2, canChangeIcon: true) {
			state "off", label: '${name}', action: "on2", icon: "st.switches.switch.off", backgroundColor: "#ffffff"
			state "on", label: '${name}', action: "off2", icon: "st.switches.switch.on", backgroundColor: "#79b821"
		}

        valueTile("temperature", "device.temperature", width: 2, height: 2) {
			state("temperature", label:'${currentValue}°',
				backgroundColors:[
					[value: 31, color: "#153591"],
					[value: 44, color: "#1e9cbb"],
					[value: 59, color: "#90d2a7"],
					[value: 74, color: "#44b621"],
					[value: 84, color: "#f1d801"],
					[value: 95, color: "#d04e00"],
					[value: 96, color: "#bc2323"]
				]
			)
		}
        standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
            state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
        }
        main (["switch", "switch2", "temperature"])
        details(["switch", "switch2", "temperature", "refresh"])
    }
}

// Parse incoming device messages to generate events
def parse(String description) {
   log.debug "Parsing '${description}'"
   def value = zigbee.parse(description)?.text
   log.debug "Parse: $value"
   Map map = [:]
   
   if (description?.startsWith('catchall:')) {
		map = parseCatchAllMessage(description)
	}
	else if (description?.startsWith('read attr -')) {
		map = parseReportAttributeMessage(description)
	}
    else if (description?.startsWith('on/off: ')){
    	def resultMap = zigbee.getKnownDescription(description)
   		log.debug "${resultMap}"
        
        map = parseCustomMessage(description) 
    }

	log.debug "Parse returned $map"
    //  send event for heartbeat    
    def now = new Date()
    sendEvent(name: "lastCheckin", value: now)
    
	def results = map ? createEvent(map) : null
	return results;
}

private Map parseCatchAllMessage(String description) {
	Map resultMap = [:]
	def cluster = zigbee.parse(description)
	log.debug cluster
    
    if (cluster.clusterId == 0x0006 && cluster.command == 0x01){
    	def onoff = cluster.data[-1]
        if (onoff == 1)
        	resultMap = createEvent(name: "switch", value: "on")
        else if (onoff == 0)
            resultMap = createEvent(name: "switch", value: "off")
    }
    
	return resultMap
}

private Map parseReportAttributeMessage(String description) {
	Map descMap = (description - "read attr - ").split(",").inject([:]) { map, param ->
		def nameAndValue = param.split(":")
		map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
	}
	//log.debug "Desc Map: $descMap"
 
	Map resultMap = [:]

	if (descMap.cluster == "0001" && descMap.attrId == "0020") {
		resultMap = getBatteryResult(convertHexToInt(descMap.value / 2))
 	}
    if (descMap.cluster == "0002" && descMap.attrId == "0000") {
		resultMap = createEvent(name: "temperature", value: zigbee.parseHATemperatureValue("temperature: " + (convertHexToInt(descMap.value) / 2), "temperature: ", getTemperatureScale()), unit: getTemperatureScale())
		log.debug "Temperature Hex convert to ${resultMap.value}%"
    }
    else if (descMap.cluster == "0008" && descMap.attrId == "0000") {
    	resultMap = createEvent(name: "switch", value: "off")
    } 
	return resultMap
}

def off() {
    log.debug "off()"
	sendEvent(name: "switch", value: "off")
	"st cmd 0x${device.deviceNetworkId} 2 6 0 {}"
}

def on() {
   log.debug "on()"
	sendEvent(name: "switch", value: "on")
	"st cmd 0x${device.deviceNetworkId} 2 6 1 {}"
}

def off2() {
    log.debug "off2()"
	sendEvent(name: "switch2", value: "off")
	"st cmd 0x${device.deviceNetworkId} 3 6 0 {}"
}

def on2() {
   log.debug "on2()"
	sendEvent(name: "switch2", value: "on")
	"st cmd 0x${device.deviceNetworkId} 3 6 1 {}"
}

def refresh() {
	log.debug "refreshing"
    [
        "st rattr 0x${device.deviceNetworkId} 1 6 0", "delay 500",
        "st rattr 0x${device.deviceNetworkId} 1 6 0", "delay 250",
        "st rattr 0x${device.deviceNetworkId} 1 2 0", "delay 250",
        "st rattr 0x${device.deviceNetworkId} 1 1 0", "delay 250",
        "st rattr 0x${device.deviceNetworkId} 1 0 0"
    ]
}

private Map parseCustomMessage(String description) {
	def result
	if (description?.startsWith('on/off: ')) {
    	if (description == 'on/off: 0')
    		result = createEvent(name: "switch", value: "off")
    	else if (description == 'on/off: 1')
    		result = createEvent(name: "switch", value: "on")
	}
    
    return result
}

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

Also I think you forgot to turn off the caps lock after you typed QBKG03LM in the title of the thread :wink:. Sorry couldn’t help myself :grin:.

There isn't a binding nor any reporting configured for the switch cluster (0x0006)..., without that the thing will be mute on physical actuation, thats assuming the device will even report on its own, which i do not know...

I have managed to Mod the aqara wireless button controller driver and that registers the button pushes on the device, so now need to merge the wired wall switch driver with the button driver, bit hot today though so might wait tll the evening, just got o 32c here in the UK :sunglasses:

Hi do you think I would be right in assuming this two button light switch with relays appears to have 4 endpoints, ie two button for the 'button' and two for the relay. It reads from the bottom up Button 1 pressed, then button 2, then button 2 off and button 1 off

peter

Logs Below

dev:3862018-08-05 12:35:02.863:debugDevice, parse description: read attr - raw: 0567040006100000100000001001, dni: 0567, endpoint: 04, cluster: 0006, size: 10, attrId: 0000, encoding: 10, value: 0000001001

dev:3862018-08-05 12:35:02.485:debugDevice, parse description: read attr - raw: 0567020006160000100000F02300670503, dni: 0567, endpoint: 02, cluster: 0006, size: 16, attrId: 0000, encoding: 10, value: 0000F02300670503

dev:3862018-08-05 12:35:00.616:debugDevice, parse description: read attr - raw: 0567050006100000100000001001, dni: 0567, endpoint: 05, cluster: 0006, size: 10, attrId: 0000, encoding: 10, value: 0000001001

dev:3862018-08-05 12:35:00.235:debugDevice, parse description: read attr - raw: 0567030006160000100000F02300670503, dni: 0567, endpoint: 03, cluster: 0006, size: 16, attrId: 0000, encoding: 10, value: 0000F02300670503

dev:3862018-08-05 12:34:58.255:debugDevice, parse description: read attr - raw: 0567050006100000100000001001, dni: 0567, endpoint: 05, cluster: 0006, size: 10, attrId: 0000, encoding: 10, value: 0000001001

dev:3862018-08-05 12:34:57.840:debugDevice, parse description: read attr - raw: 0567030006160000100100F02300670503, dni: 0567, endpoint: 03, cluster: 0006, size: 16, attrId: 0000, encoding: 10, value: 0100F02300670503

dev:3862018-08-05 12:34:52.393:debugDevice, parse description: read attr - raw: 0567040006100000100000001001, dni: 0567, endpoint: 04, cluster: 0006, size: 10, attrId: 0000, encoding: 10, value: 0000001001

dev:3862018-08-05 12:34:51.934:debugDevice, parse description: read attr - raw: 0567020006160000100100F02300670503, dni: 0567, endpoint: 02, cluster: 0006, size: 16, attrId: 0000, encoding: 10, value: 0100F02300670503

Yeah that looks right, I would use parseDescriptionAsMap(description) for the main parser method dump that straight to debug, the values being returned look wonky, appears that there's other data being encoded in them.

This is the info when connected

manufacturer:LUMI
address64bit:00158D00024DCC00
address16bit:AA4C
model:lumi.ctrl_neutral2
basicAttributesInitialized:true
application:12
endpoints.01.manufacturer:LUMI
endpoints.01.idAsInt:1
endpoints.01.inClusters:0000,0003,0001,0002,0019,000A
endpoints.01.endpointId:01
endpoints.01.profileId:0104
endpoints.01.application:12
endpoints.01.outClusters:0000,000A,0019
endpoints.01.initialized:true
endpoints.01.model:lumi.ctrl_neutral2
endpoints.01.stage:4
endpoints.02.manufacturer:null
endpoints.02.idAsInt:2
endpoints.02.inClusters:0010,0006,0004,0005
endpoints.02.endpointId:02
endpoints.02.profileId:0104
endpoints.02.application:null
endpoints.02.outClusters:null
endpoints.02.initialized:true
endpoints.02.model:null
endpoints.02.stage:4
endpoints.03.manufacturer:null
endpoints.03.idAsInt:3
endpoints.03.inClusters:0010,0006,0004,0005
endpoints.03.endpointId:03
endpoints.03.profileId:0104
endpoints.03.application:null
endpoints.03.outClusters:null
endpoints.03.initialized:true
endpoints.03.model:null
endpoints.03.stage:4
endpoints.04.manufacturer:null
endpoints.04.idAsInt:4
endpoints.04.inClusters:0012,0006
endpoints.04.endpointId:04
endpoints.04.profileId:0104
endpoints.04.application:null
endpoints.04.outClusters:null
endpoints.04.initialized:true
endpoints.04.model:null
endpoints.04.stage:4
endpoints.05.manufacturer:null
endpoints.05.idAsInt:5
endpoints.05.inClusters:0012,0006
endpoints.05.endpointId:05
endpoints.05.profileId:0104
endpoints.05.application:null
endpoints.05.outClusters:null
endpoints.05.initialized:true
endpoints.05.model:null
endpoints.05.stage:4
endpoints.06.manufacturer:null
endpoints.06.idAsInt:6
endpoints.06.inClusters:0012,0006
endpoints.06.endpointId:06
endpoints.06.profileId:0104
endpoints.06.application:null
endpoints.06.outClusters:null
endpoints.06.initialized:true
endpoints.06.model:null
endpoints.06.stage:4
endpoints.08.manufacturer:null
endpoints.08.idAsInt:8
endpoints.08.inClusters:000C
endpoints.08.endpointId:08
endpoints.08.profileId:0104
endpoints.08.application:null
endpoints.08.outClusters:null
endpoints.08.initialized:true
endpoints.08.model:null
endpoints.08.stage:4

Copyright © 2018 Hubitat, Inc. | Community | Knowledge Base

i would bind and set reporting to cluster 6 on all endpoints except 8, and cluster 5 on 2 and 3.
then just start pushing, holding, releasing the buttons.
write down the cluster id, endpoint and value for each response, after that it's just a matter of generating the events based on what you find.

I have been playing with this for a while, it all works except I cannot work out how to register the relays turning on when the button is pressed, the device automatically turns on the relay when the button is pressed and I can se the button being pushed but I don't know how to register the relay is activated on the hub. If you use the commands under devices you can switch the relay on and it is registered as on.

I also have no idea how to register the second relay and button under devices other than creating a virtual switch, any pointers would be appreciated.

Cheers peter

  /**
 *
 *  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.
 *
 *  Modified from DTH and simic by a4refillpad and modded again by picturepete
 
 *  08/2018 first release
 */

metadata {
    definition (name: "Aqara 2 Button Wired Test", namespace: "picturepete", author: "picturepete") {
        // capability "Actuator"
	    capability "PushableButton"
        capability "Configuration"
        capability "Refresh"
        capability "Switch"
              
        command "on2"
        command "off2"
		
        attribute "numberofbuttons", "6"
        attribute "switch2","ENUM", ["on","off"]
        attribute "lastCheckin", "string"
	    attribute "buttonPressed", "String"
    } 

}

// Parse incoming device messages to generate events
def parse(String description) {
   log.debug "Parsing '${description}'"
   def value = zigbee.parse(description)?.text
   def endpoint = description.split(",").find {it.split(":")[0].trim() == "endpoint"}?.split(":")[1].trim()
   def cluster	= description.split(",").find {it.split(":")[0].trim() == "cluster"}?.split(":")[1].trim()
   def attrId = description.split(",").find {it.split(":")[0].trim() == "attrId"}?.split(":")[1].trim()
   def valueHex = description.split(",").find {it.split(":")[0].trim() == "value"}?.split(":")[1].trim()
   log.debug "Parse: $value"
   Map map = [:]
   
	if (cluster == "0006") {
		// Parse button press: endpoint 04 = left, 05 = right, 06 = both
		map = parseButtonPress(Integer.parseInt(endpoint))
	}
	else if (description?.startsWith('read attr -')) {
		map = parseReportAttributeMessage(description)
	}
    else if (description?.startsWith('on/off: ')){
    	def resultMap = zigbee.getKnownDescription(description)
   		log.debug "${resultMap}"
        
        map = parseCustomMessage(description) 
    }
	
	log.debug "Parse returned $map"
    //  send event for heartbeat    
    def now = new Date()
    sendEvent(name: "lastCheckin", value: now)
    
	def results = map ? createEvent(map) : null
	return results;
}




private Map parseButtonPress(value) {
	def pushType = ["", "Null", "Relay1", "Relay2", "Right", "Left", "Both"]
	def descText = "${pushType[value]} button${(value == 6) ? "s" : ""} pressed (Button $value pushed)"
	displayInfoLog(descText)
	displayDebugLog("Setting buttonPressed to current date/time for webCoRE")
	sendEvent(name: "buttonPressed", value: now(), descriptionText: "Updated buttonPressed (webCoRE)")
		return [
		name: 'pushed',
		value: value,
		isStateChange: true,
		descriptionText: descText
		
		]
	
}

private def displayDebugLog(message) {
	if (debugLogging) log.debug "${device.displayName}: ${message}"
}

private def displayInfoLog(message) {
	if (infoLogging || state.prefsSetCount != 1)
		log.info "${device.displayName}: ${message}"
}

private Map parseCatchAllMessage(String description) {
	Map resultMap = [:]
	def cluster = zigbee.parse(description)
	log.debug cluster
    
    if (cluster.clusterId == 0x0006 && cluster.command == 0x01){
    	def onoff = cluster.data[-1]
        if (onoff == 1)
        	resultMap = createEvent(name: "switch", value: "on")
        else if (onoff == 0)
            resultMap = createEvent(name: "switch", value: "off")
    }
    
	return resultMap
}

def off() {
    //log.debug "off()"
	sendEvent(name: "switch", value: "off")
	"st cmd 0x${device.deviceNetworkId} 2 6 0 {}"
}

def on() {
   //log.debug "on()"
	sendEvent(name: "switch", value: "on")
	"st cmd 0x${device.deviceNetworkId} 2 6 1 {}"
}

def off2() {
    //log.debug "off2()"
	sendEvent(name: "switch2", value: "off")
	"st cmd 0x${device.deviceNetworkId} 3 6 0 {}"
}

def on2() {
   //log.debug "on2()"
	sendEvent(name: "switch2", value: "on")
	"st cmd 0x${device.deviceNetworkId} 3 6 1 {}"
}


private Map parseCustomMessage(String description) {
	def result
	if (description?.startsWith('on/off: ')) {
    	if (description == 'on/off: 0')
    		result = createEvent(name: "switch", value: "off")
    	else if (description == 'on/off: 1')
    		result = createEvent(name: "switch", value: "on")
	}
    
    return result
}

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

I have a similar issue!

I have 12 of the Xiaomi Aqara double light switches fitted that require a neutral, aka the QBKG12LM or lumi.ctrl_ln2.aq1. It's sometimes referred to the "Zero Line" version on sites like GearBest.

Out of the box, @peter's modified DH for the QBKG03LM half works with my QBKG12LM. I can control each switch from the Hubitat UI and additionally the button press detection code works in that I receive button 1 or 2 presses reflected. However, I can't get the status of each switch to update on physical light switch presses.

I have had a long tinker around in the logs and also changing many of the clusters with no success.

Best wishes,
Mark

QBKG12LM Payload Data on pairing:

manufacturer:null
address64bit:00158D0002114814
address16bit:782B
model:lumi.ctrl_ln2.aq1
basicAttributesInitialized:true
application:1F
endpoints.01.manufacturer:null
endpoints.01.idAsInt:1
endpoints.01.inClusters:0000,0004,0003,0006,0010,0005,000A,0001,0002
endpoints.01.endpointId:01
endpoints.01.profileId:0104
endpoints.01.application:1F
endpoints.01.outClusters:0019,000A
endpoints.01.initialized:true
endpoints.01.model:lumi.ctrl_ln2.aq1
endpoints.01.stage:4
endpoints.02.manufacturer:null
endpoints.02.idAsInt:2
endpoints.02.inClusters:0006,0010
endpoints.02.endpointId:02
endpoints.02.profileId:0104
endpoints.02.application:null
endpoints.02.outClusters:null
endpoints.02.initialized:true
endpoints.02.model:null
endpoints.02.stage:4
endpoints.03.manufacturer:null
endpoints.03.idAsInt:3
endpoints.03.inClusters:000C
endpoints.03.endpointId:03
endpoints.03.profileId:0104
endpoints.03.application:null
endpoints.03.outClusters:000C,0004
endpoints.03.initialized:true
endpoints.03.model:null
endpoints.03.stage:4
endpoints.04.manufacturer:null
endpoints.04.idAsInt:4
endpoints.04.inClusters:000C
endpoints.04.endpointId:04
endpoints.04.profileId:0104
endpoints.04.application:null
endpoints.04.outClusters:000C
endpoints.04.initialized:true
endpoints.04.model:null
endpoints.04.stage:4
endpoints.05.manufacturer:null
endpoints.05.idAsInt:5
endpoints.05.inClusters:0010,0012
endpoints.05.endpointId:05
endpoints.05.profileId:0104
endpoints.05.application:null
endpoints.05.outClusters:null
endpoints.05.initialized:true
endpoints.05.model:null
endpoints.05.stage:4
endpoints.06.manufacturer:null
endpoints.06.idAsInt:6
endpoints.06.inClusters:0012,0010
endpoints.06.endpointId:06
endpoints.06.profileId:0104
endpoints.06.application:null
endpoints.06.outClusters:null
endpoints.06.initialized:true
endpoints.06.model:null
endpoints.06.stage:4
endpoints.07.manufacturer:null
endpoints.07.idAsInt:7
endpoints.07.inClusters:0012,0010
endpoints.07.endpointId:07
endpoints.07.profileId:0104
endpoints.07.application:null
endpoints.07.outClusters:null
endpoints.07.initialized:true
endpoints.07.model:null
endpoints.07.stage:4