Zooz ZSE43 Tilt Shock

Hey Folks,

I had an issue with the built in driver not having the attribute acceleration, and it was missing config button, and a few other things. The attribute shock was there but it is not recognized by some of the apps I used, they needed acceleration. Here is a driver I found on GitHub for Smarthings that I made an attempt to bring over to Hubitat. I went through it using some of the guides posted in the forums and kept at it until the errors went away. I never wrote this code and I am not a code writer by any stretch, but it did fix my problem. Hope I never buggered it up too much. So thanks to Mr. LaFramboise, not sure if he is here in these forums and I hope I didn't violate anything by doing this. If I did, someone please let me know, I am not up on the code thing and have read some post where people get pretty upset. I just want to help others like myself,

Edit, pulled the original and went with something different. Thanks @JasonJoel for the code to use to get this one going (again, lol). Lot of changes, but the layout I like. Again, I am just tinkering and trying to learn. I wonder if anyone has these to test it with me? Also, I added a battery install state, but not sure how that's going to work out, will the .state keep it in memory during shutdown's and software rev's? Please give me your thoughts on that as well @jtp10181.

/**
 *
 *  Zooz ZSE43 Tilt Shock XS Sensor v0.1.0
 *
 *  0.1.0 (08/16/2021) - Initial Version - C. Andrews
 */

import groovy.transform.Field

@Field static Map commandClassVersions = 
[
	 0x30: 2  //Sensor Binary
	,0x55: 1  //Transport Service
	,0x59: 3  //Association Group Info
	,0x5A: 1  //Device Reset Locally
	,0x5E: 2  //Z-Wave Plus Info
	,0x6C: 1  //Supervision
	,0x70: 2  //Configuration
	,0x71: 3  //Notification
	,0x72: 2  //Manufacturer Specific
	,0x73: 1  //Powerlevel
	,0x7A: 5  //Firmware Update MD
	,0x80: 1  //Battery
	,0x84: 2  //Wakeup
	,0x85: 3  //Association
	,0x86: 2  //Version
	,0x87: 3  //Indicator
	,0x8E: 2  //Multi Channel Association
	,0x9F: 1  //Security
]

metadata {
	definition (name: "Zooz ZSE43 Tilt Shock XS Sensor v0.1.0",
		namespace: "C. Andrews",
		author: "C. Andrews",
		) 
		
		{
		capability "AccelerationSensor"
		capability "Battery"
		capability "Sensor"
		capability "ContactSensor"

        command "DebugLogging", [[name:"Debug Logging",type:"ENUM", description:"Turn Debug Logging OFF/ON", constraints:["-", "OFF", "30m", "1h", "3h", "6h", "12h", "24h", "ON"]]]
        command "BatteryInstallation", [[name:"Battery Install Date YYYY-MM-DD",type:"STRING"]]
            
		// zw:Ss2a type:0701 mfr:027A prod:7000 model:E003 ver:1.10 zwv:7.13 lib:03 cc:5E,55,9F,6C sec:86,85,8E,59,72,5A,87,73,80,71,30,70,84,7A
		fingerprint mfr:"027A", prod:"7000", model:"E003", deviceJoinName: "Zooz ZSE43 Tilt Shock XS Sensor"
	}

	preferences {
		input name: "paramLEDIndicatorMode", type: "enum", title: "LED Indicator Mode", multiple: false, defaultValue: "3", options: ["0" : "No LED blink on status change", "1" : "LED blink on vibration only", "2" : "LED blink on open/close only", "3" : "LED blink for any change [DEFAULT]"], required: false, displayDuringSetup: true
		input name: "paramBatteryReportTime", type: "number", title: "Battery Report Time (in %), Default 5", multiple: false, defaultValue: "5 [DEFAULT]", range: "1..50", required: false, displayDuringSetup: true
		input name: "paramLowBatteryAlertThreshold", type: "number", title: "Low Battery Alert Threshold (in %), Default 20", multiple: false, defaultValue: "20", range: "10..50", required: false, displayDuringSetup: true
		input name: "paramVibrationSensitivity", type: "enum", title: "Vibration Sensitivity", multiple: false, defaultValue: "0", options: ["0" : "High [DEFAULT]", "1" : "Medium", "2" : "Low"], required: false, displayDuringSetup: true		
		input name: "paramGroup2OnDelay", type: "number", title: "Delay before sending ON to Assoc Group2 Devices (in seconds)", multiple: false, defaultValue: "0", range: "0..3600", required: false, displayDuringSetup: true
		input name: "paramGroup2OffDelay", type: "number", title: "Delay before sending OFF to Assoc Group2 Devices (in seconds)", multiple: false, defaultValue: "0", range: "0..3600", required: false, displayDuringSetup: true
		input name: "paramSensorReports", type: "enum", title: "Sensor Reports", multiple: false, defaultValue: "2", options: ["0" : "Only tilt sensor enabled", "1" : "Only vibration sensor enabled", "2" : "Both tilt and vibration enabled [DEFAULT]"], required: false, displayDuringSetup: true
		input name: "requestedGroup2", title: "Association Group 2 Members (Max of 5):", type: "text", required: false
		input name: "requestedGroup3", title: "Association Group 3 Members (Max of 4):", type: "text", required: false
		input name: "logEnable", type: "bool", title: "Enable debug logging", defaultValue: false	
		input name: "logDesc", type: "bool", title: "Enable descriptionText logging", defaultValue: true	
	}
}

// Parse //

void parse(String description){
    if (logEnable) log.debug "parse description: ${description}"
    hubitat.zwave.Command cmd = zwave.parse(description,commandClassVersions)
    if (cmd) {
        zwaveEvent(cmd)
    }
}

// Z-Wave Messages //

String secure(String cmd){
    return zwaveSecureEncap(cmd)
}

String secure(hubitat.zwave.Command cmd){
    return zwaveSecureEncap(cmd)
}

void zwaveEvent(hubitat.zwave.commands.supervisionv1.SupervisionGet cmd){
    hubitat.zwave.Command encapCmd = cmd.encapsulatedCommand(commandClassVersions)
    
    if (encapCmd) {
        zwaveEvent(encapCmd)
    }
    sendHubCommand(new hubitat.device.HubAction(secure(zwave.supervisionV1.supervisionReport(sessionID: cmd.sessionID, reserved: 0, moreStatusUpdates: false, status: 0xFF, duration: 0)), hubitat.device.Protocol.ZWAVE))
}

def zwaveEvent(hubitat.zwave.commands.associationv3.AssociationReport cmd) {
	log.info "---ASSOCIATION REPORT V3--- ${device.displayName} sent groupingIdentifier: ${cmd.groupingIdentifier} maxNodesSupported: ${cmd.maxNodesSupported} nodeId: ${cmd.nodeId} reportsToFollow: ${cmd.reportsToFollow}"
}

def zwaveEvent(hubitat.zwave.commands.sensorbinaryv2.SensorBinaryReport cmd) {
	if (logEnable) log.debug "---SensorBinaryReport--- ${device.displayName} sent ${cmd}"
}

def zwaveEvent(hubitat.zwave.commands.notificationv3.NotificationReport cmd)
{
	if (logEnable) log.debug "---NOTIFICATION REPORT V3--- ${device.displayName} sent ${cmd}"

	if (cmd.notificationType == 0x06) {
		if ((cmd.event == 0x16)) {
            if (logDesc) log.info "$device.displayName is open"
			sendEvent(name: "contact", value: "open", descriptionText: "$device.displayName is open", type: "physical")
		} else if (cmd.event == 0x17) {
            if (logDesc) log.info "$device.displayName is closed"
			sendEvent(name: "contact", value: "closed", descriptionText: "$device.displayName is closed", type: "physical")
        }
    }
    if (cmd.notificationType == 0x07) {
		if ((cmd.event == 0x03)) {
            if (logDesc) log.info "$device.displayName is active"
			sendEvent(name: "acceleration", value: "active", descriptionText: "$device.displayName is active", type: "physical")
		} else if (cmd.event == 0x00) {
            if (logDesc) log.info "$device.displayName is inactive"
			sendEvent(name: "acceleration", value: "inactive", descriptionText: "$device.displayName is inactive", type: "physical")            
    } else {
        log.debug "Unhandled notification: Type ${cmd.notificationType}, Event ${cmd.event}"
        }
    }
}

def zwaveEvent(hubitat.zwave.commands.indicatorv3.IndicatorReport cmd) {
	if (logEnable) log.debug "---IndicatorReport REPORT V3--- ${device.displayName} sent ${cmd}"
}

def zwaveEvent(hubitat.zwave.commands.batteryv1.BatteryReport cmd) {
	if (logEnable) log.debug "---BatteryReport REPORT V1--- ${device.displayName} sent ${cmd}"
	def result = []

	if (cmd.batteryLevel == 0xFF) {
		if (logDesc) log.info "$device.displayName battery is low"	
		sendEvent(name: "battery", value: 1, descriptionText: "$device.displayName battery is low", type: "physical", isStateChange: true)
	} else {
		if (logDesc) log.info "$device.displayName battery is ${cmd.batteryLevel}%"
		sendEvent(name: "battery", value: cmd.batteryLevel, descriptionText: "$device.displayName battery is ${cmd.batteryLevel}%",type: "physical", isStateChange: true)
	}
}

def zwaveEvent(hubitat.zwave.commands.deviceresetlocallyv1.DeviceResetLocallyNotification cmd) {
	if (logEnable) log.debug "---DeviceResetLocallyNotification--- ${device.displayName} sent ${cmd}"
	log.info ("$device.displayName has reset.")
}

def zwaveEvent(hubitat.zwave.commands.configurationv2.ConfigurationReport cmd) {
	if (logEnable) log.debug "---CONFIGURATION REPORT V2--- ${device.displayName} sent ${cmd}"
}

def zwaveEvent(hubitat.zwave.commands.manufacturerspecificv2.DeviceSpecificReport cmd) {
    if (logEnable) log.debug "---Device Specific Report: ${cmd}---"
    switch (cmd.deviceIdType) {
        case 1:
            // serial number
            def serialNumber=""
            if (cmd.deviceIdDataFormat==1) {
                cmd.deviceIdData.each { serialNumber += hubitat.helper.HexUtils.integerToHexString(it & 0xff,1).padLeft(2, '0')}
            } else {
                cmd.deviceIdData.each { serialNumber += (char) it }
            }
            device.updateDataValue("serialNumber", serialNumber)
            break
    }
}

void zwaveEvent(hubitat.zwave.commands.wakeupv2.WakeUpIntervalReport cmd) {
    if (logEnable) log.debug "---WakeUpIntervalReport: ${cmd}---"
}

void zwaveEvent(hubitat.zwave.commands.wakeupv2.WakeUpIntervalCapabilitiesReport cmd) {
    if (logEnable) log.debug "---WakeUpIntervalCapabilitiesReport: ${cmd}---"
}

void zwaveEvent(hubitat.zwave.commands.wakeupv2.WakeUpNotification cmd) {
    if (logEnable) log.debug "---WakeUpNotification: ${cmd}---"

	if (state.queuedConfig) {
		state.queuedConfig = false
		updateConfig()
	} else {
		sendToDevice(zwave.batteryV1.batteryGet().format())
	}
}
void zwaveEvent(hubitat.zwave.commands.versionv2.VersionReport cmd) {
    if (logEnable) log.debug "---Version Report: ${cmd}"
    device.updateDataValue("firmwareVersion", "${cmd.firmware0Version}.${cmd.firmware0SubVersion}")
    device.updateDataValue("protocolVersion", "${cmd.zWaveProtocolVersion}.${cmd.zWaveProtocolSubVersion}")
    device.updateDataValue("hardwareVersion", "${cmd.hardwareVersion}")
}

def zwaveEvent(hubitat.zwave.Command cmd) {
    log.warn "${device.displayName} received unhandled command: ${cmd}"
}

// Driver Commands / Functions //

def BatteryInstallation (value) {	
    state.BatteryInstallationDate = (value)
    log.info "$device.displayName Battery Installation Date is ${value}"
}
    
void sendToDevice(List<String> cmds, Long delay = 300) {
    sendHubCommand(new hubitat.device.HubMultiAction(commands(cmds, delay), hubitat.device.Protocol.ZWAVE))
}

void sendToDevice(String cmd, Long delay = 300) {
    sendHubCommand(new hubitat.device.HubAction(zwaveSecureEncap(cmd), hubitat.device.Protocol.ZWAVE))
}

List<String> commands(List<String> cmds, Long delay = 300) {
    return delayBetween(cmds.collect { zwaveSecureEncap(it) }, delay)
}

def installed() {
	state.queuedConfig = false
	updated()
}

def updateConfig() {
    log.info "Updating Configuration..."

	List<String> cmds = []
	
// Associations
 
	// Group1
	cmds.add(zwave.associationV3.associationSet(groupingIdentifier:1, nodeId:zwaveHubNodeId).format())
	
	// Group2
	def nodes = []
    if (settings.requestedGroup2 != state.currentGroup2) {
        nodes = parseAssocGroupList(settings.requestedGroup2, 2)
       	cmds.add(zwave.associationV3.associationRemove(groupingIdentifier: 2, nodeId: []).format())
       	cmds.add(zwave.associationV3.associationSet(groupingIdentifier: 2, nodeId: nodes).format())
       	cmds.add(zwave.associationV3.associationGet(groupingIdentifier: 2).format())
       	state.currentGroup2 = settings.requestedGroup2
    }
	
	// Group3
    if (settings.requestedGroup3 != state.currentGroup3) {
        nodes = parseAssocGroupList(settings.requestedGroup3, 3)
       	cmds.add(zwave.associationV3.associationRemove(groupingIdentifier: 3, nodeId: []).format())
       	cmds.add(zwave.associationV3.associationSet(groupingIdentifier: 3, nodeId: nodes).format())
       	cmds.add(zwave.associationV3.associationGet(groupingIdentifier: 3).format())
       	state.currentGroup3 = settings.requestedGroup3
    }
	
	// Set LED Indicator param
	if (paramLEDIndicatorMode==null) {
		paramLEDIndicatorMode = 3
	}
	cmds.add(zwave.configurationV2.configurationSet(scaledConfigurationValue: paramLEDIndicatorMode.toInteger(), parameterNumber: 1, size: 1).format())
	cmds.add(zwave.configurationV2.configurationGet(parameterNumber: 1).format())

	// Set Battery Report Time param
	if (paramBatteryReportTime==null) {
		paramBatteryReportTime = 5
	}	
	cmds.add(zwave.configurationV2.configurationSet(scaledConfigurationValue: paramBatteryReportTime.toInteger(), parameterNumber: 2, size: 1).format())
	cmds.add(zwave.configurationV2.configurationGet(parameterNumber: 2).format())

	// Set Low Battery Alert Threshold param
	if (paramLowBatteryAlertThreshold==null) {
		paramLowBatteryAlertThreshold = 20
	}	
	cmds.add(zwave.configurationV2.configurationSet(scaledConfigurationValue: paramLowBatteryAlertThreshold.toInteger(), parameterNumber: 3, size: 1).format())
	cmds.add(zwave.configurationV2.configurationGet(parameterNumber: 3).format())

	// Set Vibration Sensitivity param
	if (paramBatteryVibrationSensitivity==null) {
		paramBatteryVibrationSensitivity = 0
	}	
	cmds.add(zwave.configurationV2.configurationSet(scaledConfigurationValue: paramVibrationSensitivity.toInteger(), parameterNumber: 4, size: 1).format())
	cmds.add(zwave.configurationV2.configurationGet(parameterNumber: 4).format())
		
	// Set Group 2 ON Delay
	if (paramGroup2OnDelay==null) {
		paramGroup2OnDelay = 0
	}	
	cmds.add(zwave.configurationV2.configurationSet(scaledConfigurationValue: paramGroup2OnDelay.toInteger(), parameterNumber: 5, size: 4).format())
	cmds.add(zwave.configurationV2.configurationGet(parameterNumber: 5).format())
	
	// Set Group 2 OFF Delay
	if (paramGroup2OffDelay==null) {
		paramGroup2OffDelay = 0
	}	
	cmds.add(zwave.configurationV2.configurationSet(scaledConfigurationValue: paramGroup2OffDelay.toInteger(), parameterNumber: 6, size: 4).format())
	cmds.add(zwave.configurationV2.configurationGet(parameterNumber: 6).format())
	
	// Set Sensor Reports param
	if (paramSensorReports==null) {
		paramSensorReports = 2
	}	
	cmds.add(zwave.configurationV2.configurationSet(scaledConfigurationValue: paramSensorReports.toInteger(), parameterNumber: 7, size: 1).format())
	cmds.add(zwave.configurationV2.configurationGet(parameterNumber: 7).format())
	
// Get current sensor state

	// Get a battery Update
	cmds.add(zwave.batteryV1.batteryGet().format())
	
	// Get version info
	cmds.add(new hubitat.zwave.commands.versionv3.VersionGet().format())

	// Wakeup Interval
	cmds.add(zwave.wakeUpV2.wakeUpIntervalSet(seconds: 43200, nodeid:zwaveHubNodeId).format())

	// Send No More Information
	cmds.add(zwave.wakeUpV2.wakeUpNoMoreInformation().format())

	// Send Commands
	log.info "Sending configuration parameters to the device..."
	sendToDevice(cmds,500)
}

def updated() {
    log.info "updated..."
    log.warn "debug logging is: ${logEnable == true}"
    log.warn "description logging is: ${txtEnable == true}"
    if (logEnable) runIn(1800,logsOff)

    if (state.lastUpdated && now() <= state.lastUpdated + 3000) return
    state.lastUpdated = now()

	if (getDataValue("inClusters").contains("0x80") || getDataValue("secureInClusters").contains("0x80")) {
		state.queuedConfig = true
	} else {
		state.queuedConfig = false
		updateConfig()
	}
}

private parseAssocGroupList(list, group) {
    def nodes = group == 2 ? [] : [zwaveHubNodeId]
    if (list) {
        def nodeList = list.split(',')
        def max = group == 2 ? 5 : 4
        def count = 0

        nodeList.each { node ->
            node = node.trim()
            if ( count >= max) {
                log.warn "Association Group ${group}: Too many members. Greater than ${max}! This one was discarded: ${node}"
            }
            else if (node.matches("\\p{XDigit}+")) {
                def nodeId = Integer.parseInt(node,16)
                if (nodeId <= 5) {
                	log.warn "Association Group ${group}: Adding the hub id as an association is not allowed."
                }
                else if ( (nodeId > 5) & (nodeId < 256) ) {
                    nodes << nodeId
                    count++
                }
                else {
                    log.warn "Association Group ${group}: Invalid member: ${node}"
                }
            }
            else {
                log.warn "Association Group ${group}: Invalid member: ${node}"
            }
        }
    }  
    return nodes
}

void DebugLogging(value) {
	if (value=="OFF") {
		unschedule(logsOff)
		logsOff()
	} else

	if (value=="30m") {
		unschedule(logsOff)
		log.debug "debug logging is enabled."
		device.updateSetting("logEnable",[value:"true",type:"bool"])
		runIn(1800,logsOff)		
	} else

	if (value=="1h") {
		unschedule(logsOff)
		log.debug "debug logging is enabled."
		device.updateSetting("logEnable",[value:"true",type:"bool"])
		runIn(3600,logsOff)		
	} else

	if (value=="3h") {
		unschedule(logsOff)
		log.debug "debug logging is enabled."
		device.updateSetting("logEnable",[value:"true",type:"bool"])
		runIn(10800,logsOff)		
	} else

	if (value=="6h") {
		unschedule(logsOff)
		log.debug "debug logging is enabled."
		device.updateSetting("logEnable",[value:"true",type:"bool"])
		runIn(21699,logsOff)		
	} else

	if (value=="12h") {
		unschedule(logsOff)
		log.debug "debug logging is enabled."
		device.updateSetting("logEnable",[value:"true",type:"bool"])
		runIn(43200,logsOff)		
	} else

	if (value=="24h") {
		unschedule(logsOff)
		log.debug "debug logging is enabled."
		device.updateSetting("logEnable",[value:"true",type:"bool"])
		runIn(86400,logsOff)		
	} else
		
	if (value=="ON") {
		unschedule(logsOff)
		log.debug "debug logging is enabled."
		device.updateSetting("logEnable",[value:"true",type:"bool"])
	}
}

def logsOff(){
    log.warn "debug logging disabled..."
    device.updateSetting("logEnable",[value:"false",type:"bool"])
}
1 Like

You don't need/want a config button on battery devices - it doesn't do anything since the device only updates paramters when it wakes up (not on demand). For those you can just re-save preferences.

A config button on battery devices misleads the end user in thinking that it was going to do something when they hit that button, and it doesn't.

2 Likes

Thanks @JasonJoel I had no idea about the config button, but it makes sense now that I think about it. Your right, it's misleading. I am still learning! lol.

No worries! I used to put config buttons on every driver I made - battery or otherwise - until that was pointed out to me. I learn something new almost every driver I try.

Also, if you want other Hubitat driver examples almost all the developers on here put theirs on github. I made ones for the ZSE41 and 42 (but not 43).

@krlaframboise is on here sometimes. I would suggest in your driver that you give direct attribution to Kevin, though. Maybe change the 1.0 comment line to explicitly point back to Kevin and/or his driver. Just an opinion though - I'm not Kevin :slight_smile: .

3 Likes

I was looking for your driver for the ZSE43 as I use a lot of your other ones. They work well! I must look into my own space on GitHub, at the very least it's somewhere to put these that others can easily grab. Good idea on the 1.0, I'll do that.

2 Likes

@JasonJoel quick question for you, the zwWakeupInterval is preprogrammed in the device firmware for 43200 s, when the device wakes up should I be seeing any information like battery level being reported or is this completely dependent on what the manufacturer decides to do upon wake up?
Reason I am asking is when the device is not being used for extended periods, it looks like it could be dead or offline, but in fact it is waking up, I am just not seeing any events or activity. I have reached out to Zooz and also asked this question regarding what is sent during the wake up.

On the flip side, can you build it into the driver to ask for the battery level after the controller sees a wake up?

Yes, I have done that on my ZSE40 driver.

What if you want to force ALL parameters to sync up? On my ZSE40 driver it only syncs up settings that are changed on a wakeup, but if configure is pressed it will force a full sync on the next wake up. I suppose I could just rename that command to something else so it is not confused.

Thanks Jeff. I must have a closer look how you implemented that in your driver.

Yes, that should be named something else entirely in my opinion, as it is definitely not a device "configure".

That's an interesting topic within itself guys. I was experiencing what I think is similar to what your talking about with the driver in my original post. Every night at 3 am I noticed Sync'd changes waiting to be written to the sensor. All parameters were being overwritten the next time the sensor woke up. This was due to the Device Watchdog app calling for a refresh (or configure?) at 3 am. I removed the refresh and configure from the driver and it hasn't happened since.

Yes it will keep it there, but if you want to able to pull that in from an App it should be saved as "Data" which shows up way at the bottom of the device page. There are some apps from thebearmay out there to save and read info from the device data on multiple devices at once.

1 Like

Hi Guys, I see thesmartesthouse has a sale. How are the zooz shock/tilt sensors performing?

I got one to put on my garage door to replace my failed Smartthings sensor. So far it has worked pretty well. It will ocassionally get stuck and not change states. I have had one occasion of that myself.

1 Like

Similarly, I observed one instance of the state being incorrect. It has been on my garage door for over 5 months now and I would buy it again.

2 Likes

I have one on my garage door and overall it has performed well. I have had a few occasions (maybe 3 or 4 times) where it failed to reflect the correct state. Initially the battery reporting was very odd. It went from like 98% to like 12% overnight. I believe that one is currently sitting at about 56% after having been in service since Christmas.

1 Like

@lcw731 @mavrrick58

My ZSE43 is also showing an incorrect state now. How did you guys resolve this? I am using the built in driver from Zooz.

For me i just opened the door again and closed it. That is all it has ever taken.

1 Like

Tried that several times. not working. stays in the contact open state.

By chance could it be on the edge of the zwave network, or could you have a zwave repeater issue?

@juju

This is also pretty much what I have done as well. Nothing long term. but it hasn't happened to me enough that I've been overly concerned enough to dig into it further.