From d9ab3bca00f2fe80036dab02708cb2e7bc583f5c Mon Sep 17 00:00:00 2001 From: Tom Manley Date: Thu, 14 Jan 2016 15:34:08 -0600 Subject: [PATCH 1/3] arrival: Keep 'read attr' messages from being turned into events Previously parse was returning null which causes the platform to create an event using the message passed to parse. We don't want that to happen so return an empty list instead. Resolves: https://smartthings.atlassian.net/browse/SMJN-38 --- .../smartthings/arrival-sensor-ha.src/arrival-sensor-ha.groovy | 2 ++ 1 file changed, 2 insertions(+) diff --git a/devicetypes/smartthings/arrival-sensor-ha.src/arrival-sensor-ha.groovy b/devicetypes/smartthings/arrival-sensor-ha.src/arrival-sensor-ha.groovy index d9cdcc2..62519f2 100644 --- a/devicetypes/smartthings/arrival-sensor-ha.src/arrival-sensor-ha.groovy +++ b/devicetypes/smartthings/arrival-sensor-ha.src/arrival-sensor-ha.groovy @@ -76,6 +76,8 @@ def parse(String description) { if (description?.startsWith('read attr -')) { handleReportAttributeMessage(description) } + + return [] } private handleReportAttributeMessage(String description) { From e51a38eb28c1c445f6222d0c4a1d5a6d4e71d02d Mon Sep 17 00:00:00 2001 From: Tom Manley Date: Mon, 18 Jan 2016 10:42:42 -0600 Subject: [PATCH 2/3] Fix whitespace issues - no code changes Replaced spaces with tabs for indentation and removed some unnecessary white space. --- .../smartsense-moisture-sensor.groovy | 200 +++++----- .../smartsense-motion-sensor.groovy | 192 +++++----- .../smartsense-multi-sensor.groovy | 361 +++++++++--------- 3 files changed, 371 insertions(+), 382 deletions(-) diff --git a/devicetypes/smartthings/smartsense-moisture-sensor.src/smartsense-moisture-sensor.groovy b/devicetypes/smartthings/smartsense-moisture-sensor.src/smartsense-moisture-sensor.groovy index 0d04bce..64192df 100644 --- a/devicetypes/smartthings/smartsense-moisture-sensor.src/smartsense-moisture-sensor.groovy +++ b/devicetypes/smartthings/smartsense-moisture-sensor.src/smartsense-moisture-sensor.groovy @@ -20,8 +20,8 @@ metadata { capability "Refresh" capability "Temperature Measurement" capability "Water Sensor" - - command "enrollResponse" + + command "enrollResponse" fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3315-S", deviceJoinName: "Water Leak Sensor" @@ -29,9 +29,9 @@ metadata { fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3315-Seu", deviceJoinName: "Water Leak Sensor" fingerprint inClusters: "0000,0001,0003,000F,0020,0402,0500", outClusters: "0019", manufacturer: "SmartThings", model: "moisturev4", deviceJoinName: "Water Leak Sensor" } - + simulator { - + } preferences { @@ -47,7 +47,7 @@ metadata { input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false } } - + tiles(scale: 2) { multiAttributeTile(name:"water", type: "generic", width: 6, height: 4){ tileAttribute ("device.water", key: "PRIMARY_CONTROL") { @@ -78,7 +78,7 @@ metadata { details(["water", "temperature", "battery", "refresh"]) } } - + def parse(String description) { log.debug "description: $description" @@ -92,59 +92,59 @@ def parse(String description) { else if (description?.startsWith('temperature: ')) { map = parseCustomMessage(description) } - else if (description?.startsWith('zone status')) { - map = parseIasMessage(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 + + 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 + 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 - } - } + 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 + 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 + // 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) @@ -153,10 +153,10 @@ private Map parseReportAttributeMessage(String description) { 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: ')) { @@ -167,42 +167,42 @@ private Map parseCustomMessage(String description) { } 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 + List parsedMsg = description.split(' ') + String msgCode = parsedMsg[2] - case '0x0021': // Open/Motion/Wet - resultMap = getMoistureResult('wet') - break + Map resultMap = [:] + switch(msgCode) { + case '0x0020': // Closed/No Motion/Dry + resultMap = getMoistureResult('dry') + break - case '0x0022': // Tamper Alarm - break + case '0x0021': // Open/Motion/Wet + resultMap = getMoistureResult('wet') + break - case '0x0023': // Battery Alarm - break + case '0x0022': // Tamper Alarm + break - case '0x0024': // Supervision Report - log.debug 'dry with tamper alarm' - resultMap = getMoistureResult('dry') - break + case '0x0023': // Battery Alarm + break - case '0x0025': // Restore Report - log.debug 'water with tamper alarm' - resultMap = getMoistureResult('wet') - break + case '0x0024': // Supervision Report + log.debug 'dry with tamper alarm' + resultMap = getMoistureResult('dry') + break - case '0x0026': // Trouble/Failure - break + case '0x0025': // Restore Report + log.debug 'water with tamper alarm' + resultMap = getMoistureResult('wet') + break - case '0x0028': // Test Mode - break - } - return resultMap + case '0x0026': // Trouble/Failure + break + + case '0x0028': // Test Mode + break + } + return resultMap } def getTemperature(value) { @@ -217,11 +217,11 @@ def getTemperature(value) { private Map getBatteryResult(rawValue) { log.debug 'Battery' def linkText = getLinkText(device) - - def result = [ - name: 'battery' - ] - + + def result = [ + name: 'battery' + ] + def volts = rawValue / 10 def descriptionText if (volts > 3.5) { @@ -229,7 +229,7 @@ private Map getBatteryResult(rawValue) { } else { def minVolts = 2.1 - def maxVolts = 3.0 + 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}%" @@ -267,7 +267,7 @@ private Map getMoistureResult(value) { def refresh() { log.debug "Refreshing Temperature and Battery" def refreshCmds = [ - "st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200", + "st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200", "st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 200" ] @@ -277,32 +277,32 @@ def refresh() { def configure() { String zigbeeEui = swapEndianHex(device.hub.zigbeeEui) log.debug "Configuring Reporting, IAS CIE, and Bindings." - def configCmds = [ + def configCmds = [ "zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200", "send 0x${device.deviceNetworkId} 1 1", "delay 500", "zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 500", "zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", //checkin time 6 hrs - "send 0x${device.deviceNetworkId} 1 1", "delay 500", + "send 0x${device.deviceNetworkId} 1 1", "delay 500", "zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x402 {${device.zigbeeId}} {}", "delay 500", - "zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}", - "send 0x${device.deviceNetworkId} 1 1", "delay 500" + "zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}", + "send 0x${device.deviceNetworkId} 1 1", "delay 500" ] - return configCmds + refresh() // send refresh cmds as part of config + return configCmds + refresh() // send refresh cmds as part of config } def enrollResponse() { log.debug "Sending enroll response" String zigbeeEui = swapEndianHex(device.hub.zigbeeEui) - [ + [ //Resending the CIE in case the enroll request is sent before CIE is written "zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200", "send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500", //Enroll Response "raw 0x500 {01 23 00 00 00}", "send 0x${device.deviceNetworkId} 1 1", "delay 200" - ] + ] } private getEndpointId() { @@ -314,19 +314,19 @@ private hex(value) { } private String swapEndianHex(String hex) { - reverseArray(hex.decodeHex()).encodeHex() + 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 + 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 } diff --git a/devicetypes/smartthings/smartsense-motion-sensor.src/smartsense-motion-sensor.groovy b/devicetypes/smartthings/smartsense-motion-sensor.src/smartsense-motion-sensor.groovy index 0012cf7..6e0cc93 100644 --- a/devicetypes/smartthings/smartsense-motion-sensor.src/smartsense-motion-sensor.groovy +++ b/devicetypes/smartthings/smartsense-motion-sensor.src/smartsense-motion-sensor.groovy @@ -19,17 +19,17 @@ metadata { capability "Motion Sensor" capability "Configuration" capability "Battery" - capability "Temperature Measurement" + capability "Temperature Measurement" capability "Refresh" - - command "enrollResponse" + + command "enrollResponse" fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3305-S" - fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3325-S", deviceJoinName: "Motion Sensor" - fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3305" - fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3325" - fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3326" - fingerprint inClusters: "0000,0001,0003,000F,0020,0402,0500", outClusters: "0019", manufacturer: "SmartThings", model: "motionv4", deviceJoinName: "Motion Sensor" + fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3325-S", deviceJoinName: "Motion Sensor" + fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3305" + fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3325" + fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3326" + fingerprint inClusters: "0000,0001,0003,000F,0020,0402,0500", outClusters: "0019", manufacturer: "SmartThings", model: "motionv4", deviceJoinName: "Motion Sensor" } simulator { @@ -85,7 +85,7 @@ metadata { def parse(String description) { log.debug "description: $description" - + Map map = [:] if (description?.startsWith('catchall:')) { map = parseCatchAllMessage(description) @@ -96,55 +96,55 @@ def parse(String description) { else if (description?.startsWith('temperature: ')) { map = parseCustomMessage(description) } - else if (description?.startsWith('zone status')) { - map = parseIasMessage(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 + + 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 + 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 + 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 case 0x0406: - log.debug 'motion' - resultMap.name = 'motion' - break - } - } + log.debug 'motion' + resultMap.name = 'motion' + break + } + } - return resultMap + 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 + // 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) { @@ -153,7 +153,7 @@ private Map parseReportAttributeMessage(String description) { 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) @@ -162,14 +162,14 @@ private Map parseReportAttributeMessage(String description) { else if (descMap.cluster == "0001" && descMap.attrId == "0020") { resultMap = getBatteryResult(Integer.parseInt(descMap.value, 16)) } - else if (descMap.cluster == "0406" && descMap.attrId == "0000") { - def value = descMap.value.endsWith("01") ? "active" : "inactive" - resultMap = getMotionResult(value) - } - + else if (descMap.cluster == "0406" && descMap.attrId == "0000") { + def value = descMap.value.endsWith("01") ? "active" : "inactive" + resultMap = getMotionResult(value) + } + return resultMap } - + private Map parseCustomMessage(String description) { Map resultMap = [:] if (description?.startsWith('temperature: ')) { @@ -180,44 +180,44 @@ private Map parseCustomMessage(String description) { } private Map parseIasMessage(String description) { - List parsedMsg = description.split(' ') - String msgCode = parsedMsg[2] - - Map resultMap = [:] - switch(msgCode) { - case '0x0020': // Closed/No Motion/Dry - resultMap = getMotionResult('inactive') - break + List parsedMsg = description.split(' ') + String msgCode = parsedMsg[2] - case '0x0021': // Open/Motion/Wet - resultMap = getMotionResult('active') - break + Map resultMap = [:] + switch(msgCode) { + case '0x0020': // Closed/No Motion/Dry + resultMap = getMotionResult('inactive') + break - case '0x0022': // Tamper Alarm - log.debug 'motion with tamper alarm' - resultMap = getMotionResult('active') - break + case '0x0021': // Open/Motion/Wet + resultMap = getMotionResult('active') + break - case '0x0023': // Battery Alarm - break + case '0x0022': // Tamper Alarm + log.debug 'motion with tamper alarm' + resultMap = getMotionResult('active') + break - case '0x0024': // Supervision Report - log.debug 'no motion with tamper alarm' - resultMap = getMotionResult('inactive') - break + case '0x0023': // Battery Alarm + break - case '0x0025': // Restore Report - break + case '0x0024': // Supervision Report + log.debug 'no motion with tamper alarm' + resultMap = getMotionResult('inactive') + break - case '0x0026': // Trouble/Failure - log.debug 'motion with failure alarm' - resultMap = getMotionResult('active') - break + case '0x0025': // Restore Report + break - case '0x0028': // Test Mode - break - } - return resultMap + case '0x0026': // Trouble/Failure + log.debug 'motion with failure alarm' + resultMap = getMotionResult('active') + break + + case '0x0028': // Test Mode + break + } + return resultMap } def getTemperature(value) { @@ -338,19 +338,19 @@ private hex(value) { } private String swapEndianHex(String hex) { - reverseArray(hex.decodeHex()).encodeHex() + 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 + 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 } diff --git a/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy b/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy index b950034..ab66857 100644 --- a/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy +++ b/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy @@ -14,28 +14,28 @@ * */ - metadata { - definition (name: "SmartSense Multi Sensor", namespace: "smartthings", author: "SmartThings") { - - capability "Three Axis" +metadata { + definition (name: "SmartSense Multi Sensor", namespace: "smartthings", author: "SmartThings") { + + capability "Three Axis" capability "Battery" - capability "Configuration" - capability "Sensor" + capability "Configuration" + capability "Sensor" capability "Contact Sensor" capability "Acceleration Sensor" capability "Refresh" capability "Temperature Measurement" - + command "enrollResponse" fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05,FC02", outClusters: "0019", manufacturer: "CentraLite", model: "3320" fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05,FC02", outClusters: "0019", manufacturer: "CentraLite", model: "3321" - fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05,FC02", outClusters: "0019", manufacturer: "CentraLite", model: "3321-S", deviceJoinName: "Multipurpose Sensor" - fingerprint inClusters: "0000,0001,0003,000F,0020,0402,0500,FC02", outClusters: "0019", manufacturer: "SmartThings", model: "multiv4", deviceJoinName: "Multipurpose Sensor" + fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05,FC02", outClusters: "0019", manufacturer: "CentraLite", model: "3321-S", deviceJoinName: "Multipurpose Sensor" + fingerprint inClusters: "0000,0001,0003,000F,0020,0402,0500,FC02", outClusters: "0019", manufacturer: "SmartThings", model: "multiv4", deviceJoinName: "Multipurpose Sensor" attribute "status", "string" - } + } - simulator { + simulator { status "open": "zone report :: type: 19 value: 0031" status "closed": "zone report :: type: 19 value: 0030" @@ -52,7 +52,7 @@ status "x,y,z: 0,1000,0": "x: 0, y: 1000, z: 0" status "x,y,z: 0,0,1000": "x: 0, y: 0, z: 1000" } - preferences { + preferences { section { image(name: 'educationalcontent', multiple: true, images: [ "http://cdn.device-gse.smartthings.com/Multi/Multi1.jpg", @@ -62,13 +62,13 @@ ]) } section { - input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph" - input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false - } - section { - input("garageSensor", "enum", title: "Do you want to use this sensor on a garage door?", options: ["Yes", "No"], defaultValue: "No", required: false, displayDuringSetup: false) + input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph" + input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false } - } + section { + input("garageSensor", "enum", title: "Do you want to use this sensor on a garage door?", options: ["Yes", "No"], defaultValue: "No", required: false, displayDuringSetup: false) + } + } tiles(scale: 2) { multiAttributeTile(name:"status", type: "generic", width: 6, height: 4){ @@ -106,9 +106,9 @@ valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, 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("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { + state "default", action:"refresh.refresh", icon:"st.secondary.refresh" + } main(["status", "acceleration", "temperature"]) @@ -121,61 +121,61 @@ def parse(String description) { if (description?.startsWith('catchall:')) { map = parseCatchAllMessage(description) } - else if (description?.startsWith('temperature: ')) { + else if (description?.startsWith('temperature: ')) { map = parseCustomMessage(description) } else if (description?.startsWith('zone status')) { map = parseIasMessage(description) } - def result = map ? createEvent(map) : null + 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) } } - else if (description?.startsWith('read attr -')) { - result = parseReportAttributeMessage(description).each { createEvent(it) } - } + else if (description?.startsWith('read attr -')) { + result = parseReportAttributeMessage(description).each { createEvent(it) } + } return result } - private Map parseCatchAllMessage(String description) { - Map resultMap = [:] - def cluster = zigbee.parse(description) - log.debug cluster - if (shouldProcessMessage(cluster)) { - switch(cluster.clusterId) { - case 0x0001: - resultMap = getBatteryResult(cluster.data.last()) - break +private Map parseCatchAllMessage(String description) { + Map resultMap = [:] + def cluster = zigbee.parse(description) + log.debug cluster + if (shouldProcessMessage(cluster)) { + switch(cluster.clusterId) { + case 0x0001: + resultMap = getBatteryResult(cluster.data.last()) + break - case 0xFC02: - log.debug 'ACCELERATION' - break + case 0xFC02: + log.debug 'ACCELERATION' + break - case 0x0402: - log.debug 'TEMP' - // 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 - } - } + case 0x0402: + log.debug 'TEMP' + // 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 - } + 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 + // 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 List parseReportAttributeMessage(String description) { @@ -202,7 +202,7 @@ private List parseReportAttributeMessage(String description) { result << parseAxis(threeAxisAttributes) descMap.value = descMap.value[-2..-1] } - result << getAccelerationResult(descMap.value) + result << getAccelerationResult(descMap.value) } else if (descMap.cluster == "FC02" && descMap.attrId == "0012" && descMap.value.size() == 24) { // The size is checked to ensure the attribute report contains X, Y and Z values @@ -231,43 +231,43 @@ private Map parseIasMessage(String description) { Map resultMap = [:] switch(msgCode) { - case '0x0020': // Closed/No Motion/Dry + case '0x0020': // Closed/No Motion/Dry if (garageSensor != "Yes"){ resultMap = getContactResult('closed') } - break + break - case '0x0021': // Open/Motion/Wet + case '0x0021': // Open/Motion/Wet if (garageSensor != "Yes"){ resultMap = getContactResult('open') } - break + break - case '0x0022': // Tamper Alarm - break + case '0x0022': // Tamper Alarm + break - case '0x0023': // Battery Alarm - break + case '0x0023': // Battery Alarm + break - case '0x0024': // Supervision Report + case '0x0024': // Supervision Report if (garageSensor != "Yes"){ resultMap = getContactResult('closed') } - break + break - case '0x0025': // Restore Report + case '0x0025': // Restore Report if (garageSensor != "Yes"){ resultMap = getContactResult('open') } - break + break - case '0x0026': // Trouble/Failure - break + case '0x0026': // Trouble/Failure + break - case '0x0028': // Test Mode - break - } - return resultMap + case '0x0028': // Test Mode + break + } + return resultMap } def updated() { @@ -302,23 +302,22 @@ def getTemperature(value) { } } - private Map getBatteryResult(rawValue) { - log.debug "Battery" - log.debug rawValue - def linkText = getLinkText(device) +private Map getBatteryResult(rawValue) { + log.debug "Battery" + log.debug rawValue + def linkText = getLinkText(device) - def result = [ + def result = [ name: 'battery', - value: '--' - ] + value: '--' + ] - def volts = rawValue / 10 - def descriptionText - - if (rawValue == 255) {} - else { - - if (volts > 3.5) { + def volts = rawValue / 10 + def descriptionText + + if (rawValue == 255) {} + else { + if (volts > 3.5) { result.descriptionText = "${linkText} battery has too much power (${volts} volts)." } else { @@ -327,107 +326,100 @@ def getTemperature(value) { 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 getTemperatureResult(value) { - log.debug "Temperature" - 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', + } + + return result +} + +private Map getTemperatureResult(value) { + log.debug "Temperature" + 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 + ] +} + +private Map getContactResult(value) { + log.debug "Contact" + def linkText = getLinkText(device) + def descriptionText = "${linkText} was ${value == 'open' ? 'opened' : 'closed'}" + sendEvent(name: 'contact', value: value, descriptionText: descriptionText, displayed:false) + sendEvent(name: 'status', value: value, descriptionText: descriptionText) +} + +private getAccelerationResult(numValue) { + log.debug "Acceleration" + def name = "acceleration" + def value = numValue.endsWith("1") ? "active" : "inactive" + def linkText = getLinkText(device) + def descriptionText = "$linkText was $value" + def isStateChange = isStateChange(device, name, value) + [ + name: name, value: value, - descriptionText: descriptionText + descriptionText: descriptionText, + isStateChange: isStateChange + ] +} + +def refresh() { + log.debug "Refreshing Values " + + def refreshCmds = [] + + if (device.getDataValue("manufacturer") == "SmartThings") { + log.debug "Refreshing Values for manufacturer: SmartThings " + refreshCmds = refreshCmds + [ + /* These values of Motion Threshold Multiplier(01) and Motion Threshold (7602) + seem to be giving pretty accurate results for the XYZ co-ordinates for this manufacturer. + Separating these out in a separate if-else because I do not want to touch Centralite part + as of now. + */ + + "zcl mfg-code ${manufacturerCode}", "delay 200", + "zcl global write 0xFC02 0 0x20 {01}", "delay 200", + "send 0x${device.deviceNetworkId} 1 1", "delay 400", + + "zcl mfg-code ${manufacturerCode}", "delay 200", + "zcl global write 0xFC02 2 0x21 {7602}", "delay 200", + "send 0x${device.deviceNetworkId} 1 1", "delay 400", + ] + } else { + refreshCmds = refreshCmds + [ + /* sensitivity - default value (8) */ + "zcl mfg-code ${manufacturerCode}", "delay 200", + "zcl global write 0xFC02 0 0x20 {02}", "delay 200", + "send 0x${device.deviceNetworkId} 1 1", "delay 400", ] } - private Map getContactResult(value) { - log.debug "Contact" - def linkText = getLinkText(device) - def descriptionText = "${linkText} was ${value == 'open' ? 'opened' : 'closed'}" - sendEvent(name: 'contact', value: value, descriptionText: descriptionText, displayed:false) - sendEvent(name: 'status', value: value, descriptionText: descriptionText) - } + //Common refresh commands + refreshCmds = refreshCmds + [ + "st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200", + "st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 200", - private getAccelerationResult(numValue) { - log.debug "Acceleration" - def name = "acceleration" - def value = numValue.endsWith("1") ? "active" : "inactive" - def linkText = getLinkText(device) - def descriptionText = "$linkText was $value" - def isStateChange = isStateChange(device, name, value) - [ - name: name, - value: value, - descriptionText: descriptionText, - isStateChange: isStateChange - ] - } + "zcl mfg-code ${manufacturerCode}", "delay 200", + "zcl global read 0xFC02 0x0010", + "send 0x${device.deviceNetworkId} 1 1","delay 400" + ] - def refresh() { - log.debug "Refreshing Values " - - def refreshCmds = [] - - if (device.getDataValue("manufacturer") == "SmartThings") { - - log.debug "Refreshing Values for manufacturer: SmartThings " - refreshCmds = refreshCmds + [ + return refreshCmds + enrollResponse() +} - /* These values of Motion Threshold Multiplier(01) and Motion Threshold (7602) - seem to be giving pretty accurate results for the XYZ co-ordinates for this manufacturer. - Separating these out in a separate if-else because I do not want to touch Centralite part - as of now. - */ - - "zcl mfg-code ${manufacturerCode}", "delay 200", - "zcl global write 0xFC02 0 0x20 {01}", "delay 200", - "send 0x${device.deviceNetworkId} 1 1", "delay 400", - - "zcl mfg-code ${manufacturerCode}", "delay 200", - "zcl global write 0xFC02 2 0x21 {7602}", "delay 200", - "send 0x${device.deviceNetworkId} 1 1", "delay 400", - - ] - - - } else { - refreshCmds = refreshCmds + [ - - /* sensitivity - default value (8) */ - "zcl mfg-code ${manufacturerCode}", "delay 200", - "zcl global write 0xFC02 0 0x20 {02}", "delay 200", - "send 0x${device.deviceNetworkId} 1 1", "delay 400", - ] - } - - //Common refresh commands - refreshCmds = refreshCmds + [ - "st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200", - "st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 200", - - "zcl mfg-code ${manufacturerCode}", "delay 200", - "zcl global read 0xFC02 0x0010", - "send 0x${device.deviceNetworkId} 1 1","delay 400" - ] - - return refreshCmds + enrollResponse() - } - - def configure() { - - String zigbeeEui = swapEndianHex(device.hub.zigbeeEui) - log.debug "Configuring Reporting" - - def configCmds = [ +def configure() { + String zigbeeEui = swapEndianHex(device.hub.zigbeeEui) + log.debug "Configuring Reporting" + def configCmds = [ "zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200", "send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500", @@ -455,10 +447,9 @@ def getTemperature(value) { "zcl mfg-code ${manufacturerCode}", "zcl global send-me-a-report 0xFC02 0x0014 0x29 1 3600 {01}", "send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500" - ] - - return configCmds + refresh() + + return configCmds + refresh() } private getEndpointId() { @@ -582,5 +573,3 @@ private byte[] reverseArray(byte[] array) { } return array } - - From 968834e33ec97fdec787f9fb38bd07fa4171d495 Mon Sep 17 00:00:00 2001 From: Tom Manley Date: Mon, 18 Jan 2016 13:29:48 -0600 Subject: [PATCH 3/3] Use table for battery voltage to percent remaining calculation The new table based approach yields a more accurate battery percentage remaining than the old linear calculation. Resolves: https://smartthings.atlassian.net/browse/SMJN-39 --- .../arrival-sensor-ha.groovy | 44 ++++++++++-------- .../smartsense-moisture-sensor.groovy | 45 ++++++++++++++----- .../smartsense-motion-sensor.groovy | 38 +++++++++++----- .../smartsense-multi-sensor.groovy | 35 +++++++++++---- 4 files changed, 112 insertions(+), 50 deletions(-) diff --git a/devicetypes/smartthings/arrival-sensor-ha.src/arrival-sensor-ha.groovy b/devicetypes/smartthings/arrival-sensor-ha.src/arrival-sensor-ha.groovy index 62519f2..d2f1660 100644 --- a/devicetypes/smartthings/arrival-sensor-ha.src/arrival-sensor-ha.groovy +++ b/devicetypes/smartthings/arrival-sensor-ha.src/arrival-sensor-ha.groovy @@ -88,31 +88,37 @@ private handleReportAttributeMessage(String description) { } } -private handleBatteryEvent(rawValue) { - def linkText = getLinkText(device) - - def eventMap = [ - name: 'battery', - value: '--' - ] - - def volts = rawValue / 10 - if (volts > 0){ - def minVolts = 2.0 - def maxVolts = 2.8 +/** + * Create battery event from reported battery voltage. + * + * @param volts Battery voltage in .1V increments + */ +private handleBatteryEvent(volts) { + if (volts == 0 || volts == 255) { + log.debug "Ignoring invalid value for voltage (${volts/10}V)" + } + else { + def batteryMap = [28:100, 27:100, 26:100, 25:90, 24:90, 23:70, + 22:70, 21:50, 20:50, 19:30, 18:30, 17:15, 16:1, 15:0] + def minVolts = 15 + def maxVolts = 28 if (volts < minVolts) volts = minVolts else if (volts > maxVolts) volts = maxVolts - def pct = (volts - minVolts) / (maxVolts - minVolts) - - eventMap.value = Math.round(pct * 100) - eventMap.descriptionText = "${linkText} battery was ${eventMap.value}%" + def pct = batteryMap[volts] + if (pct != null) { + def linkText = getLinkText(device) + def eventMap = [ + name: 'battery', + value: pct, + descriptionText: "${linkText} battery was ${pct}%" + ] + log.debug "Creating battery event for voltage=${volts/10}V: ${eventMap}" + sendEvent(eventMap) + } } - - log.debug "Creating battery event: ${eventMap}" - sendEvent(eventMap) } private handlePresenceEvent(present) { diff --git a/devicetypes/smartthings/smartsense-moisture-sensor.src/smartsense-moisture-sensor.groovy b/devicetypes/smartthings/smartsense-moisture-sensor.src/smartsense-moisture-sensor.groovy index 64192df..1c38e23 100644 --- a/devicetypes/smartthings/smartsense-moisture-sensor.src/smartsense-moisture-sensor.groovy +++ b/devicetypes/smartthings/smartsense-moisture-sensor.src/smartsense-moisture-sensor.groovy @@ -215,24 +215,47 @@ def getTemperature(value) { } private Map getBatteryResult(rawValue) { - log.debug 'Battery' + log.debug "Battery rawValue = ${rawValue}" def linkText = getLinkText(device) def result = [ - name: 'battery' + name: 'battery', + value: '--' ] def volts = rawValue / 10 - def descriptionText - if (volts > 3.5) { - result.descriptionText = "${linkText} battery has too much power (${volts} volts)." - } + + if (rawValue == 0 || rawValue == 255) {} 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}%" + if (volts > 3.5) { + result.descriptionText = "${linkText} battery has too much power (${volts} volts)." + } + else { + if (device.getDataValue("manufacturer") == "SmartThings") { + volts = rawValue // For the batteryMap to work the key needs to be an int + def batteryMap = [28:100, 27:100, 26:100, 25:90, 24:90, 23:70, + 22:70, 21:50, 20:50, 19:30, 18:30, 17:15, 16:1, 15:0] + def minVolts = 15 + def maxVolts = 28 + + if (volts < minVolts) + volts = minVolts + else if (volts > maxVolts) + volts = maxVolts + def pct = batteryMap[volts] + if (pct != null) { + result.value = pct + result.descriptionText = "${linkText} battery was ${result.value}%" + } + } + 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 diff --git a/devicetypes/smartthings/smartsense-motion-sensor.src/smartsense-motion-sensor.groovy b/devicetypes/smartthings/smartsense-motion-sensor.src/smartsense-motion-sensor.groovy index 6e0cc93..10b0740 100644 --- a/devicetypes/smartthings/smartsense-motion-sensor.src/smartsense-motion-sensor.groovy +++ b/devicetypes/smartthings/smartsense-motion-sensor.src/smartsense-motion-sensor.groovy @@ -230,30 +230,46 @@ def getTemperature(value) { } private Map getBatteryResult(rawValue) { - log.debug 'Battery' + log.debug "Battery rawValue = ${rawValue}" def linkText = getLinkText(device) - log.debug rawValue - def result = [ name: 'battery', value: '--' ] def volts = rawValue / 10 - def descriptionText - if (rawValue == 0) {} + if (rawValue == 0 || rawValue == 255) {} else { if (volts > 3.5) { result.descriptionText = "${linkText} battery has too much power (${volts} volts)." } - else if (volts > 0){ - 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}%" + else { + if (device.getDataValue("manufacturer") == "SmartThings") { + volts = rawValue // For the batteryMap to work the key needs to be an int + def batteryMap = [28:100, 27:100, 26:100, 25:90, 24:90, 23:70, + 22:70, 21:50, 20:50, 19:30, 18:30, 17:15, 16:1, 15:0] + def minVolts = 15 + def maxVolts = 28 + + if (volts < minVolts) + volts = minVolts + else if (volts > maxVolts) + volts = maxVolts + def pct = batteryMap[volts] + if (pct != null) { + result.value = pct + result.descriptionText = "${linkText} battery was ${result.value}%" + } + } + 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}%" + } } } diff --git a/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy b/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy index ab66857..70cc9ee 100644 --- a/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy +++ b/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy @@ -303,8 +303,7 @@ def getTemperature(value) { } private Map getBatteryResult(rawValue) { - log.debug "Battery" - log.debug rawValue + log.debug "Battery rawValue = ${rawValue}" def linkText = getLinkText(device) def result = [ @@ -313,19 +312,37 @@ private Map getBatteryResult(rawValue) { ] def volts = rawValue / 10 - def descriptionText - if (rawValue == 255) {} + if (rawValue == 0 || rawValue == 255) {} else { 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}%" + if (device.getDataValue("manufacturer") == "SmartThings") { + volts = rawValue // For the batteryMap to work the key needs to be an int + def batteryMap = [28:100, 27:100, 26:100, 25:90, 24:90, 23:70, + 22:70, 21:50, 20:50, 19:30, 18:30, 17:15, 16:1, 15:0] + def minVolts = 15 + def maxVolts = 28 + + if (volts < minVolts) + volts = minVolts + else if (volts > maxVolts) + volts = maxVolts + def pct = batteryMap[volts] + if (pct != null) { + result.value = pct + result.descriptionText = "${linkText} battery was ${result.value}%" + } + } + 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}%" + } } }