Heiman Device handler - Zigbee 4 button

Could someone help me with this device?
Captura de Tela 2020-12-04 às 10.46.20
I need a device handler to work, and test with Zemismart Button DH, OSRAM, EcoSmart, and many other, but none work.

The Raw description is:
01 0104 0401 00 04 0000 0001 0003 0500 03 0003 0019 0501

  • manufacturer: HEIMAN
  • model: RC-EM

I tried to modify this device handler but don’t works.

Summary

/**

  • Copyright 2019 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.
  • Author: SRPOL
  • Date: 2019-02-18
    */

import groovy.json.JsonOutput
import physicalgraph.zigbee.zcl.DataType

metadata {
definition (name: “My modified Zigbee Multi Button”, namespace: “smartthings”, author: “SmartThings”, mcdSync: true, ocfDeviceType: “x.com.st.d.remotecontroller”) {
capability “Actuator”
capability “Battery”
capability “Button”
capability “Holdable Button”
capability “Configuration”
capability “Refresh”
capability “Sensor”
capability “Health Check”

	fingerprint inClusters: "0000, 0001, 0003, 0007, 0020, 0B05", outClusters: "0003, 0006, 0019", manufacturer: "CentraLite", model:"3450-L", deviceJoinName: "Iris Remote Control", mnmn: "SmartThings", vid: "generic-4-button" //Iris KeyFob
	fingerprint inClusters: "0000, 0001, 0003, 0007, 0020, 0B05", outClusters: "0003, 0006, 0019", manufacturer: "CentraLite", model:"3450-L2", deviceJoinName: "Iris Remote Control", mnmn: "SmartThings", vid: "generic-4-button" //Iris KeyFob
	fingerprint profileId: "0104", inClusters: "0004", outClusters: "0000, 0001, 0003, 0004, 0005, 0B05", manufacturer: "HEIMAN", model: "SceneSwitch-EM-3.0", deviceJoinName: "HEIMAN Remote Control", vid: "generic-4-button" //HEIMAN Scene Keypad

	//AduroSmart
	fingerprint inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, FCCC, 1000", outClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, FCCC, 1000", manufacturer: "AduroSmart Eria", model: "ADUROLIGHT_CSC", deviceJoinName: "Eria Remote Control", mnmn: "SmartThings", vid: "generic-4-button" //Eria scene button switch V2.1
	fingerprint inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, FCCC, 1000", outClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, FCCC, 1000", manufacturer: "ADUROLIGHT", model: "ADUROLIGHT_CSC", deviceJoinName: "Eria Remote Control", mnmn: "SmartThings", vid: "generic-4-button" //Eria scene button switch V2.0
	fingerprint inClusters: "0000, 0003, 0008, FCCC, 1000", outClusters: "0003, 0004, 0006, 0008, FCCC, 1000", manufacturer: "ADUROLIGHT", model: "AduroLight_NCC", deviceJoinName: "Eria Remote Control", mnmn: "SmartThings", vid: "generic-4-button" //Eria dimming button switch V2.1 new model name
    fingerprint inClusters: "0000, 0003, 0008, FCCC, 1000", outClusters: "0003, 0004, 0006, 0008, FCCC, 1000", manufacturer: "AduroSmart Eria", model: "Adurolight_NCC", deviceJoinName: "Eria Remote Control", mnmn: "SmartThings", vid: "generic-4-button" //Eria dimming button switch V2.1
	fingerprint inClusters: "0000, 0003, 0008, FCCC, 1000", outClusters: "0003, 0004, 0006, 0008, FCCC, 1000", manufacturer: "ADUROLIGHT", model: "Adurolight_NCC", deviceJoinName: "Eria Remote Control", mnmn: "SmartThings", vid: "generic-4-button" //Eria dimming button switch V2.0
}

tiles {
	standardTile("button", "device.button", width: 2, height: 2) {
		state "default", label: "", icon: "st.unknown.zwave.remote-controller", backgroundColor: "#ffffff"
		state "button 1 pushed", label: "pushed #1", icon: "st.unknown.zwave.remote-controller", backgroundColor: "#00A0DC"
	}

	valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false) {
		state "battery", label:'${currentValue}% battery', unit:""
	}

	standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat") {
		state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
	}
	main (["button"])
	details(["button", "battery", "refresh"])
}

}

def parse(String description) {
def map = zigbee.getEvent(description)
def result = map ? map : parseAttrMessage(description)
if (result.name == “switch”) {
result = createEvent(descriptionText: “Wake up event came in”, isStateChange: true)
}
log.debug “Description {description} parsed to {result}”
return result
}

def parseAttrMessage(description) {
def descMap = zigbee.parseDescriptionAsMap(description)
def map = [:]
if (descMap?.clusterInt == zigbee.POWER_CONFIGURATION_CLUSTER && descMap.commandInt != 0x07 && descMap?.value) {
map = getBatteryPercentageResult(Integer.parseInt(descMap.value, 16))
} else if (isAduroSmartRemote()) {
map = parseAduroSmartButtonMessage(descMap)
} else if (descMap?.clusterInt == zigbee.ONOFF_CLUSTER && descMap.isClusterSpecific) {
map = getButtonEvent(descMap)
} else if (descMap?.clusterInt == 0x0005) {
def buttonNumber
buttonNumber = buttonMap[device.getDataValue(“model”)][descMap.data[2]]

	log.debug "Number is ${buttonNumber}"
	def descriptionText = getButtonName() + " ${buttonNumber} was pushed"
	sendEventToChild(buttonNumber, createEvent(name: "button", value: "pushed", data: [buttonNumber: buttonNumber], descriptionText: descriptionText, isStateChange: true))
	map = createEvent(name: "button", value: "pushed", data: [buttonNumber: buttonNumber], descriptionText: descriptionText, isStateChange: true)
}
map

}

def getButtonEvent(descMap) {
if (descMap.commandInt == 1) {
getButtonResult(“press”)
}
else if (descMap.commandInt == 0) {
def button = buttonMap[device.getDataValue(“model”)][descMap.sourceEndpoint]
getButtonResult(“release”, button)
}
}

def getButtonResult(buttonState, buttonNumber = 1) {
def event = [:]
if (buttonState == ‘release’) {
def timeDiff = now() - state.pressTime
if (timeDiff > 10000) {
return event
} else {
buttonState = timeDiff < holdTime ? “pushed” : “held”
def descriptionText = getButtonName() + " {buttonNumber} was {buttonState}"
event = createEvent(name: “button”, value: buttonState, data: [buttonNumber: buttonNumber], descriptionText: descriptionText, isStateChange: true)
sendEventToChild(buttonNumber, event)
return createEvent(descriptionText: descriptionText)
}
} else if (buttonState == ‘press’) {
state.pressTime = now()
return event
}
}

def sendEventToChild(buttonNumber, event) {
String childDni = “${device.deviceNetworkId}:$buttonNumber”
def child = childDevices.find { it.deviceNetworkId == childDni }
child?.sendEvent(event)
}

def getBatteryPercentageResult(rawValue) {
log.debug ‘Battery’
def volts = rawValue / 10
if (volts > 3.0 || volts == 0 || rawValue == 0xFF) {
[:]
} else {
def result = [
name: ‘battery’
]
def minVolts = 2.1
def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts)
result.value = Math.min(100, (int)(pct * 100))
def linkText = getLinkText(device)
result.descriptionText = “{linkText} battery was {result.value}%”
createEvent(result)
}
}

def refresh() {
return zigbee.readAttribute(zigbee.POWER_CONFIGURATION_CLUSTER, batteryVoltage) +
zigbee.readAttribute(zigbee.ONOFF_CLUSTER, switchType)
zigbee.enrollResponse()
}

def ping() {
refresh()
}

def configure() {
def bindings = getModelBindings(device.getDataValue(“model”))
def cmds = zigbee.onOffConfig() +
zigbee.configureReporting(zigbee.POWER_CONFIGURATION_CLUSTER, batteryVoltage, DataType.UINT8, 30, 21600, 0x01) +
zigbee.enrollResponse() +
zigbee.readAttribute(zigbee.POWER_CONFIGURATION_CLUSTER, batteryVoltage) + bindings
if (isHeimanButton())
cmds += zigbee.writeAttribute(0x0000, 0x0012, DataType.BOOLEAN, 0x01) +
addHubToGroup(0x000F) + addHubToGroup(0x0010) + addHubToGroup(0x0011) + addHubToGroup(0x0012)
return cmds
}

def installed() {
sendEvent(name: “button”, value: “pushed”, isStateChange: true, displayed: false)
sendEvent(name: “supportedButtonValues”, value: supportedButtonValues.encodeAsJSON(), displayed: false)

initialize()

}

def updated() {
runIn(2, “initialize”, [overwrite: true])
}

def initialize() {
def numberOfButtons = modelNumberOfButtons[device.getDataValue(“model”)]
sendEvent(name: “numberOfButtons”, value: numberOfButtons, displayed: false)
sendEvent(name: “checkInterval”, value: 2 * 60 * 60 + 2 * 60, displayed: false, data: [protocol: “zigbee”, hubHardwareId: device.hub.hardwareID])

if(!childDevices) {
	addChildButtons(numberOfButtons)
}
if(childDevices) {
	def event
	for(def endpoint : 1..device.currentValue("numberOfButtons")) {
		event = createEvent(name: "button", value: "pushed", isStateChange: true)
		sendEventToChild(endpoint, event)
	}
}

}

private addChildButtons(numberOfButtons) {
for(def endpoint : 1…numberOfButtons) {
try {
String childDni = “${device.deviceNetworkId}:endpoint" def componentLabel = getButtonName() + "{endpoint}”

		if (isAduroSmartRemote()) {
			componentLabel = device.displayName + " - ${endpoint}"
		}
		def child = addChildDevice("Child Button", childDni, device.getHub().getId(), [
				completedSetup: true,
				label         : componentLabel,
				isComponent   : true,
				componentName : "button$endpoint",
				componentLabel: "Button $endpoint"
		])
		child.sendEvent(name: "supportedButtonValues", value: supportedButtonValues.encodeAsJSON(), displayed: false)
	} catch(Exception e) {
		log.debug "Exception: ${e}"
	}
}

}

private getBatteryVoltage() { 0x0020 }
private getSwitchType() { 0x0000 }
private getHoldTime() { 1000 }
private getButtonMap() {[
“3450-L” : [
“01” : 4,
“02” : 3,
“03” : 1,
“04” : 2
],
“3450-L2” : [
“01” : 4,
“02” : 3,
“03” : 1,
“04” : 2
],
“SceneSwitch-EM-3.0” : [
“01” : 1,
“02” : 2,
“03” : 3,
“04” : 4
]
]}

private getSupportedButtonValues() {
def values
if (device.getDataValue(“model”) == “SceneSwitch-EM-3.0”) {
values = [“pushed”]
} else if (isAduroSmartRemote()) {
values = [“pushed”]
} else {
values = [“pushed”, “held”]
}
return values
}

private getModelNumberOfButtons() {[
“3450-L” : 4,
“3450-L2” : 4,
“SceneSwitch-EM-3.0” : 4,
“ADUROLIGHT_CSC” : 4,
“AduroLight_NCC” : 4,
“Adurolight_NCC” : 4
]}

private getModelBindings(model) {
def bindings =
for(def endpoint : 1…modelNumberOfButtons[model]) {
bindings += zigbee.addBinding(zigbee.ONOFF_CLUSTER, [“destEndpoint” : endpoint])
}
if (isAduroSmartRemote()) {
bindings += zigbee.addBinding(zigbee.LEVEL_CONTROL_CLUSTER, [“destEndpoint” : 2]) +
zigbee.addBinding(zigbee.LEVEL_CONTROL_CLUSTER, [“destEndpoint” : 3])
}
bindings
}

private getButtonName() {
def values = device.displayName.endsWith(’ 1’) ? “{device.displayName[0..-2]}" : "{device.displayName}”
return values
}

private Map parseAduroSmartButtonMessage(Map descMap){
def buttonState = “pushed”
def buttonNumber = 0
if (descMap.clusterInt == ADUROSMART_SPECIFIC_CLUSTER) {
def list2 = descMap.data
buttonNumber = (list2[1] as int) + 1
}
if (buttonNumber != 0) {
def childevent = createEvent(name: “button”, value: “pushed”, data: [buttonNumber: 1], isStateChange: true)
sendEventToChild(buttonNumber, childevent)
def descriptionText = “$device.displayName button $buttonNumber was $buttonState”
return createEvent(name: “button”, value: buttonState, data: [buttonNumber: buttonNumber], descriptionText: descriptionText, isStateChange: true)
} else {
return [:]
}
}

def isAduroSmartRemote(){
((device.getDataValue(“model”) == “Adurolight_NCC”) || (device.getDataValue(“model”) == “AduroLight_NCC”) || (device.getDataValue(“model”) == “ADUROLIGHT_CSC”))
}

def getADUROSMART_SPECIFIC_CLUSTER() {0xFCCC}

private getCLUSTER_GROUPS() { 0x0004 }

private List addHubToGroup(Integer groupAddr) {
[“st cmd 0x0000 0x01 {CLUSTER_GROUPS} 0x00 {{zigbee.swapEndianHex(zigbee.convertToHexString(groupAddr,4))} 00}”,
“delay 200”]
}

def isHeimanButton(){
device.getDataValue(“model”) == “SceneSwitch-EM-3.0”
}

Pleeeeaaasseeee =(
Could someone help me

I trying to modified the device handler.
I’ll be able to do this. :pray:

hi, i got it this week. i dont have time, so i just change other handler. it is workibg for me (just the push event), so you can use it and improve it.

/*

  • Copyright 2020 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.
    */

import groovy.json.JsonOutput
import physicalgraph.zigbee.zcl.DataType

metadata {
definition (name: “Heimen 4-button Remote”, namespace: “smartthings”, author: “SmartThings”, ocfDeviceType: “x.com.st.d.remotecontroller”, mcdSync: true, runLocally: false, executeCommandsLocally: false, mnmn: “SmartThings”, vid: “generic-4-button”) {
capability “Actuator”
capability “Battery”
capability “Button”
capability “Holdable Button”
capability “Configuration”
capability “Sensor”
capability “Health Check”

	fingerprint inClusters: "0000, 0001, 0003, 1000, FD01", outClusters: "0003, 0004, 0006, 0008, 0019, 0300, 1000", manufacturer: "LDS", model: "ZBT-CCTSwitch-D0001", deviceJoinName: "EcoSmart Remote Control" //EcoSmart 4-button remote
}

tiles {
	standardTile("button", "device.button", width: 2, height: 2) {
		state "default", label: "", icon: "st.unknown.zwave.remote-controller", backgroundColor: "#ffffff"
		state "button 1 pushed", label: "pushed #1", icon: "st.unknown.zwave.remote-controller", backgroundColor: "#00A0DC"
	}

	valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false) {
		state "battery", label:'${currentValue}% battery', unit:""
	}

	main (["button"])
	details(["button", "battery"])
}

}

private getCLUSTER_GROUPS() { 0x0004 }

private channelNumber(String dni) {
dni.split(":")[-1] as Integer
}

private getButtonName(buttonNum) {
return “{device.displayName} " + "Button {buttonNum}”
}

private void createChildButtonDevices(numberOfButtons) {
state.oldLabel = device.label

def existingChildren = getChildDevices()

log.debug "Creating $numberOfButtons children"

for (i in 1..numberOfButtons) {
	def newChildNetworkId = "${device.deviceNetworkId}:${i}"
	def childExists = (existingChildren.find {child -> child.getDeviceNetworkId() == newChildNetworkId} != NULL)

	if (!childExists) {
		log.debug "Creating child $i"
		def child = addChildDevice("Child Button", newChildNetworkId, device.hubId,
				[completedSetup: true, label: getButtonName(i),
				 isComponent: true, componentName: "button$i", componentLabel: "Button ${i}"])

		child.sendEvent(name: "supportedButtonValues", value: ["pushed"].encodeAsJSON(), displayed: false)
		child.sendEvent(name: "numberOfButtons", value: 1, displayed: false)
		child.sendEvent(name: "button", value: "pushed", data: [buttonNumber: 1], displayed: false)
	} else {
		log.debug "Child $i already exists, not creating"
	}
}

}

def installed() {
def numberOfButtons = 4
state.ignoreNextButton3 = false

createChildButtonDevices(numberOfButtons)

sendEvent(name: "supportedButtonValues", value: ["pushed"].encodeAsJSON(), displayed: false)
sendEvent(name: "numberOfButtons", value: numberOfButtons, displayed: false)
numberOfButtons.times {
	sendEvent(name: "button", value: "pushed", data: [buttonNumber: it+1], displayed: false)
}

// These devices don't report regularly so they should only go OFFLINE when Hub is OFFLINE
sendEvent(name: "DeviceWatch-Enroll", value: JsonOutput.toJson([protocol: "zigbee", scheme:"untracked"]), displayed: false)

}

def updated() {
if (childDevices && device.label != state.oldLabel) {
childDevices.each {
def newLabel = getButtonName(channelNumber(it.deviceNetworkId))
it.setLabel(newLabel)
}
state.oldLabel = device.label
}
}

def configure() {
log.debug “Configuring device ${device.getDataValue(“model”)}”

def cmds = zigbee.configureReporting(zigbee.POWER_CONFIGURATION_CLUSTER, 0x21, DataType.UINT8, 30, 21600, 0x01) +
		zigbee.readAttribute(zigbee.POWER_CONFIGURATION_CLUSTER, 0x21) +
		zigbee.addBinding(zigbee.ONOFF_CLUSTER) +
		// This device doesn't report a binding to this group but will send all messages to this group ID
		addHubToGroup(0x4003)


cmds

}

def parse(String description) {
log.debug “${device.displayName} parsing: description" def event = zigbee.getEvent(description) if (event) { //log.debug "sendEvent: {event}”
sendEvent(event)
} else {
//log.debug “sendEvent: Else”
if (description?.startsWith(“catchall:”)) {

        if (description?.startsWith("read attr -")) {
			//def descMap = zigbee.parseDescriptionAsMap(description)
			//if (descMap.clusterInt == zigbee.POWER_CONFIGURATION_CLUSTER && descMap.attrInt == 0x0021) {
        		//log.debug "BatteryEventStart: ${descMap}"
                //event = getBatteryEvent(zigbee.convertHexToInt(descMap.value))
                //log.debug "BatteryEventEnd: ${device.displayName} battery was ${zigbee.convertHexToInt(descMap.value)}%"
			//} 
        }
        else {
            //log.debug "getButtonEventStart"
            def descMap = zigbee.parseDescriptionAsMap(description)
			event = getButtonEvent(descMap)
            //log.debug "getButtonEventEnd"
		}
	}

	def result = []
	if (event) {
		//log.debug "createEvent: ${event}"
		result = createEvent(event)
	}

	return result
}

}

private Map getBatteryEvent(value) {
def result = [:]
result.value = value / 2
result.name = ‘battery’
result.descriptionText = “{device.displayName} battery was {result.value}%”
return result
}

private sendButtonEvent(buttonNumber, buttonState) {
def child = childDevices?.find { channelNumber(it.deviceNetworkId) == buttonNumber }

if (child) {
    //log.debug "childStart"
	def descriptionText = "$child.displayName was $buttonState" // TODO: Verify if this is needed, and if capability template already has it handled
	//log.debug "childSend"
	child?.sendEvent([name: "button", value: buttonState, data: [buttonNumber: 1], descriptionText: descriptionText, isStateChange: true])
    //log.debug "childEnd"
} else {
	log.debug "Child device $buttonNumber not found!"
}

}

private Map getButtonEvent(Map descMap) {
def buttonState = “”
def buttonNumber = 0
Map result = [:]
//log.debug “getButtonEvent: ${descMap}”
// Button 1
if (descMap.data[0] == “00”) {
buttonNumber = 1

// Button 2
} else if (descMap.data[0] == "03") {
	buttonNumber = 2

// Button 3
} else if (descMap.data[0] == "01") {
		//if (state.ignoreNextButton3) {
			// button 4 sends 2 cmds; one is a button 3 cmd. We want to ignore these specific cmds
			//state.ignoreNextButton3 = false
		//} else {
			buttonNumber = 3
		//}
	//}

// Button 4
} else //if (descMap.clusterInt == zigbee.LEVEL_CONTROL_CLUSTER &&
	//descMap.commandInt == 0x04) 
    {
	// remember to ignore the next button 3 message we get
	//state.ignoreNextButton3 = true
	buttonNumber = 4
}


if (buttonNumber != 0) {
	// Create and send component event
    log.debug "${device.displayName} buttonNumber: ${buttonNumber}"
	sendButtonEvent(buttonNumber, "pushed")
}
result

}

private List addHubToGroup(Integer groupAddr) {
[“st cmd 0x0000 0x01 {CLUSTER_GROUPS} 0x00 {{zigbee.swapEndianHex(zigbee.convertToHexString(groupAddr,4))} 00}”,
“delay 200”]
}

1 Like

Hi

I don’t seem to be able to get this device handler to work. Pasted it in as above but getting errors. I don’t suppose you have it on GitHub or has anyone else got it working?

Thanks I’m advance

sorry, it was example.
try take it from:

Ohhh man, its works thank you very much.
But have a simple bug, when i press the button 1, 2,3 or 4, the SmartThings execute 4 times the action.
My SHM its crazy, hahahhaa arm and disarm, arm, and disarm :joy::joy::joy:
can u fix it ? :thinking:
I am testing the code variations but learning to programming on Smartthings platform.

its known problem of 4 buttons device. i think i found someone maybe solve it on different system. when i have time, i will try implement it.

Trying to understand what I might need to do to get this to work. I’ve updated the device handler fingerprint and it was able to pick up the remote I just paired although it never got pass searching for device, but when I exit I was able to see it in my device list.
When I press the buttons, I am getting Child device 1 not found (or 2, 3 or 4 when I press the other button).
Any clue what I might have done wrong?

Have fixed the child device issue. Updated line 71 to:
def child = addChildDevice("smartthings", "Child Button", newChildNetworkId, device.hubId,

Also got the battery % working by merging some codes from Zigbee Multi Button device handler.

But I am getting this error.
error java.lang.NullPointerException: Cannot invoke method getAt() on null object @line 227 (getButtonEvent)

Anyone know what this is and how to parse it?
read attr - raw: 0C260100001C050000420952432D45462D332E30, dni: 0C26, endpoint: 01, cluster: 0000, size: 28, attrId: 0005, result: success, encoding: 42, value: 52432D45462D332E30