Could the zigbee generic contact sensor (no temp) be published as another example?

Hello,

I’m a SmartThings refugee - who had most of my devices removed by SmartThings in early April. I’m really enjoying Hubitat!

I have many Quirky Trippers - they work fairly well with the Zigbee Contact Sensor (no temp) - but have an occasional problem (as in 3-4 times a day) with the ‘close’ status not registering - so it looks like the door stays open though it’s closed,

The Quirky Tripper does indeed have some quirky features, so I’d like to port over the SmartThings Quirky Tripper groovy driver I modified based on Mitch Pond’s original(
SmartThingsAlecM/devicetypes/alecm at master · alecm/SmartThingsAlecM · GitHub) - but it would be much easier if I had a template to adjust rather than having to figure out the porting myself from scratch. When I look at the example drivers on your GitHub none of them is a contact sensor (or any type of sensor).

Anyhow, any chance you’d be willing to publish the zigbee generic contact sensor driver to your GitHub as another example?

If you’d rather not - I’ll try to figure out the porting myself - but it will take me a while. I hope you’ll be willing to share it.

Thanks!

1 Like

FWIW, I have a Quirky Tripper on my C7 that is used for triggering the lights in my kitchen pantry. It's opened/closed many times a day and I've never seen it not register the close status. I'm just using "Zigbee Contact Sensor (no temp)".

3 Likes

Here is a very quickly revised version of the ST Driver you linked above that saves on Hubitat without any errors. It should give you a little bit of a head start.

All I did was remove all of the Tiles section as it has not been used on ST for a few years now, and was never used on Hubitat. I also changed 'physicalgraph' to 'hubitat' in the one call where I found it. Hope this helps, if you cannot get the built-in driver to work for you.

/**
 *  Quirky/Wink Tripper Contact Sensor
 *
 *  Copyright 2015 Mitch Pond
 *
 *  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.
 *
 * 2016-17  AlecM - Minor tweak I updated this driver by by Mitch Pond to have different values for battery percentages 
 * and fix a couple of typos in lines around 210
 * 2/19/2018 changed namespace to "alecm" to enable github repo sync and cleaned out some testing code
 *
 */

metadata {
	definition (name: "Quirky Wink Tripper AlecM", namespace: "alecm", author: "Mitch Pond") {
    
		capability "Contact Sensor"
		capability "Battery"
		capability "Configuration"
		capability "Sensor"
		capability "Tamper Alert"
    
		command "resetTamper"
		command "testTamper"
        
		fingerprint endpointId: "01", profileId: "0104", deviceId: "0402", inClusters: "0000,0001,0003,0500,0020,0B05", outClusters: "0003,0019", manufacturer: "Sercomm Corp.", model: "Tripper"
	}
}
// Parse incoming device messages to generate events
def parse(String description) {
	log.debug "description: $description"

	def results = []
	if (description?.startsWith('catchall:')) {
		results = parseCatchAllMessage(description)
	}
	else if (description?.startsWith('read attr -')) {
		results = parseReportAttributeMessage(description)
	}
	else if (description?.startsWith('zone status')) {
		results = parseIasMessage(description)
	}

	log.debug "Parse returned $results"

	if (description?.startsWith('enroll request')) {
		List cmds = enrollResponse()
		log.debug "enroll response: ${cmds}"
		results = cmds?.collect { new hubitat.device.HubAction(it) }
	}
	return results
}

//Initializes device and sets up reporting
def configure() {
	String zigbeeId = swapEndianHex(device.hub.zigbeeId)
	log.debug "Configuring Reporting, IAS CIE, and Bindings."
    
	def cmd = [
		"zcl global write 0x500 0x10 0xf0 {${zigbeeId}}", "delay 200",
		"send 0x${device.deviceNetworkId} 1 1", "delay 1500",
	
		"zcl global send-me-a-report 0x500 0x0012 0x19 0 0xFF {}", "delay 200", //get notified on tamper
		"send 0x${device.deviceNetworkId} 1 1", "delay 1500",
		
		"zcl global send-me-a-report 1 0x20 0x20 5 21600 {01}", "delay 200", //battery report request
		"send 0x${device.deviceNetworkId} 1 1", "delay 1500",
	
		"zdo bind 0x${device.deviceNetworkId} 1 1 0x500 {${device.zigbeeId}} {}", "delay 500",
		"zdo bind 0x${device.deviceNetworkId} 1 1 1 {${device.zigbeeId}} {}", "delay 500",
		"st rattr 0x${device.deviceNetworkId} 1 1 0x20"
		]
	cmd
}

//Sends IAS Zone Enroll response
def enrollResponse() {
	log.debug "Sending enroll response"
	[	
	"raw 0x500 {01 23 00 00 00}", "delay 200",
	"send 0x${device.deviceNetworkId} 1 1"
	]
}

private Map parseCatchAllMessage(String description) {
 	def results = [:]
 	def cluster = zigbee.parse(description)
 	if (shouldProcessMessage(cluster)) {
		switch(cluster.clusterId) {
			case 0x0001:
				log.debug "Received a catchall message for battery status. This should not happen."
				results << createEvent(getBatteryResult(cluster.data.last()))
				break
            }
        }

	return results
}

private boolean shouldProcessMessage(cluster) {
	// 0x0B is default response indicating message got through
	// 0x07 is bind message
	boolean ignoredMessage = cluster.profileId != 0x0104 || 
		cluster.command == 0x0B ||
		cluster.command == 0x07 ||
		(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
	return !ignoredMessage
}

private 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"

	def results = []
    
	if (descMap.cluster == "0001" && descMap.attrId == "0020") {
		log.debug "Received battery level report"
		results = createEvent(getBatteryResult(Integer.parseInt(descMap.value, 16)))
	}
 

	return results
}

private parseIasMessage(String description) {
	List parsedMsg = description.split(' ')
	String msgCode = parsedMsg[2]
	int status = Integer.decode(msgCode)
	def linkText = getLinkText(device)

	def results = []
	//log.debug(description)
	if (status & 0b00000001) {results << createEvent(getContactResult('open'))}
	else if (~status & 0b00000001) results << createEvent(getContactResult('closed'))

	if (status & 0b00000100) {
    		//log.debug "Tampered"
            results << createEvent([name: "tamper", value:"detected"])
	}
	else if (~status & 0b00000100) {
		//don't reset the status here as we want to force a manual reset
		//log.debug "Not tampered"
		//results << createEvent([name: "tamper", value:"OK"])
	}
	
	if (status & 0b00001000) {
		//battery reporting seems unreliable with these devices. However, they do report when low.
		//Just in case the battery level reporting has stopped working, we'll at least catch the low battery warning.
		//
		//** Commented this out as this is currently conflicting with the battery level report **/
		//log.debug "${linkText} reports low battery!"
		//results << createEvent([name: "battery", value: 10])
	}
	else if (~status & 0b00001000) {
		//log.debug "${linkText} battery OK"
	}
	//log.debug results
	return results
}

//Converts the battery level response into a percentage to display in ST
//and creates appropriate message for given level

//AlecM - 8-31 Added mapping values for 34 -29 to map below -since they were valid values - 3 or 2.9  volts were returning null

private getBatteryResult(volts) {
	def batteryMap = [34:100, 33:100, 32:100, 31:100, 30:100, 29:95, 28:90, 27:80, 26:75, 25:50, 24:25, 23:20,
                          22:10, 21:0]
	def minVolts = 21
    //AlecM 10-2 changed maxvolts to 34 to see what really getting w/ new battery
	def maxVolts = 34  
	def linkText = getLinkText(device)
	def result = [name: 'battery']
	
    
    if (volts < minVolts) volts = minVolts
    	else if (volts > maxVolts) volts = maxVolts
    
    result.value = batteryMap[volts]   
    result.descriptionText = "${linkText} battery was ${result.value}%"
    log.debug("${linkText} reports battery voltage at ${result.value}%") //added logging for voltage level to help determine actual min voltage from users
	return result
}


private Map getContactResult(value) {
	def linkText = getLinkText(device)
	def descriptionText = "${linkText} was ${value == 'open' ? 'opened' : 'closed'}"
	return [
		name: 'contact',
		value: value,
		descriptionText: descriptionText
		]
}

//Resets the tamper switch state
private resetTamper(){
	log.debug "Tamper alarm reset."
	sendEvent([name: "tamper", value:"clear"])
}

private hex(value) {
	new BigInteger(Math.round(value).toString()).toString(16)
}

private String swapEndianHex(String hex) {
	reverseArray(hex.decodeHex()).encodeHex()
}

private byte[] reverseArray(byte[] array) {
	int i = 0;
	int j = array.length - 1;
	byte tmp;
	while (j > i) {
		tmp = array[j];
		array[j] = array[i];
		array[i] = tmp;
		j--;
		i++;
	}
	return array
}
private testTamper() {
	sendEvent([name: "tamper", value: "detected"])
}
5 Likes

You may also find this thread useful if you're porting old ST Groovy code to Hubitat.

3 Likes

This points to a mesh insufficiency. Rather than a driver issue.

2 Likes

Thank you, thank you, thank you!!!! I’d seen the thread and, after spending months learning to adapt Edge drivers and having most of my devices erased in the SmartThings April Device Massacre I didn’t quite have the wherewithal to start this from scratch.

And, it’s even better that you ported this. When I switched some of them on SmartThings from the custom DTH to the standard SmartThings Open/Close Sensor in the hope that they would migrate somewhat smoothly I started seeing this on some of them.

This will let me tweak and test their idiosyncrasies with a DTH as close as possible to the one that I know works! Thanks again!

1 Like

Hello @aaiyar

Thanks for the thought, but I think that’s very unlikely. My Zigbee devices are supported by 5 Xbees carefully spread through a 1,400 foot house. Mapping using XBees shows strong connections throughout.

Of course, it’s possible that the XBees need some tweaking given their switching from SmartThings to Hubitat - but I followed guidance found elsewhere on the forum.

My hunch is that it is going to be a driver issue. These old devices have their idiosyncrasies, and now, thanks to @ogiewon - I’ll be able to test that sooner rather than later!

(Note, none of my other devices (including Aquara motion sensors which are notoriously fickle) are giving me any trouble.)

You did, however, jog my mind to look at channel assignment just to see if it could be affecting them.

Best,
Alec

1 Like