diff --git a/devicetypes/smartthings/smartpower-dimming-outlet.src/smartpower-dimming-outlet.groovy b/devicetypes/smartthings/smartpower-dimming-outlet.src/smartpower-dimming-outlet.groovy index faaed37..80ca040 100644 --- a/devicetypes/smartthings/smartpower-dimming-outlet.src/smartpower-dimming-outlet.groovy +++ b/devicetypes/smartthings/smartpower-dimming-outlet.src/smartpower-dimming-outlet.groovy @@ -69,292 +69,35 @@ metadata { def parse(String description) { log.debug "description is $description" - def event = [:] - def finalResult = isKnownDescription(description) - if (finalResult) { - log.info finalResult - if (finalResult.type == "update") { - log.info "$device updates: ${finalResult.value}" - event = null - } - else if (finalResult.type == "power") { - def powerValue = (finalResult.value as Integer)/10 - event = createEvent(name: "power", value: powerValue) - - /* - Dividing by 10 as the Divisor is 10000 and unit is kW for the device. AttrId: 0302 and 0300. Simplifying to 10 - - power level is an integer. The exact power level with correct units needs to be handled in the device type - to account for the different Divisor value (AttrId: 0302) and POWER Unit (AttrId: 0300). CLUSTER for simple metering is 0702 - */ - } - else { - event = createEvent(name: finalResult.type, value: finalResult.value) - } - } - else { + def event = zigbee.getEvent(description) + if (!event) { log.warn "DID NOT PARSE MESSAGE for description : $description" - log.debug parseDescriptionAsMap(description) + log.debug zigbee.parseDescriptionAsMap(description) + } else if (event.name == "power") { + /* + Dividing by 10 as the Divisor is 10000 and unit is kW for the device. Simplifying to 10 power level is an integer. + */ + event.value = event.value / 10 } return event } -// Commands to device -def zigbeeCommand(cluster, attribute){ - "st cmd 0x${device.deviceNetworkId} ${endpointId} ${cluster} ${attribute} {}" +def setLevel(value) { + zigbee.setLevel(value) } def off() { - zigbeeCommand("6", "0") + zigbee.off() } def on() { - zigbeeCommand("6", "1") -} - -def setLevel(value) { - value = value as Integer - if (value == 0) { - off() - } - else { - if (device.latestValue("switch") == "off") { - sendEvent(name: "switch", value: "on") - } - sendEvent(name: "level", value: value) - setLevelWithRate(value, "0000") //value is between 0 to 100 - } + zigbee.on() } def refresh() { - [ - "st rattr 0x${device.deviceNetworkId} ${endpointId} 6 0", "delay 2000", - "st rattr 0x${device.deviceNetworkId} ${endpointId} 8 0", "delay 2000", - "st rattr 0x${device.deviceNetworkId} ${endpointId} 0x0B04 0x050B", "delay 2000" - ] - + zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.electricMeasurementPowerRefresh() } def configure() { - refresh() + onOffConfig() + levelConfig() + powerConfig() -} - - -private getEndpointId() { - new BigInteger(device.endpointId, 16).toString() -} - -private hex(value, width=2) { - def s = new BigInteger(Math.round(value).toString()).toString(16) - while (s.size() < width) { - s = "0" + s - } - s -} - -private String swapEndianHex(String hex) { - reverseArray(hex.decodeHex()).encodeHex() -} - -private Integer convertHexToInt(hex) { - Integer.parseInt(hex,16) -} - -//Need to reverse array of size 2 -private byte[] reverseArray(byte[] array) { - byte tmp; - tmp = array[1]; - array[1] = array[0]; - array[0] = tmp; - return array -} - -def parseDescriptionAsMap(description) { - if (description?.startsWith("read attr -")) { - (description - "read attr - ").split(",").inject([:]) { map, param -> - def nameAndValue = param.split(":") - map += [(nameAndValue[0].trim()): nameAndValue[1].trim()] - } - } - else if (description?.startsWith("catchall: ")) { - def seg = (description - "catchall: ").split(" ") - def zigbeeMap = [:] - zigbeeMap += [raw: (description - "catchall: ")] - zigbeeMap += [profileId: seg[0]] - zigbeeMap += [clusterId: seg[1]] - zigbeeMap += [sourceEndpoint: seg[2]] - zigbeeMap += [destinationEndpoint: seg[3]] - zigbeeMap += [options: seg[4]] - zigbeeMap += [messageType: seg[5]] - zigbeeMap += [dni: seg[6]] - zigbeeMap += [isClusterSpecific: Short.valueOf(seg[7], 16) != 0] - zigbeeMap += [isManufacturerSpecific: Short.valueOf(seg[8], 16) != 0] - zigbeeMap += [manufacturerId: seg[9]] - zigbeeMap += [command: seg[10]] - zigbeeMap += [direction: seg[11]] - zigbeeMap += [data: seg.size() > 12 ? seg[12].split("").findAll { it }.collate(2).collect { - it.join('') - } : []] - - zigbeeMap - } -} - -def isKnownDescription(description) { - if ((description?.startsWith("catchall:")) || (description?.startsWith("read attr -"))) { - def descMap = parseDescriptionAsMap(description) - if (descMap.cluster == "0006" || descMap.clusterId == "0006") { - isDescriptionOnOff(descMap) - } - else if (descMap.cluster == "0008" || descMap.clusterId == "0008"){ - isDescriptionLevel(descMap) - } - else if (descMap.cluster == "0B04" || descMap.clusterId == "0B04"){ - isDescriptionPower(descMap) - } - else { - return [:] - } - } - else if(description?.startsWith("on/off:")) { - def switchValue = description?.endsWith("1") ? "on" : "off" - return [type: "switch", value : switchValue] - } - else { - return [:] - } -} - -def isDescriptionOnOff(descMap) { - def switchValue = "undefined" - if (descMap.cluster == "0006") { //cluster info from read attr - value = descMap.value - if (value == "01"){ - switchValue = "on" - } - else if (value == "00"){ - switchValue = "off" - } - } - else if (descMap.clusterId == "0006") { - //cluster info from catch all - //command 0B is Default response and the last two bytes are [on/off][success]. on/off=00, success=00 - //command 01 is Read attr response. the last two bytes are [datatype][value]. boolean datatype=10; on/off value = 01/00 - if ((descMap.command=="0B" && descMap.raw.endsWith("0100")) || (descMap.command=="01" && descMap.raw.endsWith("1001"))){ - switchValue = "on" - } - else if ((descMap.command=="0B" && descMap.raw.endsWith("0000")) || (descMap.command=="01" && descMap.raw.endsWith("1000"))){ - switchValue = "off" - } - else if(descMap.command=="07"){ - return [type: "update", value : "switch (0006) capability configured successfully"] - } - } - - if (switchValue != "undefined"){ - return [type: "switch", value : switchValue] - } - else { - return [:] - } - -} - -//@return - false or "success" or level [0-100] -def isDescriptionLevel(descMap) { - def dimmerValue = -1 - if (descMap.cluster == "0008"){ - //TODO: the message returned with catchall is command 0B with clusterId 0008. That is just a confirmation message - def value = convertHexToInt(descMap.value) - dimmerValue = Math.round(value * 100 / 255) - if(dimmerValue==0 && value > 0) { - dimmerValue = 1 //handling for non-zero hex value less than 3 - } - } - else if(descMap.clusterId == "0008") { - if(descMap.command=="0B"){ - return [type: "update", value : "level updated successfully"] //device updating the level change was successful. no value sent. - } - else if(descMap.command=="07"){ - return [type: "update", value : "level (0008) capability configured successfully"] - } - } - - if (dimmerValue != -1){ - return [type: "level", value : dimmerValue] - } - else { - return [:] - } -} - -def isDescriptionPower(descMap) { - def powerValue = "undefined" - if (descMap.cluster == "0B04") { - if (descMap.attrId == "050b") { - if(descMap.value!="ffff") - powerValue = convertHexToInt(descMap.value) - } - } - else if (descMap.clusterId == "0B04") { - if(descMap.command=="07"){ - return [type: "update", value : "power (0B04) capability configured successfully"] - } - } - - if (powerValue != "undefined"){ - return [type: "power", value : powerValue] - } - else { - return [:] - } -} - - -def onOffConfig() { - [ - "zdo bind 0x${device.deviceNetworkId} 1 ${endpointId} 6 {${device.zigbeeId}} {}", "delay 2000", - "zcl global send-me-a-report 6 0 0x10 0 600 {01}", "delay 200", - "send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 2000" - ] -} - -//level config for devices with min reporting interval as 5 seconds and reporting interval if no activity as 1hour (3600s) -//min level change is 01 -def levelConfig() { - [ - "zdo bind 0x${device.deviceNetworkId} 1 ${endpointId} 8 {${device.zigbeeId}} {}", "delay 2000", - "zcl global send-me-a-report 8 0 0x20 5 3600 {01}", "delay 200", - "send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 2000" - ] -} - -//power config for devices with min reporting interval as 1 seconds and reporting interval if no activity as 10min (600s) -//min change in value is 05 -def powerConfig() { - [ - "zdo bind 0x${device.deviceNetworkId} 1 ${endpointId} 0x0B04 {${device.zigbeeId}} {}", "delay 2000", - "zcl global send-me-a-report 0x0B04 0x050B 0x29 1 600 {05 00}", //The send-me-a-report is custom to the attribute type for CentraLite - "delay 200", - "send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 2000" - ] -} - -def setLevelWithRate(level, rate) { - if(rate == null){ - rate = "0000" - } - level = convertToHexString(level * 255 / 100) //Converting the 0-100 range to 0-FF range in hex - [ - "st cmd 0x${device.deviceNetworkId} ${endpointId} 8 4 {$level $rate}", - "delay 2000" - ] -} - -String convertToHexString(value, width=2) { - def s = new BigInteger(Math.round(value).toString()).toString(16) - while (s.size() < width) { - s = "0" + s - } - s + refresh() + zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.electricMeasurementPowerConfig() } diff --git a/devicetypes/smartthings/smartpower-outlet.src/smartpower-outlet.groovy b/devicetypes/smartthings/smartpower-outlet.src/smartpower-outlet.groovy index 931d339..6a42ae9 100644 --- a/devicetypes/smartthings/smartpower-outlet.src/smartpower-outlet.groovy +++ b/devicetypes/smartthings/smartpower-outlet.src/smartpower-outlet.groovy @@ -15,7 +15,7 @@ */ metadata { // Automatically generated. Make future change here. - definition (name: "SmartPower Outlet", namespace: "smartthings", author: "SmartThings") { + definition(name: "SmartPower Outlet", namespace: "smartthings", author: "SmartThings") { capability "Actuator" capability "Switch" capability "Power Meter" @@ -24,9 +24,9 @@ metadata { capability "Sensor" capability "Health Check" - fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B04,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3200", deviceJoinName: "Outlet" - fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B04,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3200-Sgb", deviceJoinName: "Outlet" - fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B04,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "4257050-RZHAC", deviceJoinName: "Outlet" + fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B04,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3200", deviceJoinName: "Outlet" + fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B04,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3200-Sgb", deviceJoinName: "Outlet" + fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B04,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "4257050-RZHAC", deviceJoinName: "Outlet" fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B04,0B05", outClusters: "0019" } @@ -44,32 +44,32 @@ metadata { preferences { section { image(name: 'educationalcontent', multiple: true, images: [ - "http://cdn.device-gse.smartthings.com/Outlet/US/OutletUS1.jpg", - "http://cdn.device-gse.smartthings.com/Outlet/US/OutletUS2.jpg" - ]) + "http://cdn.device-gse.smartthings.com/Outlet/US/OutletUS1.jpg", + "http://cdn.device-gse.smartthings.com/Outlet/US/OutletUS2.jpg" + ]) } } // UI tile definitions tiles(scale: 2) { - multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){ - tileAttribute ("device.switch", key: "PRIMARY_CONTROL") { + multiAttributeTile(name: "switch", type: "lighting", width: 6, height: 4, canChangeIcon: true) { + tileAttribute("device.switch", key: "PRIMARY_CONTROL") { attributeState "on", label: 'On', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#79b821", nextState: "turningOff" attributeState "off", label: 'Off', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff", nextState: "turningOn" attributeState "turningOn", label: 'Turning On', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#79b821", nextState: "turningOff" attributeState "turningOff", label: 'Turning Off', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff", nextState: "turningOn" } - tileAttribute ("power", key: "SECONDARY_CONTROL") { - attributeState "power", label:'${currentValue} W' + tileAttribute("power", key: "SECONDARY_CONTROL") { + attributeState "power", label: '${currentValue} W' } } standardTile("refresh", "device.power", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { - state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh" + state "default", label: '', action: "refresh.refresh", icon: "st.secondary.refresh" } main "switch" - details(["switch","refresh"]) + details(["switch", "refresh"]) } } @@ -77,47 +77,29 @@ metadata { def parse(String description) { log.debug "description is $description" - def finalResult = zigbee.getKnownDescription(description) - def event = [:] + def event = zigbee.getEvent(description) - //TODO: Remove this after getKnownDescription can parse it automatically - if (!finalResult && description!="updated") - finalResult = getPowerDescription(zigbee.parseDescriptionAsMap(description)) - - if (finalResult) { - log.info "final result = $finalResult" - if (finalResult.type == "update") { - log.info "$device updates: ${finalResult.value}" - event = null + if (event) { + if (event.name == "power") { + event.value = event.value / 10 + event.descriptionText = '{{ device.displayName }} power is {{ value }} Watts' + event.translatable = true + } else if (event.name == "switch") { + def descriptionText = event.value == "on" ? '{{ device.displayName }} is On' : '{{ device.displayName }} is Off' + event = createEvent(name: event.name, value: event.value, descriptionText: descriptionText, translatable: true) } - else if (finalResult.type == "power") { - def powerValue = (finalResult.value as Integer)/10 - event = createEvent(name: "power", value: powerValue, descriptionText: '{{ device.displayName }} power is {{ value }} Watts', translatable: true) - /* - Dividing by 10 as the Divisor is 10000 and unit is kW for the device. AttrId: 0302 and 0300. Simplifying to 10 - power level is an integer. The exact power level with correct units needs to be handled in the device type - to account for the different Divisor value (AttrId: 0302) and POWER Unit (AttrId: 0300). CLUSTER for simple metering is 0702 - */ - } - else { - def descriptionText = finalResult.value == "on" ? '{{ device.displayName }} is On' : '{{ device.displayName }} is Off' - event = createEvent(name: finalResult.type, value: finalResult.value, descriptionText: descriptionText, translatable: true) - } - } - else { + } else { def cluster = zigbee.parse(description) - if (cluster && cluster.clusterId == 0x0006 && cluster.command == 0x07){ + if (cluster && cluster.clusterId == 0x0006 && cluster.command == 0x07) { if (cluster.data[0] == 0x00) { log.debug "ON/OFF REPORTING CONFIG RESPONSE: " + cluster event = createEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID]) - } - else { + } else { log.warn "ON/OFF REPORTING CONFIG FAILED- error code:${cluster.data[0]}" event = null } - } - else { + } else { log.warn "DID NOT PARSE MESSAGE for description : $description" log.debug "${cluster}" } @@ -152,29 +134,3 @@ def configure() { refresh() + zigbee.onOffConfig(0, 300) + zigbee.electricMeasurementPowerConfig() } -private getEndpointId() { - new BigInteger(device.endpointId, 16).toString() -} - -//TODO: Remove this after getKnownDescription can parse it automatically -def getPowerDescription(descMap) { - def powerValue = "undefined" - if (descMap.cluster == "0B04") { - if (descMap.attrId == "050b") { - if(descMap.value!="ffff") - powerValue = zigbee.convertHexToInt(descMap.value) - } - } - else if (descMap.clusterId == "0B04") { - if(descMap.command=="07"){ - return [type: "update", value : "power (0B04) capability configured successfully"] - } - } - - if (powerValue != "undefined"){ - return [type: "power", value : powerValue] - } - else { - return [:] - } -} 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 2692edd..4d27081 100644 --- a/devicetypes/smartthings/smartsense-moisture-sensor.src/smartsense-moisture-sensor.groovy +++ b/devicetypes/smartthings/smartsense-moisture-sensor.src/smartsense-moisture-sensor.groovy @@ -17,7 +17,7 @@ import physicalgraph.zigbee.clusters.iaszone.ZoneStatus metadata { - definition (name: "SmartSense Moisture Sensor",namespace: "smartthings", author: "SmartThings") { + definition(name: "SmartSense Moisture Sensor", namespace: "smartthings", author: "SmartThings") { capability "Configuration" capability "Battery" capability "Refresh" @@ -43,10 +43,10 @@ metadata { preferences { section { image(name: 'educationalcontent', multiple: true, images: [ - "http://cdn.device-gse.smartthings.com/Moisture/Moisture1.png", - "http://cdn.device-gse.smartthings.com/Moisture/Moisture2.png", - "http://cdn.device-gse.smartthings.com/Moisture/Moisture3.png" - ]) + "http://cdn.device-gse.smartthings.com/Moisture/Moisture1.png", + "http://cdn.device-gse.smartthings.com/Moisture/Moisture2.png", + "http://cdn.device-gse.smartthings.com/Moisture/Moisture3.png" + ]) } 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" @@ -55,32 +55,32 @@ metadata { } tiles(scale: 2) { - multiAttributeTile(name:"water", type: "generic", width: 6, height: 4){ - tileAttribute ("device.water", key: "PRIMARY_CONTROL") { - attributeState "dry", label: "Dry", icon:"st.alarm.water.dry", backgroundColor:"#ffffff" - attributeState "wet", label: "Wet", icon:"st.alarm.water.wet", backgroundColor:"#53a7c0" + multiAttributeTile(name: "water", type: "generic", width: 6, height: 4) { + tileAttribute("device.water", key: "PRIMARY_CONTROL") { + attributeState "dry", label: "Dry", icon: "st.alarm.water.dry", backgroundColor: "#ffffff" + attributeState "wet", label: "Wet", icon: "st.alarm.water.wet", backgroundColor: "#53a7c0" } } valueTile("temperature", "device.temperature", inactiveLabel: false, width: 2, height: 2) { - state "temperature", label:'${currentValue}°', - backgroundColors:[ - [value: 31, color: "#153591"], - [value: 44, color: "#1e9cbb"], - [value: 59, color: "#90d2a7"], - [value: 74, color: "#44b621"], - [value: 84, color: "#f1d801"], - [value: 95, color: "#d04e00"], - [value: 96, color: "#bc2323"] - ] + state "temperature", label: '${currentValue}°', + backgroundColors: [ + [value: 31, color: "#153591"], + [value: 44, color: "#1e9cbb"], + [value: 59, color: "#90d2a7"], + [value: 74, color: "#44b621"], + [value: 84, color: "#f1d801"], + [value: 95, color: "#d04e00"], + [value: 96, color: "#bc2323"] + ] } valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) { - state "battery", label:'${currentValue}% battery', unit:"" + 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" + state "default", action: "refresh.refresh", icon: "st.secondary.refresh" } - main (["water", "temperature"]) + main(["water", "temperature"]) details(["water", "temperature", "battery", "refresh"]) } } @@ -88,18 +88,24 @@ metadata { 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('temperature: ')) { - map = parseCustomMessage(description) - } - else if (description?.startsWith('zone status')) { - map = parseIasMessage(description) + // getEvent will handle temperature and humidity + Map map = zigbee.getEvent(description) + if (!map) { + if (description?.startsWith('zone status')) { + map = parseIasMessage(description) + } else { + Map descMap = zigbee.parseDescriptionAsMap(description) + if (descMap.clusterInt == 0x0001 && descMap.commandInt != 0x07 && descMap?.value) { + map = getBatteryResult(Integer.parseInt(descMap.value, 16)) + } else if (descMap?.clusterInt == zigbee.TEMPERATURE_MEASUREMENT_CLUSTER && descMap.commandInt == 0x07) { + if (descMap.data[0] == "00") { + log.debug "TEMP REPORTING CONFIG RESPONSE: $descMap" + sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID]) + } else { + log.warn "TEMP REPORTING CONFIG FAILED- error code: ${descMap.data[0]}" + } + } + } } log.debug "Parse returned $map" @@ -113,92 +119,12 @@ def parse(String description) { return result } -private Map parseCatchAllMessage(String description) { - Map resultMap = [:] - def cluster = zigbee.parse(description) - if (shouldProcessMessage(cluster)) { - switch(cluster.clusterId) { - case 0x0001: - // 0x07 - configure reporting - if (cluster.command != 0x07) { - resultMap = getBatteryResult(cluster.data.last()) - } - break - - case 0x0402: - if (cluster.command == 0x07) { - if (cluster.data[0] == 0x00){ - log.debug "TEMP REPORTING CONFIG RESPONSE" + cluster - resultMap = [name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID]] - } - else { - log.warn "TEMP REPORTING CONFIG FAILED- error code:${cluster.data[0]}" - } - } - else { - // 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 - boolean ignoredMessage = cluster.profileId != 0x0104 || - cluster.command == 0x0B || - (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) { ZoneStatus zs = zigbee.parseZoneStatus(description) return zs.isAlarm1Set() ? getMoistureResult('wet') : getMoistureResult('dry') } -def getTemperature(value) { - def celsius = Integer.parseInt(value, 16).shortValue() / 100 - if(getTemperatureScale() == "C"){ - return Math.round(celsius) - } else { - return Math.round(celsiusToFahrenheit(celsius)) - } -} - private Map getBatteryResult(rawValue) { log.debug "Battery rawValue = ${rawValue}" def linkText = getLinkText(device) @@ -239,40 +165,18 @@ private Map getBatteryResult(rawValue) { return result } -private Map getTemperatureResult(value) { - log.debug 'TEMP' - if (tempOffset) { - def offset = tempOffset as int - def v = value as int - value = v + offset - } - def descriptionText - if ( temperatureScale == 'C' ) - descriptionText = '{{ device.displayName }} was {{ value }}°C' - else - descriptionText = '{{ device.displayName }} was {{ value }}°F' - - return [ - name: 'temperature', - value: value, - descriptionText: descriptionText, - translatable: true, - unit: temperatureScale - ] -} - private Map getMoistureResult(value) { log.debug "water" - def descriptionText - if ( value == "wet" ) - descriptionText = '{{ device.displayName }} is wet' - else - descriptionText = '{{ device.displayName }} is dry' + def descriptionText + if (value == "wet") + descriptionText = '{{ device.displayName }} is wet' + else + descriptionText = '{{ device.displayName }} is dry' return [ - name: 'water', - value: value, - descriptionText: descriptionText, - translatable: true + name : 'water', + value : value, + descriptionText: descriptionText, + translatable : true ] } @@ -286,7 +190,7 @@ def ping() { def refresh() { log.debug "Refreshing Temperature and Battery" def refreshCmds = zigbee.readAttribute(zigbee.TEMPERATURE_MEASUREMENT_CLUSTER, 0x0000) + - zigbee.readAttribute(zigbee.POWER_CONFIGURATION_CLUSTER, 0x0020) + zigbee.readAttribute(zigbee.POWER_CONFIGURATION_CLUSTER, 0x0020) return refreshCmds + zigbee.enrollResponse() } 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 bc6271f..a93e927 100644 --- a/devicetypes/smartthings/smartsense-motion-sensor.src/smartsense-motion-sensor.groovy +++ b/devicetypes/smartthings/smartsense-motion-sensor.src/smartsense-motion-sensor.groovy @@ -17,7 +17,7 @@ import physicalgraph.zigbee.clusters.iaszone.ZoneStatus metadata { - definition (name: "SmartSense Motion Sensor", namespace: "smartthings", author: "SmartThings") { + definition(name: "SmartSense Motion Sensor", namespace: "smartthings", author: "SmartThings") { capability "Motion Sensor" capability "Configuration" capability "Battery" @@ -45,10 +45,10 @@ metadata { preferences { section { image(name: 'educationalcontent', multiple: true, images: [ - "http://cdn.device-gse.smartthings.com/Motion/Motion1.jpg", - "http://cdn.device-gse.smartthings.com/Motion/Motion2.jpg", - "http://cdn.device-gse.smartthings.com/Motion/Motion3.jpg" - ]) + "http://cdn.device-gse.smartthings.com/Motion/Motion1.jpg", + "http://cdn.device-gse.smartthings.com/Motion/Motion2.jpg", + "http://cdn.device-gse.smartthings.com/Motion/Motion3.jpg" + ]) } 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" @@ -57,30 +57,30 @@ metadata { } tiles(scale: 2) { - multiAttributeTile(name:"motion", type: "generic", width: 6, height: 4){ - tileAttribute ("device.motion", key: "PRIMARY_CONTROL") { - attributeState "active", label:'motion', icon:"st.motion.motion.active", backgroundColor:"#53a7c0" - attributeState "inactive", label:'no motion', icon:"st.motion.motion.inactive", backgroundColor:"#ffffff" + multiAttributeTile(name: "motion", type: "generic", width: 6, height: 4) { + tileAttribute("device.motion", key: "PRIMARY_CONTROL") { + attributeState "active", label: 'motion', icon: "st.motion.motion.active", backgroundColor: "#53a7c0" + attributeState "inactive", label: 'no motion', icon: "st.motion.motion.inactive", backgroundColor: "#ffffff" } } valueTile("temperature", "device.temperature", width: 2, height: 2) { - state("temperature", label:'${currentValue}°', unit:"F", - backgroundColors:[ - [value: 31, color: "#153591"], - [value: 44, color: "#1e9cbb"], - [value: 59, color: "#90d2a7"], - [value: 74, color: "#44b621"], - [value: 84, color: "#f1d801"], - [value: 95, color: "#d04e00"], - [value: 96, color: "#bc2323"] - ] + state("temperature", label: '${currentValue}°', unit: "F", + backgroundColors: [ + [value: 31, color: "#153591"], + [value: 44, color: "#1e9cbb"], + [value: 59, color: "#90d2a7"], + [value: 74, color: "#44b621"], + [value: 84, color: "#f1d801"], + [value: 95, color: "#d04e00"], + [value: 96, color: "#bc2323"] + ] ) } valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) { - state "battery", label:'${currentValue}% battery', unit:"" + 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" + state "default", action: "refresh.refresh", icon: "st.secondary.refresh" } main(["motion", "temperature"]) @@ -90,19 +90,27 @@ metadata { 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('temperature: ')) { - map = parseCustomMessage(description) - } - else if (description?.startsWith('zone status')) { - map = parseIasMessage(description) + Map map = zigbee.getEvent(description) + if (!map) { + if (description?.startsWith('zone status')) { + map = parseIasMessage(description) + } else { + Map descMap = zigbee.parseDescriptionAsMap(description) + if (descMap?.clusterInt == 0x0001 && descMap.commandInt != 0x07 && descMap?.value) { + map = getBatteryResult(Integer.parseInt(descMap.value, 16)) + } else if (descMap?.clusterInt == zigbee.TEMPERATURE_MEASUREMENT_CLUSTER && descMap.commandInt == 0x07) { + if (descMap.data[0] == "00") { + log.debug "TEMP REPORTING CONFIG RESPONSE: $descMap" + sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID]) + } else { + log.warn "TEMP REPORTING CONFIG FAILED- error code: ${descMap.data[0]}" + } + } else if (descMap.clusterInt == 0x0406 && descMap.attrInt == 0x0000) { + def value = descMap.value.endsWith("01") ? "active" : "inactive" + log.warn "Doing a read attr motion event" + resultMap = getMotionResult(value) + } + } } log.debug "Parse returned $map" @@ -116,90 +124,6 @@ def parse(String description) { return result } -private Map parseCatchAllMessage(String description) { - Map resultMap = [:] - def cluster = zigbee.parse(description) - if (shouldProcessMessage(cluster)) { - switch(cluster.clusterId) { - case 0x0001: - // 0x07 - configure reporting - if (cluster.command != 0x07) { - resultMap = getBatteryResult(cluster.data.last()) - } - break - - case 0x0402: - if (cluster.command == 0x07) { - if (cluster.data[0] == 0x00) { - log.debug "TEMP REPORTING CONFIG RESPONSE" + cluster - resultMap = [name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID]] - } - else { - log.warn "TEMP REPORTING CONFIG FAILED- error code:${cluster.data[0]}" - } - - } - else { - // 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: - // 0x07 - configure reporting - if (cluster.command != 0x07) { - log.debug 'motion' - resultMap.name = 'motion' - } - break - } - } - - return resultMap -} - -private boolean shouldProcessMessage(cluster) { - // 0x0B is default response indicating message got through - boolean ignoredMessage = cluster.profileId != 0x0104 || - cluster.command == 0x0B || - (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)) - } - 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: ')) { - def value = zigbee.parseHATemperatureValue(description, "temperature: ", getTemperatureScale()) - resultMap = getTemperatureResult(value) - } - return resultMap -} - private Map parseIasMessage(String description) { ZoneStatus zs = zigbee.parseZoneStatus(description) @@ -207,15 +131,6 @@ private Map parseIasMessage(String description) { return (zs.isAlarm1Set() || zs.isAlarm2Set()) ? getMotionResult('active') : getMotionResult('inactive') } -def getTemperature(value) { - def celsius = Integer.parseInt(value, 16).shortValue() / 100 - if(getTemperatureScale() == "C"){ - return Math.round(celsius) - } else { - return Math.round(celsiusToFahrenheit(celsius)) - } -} - private Map getBatteryResult(rawValue) { log.debug "Battery rawValue = ${rawValue}" def linkText = getLinkText(device) @@ -255,36 +170,14 @@ private Map getBatteryResult(rawValue) { return result } -private Map getTemperatureResult(value) { - log.debug 'TEMP' - if (tempOffset) { - def offset = tempOffset as int - def v = value as int - value = v + offset - } - def descriptionText - if ( temperatureScale == 'C' ) - descriptionText = '{{ device.displayName }} was {{ value }}°C' - else - descriptionText = '{{ device.displayName }} was {{ value }}°F' - - return [ - name: 'temperature', - value: value, - descriptionText: descriptionText, - translatable: true, - unit: temperatureScale - ] -} - private Map getMotionResult(value) { log.debug 'motion' String descriptionText = value == 'active' ? "{{ device.displayName }} detected motion" : "{{ device.displayName }} motion has stopped" return [ - name: 'motion', - value: value, - descriptionText: descriptionText, - translatable: true + name : 'motion', + value : value, + descriptionText: descriptionText, + translatable : true ] } @@ -299,7 +192,7 @@ def refresh() { log.debug "refresh called" def refreshCmds = zigbee.readAttribute(zigbee.POWER_CONFIGURATION_CLUSTER, 0x0020) + - zigbee.readAttribute(zigbee.TEMPERATURE_MEASUREMENT_CLUSTER, 0x0000) + zigbee.readAttribute(zigbee.TEMPERATURE_MEASUREMENT_CLUSTER, 0x0000) return refreshCmds + zigbee.enrollResponse() } 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 daf9a61..29fdf7c 100644 --- a/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy +++ b/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy @@ -17,20 +17,20 @@ import physicalgraph.zigbee.clusters.iaszone.ZoneStatus import physicalgraph.zigbee.zcl.DataType metadata { - definition (name: "SmartSense Multi Sensor", namespace: "smartthings", author: "SmartThings") { + definition(name: "SmartSense Multi Sensor", namespace: "smartthings", author: "SmartThings") { capability "Three Axis" capability "Battery" capability "Configuration" capability "Sensor" - capability "Contact Sensor" - capability "Acceleration Sensor" - capability "Refresh" - capability "Temperature Measurement" + capability "Contact Sensor" + capability "Acceleration Sensor" + capability "Refresh" + capability "Temperature Measurement" capability "Health Check" - command "enrollResponse" - fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05,FC02", outClusters: "0019", manufacturer: "CentraLite", model: "3320" + 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" @@ -58,11 +58,11 @@ metadata { preferences { section { image(name: 'educationalcontent', multiple: true, images: [ - "http://cdn.device-gse.smartthings.com/Multi/Multi1.jpg", - "http://cdn.device-gse.smartthings.com/Multi/Multi2.jpg", - "http://cdn.device-gse.smartthings.com/Multi/Multi3.jpg", - "http://cdn.device-gse.smartthings.com/Multi/Multi4.jpg" - ]) + "http://cdn.device-gse.smartthings.com/Multi/Multi1.jpg", + "http://cdn.device-gse.smartthings.com/Multi/Multi2.jpg", + "http://cdn.device-gse.smartthings.com/Multi/Multi3.jpg", + "http://cdn.device-gse.smartthings.com/Multi/Multi4.jpg" + ]) } 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" @@ -74,58 +74,74 @@ metadata { } tiles(scale: 2) { - multiAttributeTile(name:"status", type: "generic", width: 6, height: 4){ - tileAttribute ("device.status", key: "PRIMARY_CONTROL") { - attributeState "open", label:'Open', icon:"st.contact.contact.open", backgroundColor:"#ffa81e" - attributeState "closed", label:'Closed', icon:"st.contact.contact.closed", backgroundColor:"#79b821" - attributeState "garage-open", label:'Open', icon:"st.doors.garage.garage-open", backgroundColor:"#ffa81e" - attributeState "garage-closed", label:'Closed', icon:"st.doors.garage.garage-closed", backgroundColor:"#79b821" + multiAttributeTile(name: "status", type: "generic", width: 6, height: 4) { + tileAttribute("device.status", key: "PRIMARY_CONTROL") { + attributeState "open", label: 'Open', icon: "st.contact.contact.open", backgroundColor: "#ffa81e" + attributeState "closed", label: 'Closed', icon: "st.contact.contact.closed", backgroundColor: "#79b821" + attributeState "garage-open", label: 'Open', icon: "st.doors.garage.garage-open", backgroundColor: "#ffa81e" + attributeState "garage-closed", label: 'Closed', icon: "st.doors.garage.garage-closed", backgroundColor: "#79b821" } } standardTile("contact", "device.contact", width: 2, height: 2) { - state("open", label:'Open', icon:"st.contact.contact.open", backgroundColor:"#ffa81e") - state("closed", label:'Closed', icon:"st.contact.contact.closed", backgroundColor:"#79b821") + state("open", label: 'Open', icon: "st.contact.contact.open", backgroundColor: "#ffa81e") + state("closed", label: 'Closed', icon: "st.contact.contact.closed", backgroundColor: "#79b821") } standardTile("acceleration", "device.acceleration", width: 2, height: 2) { - state("active", label:'Active', icon:"st.motion.acceleration.active", backgroundColor:"#53a7c0") - state("inactive", label:'Inactive', icon:"st.motion.acceleration.inactive", backgroundColor:"#ffffff") + state("active", label: 'Active', icon: "st.motion.acceleration.active", backgroundColor: "#53a7c0") + state("inactive", label: 'Inactive', icon: "st.motion.acceleration.inactive", backgroundColor: "#ffffff") } valueTile("temperature", "device.temperature", width: 2, height: 2) { - state("temperature", label:'${currentValue}°', - backgroundColors:[ - [value: 31, color: "#153591"], - [value: 44, color: "#1e9cbb"], - [value: 59, color: "#90d2a7"], - [value: 74, color: "#44b621"], - [value: 84, color: "#f1d801"], - [value: 95, color: "#d04e00"], - [value: 96, color: "#bc2323"] - ] + state("temperature", label: '${currentValue}°', + backgroundColors: [ + [value: 31, color: "#153591"], + [value: 44, color: "#1e9cbb"], + [value: 59, color: "#90d2a7"], + [value: 74, color: "#44b621"], + [value: 84, color: "#f1d801"], + [value: 95, color: "#d04e00"], + [value: 96, color: "#bc2323"] + ] ) } valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) { - state "battery", label:'${currentValue}% battery', unit:"" + 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" + state "default", action: "refresh.refresh", icon: "st.secondary.refresh" } main(["status", "acceleration", "temperature"]) details(["status", "acceleration", "temperature", "battery", "refresh"]) } - } +} def parse(String description) { - Map map = [:] - if (description?.startsWith('catchall:')) { - map = parseCatchAllMessage(description) - } - else if (description?.startsWith('temperature: ')) { - map = parseCustomMessage(description) - } - else if (description?.startsWith('zone status')) { - map = parseIasMessage(description) + Map map = zigbee.getEvent(description) + if (!map) { + if (description?.startsWith('zone status')) { + map = parseIasMessage(description) + } else { + Map descMap = zigbee.parseDescriptionAsMap(description) + if (descMap?.clusterInt == 0x0001 && descMap.commandInt != 0x07 && descMap?.value) { + map = getBatteryResult(Integer.parseInt(descMap.value, 16)) + } else if (descMap?.clusterInt == zigbee.TEMPERATURE_MEASUREMENT_CLUSTER && descMap.commandInt == 0x07) { + if (descMap.data[0] == "00") { + log.debug "TEMP REPORTING CONFIG RESPONSE: $descMap" + sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID]) + } else { + log.warn "TEMP REPORTING CONFIG FAILED- error code: ${descMap.data[0]}" + } + } else { + map = handleAcceleration(descMap) + } + } + } else if (map.name == "temperature") { + if (tempOffset) { + map.value = (int) map.value + (int) tempOffset + } + map.descriptionText = temperatureScale == 'C' ? '{{ device.displayName }} was {{ value }}°C' : '{{ device.displayName }} was {{ value }}°F' + map.translatable = true } def result = map ? createEvent(map) : [:] @@ -135,72 +151,12 @@ def parse(String description) { 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) } - } 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: - // 0x07 - configure reporting - if (cluster.command != 0x07) { - resultMap = getBatteryResult(cluster.data.last()) - } - break - - case 0xFC02: - log.debug 'ACCELERATION' - break - - case 0x0402: - if (cluster.command == 0x07) { - if(cluster.data[0] == 0x00) { - log.debug "TEMP REPORTING CONFIG RESPONSE" + cluster - resultMap = [name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID]] - } - else { - log.warn "TEMP REPORTING CONFIG FAILED- error code:${cluster.data[0]}" - } - } - else { - // 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 - boolean ignoredMessage = cluster.profileId != 0x0104 || - cluster.command == 0x0B || - (cluster.data.size() > 0 && cluster.data.first() == 0x3e) - return !ignoredMessage -} - -private List parseReportAttributeMessage(String description) { - Map descMap = (description - "read attr - ").split(",").inject([:]) { map, param -> - def nameAndValue = param.split(":") - map += [(nameAndValue[0].trim()):nameAndValue[1].trim()] - } - - List result = [] - if (descMap.cluster == "0402" && descMap.attrId == "0000") { - def value = getTemperature(descMap.value) - result << getTemperatureResult(value) - } - else if (descMap.cluster == "FC02" && descMap.attrId == "0010") { +private Map handleAcceleration(descMap) { + Map result = [:] + if (descMap.clusterInt == 0xFC02 && descMap.attrInt == 0x0010) { if (descMap.value.size() == 32) { // value will look like 00ae29001403e2290013001629001201 // breaking this apart and swapping byte order where appropriate, this breaks down to: @@ -213,36 +169,22 @@ private List parseReportAttributeMessage(String description) { result << parseAxis(threeAxisAttributes) descMap.value = descMap.value[-2..-1] } - result << getAccelerationResult(descMap.value) - } - else if (descMap.cluster == "FC02" && descMap.attrId == "0012" && descMap.value.size() == 24) { + result = getAccelerationResult(descMap.value) + } else if (descMap.clusterInt == 0xFC02 && descMap.attrInt == 0x0012 && descMap.value.size() == 24) { // The size is checked to ensure the attribute report contains X, Y and Z values // If all three axis are not included then the attribute report is ignored - result << parseAxis(descMap.value) + result = parseAxis(descMap.value) } - else if (descMap.cluster == "0001" && descMap.attrId == "0020") { - result << getBatteryResult(Integer.parseInt(descMap.value, 16)) - } - return result } -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) { ZoneStatus zs = zigbee.parseZoneStatus(description) Map resultMap = [:] - if (garageSensor != "Yes"){ + if (garageSensor != "Yes") { resultMap = zs.isAlarm1Set() ? getContactResult('open') : getContactResult('closed') - } + } return resultMap } @@ -254,31 +196,19 @@ def updated() { def descriptionText = "Updating device to garage sensor" if (device.latestValue("status") == "open") { sendEvent(name: 'status', value: 'garage-open', descriptionText: descriptionText, translatable: true) - } - else if (device.latestValue("status") == "closed") { + } else if (device.latestValue("status") == "closed") { sendEvent(name: 'status', value: 'garage-closed', descriptionText: descriptionText, translatable: true) } - } - else { + } else { def descriptionText = "Updating device to open/close sensor" if (device.latestValue("status") == "garage-open") { sendEvent(name: 'status', value: 'open', descriptionText: descriptionText, translatable: true) - } - else if (device.latestValue("status") == "garage-closed") { + } else if (device.latestValue("status") == "garage-closed") { sendEvent(name: 'status', value: 'closed', descriptionText: descriptionText, translatable: true) } } } -def getTemperature(value) { - def celsius = Integer.parseInt(value, 16).shortValue() / 100 - if(getTemperatureScale() == "C"){ - return Math.round(celsius) - } else { - return Math.round(celsiusToFahrenheit(celsius)) - } - } - private Map getBatteryResult(rawValue) { log.debug "Battery rawValue = ${rawValue}" @@ -317,25 +247,6 @@ private Map getBatteryResult(rawValue) { return result } -private Map getTemperatureResult(value) { - log.debug "Temperature" - if (tempOffset) { - def offset = tempOffset as int - def v = value as int - value = v + offset - } - def descriptionText = temperatureScale == 'C' ? '{{ device.displayName }} was {{ value }}°C': - '{{ device.displayName }} was {{ value }}°F' - - return [ - name: 'temperature', - value: value, - descriptionText: descriptionText, - translatable: true, - unit: temperatureScale - ] -} - private Map getContactResult(value) { log.debug "Contact: ${device.displayName} value = ${value}" def descriptionText = value == 'open' ? '{{ device.displayName }} was opened' : '{{ device.displayName }} was closed' @@ -346,24 +257,24 @@ private Map getContactResult(value) { private getAccelerationResult(numValue) { log.debug "Acceleration" def name = "acceleration" - def value - def descriptionText + def value + def descriptionText - if ( numValue.endsWith("1") ) { - value = "active" - descriptionText = '{{ device.displayName }} was active' - } else { - value = "inactive" - descriptionText = '{{ device.displayName }} was inactive' - } + if (numValue.endsWith("1")) { + value = "active" + descriptionText = '{{ device.displayName }} was active' + } else { + value = "inactive" + descriptionText = '{{ device.displayName }} was inactive' + } def isStateChange = isStateChange(device, name, value) return [ - name: name, - value: value, - descriptionText: descriptionText, - isStateChange: isStateChange, - translatable: true + name : name, + value : value, + descriptionText: descriptionText, + isStateChange : isStateChange, + translatable : true ] } @@ -377,27 +288,12 @@ def ping() { def refresh() { log.debug "Refreshing Values " - def refreshCmds = [] - - if (device.getDataValue("manufacturer") == "SmartThings") { - log.debug "Refreshing Values for manufacturer: SmartThings " - /* These values of Motion Threshold Multiplier(0x01) and Motion Threshold (0x0276) - 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. - */ - refreshCmds += zigbee.writeAttribute(0xFC02, 0x0000, 0x20, 0x01, [mfgCode: manufacturerCode]) - refreshCmds += zigbee.writeAttribute(0xFC02, 0x0002, 0x21, 0x0276, [mfgCode: manufacturerCode]) - } else { - refreshCmds += zigbee.writeAttribute(0xFC02, 0x0000, 0x20, 0x02, [mfgCode: manufacturerCode]) - } - - //Common refresh commands - refreshCmds += zigbee.readAttribute(zigbee.TEMPERATURE_MEASUREMENT_CLUSTER, 0x0000) + + def refreshCmds = zigbee.readAttribute(zigbee.TEMPERATURE_MEASUREMENT_CLUSTER, 0x0000) + zigbee.readAttribute(zigbee.POWER_CONFIGURATION_CLUSTER, 0x0020) + - zigbee.readAttribute(0xFC02, 0x0010, [mfgCode: manufacturerCode]) + zigbee.readAttribute(0xFC02, 0x0010, [mfgCode: manufacturerCode]) + + zigbee.enrollResponse() - return refreshCmds + zigbee.enrollResponse() + return refreshCmds } def configure() { @@ -406,10 +302,27 @@ def configure() { sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID]) log.debug "Configuring Reporting" + def configCmds = [] + + if (device.getDataValue("manufacturer") == "SmartThings") { + log.debug "Refreshing Values for manufacturer: SmartThings " + /* These values of Motion Threshold Multiplier(0x01) and Motion Threshold (0x0276) + 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. + */ + configCmds += zigbee.writeAttribute(0xFC02, 0x0000, 0x20, 0x01, [mfgCode: manufacturerCode]) + configCmds += zigbee.writeAttribute(0xFC02, 0x0002, 0x21, 0x0276, [mfgCode: manufacturerCode]) + } else { + // Write a motion threshold of 2 * .063g = .126g + // Currently due to a Centralite firmware issue, this will cause a read attribute response that + // indicates acceleration even when there isn't. + configCmds += zigbee.writeAttribute(0xFC02, 0x0000, 0x20, 0x02, [mfgCode: manufacturerCode]) + } // temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity // battery minReport 30 seconds, maxReportTime 6 hrs by default - def configCmds = zigbee.batteryConfig() + + configCmds += zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) + zigbee.configureReporting(0xFC02, 0x0010, DataType.BITMAP8, 10, 3600, 0x01, [mfgCode: manufacturerCode]) + zigbee.configureReporting(0xFC02, 0x0012, DataType.INT16, 1, 3600, 0x0001, [mfgCode: manufacturerCode]) + @@ -459,17 +372,16 @@ def garageEvent(zValue) { def absValue = zValue.abs() def contactValue = null def garageValue = null - if (absValue>900) { + if (absValue > 900) { contactValue = 'closed' garageValue = 'garage-closed' - } - else if (absValue < 100) { + } else if (absValue < 100) { contactValue = 'open' garageValue = 'garage-open' } - if (contactValue != null){ - def descriptionText = contactValue == 'open' ? '{{ device.displayName }} was opened' :'{{ device.displayName }} was closed' - sendEvent(name: 'contact', value: contactValue, descriptionText: descriptionText, displayed:false, translatable: true) + if (contactValue != null) { + def descriptionText = contactValue == 'open' ? '{{ device.displayName }} was opened' : '{{ device.displayName }} was closed' + sendEvent(name: 'contact', value: contactValue, descriptionText: descriptionText, displayed: false, translatable: true) sendEvent(name: 'status', value: garageValue, descriptionText: descriptionText, translatable: true) } } @@ -482,14 +394,14 @@ private Map getXyzResult(results, description) { def isStateChange = isStateChange(device, name, value) [ - name: name, - value: value, - unit: null, - linkText: linkText, - descriptionText: descriptionText, - handlerName: name, - isStateChange: isStateChange, - displayed: false + name : name, + value : value, + unit : null, + linkText : linkText, + descriptionText: descriptionText, + handlerName : name, + isStateChange : isStateChange, + displayed : false ] } @@ -504,25 +416,3 @@ private getManufacturerCode() { private hexToInt(value) { new BigInteger(value, 16) } - -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 -} diff --git a/devicetypes/smartthings/smartsense-open-closed-sensor.src/smartsense-open-closed-sensor.groovy b/devicetypes/smartthings/smartsense-open-closed-sensor.src/smartsense-open-closed-sensor.groovy index c3de34d..294717e 100644 --- a/devicetypes/smartthings/smartsense-open-closed-sensor.src/smartsense-open-closed-sensor.groovy +++ b/devicetypes/smartthings/smartsense-open-closed-sensor.src/smartsense-open-closed-sensor.groovy @@ -16,7 +16,7 @@ import physicalgraph.zigbee.clusters.iaszone.ZoneStatus metadata { - definition (name: "SmartSense Open/Closed Sensor", namespace: "smartthings", author: "SmartThings") { + definition(name: "SmartSense Open/Closed Sensor", namespace: "smartthings", author: "SmartThings") { capability "Battery" capability "Configuration" capability "Contact Sensor" @@ -43,155 +43,77 @@ metadata { } tiles(scale: 2) { - multiAttributeTile(name:"contact", type: "generic", width: 6, height: 4){ - tileAttribute ("device.contact", key: "PRIMARY_CONTROL") { - attributeState "open", label:'${name}', icon:"st.contact.contact.open", backgroundColor:"#ffa81e" - attributeState "closed", label:'${name}', icon:"st.contact.contact.closed", backgroundColor:"#79b821" + multiAttributeTile(name: "contact", type: "generic", width: 6, height: 4) { + tileAttribute("device.contact", key: "PRIMARY_CONTROL") { + attributeState "open", label: '${name}', icon: "st.contact.contact.open", backgroundColor: "#ffa81e" + attributeState "closed", label: '${name}', icon: "st.contact.contact.closed", backgroundColor: "#79b821" } } valueTile("temperature", "device.temperature", inactiveLabel: false, width: 2, height: 2) { - state "temperature", label:'${currentValue}°', - backgroundColors:[ - [value: 31, color: "#153591"], - [value: 44, color: "#1e9cbb"], - [value: 59, color: "#90d2a7"], - [value: 74, color: "#44b621"], - [value: 84, color: "#f1d801"], - [value: 95, color: "#d04e00"], - [value: 96, color: "#bc2323"] - ] + state "temperature", label: '${currentValue}°', + backgroundColors: [ + [value: 31, color: "#153591"], + [value: 44, color: "#1e9cbb"], + [value: 59, color: "#90d2a7"], + [value: 74, color: "#44b621"], + [value: 84, color: "#f1d801"], + [value: 95, color: "#d04e00"], + [value: 96, color: "#bc2323"] + ] } valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) { - state "battery", label:'${currentValue}% battery', unit:"" + 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" + state "default", action: "refresh.refresh", icon: "st.secondary.refresh" } - main (["contact", "temperature"]) - details(["contact","temperature","battery","refresh"]) + main(["contact", "temperature"]) + details(["contact", "temperature", "battery", "refresh"]) } } def parse(String description) { log.debug "description: $description" - Map map = [:] - if (description?.startsWith('catchall:')) { - map = parseCatchAllMessage(description) + Map map = zigbee.getEvent(description) + if (!map) { + if (description?.startsWith('zone status')) { + map = parseIasMessage(description) + } else { + Map descMap = zigbee.parseDescriptionAsMap(description) + if (descMap?.clusterInt == 0x0001 && descMap.commandInt != 0x07 && descMap?.value) { + map = getBatteryResult(Integer.parseInt(descMap.value, 16)) + } else if (descMap?.clusterInt == zigbee.TEMPERATURE_MEASUREMENT_CLUSTER && descMap.commandInt == 0x07) { + if (descMap.data[0] == "00") { + log.debug "TEMP REPORTING CONFIG RESPONSE: $descMap" + sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID]) + } else { + log.warn "TEMP REPORTING CONFIG FAILED- error code: ${descMap.data[0]}" + } + } + } } - else if (description?.startsWith('read attr -')) { - map = parseReportAttributeMessage(description) - } - else if (description?.startsWith('temperature: ')) { - map = parseCustomMessage(description) - } - else if (description?.startsWith('zone status')) { - map = parseIasMessage(description) - } log.debug "Parse returned $map" def result = map ? createEvent(map) : [:] - if (description?.startsWith('enroll request')) { - List cmds = zigbee.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: - // 0x07 - configure reporting - if (cluster.command != 0x07) { - resultMap = getBatteryResult(cluster.data.last()) - } - break - - case 0x0402: - if (cluster.command == 0x07){ - if (cluster.data[0] == 0x00) { - log.debug "TEMP REPORTING CONFIG RESPONSE" + cluster - resultMap = [name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID]] - } - else { - log.warn "TEMP REPORTING CONFIG FAILED- error code:${cluster.data[0]}" - } - } - else { - // 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 - boolean ignoredMessage = cluster.profileId != 0x0104 || - cluster.command == 0x0B || - (cluster.data.size() > 0 && cluster.data.first() == 0x3e) - return !ignoredMessage -} - -private int getHumidity(value) { - return Math.round(Double.parseDouble(value)) -} - -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()] + if (description?.startsWith('enroll request')) { + List cmds = zigbee.enrollResponse() + log.debug "enroll response: ${cmds}" + result = cmds?.collect { new physicalgraph.device.HubAction(it) } } - 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 + return result } -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) { ZoneStatus zs = zigbee.parseZoneStatus(description) return zs.isAlarm1Set() ? getContactResult('open') : getContactResult('closed') } -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) @@ -204,8 +126,8 @@ private Map getBatteryResult(rawValue) { def maxVolts = 3.0 def pct = (volts - minVolts) / (maxVolts - minVolts) def roundedPct = Math.round(pct * 100) - if (roundedPct <= 0) - roundedPct = 1 + if (roundedPct <= 0) + roundedPct = 1 result.value = Math.min(100, roundedPct) result.descriptionText = "${linkText} battery was ${result.value}%" result.name = 'battery' @@ -214,31 +136,14 @@ private Map getBatteryResult(rawValue) { return result } -private Map getTemperatureResult(value) { - log.debug 'TEMP' - def linkText = getLinkText(device) - if (tempOffset) { - def offset = tempOffset as int - def v = value as int - value = v + offset - } - def descriptionText = "${linkText} was ${value}°${temperatureScale}" - return [ - name: 'temperature', - value: value, - descriptionText: descriptionText, - unit: temperatureScale - ] -} - private Map getContactResult(value) { log.debug 'Contact Status' def linkText = getLinkText(device) def descriptionText = "${linkText} was ${value == 'open' ? 'opened' : 'closed'}" return [ - name: 'contact', - value: value, - descriptionText: descriptionText + name : 'contact', + value : value, + descriptionText: descriptionText ] } @@ -252,7 +157,7 @@ def ping() { def refresh() { log.debug "Refreshing Temperature and Battery" def refreshCmds = zigbee.readAttribute(zigbee.TEMPERATURE_MEASUREMENT_CLUSTER, 0x0000) + - zigbee.readAttribute(zigbee.POWER_CONFIGURATION_CLUSTER, 0x0020) + zigbee.readAttribute(zigbee.POWER_CONFIGURATION_CLUSTER, 0x0020) return refreshCmds + zigbee.enrollResponse() } @@ -266,5 +171,5 @@ def configure() { // temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity // battery minReport 30 seconds, maxReportTime 6 hrs by default - return refresh() + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) // send refresh cmds as part of config + return refresh() + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) // send refresh cmds as part of config } diff --git a/devicetypes/smartthings/smartsense-temp-humidity-sensor.src/smartsense-temp-humidity-sensor.groovy b/devicetypes/smartthings/smartsense-temp-humidity-sensor.src/smartsense-temp-humidity-sensor.groovy index f062957..c0fe4b0 100644 --- a/devicetypes/smartthings/smartsense-temp-humidity-sensor.src/smartsense-temp-humidity-sensor.groovy +++ b/devicetypes/smartthings/smartsense-temp-humidity-sensor.src/smartsense-temp-humidity-sensor.groovy @@ -16,7 +16,7 @@ import physicalgraph.zigbee.zcl.DataType metadata { - definition (name: "SmartSense Temp/Humidity Sensor",namespace: "smartthings", author: "SmartThings") { + definition(name: "SmartSense Temp/Humidity Sensor", namespace: "smartthings", author: "SmartThings") { capability "Configuration" capability "Battery" capability "Refresh" @@ -33,7 +33,7 @@ metadata { status 'H 45': 'catchall: 0104 FC45 01 01 0140 00 D9B9 00 04 C2DF 0A 01 0000218911' status 'H 57': 'catchall: 0104 FC45 01 01 0140 00 4E55 00 04 C2DF 0A 01 0000211316' status 'H 53': 'catchall: 0104 FC45 01 01 0140 00 20CD 00 04 C2DF 0A 01 0000219814' - status 'H 43': 'read attr - raw: BF7601FC450C00000021A410, dni: BF76, endpoint: 01, cluster: FC45, size: 0C, attrId: 0000, result: success, encoding: 21, value: 10a4' + status 'H 43': 'read attr - raw: BF7601FC450C00000021A410, dni: BF76, endpoint: 01, cluster: FC45, size: 0C, attrId: 0000, result: success, encoding: 21, value: 10a4' } preferences { @@ -42,28 +42,28 @@ metadata { } tiles(scale: 2) { - multiAttributeTile(name:"temperature", type: "generic", width: 6, height: 4){ - tileAttribute ("device.temperature", key: "PRIMARY_CONTROL") { - attributeState "temperature", label:'${currentValue}°', - backgroundColors:[ - [value: 31, color: "#153591"], - [value: 44, color: "#1e9cbb"], - [value: 59, color: "#90d2a7"], - [value: 74, color: "#44b621"], - [value: 84, color: "#f1d801"], - [value: 95, color: "#d04e00"], - [value: 96, color: "#bc2323"] - ] + multiAttributeTile(name: "temperature", type: "generic", width: 6, height: 4) { + tileAttribute("device.temperature", key: "PRIMARY_CONTROL") { + attributeState "temperature", label: '${currentValue}°', + backgroundColors: [ + [value: 31, color: "#153591"], + [value: 44, color: "#1e9cbb"], + [value: 59, color: "#90d2a7"], + [value: 74, color: "#44b621"], + [value: 84, color: "#f1d801"], + [value: 95, color: "#d04e00"], + [value: 96, color: "#bc2323"] + ] } } valueTile("humidity", "device.humidity", inactiveLabel: false, width: 2, height: 2) { - state "humidity", label:'${currentValue}% humidity', unit:"" + state "humidity", label: '${currentValue}% humidity', unit: "" } valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) { - state "battery", label:'${currentValue}% battery' + state "battery", label: '${currentValue}% battery' } standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { - state "default", action:"refresh.refresh", icon:"st.secondary.refresh" + state "default", action: "refresh.refresh", icon: "st.secondary.refresh" } main "temperature", "humidity" @@ -74,142 +74,31 @@ metadata { 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('temperature: ') || description?.startsWith('humidity: ')) { - map = parseCustomMessage(description) + // getEvent will handle temperature and humidity + Map map = zigbee.getEvent(description) + if (!map) { + Map descMap = zigbee.parseDescriptionAsMap(description) + if (descMap.clusterInt == 0x0001 && descMap.commandInt != 0x07 && descMap?.value) { + map = getBatteryResult(Integer.parseInt(descMap.value, 16)) + } else if (descMap?.clusterInt == zigbee.TEMPERATURE_MEASUREMENT_CLUSTER && descMap.commandInt == 0x07) { + if (descMap.data[0] == "00") { + log.debug "TEMP REPORTING CONFIG RESPONSE: $descMap" + sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID]) + } else { + log.warn "TEMP REPORTING CONFIG FAILED- error code: ${descMap.data[0]}" + } + } } log.debug "Parse returned $map" return map ? createEvent(map) : [:] } -private Map parseCatchAllMessage(String description) { - Map resultMap = [:] - def cluster = zigbee.parse(description) - if (shouldProcessMessage(cluster)) { - switch(cluster.clusterId) { - case 0x0001: - // 0x07 - configure reporting - if (cluster.command != 0x07) { - resultMap = getBatteryResult(cluster.data.last()) - } - break - - case 0x0402: - if (cluster.command == 0x07) { - if (cluster.data[0] == 0x00){ - log.debug "TEMP REPORTING CONFIG RESPONSE" + cluster - resultMap = [name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID]] - } - else { - log.warn "TEMP REPORTING CONFIG FAILED- error code:${cluster.data[0]}" - } - } - else { - // 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 0xFC45: - // 0x07 - configure reporting - if (cluster.command != 0x07) { - String pctStr = cluster.data[-1, -2].collect { Integer.toHexString(it) }.join('') - String display = Math.round(Integer.valueOf(pctStr, 16) / 100) - resultMap = getHumidityResult(display) - } - break - } - } - - return resultMap -} - -private boolean shouldProcessMessage(cluster) { - // 0x0B is default response indicating message got through - boolean ignoredMessage = cluster.profileId != 0x0104 || - cluster.command == 0x0B || - (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)) - } - else if (descMap.cluster == "FC45" && descMap.attrId == "0000") { - def value = getReportAttributeHumidity(descMap.value) - resultMap = getHumidityResult(value) - } - - return resultMap -} - -def getReportAttributeHumidity(String value) { - def humidity = null - if (value?.trim()) { - try { - // value is hex with no decimal - def pct = Integer.parseInt(value.trim(), 16) / 100 - humidity = String.format('%.0f', pct) - } catch(NumberFormatException nfe) { - log.debug "Error converting $value to humidity" - } - } - return humidity -} - -private Map parseCustomMessage(String description) { - Map resultMap = [:] - if (description?.startsWith('temperature: ')) { - def value = zigbee.parseHATemperatureValue(description, "temperature: ", getTemperatureScale()) - resultMap = getTemperatureResult(value) - } - else if (description?.startsWith('humidity: ')) { - def pct = (description - "humidity: " - "%").trim() - if (pct.isNumber()) { - def value = Math.round(new BigDecimal(pct)).toString() - resultMap = getHumidityResult(value) - } else { - log.error "invalid humidity: ${pct}" - } - } - 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 = [:] + def result = [:] def volts = rawValue / 10 if (!(rawValue == 0 || rawValue == 255)) { @@ -228,41 +117,22 @@ private Map getBatteryResult(rawValue) { return result } -private Map getTemperatureResult(value) { - log.debug 'TEMP' - def linkText = getLinkText(device) - if (tempOffset) { - def offset = tempOffset as int - def v = value as int - value = v + offset - } - def descriptionText = "${linkText} was ${value}°${temperatureScale}" - return [ - name: 'temperature', - value: value, - descriptionText: descriptionText, - unit: temperatureScale - ] -} - -private Map getHumidityResult(value) { - log.debug 'Humidity' - return value ? [name: 'humidity', value: value, unit: '%'] : [:] -} - /** * PING is used by Device-Watch in attempt to reach the Device * */ def ping() { - return zigbee.readAttribute(zigbee.POWER_CONFIGURATION_CLUSTER, 0x0020) // Read the Battery Level + return zigbee.readAttribute(0x0001, 0x0020) // Read the Battery Level } -def refresh() -{ +def refresh() { log.debug "refresh temperature, humidity, and battery" - return zigbee.readAttribute(0xFC45, 0x0000, ["mfgCode": 0xC2DF]) + // Original firmware + return zigbee.readAttribute(0xFC45, 0x0000, ["mfgCode": 0x104E]) + // New firmware + zigbee.readAttribute(0xFC45, 0x0000, ["mfgCode": 0xC2DF]) + // Original firmware zigbee.readAttribute(zigbee.TEMPERATURE_MEASUREMENT_CLUSTER, 0x0000) + - zigbee.readAttribute(zigbee.POWER_CONFIGURATION_CLUSTER, 0x0020) + zigbee.readAttribute(zigbee.POWER_CONFIGURATION_CLUSTER, 0x0020) + + zigbee.configureReporting(0xFC45, 0x0000, DataType.INT16, 30, 3600, 100) + + zigbee.batteryConfig() + + zigbee.temperatureConfig(30, 300) } def configure() { @@ -271,10 +141,8 @@ def configure() { sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID]) log.debug "Configuring Reporting and Bindings." - def humidityConfigCmds = zigbee.configureReporting(0xFC45, 0x0000, DataType.INT16, 30, 3600, 0x0064) // temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity // battery minReport 30 seconds, maxReportTime 6 hrs by default - return refresh() + humidityConfigCmds + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) // send refresh cmds as part of config + return refresh() } -