/** * Third Reality Soil Moisture Sensor * * Author: Echistas * Date: 2023-10-21 */ metadata { definition(name: "Third Reality Soil Moisture Sensor", namespace: "Echistas", author: "Echistas") { capability "Battery" capability "Temperature Measurement" capability "Refresh" capability "Configuration" // Custom attribute for soil moisture attribute "soilMoisture", "number" // Removed "Relative Humidity Measurement" capability fingerprint profileId: "0104", endpointId: "01", inClusters: "0000,FF01,0001,0020,0402,0405", outClusters: "0019", manufacturer: "Third Reality, Inc", model: "3RSM0147Z", deviceJoinName: "Zigbee Soil Moisture Sensor" } preferences { input name: "debugLogging", type: "bool", title: "Enable debug logging", defaultValue: true input name: "reportingInterval", type: "number", title: "Reporting Interval (minutes)", defaultValue: 30, range: "1..240" } } def parse(String description) { if (debugLogging) log.debug "Parsing: ${description}" def descMap = zigbee.parseDescriptionAsMap(description) if (debugLogging) log.debug "descMap: ${descMap}" def result = [] if (descMap.clusterInt == 0x0402 && descMap.attrInt == 0x0000) { // Temperature Measurement def tempC = zigbee.convertHexToInt(descMap.value) / 100.0 def tempF = (tempC * 9/5) + 32 tempF = (Math.round(tempF * 10)) / 10.0 // Round to 1 decimal place if (debugLogging) log.debug "Temperature: ${tempC}°C / ${tempF}°F" result << createEvent(name: "temperature", value: tempF, unit: "°F") } else if (descMap.clusterInt == 0x0405 && descMap.attrInt == 0x0000) { // Soil Moisture Measurement def moisture = zigbee.convertHexToInt(descMap.value) / 100.0 moisture = (Math.round(moisture * 10)) / 10.0 // Round to 1 decimal place if (debugLogging) log.debug "Soil Moisture: ${moisture}%" // Create event for custom soilMoisture attribute result << createEvent(name: "soilMoisture", value: moisture, unit: "%") // Removed the line creating the humidity event } else if (descMap.clusterInt == 0x0001 && descMap.attrInt == 0x0021) { // Battery Percentage Remaining def batteryValue = zigbee.convertHexToInt(descMap.value) def batteryLevel = batteryValue / 2 // Each unit represents 0.5% if (debugLogging) log.debug "Battery Level: ${batteryLevel}%" result << createEvent(name: "battery", value: batteryLevel, unit: "%") } else { if (debugLogging) log.debug "Unhandled attribute: ${descMap}" } return result } def configure() { if (debugLogging) log.debug "Configuring Reporting and Bindings." def minReportingInterval = 30 // Minimum reporting interval in seconds def maxReportingInterval = ((reportingInterval != null ? reportingInterval.toInteger() : 30) * 60).toInteger() // Convert minutes to seconds def batteryReportingInterval = 21600 // 6 hours in seconds if (debugLogging) log.debug "Max Reporting Interval: ${maxReportingInterval} seconds" def configCmds = [] // Ensure all intervals are Integers minReportingInterval = minReportingInterval.toInteger() batteryReportingInterval = batteryReportingInterval.toInteger() // Temperature Measurement Cluster (0x0402) configCmds += zigbee.configureReporting(0x0402, 0x0000, 0x29, minReportingInterval, maxReportingInterval, 50) // Reportable Change 0.5°C // Humidity Measurement Cluster (0x0405) used for Soil Moisture configCmds += zigbee.configureReporting(0x0405, 0x0000, 0x21, minReportingInterval, maxReportingInterval, 50) // Reportable Change 0.5% // Battery Percentage Remaining (0x0001, 0x0021) configCmds += zigbee.configureReporting(0x0001, 0x0021, 0x20, 3600, batteryReportingInterval, 1) // Reportable Change 1% return configCmds + refresh() } def refresh() { if (debugLogging) log.debug "Refreshing Values." def refreshCmds = [] refreshCmds += zigbee.readAttribute(0x0402, 0x0000) // Temperature refreshCmds += zigbee.readAttribute(0x0405, 0x0000) // Soil Moisture refreshCmds += zigbee.readAttribute(0x0001, 0x0021) // Battery Percentage Remaining return refreshCmds } def installed() { log.info "Installing." configure() } def updated() { log.info "Updating preferences." if (debugLogging) { log.debug "Debug logging is enabled." runIn(1800, logsOff) // Turn off debug logging after 30 minutes } else { unschedule(logsOff) } configure() } def logsOff() { log.warn "Debug logging disabled." device.updateSetting("debugLogging", [value: "false", type: "bool"]) }