Nyce hinge

Would someone please tell me how to add the nyce hinge? I have it showing up as a thing on my app but can’t configure it. I seen where nyce website has the st device type but can’t figure out how to add it. It’s not like the other one I have installed from st hub website. Thank you

1 Like

Download that device type they have.

Open the Groovy file (in Notepad or some other text editor).

Copy everything out of the file.

Paste it into the IDE in a new Device Handler (choose the option for From Code).

Save and Publish For Me.

Now you can go into your existing devices and change that one that’s a Thing over to the new Device Type.

1 Like

Use the IDE and change the device handler to “NYCE Open/Close Sensor”. That may work for you, if not, one will have to be created.

Or, do what @diehllane just posted.

Thank you very much

First make sure you have the right model. There are two versions. Only the one that ends in -HA is compatible with SmartThings. The other one will pair, but you will not be able to do anything with it. If you buy it from Amazon, you should be OK, but if you buy it from eBay it may have the wrong product description. Check the model number on the actual device.

http://nycesensors.com/products/ncz3010/

3 Likes

This is my problem. I bought it at lowes it is the wrong one. Thank u

1 Like

If you bought the one for Iris, it should be the same one (they have different product numbers being packaged for Iris, even the different colors have different ones).

This is the list of all hubs that the HA one works with, so if it is the one that works with Iris, it should work with ST.

http://nycesensors.com/industry/ha-compatible-system/

How much door has to be open to register open? With regular open/close sensor I can adjust distance between magnets, so even tiny crack will be registered. I need to know because my deadbolt will lock when door is closed, if it is not really fully closed and sensor reports closed it will jam lock.

NYCE did make sensors for the gen 1 lowe’s IRIS System that are not compatible with SmartThings. The -ha model will be compatible with gen 2 Iris and with SmartThings. I would still go by the model number on the device.

Ah. Okay. I know they usually have the same products just change the number to show its the Iris one (such as the Linear garage door opener).
Based on the website, they have a -1, -2, -3, and -4 which appear to just be color codes.

At this point, it seems like the safest play is to buy them from someone like Amazon where you know you’re getting the -HA model.

FYI, in case anyone is interested. I just bought one of these Door Hinge sensors from my local Lowes and after creating my own device type based on the “NYCE Open/Closed Sensor” device type it is working and pairs without issues with the hub. All I really had to do was to add the fingerprint for the sensor I got and it seems to work, shows open/closed states and battery level. The model on the back of the package I bought is NCZ-3010-01 so if you have this model number you should be able to use it.

I will post my copy of the device handler for this if anyone else is interested, I just want to do a little more testing to ensure it works since I have only had it connected for about 10 minutes at this point.

1 Like

I think this device will work for what you need, I have been testing mine and it doesn’t take much to get it to show open. The device comes with a couple of different length set screws that you can use to adjust how much the door needs to be open but even with the longest set screw adjusted all the way out it is still very sensitive.

For anyone interested I have the updated device type and posted it on my GitHub repo linked below, it has been working flawlessly with the hinge I bought from Lowes as mentioned above and I have removed and re-paired it several times to ensure that it works consistently.

@slagle and @jody.albritton, I was told by support to ping you on this so you are aware, this is just a simple update to the pre-existing “NYCE Open/Closed Sensor” device handler to add the new fingerprint. Just letting you know in case this is something that can be added to the existing code so that people don’t have to use a custom device handler.

1 Like

@Motley

So if I understand correctly the only change you made is to take the public device and add the fingerprint for the hinge sensor?

That is correct. The first fingerprint in my file is the one I added, it has a deviceId and profileId unlike the others for some reason but it does work. I also change the name if the device handler to make it different from the one already in the repo but I figure if this is going to be submitted then you would just keep the original device handler name and add the fingerprint.

NYCE provides what they call a SmartThings Driver for the HA Version of the hinge on their support page, along with code for the other devices they offer…

http://nycesensors.com/support/

/**
*	NYCE Door Hinge Sensor
*
*	Copyright 2016 NYCE Sensors Inc.
*
*	File: nyce-door-hinge-sensor.groovy
*	Version 1.0.2
*	Last Edited: 8 Mar 2016
*	By: RC
*
*/

metadata {
	definition (name: "NYCE Door Hinge Sensor", namespace: "NYCE", author: "NYCE") {
		capability "Battery"
		capability "Configuration"
		capability "Contact Sensor"
		capability "Refresh"

		command "enrollResponse"

		attribute "batteryReportType", "string"

		fingerprint inClusters: "0000,0001,0003,0406,0500,0020", manufacturer: "NYCE", model: "3010"
	}

	simulator {

	}

	tiles {
		standardTile("contact", "device.contact", width: 2, height: 2) {
			state("open", label:'${name}', icon:"st.contact.contact.open", backgroundColor:"#ffa81e")
			state("closed", label:'${name}', icon:"st.contact.contact.closed", backgroundColor:"#79b821")
		}

		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 (["contact"])
		details(["contact","battery","refresh"])
	}
}

def parse(String description) {
	Map map = [:]

	List listMap = []
	List listResult = []

	log.debug "parse: Parse message: ${description}"

	if(description?.startsWith("enroll request")) {
		List cmds = enrollResponse()

		log.debug "parse: enrollResponse() ${cmds}"
		listResult = cmds?.collect { new physicalgraph.device.HubAction(it) }
	}
	else {
		if(description?.startsWith("zone status")) {
			listMap = parseIasMessage(description)
		}
		else if(description?.startsWith("read attr -")) {
			map = parseReportAttributeMessage(description)
		}
		else if(description?.startsWith("catchall:")) {
			map = parseCatchAllMessage(description)
		}
		else if(description?.startsWith("updated")) {
			List cmds = configure()
			listResult = cmds?.collect { new physicalgraph.device.HubAction(it) }
		}

		// Create events from map or list of maps, whichever was returned
		if(listMap) {
			for(msg in listMap) {
				listResult << createEvent(msg)
			}
		}
		else if(map) {
			listResult << createEvent(map)
		}
	}

	log.debug "parse: listResult ${listResult}"
	return listResult
}

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 Map parseCatchAllMessage(String description) {
	Map resultMap = [:]
	def cluster = zigbee.parse(description)

	log.info "IN parseCatchAllMessage()"

	if (shouldProcessMessage(cluster)) {
		def msgStatus = cluster.data[2]

		log.debug "parseCatchAllMessage: msgStatus: ${msgStatus}"

		if(msgStatus == 0) {
			switch(cluster.clusterId) {
				case 0x0001:
					log.debug 'Battery'

					if(cluster.attrId == 0x0020) {
						if(state.batteryReportType == "voltage") {
							resultMap.name = 'battery'
							resultMap.value = getBatteryPercentage(cluster.data.last)
							log.debug "Battery Voltage convert to ${resultMap.value}%"
						}
					}
					else if(cluster.attrId == 0x0021) {
						if(state.batteryReportType == "percentage") {
							resultMap.name = 'battery'
							resultMap.value = (cluster.data.last / 2)
							log.debug "Battery Percentage convert to ${resultMap.value}%"
						}
					}
					break
				case 0x0402:    // temperature cluster
					log.debug 'Temperature'

					if(cluster.command == 0x01) {
						if(cluster.data[3] == 0x29) {
							def tempC = Integer.parseInt(cluster.data[-2..-1].reverse().collect{cluster.hex1(it)}.join(), 16) / 100
							resultMap = getTemperatureResult(getConvertedTemperature(tempC))
							log.debug "Temp resultMap: ${resultMap}"
						}
						else {
							log.debug "Temperature cluster Wrong data type"
						}
					}
					else {
						log.debug "Unhandled Temperature cluster command ${cluster.command}"
					}
					break
				case 0x0405:    // humidity cluster
					log.debug 'Humidity'

					if(cluster.command == 0x01) {
						if(cluster.data[3] == 0x21) {
							def hum = Integer.parseInt(cluster.data[-2..-1].reverse().collect{cluster.hex1(it)}.join(), 16) / 100
							resultMap = getHumidityResult(hum)
							log.debug "Hum resultMap: ${resultMap}"
						}
						else {
							log.debug "Humidity cluster wrong data type"
						}
					}
					else {
						log.debug "Unhandled Humidity cluster command ${cluster.command}"
					}
					break
				default:
					break
			}
		}
		else {
			log.debug "Message error code: Error code: ${msgStatus}    ClusterID: ${cluster.clusterId}    Command: ${cluster.command}"
		}
	}

	log.info "OUT parseCatchAllMessage()"
	return resultMap
}

private int getBatteryPercentage(int value) {
	def minVolts = 2.3
	def maxVolts = 3.0
	def volts = value / 10
	def pct = (volts - minVolts) / (maxVolts - minVolts)

	//for battery that may have a higher voltage than 3.0V
	if(pct > 1) {
		pct = 1
	}

	if(pct <= 0) {
		pct = 0.07
	}
	return (int) pct * 100
}

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()]
	}
	Map resultMap = [:]

	log.info "IN parseReportAttributeMessage()"
	log.debug "descMap ${descMap}"

	switch(descMap.cluster) {
		case "0001":
			log.debug "Battery"

			if(descMap.attrId == "0020") {
				if(state.batteryReportType == "voltage") {
					resultMap.name = 'battery'
					resultMap.value = getBatteryPercentage(convertHexToInt(descMap.value))
					log.debug "Battery Voltage convert to ${resultMap.value}%"
				}
			}
			else if(descMap.attrId == "0021") {
				if(descMap.result != "unsupported attr") {
					state.batteryReportType = "percentage"
				}
				else {
					state.batteryReportType = "voltage"
				}

				if(state.batteryReportType == "percentage")	{
					resultMap.name = 'battery'
					resultMap.value = (convertHexToInt(descMap.value) / 2)
					log.debug "Battery Percentage convert to ${resultMap.value}%"
				}
			}
			break
		default:
			log.info descMap.cluster
			log.info "cluster1"
			break
	}

	log.info "OUT parseReportAttributeMessage()"
	return resultMap
}
  
private List parseIasMessage(String description) {
	List parsedMsg = description.split(" ")
	String msgCode = parsedMsg[2]

	List resultListMap = []
	Map resultMap_battery = [:]
	Map resultMap_battery_state = [:]
	Map resultMap_sensor = [:]

	// Relevant bit field definitions from ZigBee spec
	def BATTERY_BIT = ( 1 << 3 )
	def TROUBLE_BIT = ( 1 << 6 )
	def SENSOR_BIT = ( 1 << 0 )		// it's ALARM1 bit from the ZCL spec

	// Convert hex string to integer
	def zoneStatus = Integer.parseInt(msgCode[-4..-1],16)

	log.info "IN parseIasMessage()"
	log.debug "zoneStatus: ${zoneStatus}"

	// Check each relevant bit, create map for it, and add to list
	log.debug "Battery Status ${zoneStatus & BATTERY_BIT}"
	log.debug "Trouble Status ${zoneStatus & TROUBLE_BIT}"
	log.debug "Sensor Status ${zoneStatus & SENSOR_BIT}"

	resultMap_sensor.name = "contact"
	resultMap_sensor.value = (zoneStatus & SENSOR_BIT) ? "open" : "closed"

	resultListMap << resultMap_battery_state
	resultListMap << resultMap_battery
	resultListMap << resultMap_sensor

	log.info "OUT parseIasMessage()"
	return resultListMap
}

def configure() {
	String zigbeeId = swapEndianHex(device.hub.zigbeeId)

	attrInit()

	def configCmds = [
		//battery reporting and heartbeat
		"zdo bind 0x${device.deviceNetworkId} 1 ${endpointId} 1 {${device.zigbeeId}} {}", "delay 200",

		//configure battery voltage report
		"zcl global send-me-a-report 1 0x20 0x20 600 3600 {01}", "delay 200",
		"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 1500",

		//configure battery percentage report
		"zcl global send-me-a-report 1 0x21 0x20 600 3600 {01}", "delay 200",
		"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 1500",

		// Writes CIE attribute on end device to direct reports to the hub's EUID
		"zcl global write 0x500 0x10 0xf0 {${zigbeeId}}", "delay 200",
		"send 0x${device.deviceNetworkId} 1 1", "delay 500",
	]

	log.debug "configure: Write IAS CIE"
	return configCmds + refresh()
}

def attrInit() {
	log.debug "Attr Init"
	state.batteryReportType = "voltage"
}

def enrollResponse() {
	[
		// Enrolling device into the IAS Zone
		"raw 0x500 {01 23 00 00 00}", "delay 200",
		"send 0x${device.deviceNetworkId} 1 1"
	]
}

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 getEndpointId() {
    new BigInteger(device.endpointId, 16).toString()
}

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

def refresh()
{
	log.debug "Refreshing Battery"
	[
		"st rattr 0x${device.deviceNetworkId} ${endpointId} 1 0x20",
		"st rattr 0x${device.deviceNetworkId} ${endpointId} 1 0x21"
	]
}

Yes, this appears to mostly be a copy of the current published code but with all fingerprints removed other than the door hinge. Not quite sure why we would need multiple files if the currently published one works fine with the added fingerprint. I suspect they have issues with multiple fingerprints because they are missing a couple of pieces of information from those fingerprints, namely the deviceId and the profileId. Someone even mentioned that they tried that device handler from the website and they were only getting battery updates and not Open/Close status but I did not test it so not sure.

Either way it is really up to ST how they want to proceed, I think updating the already working and published version of the handler is the best way but there may be some aspects that I am not aware of that make this not the best idea.

1 Like

I don’t think you need the profileID and deviceID, just the model, can you tell me if it works the same without those?

Yes, I just tried and the fingerprint works without profileId and deviceId. I guess I don’t know why the developer docs say to put that in the fingerprint if it is not required?

The fingerprint pasted below appears to work fine, the only thing different from a couple of the other fingerprints is the model and deviceJoinName.

fingerprint inClusters: "0000,0001,0003,0500,0020", manufacturer: "NYCE", model: "3010", deviceJoinName: "NYCE Door Hinge Sensor"

Cool! Thanks, do you know Git? Would you be comfortable making a pull to the device in the public repo?