Quirky Overflow

Hey Everyone,

So I’m new to the smartthings. I dumped wink to move over here. I have 2 overflow sensors that I’d like to integrate with smart things. The problem is that when I get them to pair up they just show up as a thing. Is there some device specific code out there for these as of yet? I was able to get my trippers to pair up with the smartthings thanks to the community for the code for that. Thank you all in advance for your help with this.

1 Like

Well its zigbee and not on the supported devices list, i’d have some extra options for you if it were zwave. i did some searching around and i have not seen any community driven devicetypes for the quirky overflow. so that give you 2 options if want to use the quirky overflow as a water sensor. Write a devicetype for it yourself, or ask one of the developers in the community really nicely to write it for you. If you decide to write it yourself i’d recommend using live logging to get the in and out clusters and using the GE/Jasco ZigBee Switch device and SmartSense Moisture Sensor templates as a guide.

I was able to add a zigbee key fob with the help of the community. The first step is to use a sort of generic zigbee device to get the clusters that the device supports like @sidjohn1 mentioned

I created one based on others I found in the community.Use this link and then post the clusters you find

You’ll have to create the new device type in the IDE and then point the “thing” to this new device type. Then go into the st app and press the refresh button on your thing device which should trigger the getclusters

Thank you both for your advice on this. Interestingly enough. I added the Quirky Trippers last night (wanted to know if my freezer door was left open or not). So I just added the overflow back in this morning and SmartThings thinks that its a tripper. I’m trying to get the IDE to show me the information in the live logs but its not showing me anything. I’ll have to ask the devs I do believe for some assistance here since I’m not entirely sure just what I"m doing just yet lol.

So continuing to hack my away at this.

Is this what you mean by clusers?

catchall: 0104 0008 01 01 0000 00 43D0 00 00 0000 04 01 00
0e0a63b9-e0bc-4488-b95b-4ff569c8c911 8:50:59 AM: trace read attr - raw: 43D00100080A00000020FF, dni: 43D0, endpoint: 01, cluster: 0008, size: 0A, attrId: 0000, result: success, encoding: 20, value: ff

Just adding some more notes here for my own reference while I hack away at this silly thing.

00274fe4-f75a-4f22-b8ce-6c1d32b09231 9:02:03 AM: trace catchall: 0104 0008 01 01 0000 00 C61A 00 00 0000 04 01 00
00274fe4-f75a-4f22-b8ce-6c1d32b09231 9:02:02 AM: trace read attr - raw: C61A0100080A00000020FF, dni: C61A, endpoint: 01, cluster: 0008, size: 0A, attrId: 0000, result: success, encoding: 20, value: ff
00274fe4-f75a-4f22-b8ce-6c1d32b09231 9:02:02 AM: trace catchall: 0104 0006 01 01 0000 00 C61A 00 00 0000 01 01 0000001000

I"m using the tripper code to tweak this and also looking at the smart sense as well.

If there is a wonderful developer who could lend me a hand or anyone who can help me understand these codes it would be greatly appreciated. I’m looking through the zigbee code pdf and I can’t find anything for the clustergroup 0008. I think that I’m on the right path here with this just not sure where to go next.

Cluster 0008 is for level control. I also see cluster 0006, which is for on/off. Are you sure these messages are from the Overflow sensor? They’re much more common in a lightbulb like the GE Link.

One way to check the clusters used by the device is to go into the Device details page in IDE. There should be a section labeled “Raw Description”. Copy that line here.

Another way to get the info is to use the following command as Kevin linked to. The zdo active command will tell the device to report back its information:

def getClusters() {
"zdo active 0x${device.deviceNetworkId}"
log.debug “Get Clusters Called”;
}

Thank you Scott for your help. I work in IT and I can generally hack some things once I have some understanding :). I am reading here but still just confused. Here is the raw description

01 0104 0402 00 06 0000 0001 0003 0500 0020 0B05 02 0003 0019

Thank you very much for the help

You Should be able to modify the SmartSense Moisture sensor template. Have you tried using that yet?

Here’s a breakdown of what that means

01 - End Point #
0103 = Profile - Zigbee Home Automation
0402 = Device Type - IAS Zone
00 = Ver

06 = 6 inClusters

0000 - Basic
0001 - Power Configuration (Battery Level)
0003 - Identify
0500 - IAS Zone
0020 - Poll Control
0B05 - Diagnostics Cluster

02 = 2 outClusters

0003 - Identify
0019 - OTA Upgrade

So your figerprint would be

fingerprint profileId: “0104”, deviceId: “0402”, inClusters: “0000,0001,0003,0500,0020,0B05”, outClusters: “0003,0019”

If you look at the fingerprint in the smartsense moisture it’s nearly the same (except the ss moisture supports temparature (cluster 0402) which the quirky does not

Here’s the ST one

fingerprint inClusters: “0000,0001,0003,0402,0500,0020,0B05”, outClusters: “0019”, manufacturer: “CentraLite”, model: “3315”

I suspect you should be able to modify the ST Moisture one, removing the temp stuff and it should work

12:47:53 PM: debug Parse returned [[name:contact, value:dry, descriptionText:Quirky/Wink Overflow was closed, isStateChange:true, displayed:true, linkText:Quirky/Wink Overflow]]
54050e8b-80c5-4655-9e45-db52ccff24f8 12:47:45 PM: warn stateCheck(open) called but door is closed: doing nothing
1fac563a-83e2-4313-acad-ba76bd504826 12:47:45 PM: warn stateCheck(open) called but door is closed: doing nothing
92196554-8c84-4179-a7b7-be9c3e414cd7 12:47:07 PM: debug Parse returned [:]
92196554-8c84-4179-a7b7-be9c3e414cd7 12:47:01 PM: debug Parse returned [[name:contact, value:wet, descriptionText:Quirky/Wink Overflow was closed, isStateChange:true, displayed:true, linkText:Quirky/Wink Overflow]]
e1fb8fa7-745f-43ab-a804-1868e5a77ea6 12:47:01 PM: debug lowBatteryHandler: Quirky/Wink Overflow battery is 100
92196554-8c84-4179-a7b7-be9c3e414cd7 12:47:00 PM: debug Parse returned [:]
92196554-8c84-4179-a7b7-be9c3e414cd7 12:47:00 PM: debug Parse returned [name:battery, value:100, descriptionText:Quirky/Wink Overflow battery was 100%, isStateChange:true, displayed:true, linkText:Quirky/Wink Overflow]
92196554-8c84-4179-a7b7-be9c3e414cd7 12:47:00 PM: debug Received battery level report
92196554-8c84-4179-a7b7-be9c3e414cd7 12:47:00 PM: debug Parse returned [:]
92196554-8c84-4179-a7b7-be9c3e414cd7 12:46:58 PM: debug Parse returned [:]
92196554-8c84-4179-a7b7-be9c3e414cd7 12:46:57 PM: debug enroll response: [raw 0x500 {01 23 00 00 00}, delay 200, send 0xB7A3 1 1]
92196554-8c84-4179-a7b7-be9c3e414cd7 12:46:57 PM: debug Sending enroll response
92196554-8c84-4179-a7b7-be9c3e414cd7 12:46:57 PM: debug Parse returned []
92196554-8c84-4179-a7b7-be9c3e414cd7 12:46:55 PM: debug Parse returned []
92196554-8c84-4179-a7b7-be9c3e414cd7 12:46:55 PM: debug Confuguring Reporting, IAS CIE, and Bindings.
92196554-8c84-4179-a7b7-be9c3e414cd7 12:46:55 PM: debug Parse returned []

Sticking this in for reference as I continue to hack this thing :slight_smile:

Thank you Kevin yeah I’ve been looking at that as well I’m using it as a guide. I’m closer lol the battery indicator now works. Just need to get the water sensor to work. Thank you all for your help with this.

Kevin I’m taking your advice and going to use that one as the device type and see where it goes. Thank you .

Ok so this is what I know so far. The sensor is “Closed/Wet” with 0x0030 and “Open/Dry” with 0x0031

So with that here is what I have thus far

/**
 *  Quirky Overflow Sensor
 *  Created by SmartThings for the smartsense moisture detector.  I just hacked this to work with the Quirky Overflow sensor
 *
 *  Copyright 2014 SmartThings
 *
 *  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.
 *  Modified by TheFuzz4 for Quirky Overflow
 *
 */
metadata {
	definition (name: "Quirky Overflow",namespace: "Quirky", author: "Jason Hamilton") {
		capability "Configuration"
		capability "Battery"
		capability "Refresh"
		capability "Water Sensor"
        
        command "enrollResponse"
 
 
		fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0003,0019", manufacturer: "Quirky",  model: "Overflow"
	}
 
	simulator {
 
	}


	tiles {
		standardTile("water", "device.water", width: 2, height: 2) {
			state "dry", icon:"st.alarm.water.dry", backgroundColor:"#ffffff"
			state "wet", icon:"st.alarm.water.wet", backgroundColor:"#53a7c0"
		}
        
		valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false) {
			state "battery", label:'${currentValue}% battery'
		}
        
        standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat") {
			state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
		}
 
		main "water"
		details(["water","battery", "refresh"])
	}
}
 
def parse(String description) {
	log.debug "description: $description"

	Map map = [:]
	if (description?.startsWith('catchall:')) {
		map = parseCatchAllMessage(description)
	}
	else if (description?.startsWith('read attr -')) {
		map = parseReportAttributeMessage(description)
	}
    else 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 parseCatchAllMessage(String description) {
    Map resultMap = [:]
    def cluster = zigbee.parse(description)
    if (shouldProcessMessage(cluster)) {
        switch(cluster.clusterId) {
            case 0x0001:
            	resultMap = getBatteryResult(cluster.data.last())
                break

            case 0x0402:
                // temp is last 2 data values. reverse to swap endian
                String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
                def value = getTemperature(temp)
                resultMap = getTemperatureResult(value)
                break
        }
    }

    return resultMap
}

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 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"
 
	Map resultMap = [:]
	if (descMap.cluster == "0402" && descMap.attrId == "0000") {
		def value = getTemperature(descMap.value)
		resultMap = getTemperatureResult(value)
	}
	else if (descMap.cluster == "0001" && descMap.attrId == "0020") {
		resultMap = getBatteryResult(Integer.parseInt(descMap.value, 16))
	}
 
	return resultMap
}
 
private Map parseCustomMessage(String description) {
	Map resultMap = [:]
	if (description?.startsWith('temperature: ')) {
		def value = zigbee.parseHATemperatureValue(description, "temperature: ", getTemperatureScale())
		resultMap = getTemperatureResult(value)
	}
	return resultMap
}

private Map parseIasMessage(String description) {
    List parsedMsg = description.split(' ')
    String msgCode = parsedMsg[2]
    
    Map resultMap = [:]
    switch(msgCode) {
        case '0x0030': // Closed/No Motion/Wet
        	resultMap = getMoistureResult('wet')
            break

        case '0x0031': // Open/Motion/Dry
        	resultMap = getMoistureResult('dry')
            break

        case '0x0023': // Battery Alarm
            break

        case '0x0024': // Supervision Report
        	 log.debug 'dry with tamper alarm'
        	resultMap = getMoistureResult('dry')
            break

        case '0x0025': // Restore Report
        	log.debug 'water with tamper alarm'
        	resultMap = getMoistureResult('wet')
            break

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

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

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

private Map getBatteryResult(rawValue) {
	log.debug 'Battery'
	def linkText = getLinkText(device)
    
    def result = [
    	name: 'battery'
    ]
    
	def volts = rawValue / 10
	def descriptionText
	if (volts > 3.5) {
		result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
	}
	else {
		def minVolts = 2.1
    	def maxVolts = 3.0
		def pct = (volts - minVolts) / (maxVolts - minVolts)
		result.value = Math.min(100, (int) pct * 100)
		result.descriptionText = "${linkText} battery was ${result.value}%"
	}

	return result
}

private Map getMoistureResult(value) {
	log.debug 'water'
	String descriptionText = "${device.displayName} is ${value}"
	return [
		name: 'water',
		value: value,
		descriptionText: descriptionText
	]
}

def refresh()
{
	log.debug "Refreshing Battery"
	[
		

        "st rattr 0x${device.deviceNetworkId} 1 1 0x20"

	]
}

def configure() {

	String zigbeeId = swapEndianHex(device.hub.zigbeeId)
	log.debug "Confuguring 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 300 0600 {01}", "delay 200",
        "send 0x${device.deviceNetworkId} 1 1", "delay 1500",
        
        "zcl global send-me-a-report 0x402 0 0x29 300 3600 {6400}", "delay 200",
        "send 0x${device.deviceNetworkId} 1 1", "delay 1500",
        
        
		"zdo bind 0x${device.deviceNetworkId} 1 1 0x402 {${device.zigbeeId}} {}", "delay 500",
		"zdo bind 0x${device.deviceNetworkId} 1 1 0x001 {${device.zigbeeId}} {}", "delay 1000",
        
        "raw 0x500 {01 23 00 00 00}", "delay 200",
        "send 0x${device.deviceNetworkId} 1 1", "delay 1000",
	]
    return configCmds + refresh() // 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"
        
    ]
}

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
}

Try this. It tested fine in the simulator at least

/*
Quirky Moisture Sensor

 */
metadata {
	definition (name: "Quirky Moisture Sensor",namespace: "tierneykev", author: "Kevin Tierney") {
		capability "Configuration"
		capability "Battery"
		capability "Refresh"
		capability "Water Sensor"

        command "enrollResponse"


		fingerprint profileId: "0104", deviceId: "0402", inClusters: "0000,0001,0003,0500,0020,0B05", outClusters: "0003,0019"	}

	simulator {

	}

	preferences {
	}

	tiles {
		standardTile("water", "device.water", width: 2, height: 2) {
			state "dry", icon:"st.alarm.water.dry", backgroundColor:"#ffffff"
			state "wet", icon:"st.alarm.water.wet", backgroundColor:"#53a7c0"
		}



		valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false) {
			state "battery", label:'${currentValue}% battery'
		}

        standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat") {
			state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
		}

		main "water"
		details(["water", "battery", "refresh"])
	}
}

def parse(String description) {
	log.debug "description: $description"

	Map map = [:]
	if (description?.startsWith('catchall:')) {
		map = parseCatchAllMessage(description)
	}
	else if (description?.startsWith('read attr -')) {
		map = parseReportAttributeMessage(description)
	}
    else 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 parseCatchAllMessage(String description) {
    Map resultMap = [:]
    def cluster = zigbee.parse(description)
    if (shouldProcessMessage(cluster)) {
        switch(cluster.clusterId) {
            case 0x0001:
            	resultMap = getBatteryResult(cluster.data.last())
                break

        }
    }

    return resultMap
}

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

	Map resultMap = [:]
	if (descMap.cluster == "0001" && descMap.attrId == "0020") {
		resultMap = getBatteryResult(Integer.parseInt(descMap.value, 16))
	}

	return resultMap
}



private Map parseIasMessage(String description) {
    List parsedMsg = description.split(' ')
    String msgCode = parsedMsg[2]

    Map resultMap = [:]
    switch(msgCode) {
        case '0x0020': // Closed/No Motion/Dry
        	resultMap = getMoistureResult('dry')
            break

        case '0x0021': // Open/Motion/Wet
        	resultMap = getMoistureResult('wet')
            break

        case '0x0022': // Tamper Alarm
            break

        case '0x0023': // Battery Alarm
            break

        case '0x0024': // Supervision Report
        	 log.debug 'dry with tamper alarm'
        	resultMap = getMoistureResult('dry')
            break

        case '0x0025': // Restore Report
        	log.debug 'water with tamper alarm'
        	resultMap = getMoistureResult('wet')
            break

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

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


private Map getBatteryResult(rawValue) {
	log.debug 'Battery'
	def linkText = getLinkText(device)

    def result = [
    	name: 'battery'
    ]

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

	return result
}


private Map getMoistureResult(value) {
	log.debug 'water'
	String descriptionText = "${device.displayName} is ${value}"
	return [
		name: 'water',
		value: value,
		descriptionText: descriptionText
	]
}

def refresh()
{
	log.debug "Refreshing Battery"
	[

		"st rattr 0x${device.deviceNetworkId} 1 1 0x20"

	]
}

def configure() {

	String zigbeeId = swapEndianHex(device.hub.zigbeeId)
	log.debug "Confuguring 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 300 0600 {01}", "delay 200",
        "send 0x${device.deviceNetworkId} 1 1", "delay 1500",

		"zdo bind 0x${device.deviceNetworkId} 1 1 0x001 {${device.zigbeeId}} {}", "delay 1000",

        "raw 0x500 {01 23 00 00 00}", "delay 200",
        "send 0x${device.deviceNetworkId} 1 1", "delay 1000",
	]
    return configCmds + refresh() // 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"

    ]
}

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
}
1 Like

Waiting for the platform push to come out so I can be functional again. Talked with support today supposed to be pushed in the next few hours.

But I did put your code in place in the IDE and I intend to put it to work just as soon as I become functional again.

Oh and I apologize for the delay in my response. ST has been broken for me since Thursday. Then I left Thursday night to go camping and well I was disconnected just as I should be while out in the mountains.

That code works great and my sensors now work with the hub. Thank you for your help it is greatly appreciated.

2 Likes

Glad to hear it worked out for you.

I tried to use your code, but for some reason the device will only report on its battery life and not on water detection. Any ideas?

I had to make a couple changes to it. Try this code

/**
 *  Quirky Overflow Sensor
 *  Created by SmartThings for the smartsense moisture detector.  I just hacked this to work with the Quirky Overflow sensor
 *
 *  Copyright 2014 SmartThings
 *
 *  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.
 *  Modified by TheFuzz4 for Quirky Overflow
 *
 */
metadata {
	definition (name: "Quirky Overflow",namespace: "Quirky", author: "Jason Hamilton") {
		capability "Configuration"
		capability "Battery"
		capability "Refresh"
		capability "Water Sensor"
        command "enrollResponse"
		fingerprint profileId: "0104", deviceId: "0402", inClusters: "0000,0001,0003,0500,0020,0B05", outClusters: "0003,0019", manufacturer: "Quirky",  model: "Overflow"
	}
	simulator {
	}
    tiles {
	standardTile("water", "device.water", width: 2, height: 2) {
		state "dry", icon:"st.alarm.water.dry", backgroundColor:"#ffffff"
		state "wet", icon:"st.alarm.water.wet", backgroundColor:"#53a7c0"
	}
    
	valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false) {
		state "battery", label:'${currentValue}% battery'
	}
    
    standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat") {
		state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
	}
		main "water"
		details(["water","battery", "refresh"])
	}
}
def parse(String description) {
	log.debug "description: $description"
    Map map = [:]
if (description?.startsWith('catchall:')) {
	map = parseCatchAllMessage(description)
}
else if (description?.startsWith('read attr -')) {
	map = parseReportAttributeMessage(description)
}
else 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 parseCatchAllMessage(String description) {
    Map resultMap = [:]
    def cluster = zigbee.parse(description)
    if (shouldProcessMessage(cluster)) {
        switch(cluster.clusterId) {
            case 0x0001:
            	resultMap = getBatteryResult(cluster.data.last())
                break
    }
}

return resultMap
}
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 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"
	Map resultMap = [:]
	if (descMap.cluster == "0402" && descMap.attrId == "0000") {
		def value = getTemperature(descMap.value)
		resultMap = getTemperatureResult(value)
	}
	else if (descMap.cluster == "0001" && descMap.attrId == "0020") {
		resultMap = getBatteryResult(Integer.parseInt(descMap.value, 16))
	}
	return resultMap
}
private Map parseCustomMessage(String description) {
	Map resultMap = [:]
	if (description?.startsWith('temperature: ')) {
		def value = zigbee.parseHATemperatureValue(description, "temperature: ", getTemperatureScale())
		resultMap = getTemperatureResult(value)
	}
	return resultMap
}
private Map parseIasMessage(String description) {
    List parsedMsg = description.split(' ')
    String msgCode = parsedMsg[2]
    Map resultMap = [:]
    switch(msgCode) {
        case '0x0031': // Closed/No Motion/Wet
        	resultMap = getMoistureResult('wet')
            break
        case '0x0030': // Open/Motion/Dry
    	resultMap = getMoistureResult('dry')
        break

    case '0x0023': // Battery Alarm
        break

}
return resultMap
}

private Map getBatteryResult(rawValue) {
	log.debug 'Battery'
	def linkText = getLinkText(device)
    def result = [
    	name: 'battery'
    ]
	def volts = rawValue / 10
	def descriptionText
	if (volts > 3.5) {
		result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
	}
	else {
		def minVolts = 2.1
    	def maxVolts = 3.0
		def pct = (volts - minVolts) / (maxVolts - minVolts)
		result.value = Math.min(100, (int) pct * 100)
		result.descriptionText = "${linkText} battery was ${result.value}%"
	}
    return result
}
private Map getMoistureResult(value) {
	log.debug 'water'
	String descriptionText = "${device.displayName} is ${value}"
	return [
		name: 'water',
		value: value,
		descriptionText: descriptionText
	]
}
def refresh()
{
	log.debug "Refreshing Battery"
	[
        "st rattr 0x${device.deviceNetworkId} 1 1 0x20"

]
}
def configure() {
    String zigbeeId = swapEndianHex(device.hub.zigbeeId)
log.debug "Confuguring 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 300 0600 {01}", "delay 200",
    "send 0x${device.deviceNetworkId} 1 1", "delay 1500",
    
    "zcl global send-me-a-report 0x402 0 0x29 300 3600 {6400}", "delay 200",
    "send 0x${device.deviceNetworkId} 1 1", "delay 1500",
    
    
	"zdo bind 0x${device.deviceNetworkId} 1 1 0x402 {${device.zigbeeId}} {}", "delay 500",
	"zdo bind 0x${device.deviceNetworkId} 1 1 0x001 {${device.zigbeeId}} {}", "delay 1000",
    
    "raw 0x500 {01 23 00 00 00}", "delay 200",
    "send 0x${device.deviceNetworkId} 1 1", "delay 1000",
]
return configCmds + refresh() // 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"
    ]
}
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
}
1 Like