mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-06-01 14:21:44 +01:00
4ca07ce9db
Leak Detector SmartApp allows uses to create custom rules for turning off their water and getting notifications from their flow meter. Catch a running toilet and turn off the water automatically. Detect leaky pipes and notify user of leak alert. 5 different trigger types: Continuous flow, accumulated flow, Mode, Time Period, Water Valve Status. Parent & Child app need to be installed as a pair (parent first).
320 lines
11 KiB
Groovy
320 lines
11 KiB
Groovy
/**
|
|
* Leak Detector for FortrezZ Water Meter
|
|
*
|
|
* Copyright 2016 Daniel Kurin
|
|
*
|
|
*/
|
|
definition(
|
|
name: "FortrezZ Leak Detector",
|
|
namespace: "fortrezz",
|
|
author: "FortrezZ, LLC",
|
|
description: "Use the FortrezZ Water Meter to identify leaks in your home's water system.",
|
|
category: "Green Living",
|
|
iconUrl: "http://swiftlet.technology/wp-content/uploads/2016/05/logo-square-200-1.png",
|
|
iconX2Url: "http://swiftlet.technology/wp-content/uploads/2016/05/logo-square-500.png",
|
|
iconX3Url: "http://swiftlet.technology/wp-content/uploads/2016/05/logo-square.png")
|
|
|
|
|
|
preferences {
|
|
page(name: "page2", title: "Select device and actions", install: true, uninstall: true)
|
|
}
|
|
|
|
def page2() {
|
|
dynamicPage(name: "page2") {
|
|
section("Choose a water meter to monitor:") {
|
|
input(name: "meter", type: "capability.energyMeter", title: "Water Meter", description: null, required: true, submitOnChange: true)
|
|
}
|
|
|
|
if (meter) {
|
|
section {
|
|
app(name: "childRules", appName: "Leak Detector", namespace: "fortrezz", title: "Create New Leak Detector...", multiple: true)
|
|
}
|
|
}
|
|
|
|
section("Send notifications through...") {
|
|
input(name: "pushNotification", type: "bool", title: "SmartThings App", required: false)
|
|
input(name: "smsNotification", type: "bool", title: "Text Message (SMS)", submitOnChange: true, required: false)
|
|
if (smsNotification)
|
|
{
|
|
input(name: "phone", type: "phone", title: "Phone number?", required: true)
|
|
}
|
|
input(name: "minutesBetweenNotifications", type: "number", title: "Minutes between notifications", required: true, defaultValue: 60)
|
|
}
|
|
|
|
log.debug "there are ${childApps.size()} child smartapps"
|
|
def childRules = []
|
|
childApps.each {child ->
|
|
//log.debug "child ${child.id}: ${child.settings()}"
|
|
childRules << [id: child.id, rules: child.settings()]
|
|
}
|
|
state.rules = childRules
|
|
//log.debug("Child Rules: ${state.rules} w/ length ${state.rules.toString().length()}")
|
|
log.debug "Parent Settings: ${settings}"
|
|
}
|
|
}
|
|
|
|
def installed() {
|
|
log.debug "Installed with settings: ${settings}"
|
|
|
|
initialize()
|
|
}
|
|
|
|
def updated() {
|
|
log.debug "Updated with settings: ${settings}"
|
|
|
|
unsubscribe()
|
|
initialize()
|
|
}
|
|
|
|
def initialize() {
|
|
subscribe(meter, "cumulative", cumulativeHandler)
|
|
subscribe(meter, "gpm", gpmHandler)
|
|
log.debug("Subscribing to events")
|
|
}
|
|
|
|
def cumulativeHandler(evt) {
|
|
//Date Stuff
|
|
def daysOfTheWeek = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
|
|
def today = new Date()
|
|
today.clearTime()
|
|
Calendar c = Calendar.getInstance();
|
|
c.setTime(today);
|
|
int dow = c.get(Calendar.DAY_OF_WEEK);
|
|
def dowName = daysOfTheWeek[dow-1]
|
|
|
|
def gpm = meter.latestValue("gpm")
|
|
def cumulative = new BigDecimal(evt.value)
|
|
log.debug "Cumulative Handler: [gpm: ${gpm}, cumulative: ${cumulative}]"
|
|
def rules = state.rules
|
|
rules.each { it ->
|
|
def r = it.rules
|
|
def childAppID = it.id
|
|
//log.debug("Rule: ${r}")
|
|
switch (r.type) {
|
|
case "Mode":
|
|
log.debug("Mode Test: ${location.currentMode} in ${r.modes}... ${findIn(r.modes, location.currentMode)}")
|
|
if (findIn(r.modes, location.currentMode))
|
|
{
|
|
log.debug("Threshold:${r.gpm}, Value:${gpm}")
|
|
if(gpm > r.gpm)
|
|
{
|
|
sendNotification(childAppID, gpm)
|
|
if(r.dev)
|
|
{
|
|
//log.debug("Child App: ${childAppID}")
|
|
def activityApp = getChildById(childAppID)
|
|
activityApp.devAction(r.command)
|
|
}
|
|
}
|
|
}
|
|
break
|
|
|
|
case "Time Period":
|
|
log.debug("Time Period Test: ${r}")
|
|
def boolTime = timeOfDayIsBetween(r.startTime, r.endTime, new Date(), location.timeZone)
|
|
def boolDay = !r.days || findIn(r.days, dowName) // Truth Table of this mess: http://swiftlet.technology/wp-content/uploads/2016/05/IMG_20160523_150600.jpg
|
|
def boolMode = !r.modes || findIn(r.modes, location.currentMode)
|
|
|
|
if(boolTime && boolDay && boolMode)
|
|
{
|
|
if(gpm > r.gpm)
|
|
{
|
|
sendNotification(childAppID, gpm)
|
|
if(r.dev)
|
|
{
|
|
def activityApp = getChildById(childAppID)
|
|
activityApp.devAction(r.command)
|
|
}
|
|
}
|
|
}
|
|
break
|
|
|
|
case "Accumulated Flow":
|
|
log.debug("Accumulated Flow Test: ${r}")
|
|
def boolTime = timeOfDayIsBetween(r.startTime, r.endTime, new Date(), location.timeZone)
|
|
def boolDay = !r.days || findIn(r.days, dowName) // Truth Table of this mess: http://swiftlet.technology/wp-content/uploads/2016/05/IMG_20160523_150600.jpg
|
|
def boolMode = !r.modes || findIn(r.modes, location.currentMode)
|
|
|
|
if(boolTime && boolDay && boolMode)
|
|
{
|
|
def delta = 0
|
|
if(state["accHistory${childAppID}"] != null)
|
|
{
|
|
delta = cumulative - state["accHistory${childAppID}"]
|
|
}
|
|
else
|
|
{
|
|
state["accHistory${childAppID}"] = cumulative
|
|
}
|
|
log.debug("Currently in specified time, delta from beginning of time period: ${delta}")
|
|
|
|
if(delta > r.gallons)
|
|
{
|
|
sendNotification(childAppID, delta)
|
|
if(r.dev)
|
|
{
|
|
def activityApp = getChildById(childAppID)
|
|
activityApp.devAction(r.command)
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
log.debug("Outside specified time, saving value")
|
|
state["accHistory${childAppID}"] = cumulative
|
|
}
|
|
break
|
|
|
|
case "Continuous Flow":
|
|
log.debug("Continuous Flow Test: ${r}")
|
|
def contMinutes = 0
|
|
def boolMode = !r.modes || findIn(r.modes, location.currentMode)
|
|
|
|
if(gpm != 0)
|
|
{
|
|
if(state["contHistory${childAppID}"] == [])
|
|
{
|
|
state["contHistory${childAppID}"] = new Date()
|
|
}
|
|
else
|
|
{
|
|
//def td = now() - Date.parse("yyyy-MM-dd'T'HH:mm:ss'Z'", state["contHistory${childAppID}"]).getTime()
|
|
//log.debug(state["contHistory${childAppID}"])
|
|
//def historyDate = new Date(state["contHistory${childAppID}"])
|
|
def historyDate = new Date().parse("yyyy-MM-dd'T'HH:mm:ssZ", state["contHistory${childAppID}"])
|
|
def td = now() - historyDate.getTime()
|
|
//log.debug("Now minus then: ${td}")
|
|
contMinutes = td/60000
|
|
log.debug("Minutes of constant flow: ${contMinutes}, since ${state["contHistory${childAppID}"]}")
|
|
}
|
|
}
|
|
|
|
if(contMinutes > r.flowMinutes && boolMode)
|
|
{
|
|
sendNotification(childAppID, Math.round(contMinutes))
|
|
if(r.dev)
|
|
{
|
|
def activityApp = getChildById(childAppID)
|
|
activityApp.devAction(r.command)
|
|
}
|
|
}
|
|
break
|
|
|
|
case "Water Valve Status":
|
|
log.debug("Water Valve Test: ${r}")
|
|
def child = getChildById(childAppID)
|
|
//log.debug("Water Valve Child App: ${child.id}")
|
|
if(child.isValveStatus(r.valveStatus))
|
|
{
|
|
if(gpm > r.gpm)
|
|
{
|
|
sendNotification(childAppID, gpm)
|
|
}
|
|
}
|
|
break
|
|
|
|
case "Switch Status":
|
|
break
|
|
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
def gpmHandler(evt) {
|
|
//Date Stuff
|
|
def daysOfTheWeek = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
|
|
def today = new Date()
|
|
today.clearTime()
|
|
Calendar c = Calendar.getInstance();
|
|
c.setTime(today);
|
|
int dow = c.get(Calendar.DAY_OF_WEEK);
|
|
def dowName = daysOfTheWeek[dow-1]
|
|
|
|
def gpm = evt.value
|
|
def cumulative = meter.latestValue("cumulative")
|
|
log.debug "GPM Handler: [gpm: ${gpm}, cumulative: ${cumulative}]"
|
|
def rules = state.rules
|
|
rules.each { it ->
|
|
def r = it.rules
|
|
def childAppID = it.id
|
|
switch (r.type) {
|
|
|
|
// This is down here because "cumulative" never gets sent in the case of 0 change between messages
|
|
case "Continuous Flow":
|
|
log.debug("Continuous Flow Test (GPM): ${r}")
|
|
def contMinutes = 0
|
|
|
|
if(gpm == "0.0")
|
|
{
|
|
state["contHistory${childAppID}"] = []
|
|
}
|
|
//log.debug("contHistory${childAppID} is ${state["contHistory${childAppID}"]}")
|
|
break
|
|
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
}
|
|
def sendNotification(device, gpm)
|
|
{
|
|
def set = getChildById(device).settings()
|
|
def msg = ""
|
|
if(set.type == "Accumulated Flow")
|
|
{
|
|
msg = "Water Flow Warning: \"${set.ruleName}\" is over threshold at ${gpm} gallons"
|
|
}
|
|
else if(set.type == "Continuous Flow")
|
|
{
|
|
msg = "Water Flow Warning: \"${set.ruleName}\" is over threshold at ${gpm} minutes"
|
|
}
|
|
else
|
|
{
|
|
msg = "Water Flow Warning: \"${set.ruleName}\" is over threshold at ${gpm}gpm"
|
|
}
|
|
log.debug(msg)
|
|
|
|
// Only send notifications as often as the user specifies
|
|
def lastNotification = 0
|
|
if(state["notificationHistory${device}"])
|
|
{
|
|
lastNotification = Date.parse("yyyy-MM-dd'T'HH:mm:ssZ", state["notificationHistory${device}"]).getTime()
|
|
}
|
|
def td = now() - lastNotification
|
|
log.debug("Last Notification at ${state["notificationHistory${device}"]}... ${td/(60*1000)} minutes")
|
|
if(td/(60*1000) > minutesBetweenNotifications.value)
|
|
{
|
|
log.debug("Sending Notification")
|
|
if (pushNotification)
|
|
{
|
|
sendPush(msg)
|
|
state["notificationHistory${device}"] = new Date()
|
|
}
|
|
if (smsNotification)
|
|
{
|
|
sendSms(phone, msg)
|
|
state["notificationHistory${device}"] = new Date()
|
|
}
|
|
}
|
|
}
|
|
|
|
def getChildById(app)
|
|
{
|
|
return childApps.find{ it.id == app }
|
|
}
|
|
|
|
def findIn(haystack, needle)
|
|
{
|
|
def result = false
|
|
haystack.each { it ->
|
|
//log.debug("findIn: ${it} <- ${needle}")
|
|
if (needle == it)
|
|
{
|
|
//log.debug("Found needle in haystack")
|
|
result = true
|
|
}
|
|
}
|
|
return result
|
|
} |