/**
* Laundry Monitor-Timer
*
* by Cor Dikland
*
* 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.
*
*/
import groovy.time.*
definition(
name: "Laundry Monitor-Timer",
namespace: "cdikland",
author: "Cor Dikland",
description: "This application is a modification of the Brandon Miller Laundry Monitor SmartApp. Timer is set each time watts fall below a fixed setting. If timer expires before watts rise above, Laundry is done.",
category: "Convenience",
iconUrl: "http://www.vivevita.com/wp-content/uploads/2009/10/recreation_sign_laundry.png",
iconX2Url: "http://www.vivevita.com/wp-content/uploads/2009/10/recreation_sign_laundry.png")
preferences {
section("Tell me when this washer/dryer has stopped..."){
input "sensor1", "capability.powerMeter"
}
section("Notifications") {
input "sendPushMessage", "bool", title: "Push Notifications?"
input "phone", "phone", title: "Send a text message?", required: false
}
section("System Variables"){
input "minimumWattage", "decimal", title: "Minimum running wattage", required: false, defaultValue: 50
input "timesMinimumWExceeded", "decimal", title: "No. of Times Min. Watts Events Occur Before initiating 'Cycle Started' ", required: false, defaultValue: 3
input "minimumOffTime", "number", title: "Minimum amount of below wattage time to trigger off (Secs)", required: false, defaultValue: 300
input "message", "text", title: "Notification message", description: "Laundry is done!", required: true
}
// Usage of the next two pref attributes have not been tested
section ("Additionally", hidden: hideOptionsSection(), hideable: true) {
input "switches", "capability.switch", title: "Turn on these switches?", required:false, multiple:true
input "speech", "capability.speechSynthesis", title:"Speak message via: ", multiple: true, required: false
}
}
def installed() {
log.debug "Installed with settings: ${settings}"
initialize()
}
def updated() {
log.debug "Updated with settings: ${settings}"
unsubscribe()
initialize()
}
def initialize() {
unschedule()
atomicState.isRunning = false
atomicState.jobScheduled=false
atomicState.minOffTimeMs=minimumOffTime * 1000
atomicState.aboveMinimum=0
subscribe(sensor1, "power", powerInputHandler)
subscribe(sensor1, "health", healthInputHandler)
// runNotifyAt(now() + atomicState.minOffTimeMs)
}
def healthInputHandler(evt) {
log.debug "Health check"
}
def powerInputHandler(evt) {
def latestPower = sensor1.currentValue("power")
log.debug "Power = $latestPower"
if (latestPower > minimumWattage) {
log.debug "latestPower > minimumWattage === $latestPower W ==="
atomicState.midCycleTime = now() // the latest time when wattage was above the minimum
if (atomicState.jobScheduled== true) {
log.trace "Power: ${latestPower}W is above the set minimum. Previous scheduled job cancelled."
unschedule()
atomicState.jobScheduled=false
}
}
else { // reset aboveMinimum count when wattage falls below the minimum
atomicState.aboveMinimum=0
}
if (!atomicState.isRunning && latestPower > minimumWattage) {
atomicState.aboveMinimum=atomicState.aboveMinimum+1
if (atomicState.aboveMinimum==timesMinimumWExceeded) {
cycleStarted()
atomicState.aboveMinimum=0 // do I really need this???
}
/*
atomicState.isRunning = true
atomicState.startedAt = now()
atomicState.stoppedAt = null
atomicState.midCycleCheck = null
log.trace "Cycle started."
*/
}
else if (atomicState.isRunning && latestPower < minimumWattage) {
def elapsed = (now() - atomicState.midCycleTime)/1000 // Time between last time wattage was above (atomicState.midCycleTime) minimum and now when the wattage is below the minimum
log.debug "Current Power: ${latestPower}W. Time under wattage=$elapsed"
if (atomicState.jobScheduled== false) {
runNotifyAt(now() + atomicState.minOffTimeMs)
atomicState.jobScheduled=true
}
}
}
def cycleStarted() {
atomicState.isRunning = true
atomicState.startedAt = now()
atomicState.stoppedAt = null
atomicState.midCycleCheck = null
log.trace "Cycle started."
}
def laundryIsDone() {
atomicState.jobScheduled=false
atomicState.isRunning = false
atomicState.stoppedAt = now()
log.debug "startedAt: ${atomicState.startedAt}, stoppedAt: ${atomicState.stoppedAt}"
log.info message
if (phone) {
sendSms phone, message
sendPush message
}
else {
sendPush message
}
if (switches) {
switches*.on()
}
if (speech) {
speech.speak(message)
}
}
def runNotifyAt(timeMs) {
def date = new Date(timeMs)
runOnce(date, laundryIsDone)
def stamp = new Date(timeMs).format('dd-MMM-yyyy HH:mm:ss',location.timeZone)
log.trace "'laundryIsDone' scheduled to run at ${stamp}"
}
private hideOptionsSection() {
(phone || switches) ? false : true
}