[OBSOLETE] Securifi SZ-PIR02 motion sensor

Hi,

So I have the Securifi SZ-PIR02 motion sensor working with ST now. Will post a link to the code shortly.

metadata {
	definition (name: "Securifi SZ-PIR02", namespace: "sozonoff", author: "Sozonoff") {
		capability "Motion Sensor"
		capability "Sensor"
        capability "Configuration"
        capability "Refresh"
        
        command "enrollResponse"
        
		fingerprint inClusters: "0000,0003,0500", outClusters: "0003"
	}

	// simulator metadata
	simulator {
		status "active": "zone report :: type: 19 value: 0031"
		status "inactive": "zone report :: type: 19 value: 0030"
	}

	// UI tile definitions
	tiles {
		standardTile("motion", "device.motion", 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("refresh", "device.refresh", inactiveLabel: false, decoration: "flat") {
			state ("default", action:"refresh.refresh", icon:"st.secondary.refresh")
		}
		
		main (["motion"])
		details(["motion","refresh"])
	}
}

def configure() {
	log.debug("** PIR02 ** configure called for device with network ID ${device.deviceNetworkId}")
    
	String zigbeeId = swapEndianHex(device.hub.zigbeeId)
	log.debug "Configuring Reporting, IAS CIE, and Bindings."
	def configCmds = [
    	"zcl global write 0x500 0x10 0xf0 {${zigbeeId}}", "delay 200",
		"send 0x${device.deviceNetworkId} 1 1", "delay 1500",
        
        "zcl global send-me-a-report 1 0x20 0x20 0x3600 0x3600 {01}", "delay 200",
        "send 0x${device.deviceNetworkId} 1 1", "delay 1500",
        
		"zdo bind 0x${device.deviceNetworkId} 1 1 0x001 {${device.zigbeeId}} {}", "delay 1500",
        
        "raw 0x500 {01 23 00 00 00}", "delay 200",
        "send 0x${device.deviceNetworkId} 1 1", "delay 1500",
	]
    return configCmds + refresh() // send refresh cmds as part of config
}

def refresh() {
	log.debug("** PIR02 ** refresh called for PIR02")
    [
        "st rattr 0x${device.deviceNetworkId} 1 1 0x20"
		
	]	
}

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

// Parse incoming device messages to generate events
def parse(String description) {
	log.debug("** PIR02 parse received ** ${description}")
        
	Map map = [:]
    
    if (description?.startsWith('zone status')) {
	    map = parseIasMessage(description)
    }
 
	log.debug "Parse returned $map"
	def result = map ? createEvent(map) : null
    
    if (description?.startsWith('enroll request')) {
    	List cmds = enrollResponse()
        log.debug "enroll response: ${cmds}"
        result = cmds?.collect { new physicalgraph.device.HubAction(it) }
    }
    return result
}

private Map parseIasMessage(String description) {
    List parsedMsg = description.split(' ')
    String msgCode = parsedMsg[2]
    
    Map resultMap = [:]
    switch(msgCode) {
        case '0x0030': // Closed/No Motion/Dry
            log.debug 'no motion'
            resultMap.name = 'motion'
            resultMap.value = 'inactive'
            break

        case '0x0031': // Open/Motion/Wet
            log.debug 'motion'
            resultMap.name = 'motion'
            resultMap.value = 'active'
            break

        case '0x0032': // Tamper Alarm
        	log.debug 'motion with tamper alarm'
            resultMap.name = 'motion'
            resultMap.value = 'active'
            break

        case '0x0033': // Battery Alarm
            break

        case '0x0034': // Supervision Report
        	log.debug 'no motion with tamper alarm'
            resultMap.name = 'motion'
            resultMap.value = 'inactive'
            break

        case '0x0035': // Restore Report
            break

        case '0x0036': // Trouble/Failure
        	log.debug 'motion with failure alarm'
            resultMap.name = 'motion'
            resultMap.value = 'active'
            break

        case '0x0038': // Test Mode
            break
    }
    return resultMap
}


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
}

Its not all done yet but the motion detection seems to work. Not sure at all why my Refresh Tile is not showing up in ST Appā€¦ Consider this work in progress.

Serge

1 Like

Thank you Serge!!! I will get mine setup as soon as I get home from work. Will keep my eyes open for code updates as well. Let me know if there is anything I can help you test!

Jeff

EDIT: Any chance you will be working on the keyfob as well?

Updated to support the tamper switch (I will move this stuff to github shortly)

Not sure at all why my Refresh Tile is not showing up

To answer myself, seems a width and height must be specified for the tileā€¦

Any chance you will be working on the keyfob as well?

Sorry no, I dont have one.

metadata {
	definition (name: "Securifi SZ-PIR02", namespace: "sozonoff", author: "Sozonoff") {
		capability "Motion Sensor"
		capability "Sensor"
        capability "Configuration"
        capability "Contact Sensor"
        
        command "enrollResponse"
        
		fingerprint inClusters: "0000,0003,0500", outClusters: "0003"
	}

	// simulator metadata
	simulator {
		status "active": "zone report :: type: 19 value: 0031"
		status "inactive": "zone report :: type: 19 value: 0030"
	}

	// UI tile definitions
	tiles {
		standardTile("motion", "device.motion", 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("contact", "device.contact", width: 1, height: 1) {
			state("open", label:'${name}', icon:"st.contact.contact.open", backgroundColor:"#ffa81e")
			state("closed", label:'${name}', icon:"st.contact.contact.closed", backgroundColor:"#79b821")
		}        
		
		main (["motion"])
		details(["motion","contact"])
	}
}

def configure() {
	log.debug("** PIR02 ** configure called for device with network ID ${device.deviceNetworkId}")
    
	String zigbeeId = swapEndianHex(device.hub.zigbeeId)
	log.debug "Configuring Reporting, IAS CIE, and Bindings."
	def configCmds = [
    	"zcl global write 0x500 0x10 0xf0 {${zigbeeId}}", "delay 200",
		"send 0x${device.deviceNetworkId} 1 1", "delay 1500",
        
        "zcl global send-me-a-report 1 0x20 0x20 0x3600 0x3600 {01}", "delay 200",
        "send 0x${device.deviceNetworkId} 1 1", "delay 1500",
        
		"zdo bind 0x${device.deviceNetworkId} 1 1 0x001 {${device.zigbeeId}} {}", "delay 1500",
        
        "raw 0x500 {01 23 00 00 00}", "delay 200",
        "send 0x${device.deviceNetworkId} 1 1", "delay 1500",
	]
    return configCmds // send refresh cmds as part of config
}

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

// Parse incoming device messages to generate events
def parse(String description) {
	log.debug("** PIR02 parse received ** ${description}")
        
	Map map = [:]
    
    if (description?.startsWith('zone status')) {
	    map = parseIasMessage(description)
    }
 
	log.debug "Parse returned $map"
    map.each { k, v ->
    	log.debug("sending event ${v}")
        sendEvent(v)
    }
    
//	def result = map ? createEvent(map) : null
    
    if (description?.startsWith('enroll request')) {
    	List cmds = enrollResponse()
        log.debug "enroll response: ${cmds}"
        result = cmds?.collect { new physicalgraph.device.HubAction(it) }
    }
    return result
}

private Map parseIasMessage(String description) {
    List parsedMsg = description.split(' ')
    String msgCode = parsedMsg[2]
    
    Map resultMap = [:]
    switch(msgCode) {
        case '0x0030': // Closed/No Motion/Dry
            log.debug 'no motion'
            resultMap["motion"] = [name: "motion", value: "inactive"]
            resultMap["contact"] = getContactResult("closed")            
            break

        case '0x0031': // Open/Motion/Wet
            log.debug 'motion'
            resultMap["motion"] = [name: "motion", value: "active"]
            resultMap["contact"] = getContactResult("closed")            
            break

        case '0x0032': // Tamper Alarm
        	log.debug 'motion with tamper alarm'
            resultMap["motion"] = [name: "motion", value: "active"]
            resultMap["contact"] = getContactResult("open")            
            break

        case '0x0034': // Supervision Report
        	log.debug 'no motion with tamper alarm'
            resultMap["motion"] = [name: "motion", value: "inactive"]
            resultMap["contact"] = getContactResult("open")            
            break

        case '0x0035': // Restore Report
        	log.debug 'motion with tamper alarm'
            resultMap["motion"] = [name: "motion", value: "active"]
            resultMap["contact"] = getContactResult("open") 
            break

        case '0x0036': // Trouble/Failure
        	log.debug 'msgCode 36 not handled yet'
            break

        case '0x0038': // Test Mode
            break
    }
    return resultMap
}

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


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
}

Great. It recognizes my SZ-PIR02 motion sensor now. Only, it doesnā€™t show up anywhere in the ST app. And my Securifi Door Contact sensor SZ-DWS02 is now also recognized as a motion sensor? I looked at the properties of the new Device Type and noticed that ā€˜contact sensorā€™ is listed as a property. Is that correct?

FYI, I paired mine today and they work great! Thanks for sharing the device type. Let me know if you would be able to assist somehow in getting the keyfob to work. I have not created a device type before, but I can try and get whatever information you need to do so.

Thanks!

Hmmm I might have to see if I can refine the fingerprint a little more so that it does not match the Door Contact. The idea with using the ā€œcontact sensorā€ was to deal with the anti tamper switch in the PIR02 but maybe I can deal with that differently.

Only, it doesnā€™t show up anywhere in the ST app

Dont understand that, try to quit the Smarthings App and reload or login to the developer console and check the Device list.

Serge

Updated again ā€¦

Removed the contact sensor stuff.

metadata {
	definition (name: "Securifi SZ-PIR02", namespace: "sozonoff", author: "Sozonoff") {
		capability "Motion Sensor"
		capability "Sensor"
        capability "Configuration"
        
        attribute "tamperSwitch","ENUM",["open","closed"]
                
        command "enrollResponse"
        
		fingerprint endpointId: '08', profileId: '0104', inClusters: "0000,0003,0500", outClusters: "0003"
	}

	// simulator metadata
	simulator {
		status "active": "zone report :: type: 19 value: 0031"
		status "inactive": "zone report :: type: 19 value: 0030"
	}

	// UI tile definitions
	tiles {
		standardTile("motion", "device.motion", 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("tamperSwitch", "device.tamperSwitch", width: 1, height: 1) {
			state("open", label:'${name}', icon:"st.contact.contact.open", backgroundColor:"#ffa81e")
			state("closed", label:'${name}', icon:"st.contact.contact.closed", backgroundColor:"#79b821")
		}        
		
		main (["motion"])
		details(["motion","tamperSwitch"])
	}
}

def configure() {
	log.debug("** PIR02 ** configure called for device with network ID ${device.deviceNetworkId}")
    
	String zigbeeId = swapEndianHex(device.hub.zigbeeId)
	log.debug "Configuring Reporting, IAS CIE, and Bindings."
	def configCmds = [
    	"zcl global write 0x500 0x10 0xf0 {${zigbeeId}}", "delay 200",
		"send 0x${device.deviceNetworkId} 1 1", "delay 1500",
        
        "zcl global send-me-a-report 1 0x20 0x20 0x3600 0x3600 {01}", "delay 200",
        "send 0x${device.deviceNetworkId} 1 1", "delay 1500",
        
		"zdo bind 0x${device.deviceNetworkId} 1 1 0x001 {${device.zigbeeId}} {}", "delay 1500",
        
        "raw 0x500 {01 23 00 00 00}", "delay 200",
        "send 0x${device.deviceNetworkId} 1 1", "delay 1500",
	]
    return configCmds // send refresh cmds as part of config
}

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

// Parse incoming device messages to generate events
def parse(String description) {
	log.debug("** PIR02 parse received ** ${description}")
    def result = []        
	Map map = [:]
    
    if (description?.startsWith('zone status')) {
	    map = parseIasMessage(description)
    }
 
	log.debug "Parse returned $map"
    map.each { k, v ->
    	log.debug("sending event ${v}")
        sendEvent(v)
    }
    
//	def result = map ? createEvent(map) : null
    
    if (description?.startsWith('enroll request')) {
    	List cmds = enrollResponse()
        log.debug "enroll response: ${cmds}"
        result = cmds?.collect { new physicalgraph.device.HubAction(it) }
    }
    return result
}

private Map parseIasMessage(String description) {
    List parsedMsg = description.split(' ')
    String msgCode = parsedMsg[2]
    
    Map resultMap = [:]
    switch(msgCode) {
        case '0x0030': // Closed/No Motion/Dry
            log.debug 'no motion'
            resultMap["motion"] = [name: "motion", value: "inactive"]
            resultMap["tamperSwitch"] = getContactResult("closed")            
            break

        case '0x0031': // Open/Motion/Wet
            log.debug 'motion'
            resultMap["motion"] = [name: "motion", value: "active"]
            resultMap["tamperSwitch"] = getContactResult("closed")            
            break

        case '0x0032': // Tamper Alarm
        	log.debug 'motion with tamper alarm'
            resultMap["motion"] = [name: "motion", value: "active"]
            resultMap["tamperSwitch"] = getContactResult("open")            
            break

        case '0x0034': // Supervision Report
        	log.debug 'no motion with tamper alarm'
            resultMap["motion"] = [name: "motion", value: "inactive"]
            resultMap["tamperSwitch"] = getContactResult("open")            
            break

        case '0x0035': // Restore Report
        	log.debug 'motion with tamper alarm'
            resultMap["motion"] = [name: "motion", value: "active"]
            resultMap["tamperSwitch"] = getContactResult("open") 
            break

        case '0x0036': // Trouble/Failure
        	log.debug 'msgCode 36 not handled yet'
            break

        case '0x0038': // Test Mode
            break
    }
    return resultMap
}

private Map getContactResult(value) {
	log.debug "Tamper Switch Status ${value}"
	def linkText = getLinkText(device)
	def descriptionText = "${linkText} was ${value == 'open' ? 'opened' : 'closed'}"
	return [
		name: 'tamperSwitch',
		value: value,
		descriptionText: descriptionText
	]
}


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
}

How are you getting this to pair with your ST hub? I am searching from the app, and the green light on the sensorā€™s board is blinking, but I cant get them to pair.

I simply started the device search in SmartThings and then hit the button on the PIR device and it should appeare.

Serge

I have this sensor added and it seems to be working okay, however it doesnā€™t appear to be reporting ā€œno motionā€ back to the app. It picked up motion once and that was it, now itā€™s just saying there is motion but the room is empty. Any help is appreciated!

I have the same issue. Added two Securifi SZ-PIR02 sensors. The first one works fine. The second sensor continuously reports motion and does not report ā€˜no motionā€™ anymore.

I also noticed when I select preference the sensor reports an ā€œopenā€ or ā€œclosedā€ status in the top right. Smartthings motion sensors I use do not have such a property (which I think makes sense because itā€™s not an open/close sensor).

Adam, I removed the second sensor and added it again. Now they both work! :smile:

Even better, my Securifi Door Window Sensor (SZ-DWS02) also works with this code. Changed the device type to ā€œContact Sensorā€ and it is now recognized and working in SmartThings! Only thing is that it alerts ā€œMotionā€ when a door opens or closes instead of updating the Open/Close parameter. Any ideas how to solve that?

First of all, thanks for posting this. Iā€™m running into 2 issues, and Iā€™m wondering if you or anyone else ran into them.

First, the motion detector registers motion about once an hour when itā€™s idle. This means that using it for an alarm at night will cause the alarm to go off once an hour.

When itā€™s actively detecting motion, itā€™s fine, but once things die down and an hour passes, boom.

Second, this device fingerprint picks up both the motion sensor and the open/ close sensor from Securifi. Is there a way that I can do some analysis to make it more specific? Iā€™d like to be able to use both my motion sensors and contact sensors.

I am having the same issue, it reports motion every hour from the last reportā€¦ Going to try removing and re-adding to see if it fixes that!

How are you guys pairing this? It doesnā€™t seem to detect when I try to put it in pairing mode and use ā€œConnect a Deviceā€ on Smartthings app

I added this device type in the IDE and then paired with the hub like normal.

I did find that removing the SZ-PIR02 from its previous network is important and to do this you have to hold the pairing button down for 10 seconds I believe.

I tried numerous times with shorter reset cycles and while the sensor looked like it was trying to pair nothing showed up on the hub. After I held the button down for 10 seconds and then triggered a pairing cycle it joined right away.

Was anyone able to get the door sensors working?

Yes, mostly. Just choose ā€œOpen/Closed Sensorā€ for the type and it works fine. You donā€™t get the tamper switch, but I didnā€™t care about it anyway.

Iā€™ve got these sensors working, but thereā€™s a significant (3-5) second delay when I pass the action through to Stringify. I get that delay with other actions too, so I tried to run a rule with CoRE, and it just doesnā€™t seems to catch anything!

I wrote a Latching Piston:

IF sensor CHANGES TO active AND light IS off
THEN Turn on Light

BUT IF sensor was not active for 5 minutes
THEN Turn off

Itā€™s not firing at all :frowning:

Iā€™m seeing the active/inactive flop in the Events list on the sensor, but nothing is passing into CoRE.

I wonder if this is because the sensor flops back to inactive within 350-400ms?

Any thoughts on how better to trigger this? Should i be using ā€œChanged in the last minuteā€ instead of ā€œchanges to active?ā€

Is there a general issue with CoRE refresh speed?