Leaksmart water valve?


/**
 *  LeakSMART Water Valve v 1.3
 *     (Mode: 8830000L)
 *  
 *  Capabilities:
 *      Configuration, Refresh, Switch, Valve, Polling
 *
 *  Author: 
 *     Kevin LaFramboise (krlaframboise)
 *
 *  Url to Documentation:
 *      https://community.smartthings.com/t/release-leaksmart-water-valve/48669
 *
 *  Changelog:
 *
 *    1.3 (10/23/2017)
 *      - Added support for valve attribute events.
 *
 *    1.2.1 (08/12/2017)
 *      - Create switch events when the open/close state changes.
 *
 *    1.2.1 (08/12/2017)
 *      - Create switch events when the open/close state changes.
 *
 *    1.2 (08/20/2016)
 *      - Changed lower battery limit to 5.0
 *
 *    1.1.3 (05/23/2016)
 *      - Changed lower battery limit to 5.5
 *
 *    1.1.2 (05/22/2016)
 *      - Added battery capability and tile
 *      - Added debug logging for battery map.
 *      - Changed poll method  read instead of configure.
 *      - Changed minimum batterr reporting interval to 10 minutes
 *        to avoid duplicates.  
 *      - Changed upper battery voltage to 6.0.
 *
 *    1.0.3 (05/22/2016)
 *      - Initial Release
 *      - Bug fixes
 *
 *
 *  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 *  in compliance with the License. You may obtain a copy of the License at:
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
 *  on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
 *  for the specific language governing permissions and limitations under the License.
 *
 */

metadata {
	definition (name: "LeakSmart Water Valve", namespace: "krlaframboise", author: "Kevin LaFramboise") {
		capability "Actuator"
		capability "Battery"
		capability "Configuration"
		capability "Refresh"
		capability "Switch"
		capability "Valve"
		capability "Polling"

		attribute "lastPoll", "number"	
		
		fingerprint profileId: "0104", inClusters: "0000,0001,0003,0006,0020,0B02", outClusters: "0019"
	}
	
	preferences {
		input "debugOutput", "bool", 
			title: "Enable debug logging?", 
			defaultValue: true, 
			displayDuringSetup: false, 
			required: false
	}
	
	tiles(scale: 2) {
		standardTile("contact", "device.contact", width: 4, height: 4, canChangeIcon: true) {
			state "closed", 
				label:'Closed', 
				action: "valve.open", 
				nextState: "opening", 
				icon:"st.valves.water.closed", 
				backgroundColor:"#e86d13"
			state "opening", 
				label:'Opening', 
				action: "valve.close", 
				icon:"st.valves.water.open", 
				backgroundColor:"#ffe71e"
			state "open", 
				label:'Open', 
				action: "valve.close", 
				nextState: "closing",
				icon:"st.valves.water.open", 
				backgroundColor:"#53a7c0"
			state "closing", 
				label:'Closing', 
				action: "valve.open", 
				icon:"st.valves.water.open", 
				backgroundColor:"#ffe71e"
		}
		standardTile("refresh", "device.refresh", width: 2, height: 2, canChangeIcon: true) {
			state "default", 
				label: 'Refresh', 
				action: "refresh.refresh", 
				icon: ""			
		}		
		valueTile("battery", "device.battery", width: 2, height: 2, canChangeIcon: true) {
			state "battery", 
				label: 'Battery ${currentValue}%',
				unit: "%"
				icon: ""			
		}
		main "contact"
		details(["contact", "refresh", "battery"])
	}
}

def updated() {
	if (!state.configured) {		
		return response(configure())
	}
}

def parse(String description) {
	def result = []
	def evt = zigbee.getEvent(description)
	if (evt) {
		if (evt.name == "switch") {
			def val = (evt.value == "on") ? "open" : "closed"
			logDebug "Valve is $val"
			result << createEvent(name: "contact", value: val)
			result << createEvent(name: "valve", value: val)
			result << createEvent(name: "switch", value: evt.value, displayed:false)
			result << createEvent(name: "lastPoll",value: new Date().time, isStateChange: true, displayed: false)
		}
		else {
			logDebug "Ignored Event: $evt"
		}
		result << createEvent(evt)
	}
	else {
		def map = zigbee.parseDescriptionAsMap(description)
		if (map) {
			if (map.clusterInt == 1) {
				def batteryLevel = getBatteryLevel(zigbee.convertHexToInt(map.value))
				logDebug "Battery Level is ${batteryLevel}%\nMap: $map"
				result << createEvent(name: "battery", value: batteryLevel, unit:"%")
			}
			else {
				logDebug "Ignored Map: $map"
			}
		}
		else { 
			logDebug "Ignored Description: $description"
		}
	}	
	return result
}

private getBatteryLevel(rawValue) {
	def maxVolts = 6.0
	def minVolts = 5.0
	def volts = (rawValue / 10)
	def batteryPercentages = (volts - minVolts) / (maxVolts - minVolts)	
	def batteryLevel = (int) batteryPercentages * 100
	if (batteryLevel > 100) {
		return 100
	}
	else if (batteryLevel < 0) {
		return 0
	}
	else {
		return batteryLevel
	}	
}

def on() {
	open()
}

def off() {
	close()
}

def open() {
	logDebug "Opening"
	zigbee.on()
}

def close() {
	logDebug "Closing"
	zigbee.off()
}

def poll() {
	def minimumPollMinutes = (3 * 60) // 3 Hours
	def lastPoll = device.currentValue("lastPoll")
	if ((new Date().time - lastPoll) > (minimumPollMinutes * 60 * 1000)) {
		logDebug "Poll: Refreshing because lastPoll was more than ${minimumPollMinutes} minutes ago."
		return refresh()
	}
	else {
		logDebug "Poll: Skipped because lastPoll was within ${minimumPollMinutes} minutes"
	}
}

def refresh() {
	logDebug "Refreshing"	
	return zigbee.onOffRefresh() + 
		getBatteryReport() +
		zigbee.onOffConfig() + 
		configureBatteryReporting()
}

def configure() {
	logDebug "Configuring Reporting and Bindings."
	state.configured = true
	return zigbee.onOffConfig() + 
		configureBatteryReporting() +
		zigbee.onOffRefresh() +
		getBatteryReport()
}

private configureBatteryReporting() {
	def minSeconds = (10 * 60) // 10 Minutes
	def maxSeconds = (4 * 60 * 60) // 4 Hours	
	zigbee.configureReporting(0x0001, 0x0020, 0x20, minSeconds, maxSeconds, 0x01)
}

private getSwitchReport() {
	return readAttribute(0x0006, 0x0000)
}

private getBatteryReport() {
	zigbee.readAttribute(0x0001, 0x0020)
}

private logDebug(msg) {
	if (settings.debugOutput != false) {
		log.debug "$msg"
	}
}
2 Likes