Xfinity 2nd generation keypad help

Hello smart guys from the SmartThings plannet!

Can anyone help us writing a good device handler for the latest generation of Xfinity Home keypad (see pics attached)? I did all my research and could only find handlers and smartapps for the old version centralite/iris models by @RudiP & @mitchp
I was able after a few attempts to pair it with smartthings using the DTH from miriad/Centralite-Keypad. I got it to work to show the motion and beeping, but nothing else.

I really love the aesthetics of this particular model compared with the Iris/Centralite and they sell pretty cheap on EBay. I bought mine for $16.05 used, but it looks like brand new!

I know there are many of us looking for a nice keypad that can be easily integrated with ST and use with Konnected by @heythisisnate so please help us. I am willing to make donations for the effort!!IMAG4286IMAG4288

2 Likes

@adrian.burhui Check this out https://community.smartthings.com/t/release-enhanced-keypad-lock-centralite-iris-and-xfinity-zigbee-keypads-device-handler/124776?u=augustin

1 Like

Now supporting Xfinity XHK1 and UEI keypads

And here’s an app to use with the keypad to control SHM or create your own custom security setup

2 Likes

@adrian.burhui
Did you end up writing a device handler? I bought one of these keypads on ebay not realizing that there weren’t any free device handlers currently available.

Based on the compliance document for this keypad available here https://www.zigbee.org/zigbee-products-2/#zigbeecertifiedproducts/productdetails3/57f80e5024ef84273d7af067/

it looks like it’s very similar to centralite one https://www.zigbee.org/zigbee-products-2/#zigbeecertifiedproducts/productdetails3/57c0667b1bd018ce200d35a1/

Does anyone happen to have a user manual for this keypad? I can’t figure out how to put it into pairing mode.

I ended up modifing the Centralite keypad device handler created by mitchpond to work with the Xfinity XHK1-UE Keypad.

/**
 *  Xfinity UEI Keypad
 *
 *  Copyright 2015-2016 Mitch Pond, Zack Cornelius
 *
 *  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.
 *
 */
metadata {
	definition (name: "UEI Keypad", namespace: "mitchpond", author: "Mitch Pond") {

		capability "Battery"
		capability "Configuration"
        capability "Motion Sensor"
		capability "Sensor"
		capability "Temperature Measurement"
		capability "Refresh"
		capability "Lock Codes"
		capability "Tamper Alert"
		capability "Tone"
		capability "button"
        capability "polling"
        capability "Contact Sensor"
		
		attribute "armMode", "String"
        attribute "lastUpdate", "String"
		
		command "setDisarmed"
		command "setArmedAway"
		command "setArmedStay"
		command "setArmedNight"
		command "setExitDelay", ['number']
		command "setEntryDelay", ['number']
		command "testCmd"
		command "sendInvalidKeycodeResponse"
		command "acknowledgeArmRequest"
		
		fingerprint endpointId: "01", profileId: "0104", deviceId: "0401", inClusters: "0000,0001,0003,0020,0402,0500,0B05", outClusters: "0003,0019,0501", manufacturer: "Universal Electronics Inc", model: "URC4450BC0-X-R", deviceJoinName: "Xfinity XHK1-UE Keypad"
	}
	
	preferences{
		input ("tempOffset", "number", title: "Enter an offset to adjust the reported temperature",
				defaultValue: 0, displayDuringSetup: false)
		input ("beepLength", "number", title: "Enter length of beep in seconds",
				defaultValue: 1, displayDuringSetup: false)
                
        input ("motionTime", "number", title: "Time in seconds for Motion to become Inactive (Default:10, 0=disabled)",	defaultValue: 10, displayDuringSetup: false)
	}

    tiles (scale: 2) {
        multiAttributeTile(name: "keypad", type:"generic", width:6, height:4, canChangeIcon: true) {
            tileAttribute ("device.armMode", key: "PRIMARY_CONTROL") {            		
                attributeState("disarmed", label:'${currentValue}', icon:"st.Home.home2", backgroundColor:"#44b621")
                attributeState("armedStay", label:'ARMED/STAY', icon:"st.Home.home3", backgroundColor:"#ffa81e")
                attributeState("armedAway", label:'ARMED/AWAY', icon:"st.nest.nest-away", backgroundColor:"#d04e00")
            }
            tileAttribute("device.lastUpdate", key: "SECONDARY_CONTROL") {
                attributeState("default", label:'Updated: ${currentValue}')
            }
            /*
			tileAttribute("device.battery", key: "SECONDARY_CONTROL") {
				attributeState("default", label:'Battery: ${currentValue}%', unit:"%")
			}
			tileAttribute("device.battery", key: "VALUE_CONTROL") {
				attributeState "VALUE_UP", action: "refresh"
				attributeState "VALUE_DOWN", action: "refresh"
			}
			*/
            tileAttribute("device.temperature", key: "VALUE_CONTROL") {
                attributeState "VALUE_UP", action: "refresh"
                attributeState "VALUE_DOWN", action: "refresh"
            }
        }

        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("motion", "device.motion", decoration: "flat", canChangeBackground: true, width: 2, height: 2) {
            state "active", label:'motion',icon:"st.motion.motion.active", backgroundColor:"#53a7c0"
            state "inactive", label:'no motion',icon:"st.motion.motion.inactive", backgroundColor:"#ffffff"
        }
        standardTile("tamper", "device.tamper", decoration: "flat", canChangeBackground: true, width: 2, height: 2) {
            state "clear", label: 'Tamper', icon:"st.motion.acceleration.inactive", backgroundColor: "#ffffff"
            state "detected",  label: 'Tamper', icon:"st.motion.acceleration.active", backgroundColor:"#cc5c5c"
        }
        standardTile("Panic", "device.contact", decoration: "flat", canChangeBackground: true, width: 2, height: 2) {
            state "open", label: 'Panic', icon:"st.security.alarm.alarm", backgroundColor: "#ffffff"
            state "closed",  label: 'Panic', icon:"st.security.alarm.clear", backgroundColor:"#bc2323"
        }
        
		standardTile("Mode", "device.armMode", decoration: "flat", canChangeBackground: true, width: 2, height: 2) {
            state "disarmed", label:'OFF', icon:"st.Home.home2", backgroundColor:"#44b621"
            state "armedStay", label:'OFF', icon:"st.Home.home3", backgroundColor:"#ffffff"
            state "armedAway", label:'OFF', icon:"st.net.nest-away", backgroundColor:"#ffffff"
        }
        
        standardTile("beep", "device.beep", decoration: "flat", width: 2, height: 2) {
            state "default", action:"tone.beep", icon:"st.secondary.beep", backgroundColor:"#ffffff"
        }
        valueTile("battery", "device.battery", decoration: "flat", width: 2, height: 2) {
            state "battery", label:'${currentValue}% battery', unit:""
        }
        standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
            state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
        }
        standardTile("configure", "device.configure", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
            state "default", action:"configuration.configure", icon:"st.secondary.configure"
        }
        valueTile("armMode", "device.armMode", decoration: "flat", width: 2, height: 2) {
            state "armMode", label: '${currentValue}'
        }

        main (["keypad"])
        details (["keypad","motion","tamper","Panic","Mode","beep","refresh","battery"])
    }
}

// parse events into attributes
def parse(String description) {
	log.debug "Parsing '${description}'";
	def results = [];
	
	//------Miscellaneous Zigbee message------//
	if (description?.startsWith('catchall:')) {

		//log.debug zigbee.parse(description);

		def message = zigbee.parse(description);
		
		//------Profile-wide command (rattr responses, errors, etc.)------//
		if (message?.isClusterSpecific == false) {
			//------Default response------//
			if (message?.command == 0x0B) {
				if (message?.data[1] == 0x81) 
					log.error "Device: unrecognized command: "+description;
				else if (message?.data[1] == 0x80) 
					log.error "Device: malformed command: "+description;
			}
			//------Read attributes responses------//
			else if (message?.command == 0x01) {
				if (message?.clusterId == 0x0402) {
					log.debug "Device: read attribute response: "+description;

					results = parseTempAttributeMsg(message)
				}}
			else 
				log.debug "Unhandled profile-wide command: "+description;
		}
		//------Cluster specific commands------//
		else if (message?.isClusterSpecific) {
			//------IAS ACE------//
			if (message?.clusterId == 0x0501) {
				if (message?.command == 0x07) {
                	motionON()
				}
                else if (message?.command == 0x04) {
                	results = createEvent(name: "button", value: "pushed", data: [buttonNumber: 1], descriptionText: "$device.displayName panic button was pushed", isStateChange: true)
                    panicContact()
                }
				else if (message?.command == 0x00) {
					results = handleArmRequest(message)
					log.trace results
				}
			}
			else log.debug "Unhandled cluster-specific command: "+description
		}
	}
	//------IAS Zone Enroll request------//
	else if (description?.startsWith('enroll request')) {
		log.debug "Sending IAS enroll response..."
		results = zigbee.enrollResponse()
	}
	//------Read Attribute response------//
	else if (description?.startsWith('read attr -')) {
		results = parseReportAttributeMessage(description)
	}
	//------Temperature Report------//
	else if (description?.startsWith('temperature: ')) {
		log.debug "Got ST-style temperature report.."
		results = createEvent(getTemperatureResult(zigbee.parseHATemperatureValue(description, "temperature: ", getTemperatureScale())))
		log.debug results
	}
    else if (description?.startsWith('zone status ')) {
    	results = parseIasMessage(description)
    }
	return results
}


def configure() {
    log.debug "--- Configure Called"
    String hubZigbeeId = swapEndianHex(device.hub.zigbeeEui)
    def cmd = [
        //------IAS Zone/CIE setup------//
        "zcl global write 0x500 0x10 0xf0 {${hubZigbeeId}}", "delay 100",
        "send 0x${device.deviceNetworkId} ${device.endpointId as int} 1", "delay 200",

        //------Set up binding------//
        "zdo bind 0x${device.deviceNetworkId} 1 1 0x500 {${device.zigbeeId}} {}", "delay 200",
        "zdo bind 0x${device.deviceNetworkId} 1 1 0x501 {${device.zigbeeId}} {}", "delay 200",
        
    ] + 
    zigbee.batteryConfig(3600, 43200, 0x01) +
    zigbee.temperatureConfig(30, 3600, 0x0064)

    runIn(10, refresh)

    return cmd
}

def poll() { 
	refresh()
}

def refresh() {
	 return sendStatusToDevice() +
		zigbee.readAttribute(zigbee.POWER_CONFIGURATION_CLUSTER, 0x20) + // Battery Volts
		zigbee.readAttribute(zigbee.TEMPERATURE_MEASUREMENT_CLUSTER, 0x00) // Temperature
}

private formatLocalTime(time, format = "EEE, MMM d yyyy @ h:mm a z") {
	if (time instanceof Long) {
    	time = new Date(time)
    }
	if (time instanceof String) {
    	//get UTC time
    	time = timeToday(time, location.timeZone)
    }   
    if (!(time instanceof Date)) {
    	return null
    }
	def formatter = new java.text.SimpleDateFormat(format)
	formatter.setTimeZone(location.timeZone)
	return formatter.format(time)
}

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)))
	}
    else if (descMap.cluster == "0001" && descMap.attrId == "0034")
    {
    	log.debug "Received Battery Rated Voltage: ${descMap.value}"
    }
    else if (descMap.cluster == "0001" && descMap.attrId == "0036")
    {
    	log.debug "Received Battery Alarm Voltage: ${descMap.value}"
    }
	else if (descMap.cluster == "0402" && descMap.attrId == "0000") {
		def value = getTemperature(descMap.value)
		results = createEvent(getTemperatureResult(value))
	}

	return results
}

private parseTempAttributeMsg(message) {
	byte[] temp = message.data[-2..-1].reverse()
	createEvent(getTemperatureResult(getTemperature(temp.encodeHex() as String)))
}

private Map parseIasMessage(String description) {
    List parsedMsg = description.split(' ')
    String msgCode = parsedMsg[2]
    
    Map resultMap = [:]
    switch(msgCode) {
        case '0x0020': // Closed/No Motion/Dry
        	resultMap = getContactResult('closed')
            break

        case '0x0021': // Open/Motion/Wet
        	resultMap = getContactResult('open')
            break

        case '0x0022': // Tamper Alarm
            break

        case '0x0023': // Battery Alarm
            break

        case '0x0024': // Supervision Report
        	resultMap = getContactResult('closed')
            break

        case '0x0025': // Restore Report
        	resultMap = getContactResult('open')
            break

        case '0x0026': // Trouble/Failure
            break

        case '0x0028': // Test Mode
            break
        case '0x0000':
			resultMap = createEvent(name: "tamper", value: "clear", isStateChange: true, displayed: false)
            break
        case '0x0004':
			resultMap = createEvent(name: "tamper", value: "detected", isStateChange: true, displayed: false)
            break;
        default:
        	log.debug "Invalid message code in IAS message: ${msgCode}"
    }
    return resultMap
}


private Map getMotionResult(value) {
	String linkText = getLinkText(device)
	String descriptionText = value == 'active' ? "${linkText} detected motion" : "${linkText} motion has stopped"
	return [
		name: 'motion',
		value: value,
		descriptionText: descriptionText
	]
}
def motionON() {
    log.debug "--- Motion Detected"
    sendEvent(name: "motion", value: "active", displayed:true, isStateChange: true)
    
	//-- Calculate Inactive timeout value
	def motionTimeRun = (settings.motionTime?:0).toInteger()

	//-- If Inactive timeout was configured
	if (motionTimeRun > 0) {
		log.debug "--- Will become inactive in $motionTimeRun seconds"
		runIn(motionTimeRun, "motionOFF")
	}
}

def motionOFF() {
	log.debug "--- Motion Inactive (OFF)"
    sendEvent(name: "motion", value: "inactive", displayed:true, isStateChange: true)
}

def panicContact() {
	log.debug "--- Panic button hit"
    sendEvent(name: "contact", value: "open", displayed: true, isStateChange: true)
    runIn(3, "panicContactClose")
}

def panicContactClose()
{
	sendEvent(name: "contact", value: "closed", displayed: true, isStateChange: true)
}

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

private getBatteryResult(rawValue) {
	def linkText = getLinkText(device)

	def result = [name: 'battery']

	def volts = rawValue / 10
	def descriptionText
	if (volts > 7.2) {
		result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
	}
	else {
		def minVolts = 5.2
		def maxVolts = 6.8
		def pct = (volts - minVolts) / (maxVolts - minVolts)
		result.value = Math.min(100, Math.round(pct * 100))
		result.descriptionText = "${linkText} battery was ${result.value}%"
	}

	return result
}

private getTemperature(value) {
	def celcius = Integer.parseInt(value, 16).shortValue() / 100
	if(getTemperatureScale() == "C"){
		return celcius
	} else {
		return celsiusToFahrenheit(celcius) as Integer
	}
}

private Map getTemperatureResult(value) {
	log.debug 'TEMP'
	def linkText = getLinkText(device)
	if (tempOffset) {
		def offset = tempOffset as int
		def v = value as int
		value = v + offset
	}
	def descriptionText = "${linkText} was ${value}°${temperatureScale}"
	return [
		name: 'temperature',
		value: value,
		descriptionText: descriptionText
	]
}

//------Command handlers------//
private handleArmRequest(message){
	def keycode = new String(message.data[2..-2] as byte[],'UTF-8')
	def reqArmMode = message.data[0]
	//state.lastKeycode = keycode
	log.debug "Received arm command with keycode/armMode: ${keycode}/${reqArmMode}"

	//Acknowledge the command. This may not be *technically* correct, but it works
	/*List cmds = [
				 "raw 0x501 {09 01 00 0${reqArmMode}}", "delay 200",
				 "send 0x${device.deviceNetworkId} 1 1", "delay 500"
				]
	def results = cmds?.collect { new physicalgraph.device.HubAction(it) } + createCodeEntryEvent(keycode, reqArmMode)
	*/
	def results = createCodeEntryEvent(keycode, reqArmMode)
	log.trace "Method: handleArmRequest(message): "+results
	return results
}

def createCodeEntryEvent(keycode, armMode) {
	createEvent(name: "codeEntered", value: keycode as String, data: armMode as String, 
				isStateChange: true, displayed: false)
}

//
//The keypad seems to be expecting responses that are not in-line with the HA 1.2 spec. Maybe HA 1.3 or Zigbee 3.0??
//
private sendStatusToDevice() {
	log.debug 'Sending status to device...'
	def armMode = device.currentValue("armMode")
	log.trace 'Arm mode: '+armMode
	def status = ''
	if (armMode == null || armMode == 'disarmed') status = 0
	else if (armMode == 'armedAway') status = 3
	else if (armMode == 'armedStay') status = 1
	else if (armMode == 'armedNight') status = 2
	
	// If we're not in one of the 4 basic modes, don't update the status, don't want to override beep timings, exit delay is dependent on it being correct
	if (status != '')
	{
		return sendRawStatus(status)
	}
    else
    {
    	return []
    }
}


// Statuses:
// 00 - Disarmed
// 01 - Armed partial
// 02 - Armed partial
// 03 - Armed Away
// 04 - ?
// 05 - Fast beep (1 per second)
// 05 - Entry delay (Uses seconds) Appears to keep the status lights as it was
// 06 - Amber status blink (Ignores seconds)
// 07 - ?
// 08 - Red status blink
// 09 - ?
// 10 - Exit delay Slow beep (2 per second, accelerating to 1 beep per second for the last 10 seconds) - With red flashing status - Uses seconds
// 11 - ?
// 12 - ?
// 13 - ?

private sendRawStatus(status, seconds = 00) {
	log.debug "Sending Status ${zigbee.convertToHexString(status)}${zigbee.convertToHexString(seconds)} to device..."
    
    // Seems to require frame control 9, which indicates a "Server to client" cluster specific command (which seems backward? I thought the keypad was the server)
    List cmds = ["raw 0x501 {09 01 04 ${zigbee.convertToHexString(status)}${zigbee.convertToHexString(seconds)}}",
    			 "send 0x${device.deviceNetworkId} ${device.endpointId as int} 1", 'delay 100']
                 
    def results = cmds?.collect { new physicalgraph.device.HubAction(it) };
    return results
}

def notifyPanelStatusChanged(status) {
	//TODO: not yet implemented. May not be needed.
}
//------------------------//

def setDisarmed() { setModeHelper("disarmed",0) }
def setArmedAway(def delay=0) { setModeHelper("armedAway",delay) }
def setArmedStay(def delay=0) { setModeHelper("armedStay",delay) }
def setArmedNight(def delay=0) { setModeHelper("armedNight",delay) }

def setEntryDelay(delay) {
	setModeHelper("entryDelay", delay)
	sendRawStatus(5, delay) // Entry delay beeps
}

def setExitDelay(delay) {
	setModeHelper("exitDelay", delay)
	sendRawStatus(10, delay)  // Exit delay
}

private setModeHelper(String armMode, delay) {
	sendEvent([name: "armMode", value: armMode, data: [delay: delay as int], isStateChange: true])
    def lastUpdate = formatLocalTime(now())
    sendEvent(name: "lastUpdate", value: lastUpdate, displayed: false)
	sendStatusToDevice()
}

private setKeypadArmMode(armMode){
	Map mode = [disarmed: '00', armedAway: '03', armedStay: '01', armedNight: '02', entryDelay: '', exitDelay: '']
    if (mode[armMode] != '')
    {
		return ["raw 0x501 {09 01 04 ${mode[armMode]}00}",
				 "send 0x${device.deviceNetworkId} 1 1", 'delay 100']
    }
}

def acknowledgeArmRequest(armMode){
	List cmds = [
				 "raw 0x501 {09 01 00 0${armMode}}",
				 "send 0x${device.deviceNetworkId} ${device.endpointId as int} 1", "delay 100"
				]
	def results = cmds?.collect { new physicalgraph.device.HubAction(it) }
	log.trace "Method: acknowledgeArmRequest(armMode): "+results
	return results
}

def sendInvalidKeycodeResponse(){
	List cmds = [
				 "raw 0x501 {09 01 00 04}",
				 "send 0x${device.deviceNetworkId} ${device.endpointId as int} 1", "delay 100"
				]
				 
	log.trace 'Method: sendInvalidKeycodeResponse(): '+cmds
	return (cmds?.collect { new physicalgraph.device.HubAction(it) }) + sendStatusToDevice()
}

def beep(def beepLength = settings.beepLength) {
	if ( beepLength == null )
	{
		beepLength = 0
	}
	def len = zigbee.convertToHexString(beepLength, 2)
	List cmds = ["raw 0x501 {09 01 04 05${len}}", 'delay 200',
		     "send 0x${device.deviceNetworkId} ${device.endpointId as int} 1", 'delay 500']
	runIn(beepLen, beepOff) // UEI keypad beep needs to be turned off
	cmds
}

def beepOff() {
	List cmds = ["raw 0x501 {09 01 04 0000}", 'delay 200',
		     "send 0x${device.deviceNetworkId} ${device.endpointId as int} 1", 'delay 500']
	cmds
}
//------Utility methods------//

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 testCmd(){
	//log.trace zigbee.parse('catchall: 0104 0501 01 01 0140 00 4F2D 01 00 0000 07 00 ')
	//beep(10)
	//test exit delay
	//log.debug device.zigbeeId
	//testingTesting()
	//discoverCmds()
	//zigbee.configureReporting(1,0x20,0x20,3600,43200,0x01)		//battery reporting
	//["raw 0x0001 {00 00 06 00 2000 20 100E FEFF 01}",
	//"send 0x${device.deviceNetworkId} 1 1"]
	//zigbee.command(0x0003, 0x00, "0500") //Identify: blinks connection light
    
	//log.debug 		//temperature reporting  
    
	return zigbee.readAttribute(0x0020,0x01) + 
		    zigbee.readAttribute(0x0020,0x02) +
		    zigbee.readAttribute(0x0020,0x03)
}

private discoverCmds(){
	List cmds = ["raw 0x0501 {08 01 11 0011}", 'delay 200',
				 "send 0x${device.deviceNetworkId} ${device.endpointId as int} 1", 'delay 500']
	cmds
}

private testingTesting() {
	log.debug "Delay: "+device.currentState("armMode").toString()
	List cmds = ["raw 0x501 {09 01 04 050A}", 'delay 200',
				 "send 0x${device.deviceNetworkId} ${device.endpointId as int} 1", 'delay 500']
	cmds
}

I have this working with the SHM Delay Version 2.0 smartapp.
I’ve actually stopping using the keypad because it didn’t really fit into how I want to use smartthings. So I am probably going to list it on ebay again in the near future.

2 Likes

I’m not an expert, but this worked for me to install an Xfinity keypad

Installing a keypad in SmartThings

  1. Install SmartThings Classic

Do not install latest SmartThings app.

  1. Install Device Handler for your keypad on Smartthings Groovy IDE

The Xfinity
https://graph-na04-useast2.api.smartthings.com/
You need to log in with your Samsung ID

XHK1-UE Keypad device handler is at

Install Device Handler using

My Device Handlers
arnbme : SHM Delay arbme/SHMDelay (Version 2) Published MyApps
arnbme : SHM Delay Child arbme/SHMDelay (Version 2) Unpublished MyApps
arnbme : SHM Delay ModeFix arbme/SHMDelay (Version 2) Unpublished MyApps
arnbme : SHM Delay Simkypd Child arbme/SHMDelay (Version 2) Unpublished MyApps
arnbme : SHM Delay Talker Child arbme/SHMDelay (Version 2) Unpublished MyApps
arnbme : SHM Delay User arbme/SHMDelay (Version 2) Unpublished MyApps
ethayer: Lock Manager ethayer/lock-manager Unpublished
ethayer: User Lock Manager Published Safety & Security
rboy : Lock single user code Published Safety & Security
(IProbably don’t need rboy : Lock single user code)

  1. Pair Keypad to SmartThings Classic

For example, reset Xfinity XHK1-UE Keypad by taking out all batteries for 10 seconds, then press grey button next to batteries
while putting in batteries. Light new batteries will blink several times then repeat.
On SmartThings Classic app, My Home, Things, press the top right plus sign, Add a Thing, and wait for pairing to occur.
If you see waiting message, then you chose the wrong Device Handler.
You will know keypad is paired by clicking Smartthings Classic, My Home, Xfinity XHK1-UE, Right Now, and seeing an interface.
Waving a hand over the keyboard changes “no motion” to “motion”.
Pressing the speaker icon in center bottom will make keypad beep. Pressing the circle arrow icon will turn the keypad off.
This shows that you can control the keypad from the SmartThings Classic app.

4.Install SHM Delay SmartApp

As stated in above URL, Create a Sim Contact Sensor

You can use SIMSEN01
https://graph-na04-useast2.api.smartthings.com/
My Devices, New Device
Sim Contact Sensor, Simulated Contact Sensor, Home, Home Hub, , SIMSEN01, ONLINE, Cloud

SmartThings Classic, Automation, SmartApps (tab), Add a SmartApp
SmartThings Classic, Automation, SmartApps (tab), SHM Delay
Create a New Delay Profile
Real Contact Sensor (Remove from SmartHome Monitoring)
Simulated Contact Sensor (Must Monitor in SmartHome)
Sim Contact Sensor
(Optional) Tap to set
(Optional) Tap to set
Profile name
Profile: Delay:
Next
Entry and Exit Data
Alarm entry delay time in seconds from 0 to 90
30
When arming in away mode without the keypad, set a simulated exit delay time in seconds from 0 to 90
30
When arming in away mode optional motion sensor entry delay time…
0
Beep/Chime these devices when real contact sensor opens, and Alarm State is Off (Optional)
Tap to set
Next
Open door monitor and notification settings
Maximum number of open door warning messages
2
Number of minutes between open door messages from 1 to 15
1
Log to Notifications
slide to on
Send Push Notifications?
slide to on
Send a text message to this number. For multiple SMS recipients, separate phone numbers with a semicolon(:wink:

Save

Global Settings
Disable All Functions. Default: Off/False
slide to off
A real or simulated Keypad is used to arm and disarm Smart Home Monitor (SHM) Default: Off/False
slide to ON
This app issues an intrusion message with name of triggering real sensor? Default: On/True
slide to ON
Add 3 digit emergency call number on this app’s intrusion message this pho?
Tap to set
Include this phone number as a link on this app’s intrusion message? Separate multiple phone numbers with a semicolon(:wink:
Tap to set
I have the same motion sensor defined in multiple delay profiles. Stop false motioon sensor triggered alarms…
slide to off
Mode Fix when system armed
slide to off
True Night Flag…
slide to off
I am using the RBoy Apps Keypad DTH
slide to off
Default True exit delay in seconds when arming with a delay. range 0-90, default:30
30
Keypad Disarmed/OFF executes Routine. Default: I’m Back!
I’m Back!
Keypad Stay/Partial…
Good Night!
Keypad Night…
Good Night!
Keypad Away…
Goodbye!
Iris Panic Key is Monitored…
slide to ON
Log pin entries. Default: On/True
slide to on
Log Pin to Notifications?
slide to ON
Send Pin Push Notification?
slide to ON
Send Pin text message to this number…
Tap to set
Log invalid keypad entries, pins not found in a User Profile Default: On/True
slide to ON
Log Bad Pins to Notifications?
slide to ON
Send Bad Pin Push Notification?
slide to ON
Send invalid Bad Pin text message…
Tap to set
(Optional) Contacts must be closed…
Tap to set
(Optional) Contacts must be closed…
Tap to set
Simulated sensors must be unique?..
slide to off
True Entry Delay. This is a last resort…
slide to off
Allow Multiple Motion Sensors in Delay Profile. Default: On/True
slide to On
Next
Save

Lock User Manager version and installation instructions are in
/**

  • User Lock Manager v4.1.5
  • Copyright 2015 Erik Thayer
  • Keypad support added by BLebson

*/
Installation instructions are in

User Lock Manager
Which locks?
Select Locks
Xfinity XHK1-UE Keypad
Select Locks
Xfinity XHK1-UK Keypad
(press right box so it is a white checkmark in blue box)

Number of users
1
User Settings

User Settings
Users
<name>
	Name for User
	<name>
	node
	<enter a passcode number>
	Slot
	1
	Mute entry notification?
	slide to off
	Burn after use?
	slide to off
	Enabled?
	slide to ON
	Hello Home Phrase
	Tap to set
Save
	(note Slot 1 / number [Usage: number]

Lock Info
Tap to show
Keypad Info (optional)
Tap to show
Notification Settings
Text this Number
Phone number
Send a Push Notification
slide to off
Only During These Times (optional)
Notify Starting At This Time
Notify Ending At This Time
Schedule (optional)
Global Hello Home
Tap to show
Label this SmartApp
Tap to show

Test SHM Delay by opening designated door and try to trigger motion sensor, and check to see if takes 30 seconds before motion responds.

PIN code test
Test with alarm disarmed, enter PIN code on keypad then press A in bottom left. Should hear short beeps when
touching keypad and long beep after pressing correct PIN code and pressing A (or press B in bottom right).
Test with alarm armed, set off intrusiontenter PIN code on keypad then press A in bottom left. Should hear short beeps when
touching keypad and long beep after pressing correct PIN code and pressing A.
When disarmed, when motion over keyboard, see green wireless symbol and green light at bottom of keypad.
When armed, when motion over keyboard, see red middle top and see green wireless symbol and red light at bottom of keypad.
If keyboard does not respond with a long beep to correct PIN and the letter A, then wait a day. For some reason, it may work the next day or the day after.

5.Create Disarm Routine

SmartThings Classic, Automation, Routines, Add a Routine, Give this Routine a name “Disarm Keypad”,
What do want to happen? Set Smart Home Monitor to “Disarmed”
Save
Additional settings
Automatically perform “Disarm Keypad” when “Button is Pushed or Held”
Test by arming Smart Home Monitor, then clicking PIN number and A and see if Smart Home Monitor disarms.

Other notes:

Note that SmartThings Classic, Automation, SmartApps (tab), Smart Home Monitor shows “Something’s Wrong; We can’t load your screen right now”
Feel free to uninstall SmartThings and reinstall at any time. Devices will return.

If you see a reminder text that reminds you hourly that does not turn off even though you cleared all intrusion alerts, install the latest SmartThings app (not SmartThings Classic) and see if you can turn off the intrusion alert.

Feel free to uninstall any automation. It will still be in the Samsung Groovy IDE and can be easily reinstalled.

1 Like

Hello ST Community!

I’ve been using ST and reading the community posts for years. This is however is my first post. I created this account to give back to the community a little bit.

Past few days I’ve been trying to use the Xfinity keypads. I was able to get Centralite 3400 working but no matter what device handler I tried, Xfinity XHK1-TC (the latest version) never worked.

Later after spending several days I realized that it’s not FULLY paired although it’s showing up on the ST devices.

Here’s what I did to pair it correctly:

  1. Remove batteries, hold the tamper button and insert the batteries back

  2. Keep pushing the button for a second and then release. (I also tapped the button once more after releasing it but not sure if it’s really needed)

  3. Wait a few seconds until you see a blinking pattern

  4. ONLY NOW put the SmartThings hub in pairing more by adding a new thing. This step is very important and made a difference for me.

  5. If the keypad is paired correctly, you should see a solid green signal light (i.e. antenna light on the bottom left) when you press any key.

TL;DR

To pair Xfinity XHK1 keypad, add a thing on ST only AFTER the keypad is in pairing mode (not before or at the same time).

Hope this helps :slight_smile: