Laundry Monitor for washer that does not drop to zero (most of the time)

Here’s the situation… for months now I’ve been using a smartapp cobbled together that combined features from contributions by @sudarkoff, @bmmiller, and @byoung (included below), and using it with an Aeon DSC06106 and the device handler by @jpansarasa. It all worked great until we got a new washer. The old washer had a minimum 4W running wattage and would turn itself off shortly after finishing a cycle, at which point the power draw dropped to zero. Perfect! Our new washer has a minimum running wattage of 1W, BUT toggles down to zero watts briefly every 13 minutes! It then goes back to 1W (even when off) and repeats the 13min cycle until we start a new load or turn off the Aeon switch (which happens when mode switches to Night, using ‘Lighting Automation’).

With the new washer, setting ““Minimum running wattage”” to 1 results in the notifications being delayed 13 minutes. Thinking there might have been some slight decimal fraction of difference in power draw between washer off and the several times during a wash cycle where power dips to 1W, I tried setting "“Minimum running wattage” to 1.01W. This eliminated the 13 minute delay, but introduced a false alarm every time the cycle dipped to an indicated 1W.

I think what is needed is a settable timer test to see if the 1W state has been held at least x minutes. I think 1.5 to 2 minutes would be sufficient, and I realize that it will introduce a corresponding delay before notifying, but it’ll be better than 13 minutes or living with the false alarms! I understand the principle of introducing a runin time, but am not familiar enough with Groovy to come up with the syntax to do so or adding it as a preference. Any help, or does anyone have thoughts on a better way to accomplish this?

import groovy.time.* 

definition(
    name:	"Laundry Monitor II", namespace: "HDFLucky", author: "Mr. Lucky",
    description: "This application is a modification of the SmartThings 'Laundry Monitor' SmartApp. Instead of using a vibration sensor, it utilizes Power (wattage) sensing.",
    category:	"My Apps",
    iconUrl:	"https://dl.dropboxusercontent.com/u/52901840/laundry.png",
    iconX2Url:	"https://dl.dropboxusercontent.com/u/52901840/laundry@2x.png",
    iconX3Url:	"https://dl.dropboxusercontent.com/u/52901840/laundry@3x.png")

preferences {
	section("Notify When Cycle Has Stopped"){
		input "sensor1", "capability.powerMeter", multiple: false, required: true
	}
    
	section("Notifications") {
		input "sendPushMessage", "bool", title: "Push notification(s)?"
		input "phone1", "phone", title: "Send a text message (enter tel. #)?", required: false
		input "phone2", "phone", title: "Also send a text message to:", required: false
		input "speech", "capability.speechSynthesis", title:"Speak message via:", multiple: true, required: false
		input "message", "text", title: "Notification message:", required: true
	}
    
	section("Flash or Turn On These Lights") {
		input "switches", "capability.switch", title: "Which?", multiple: true, required: false
		input "lightMode", "enum", options: ["Flash Lights", "Turn On Lights"], title: "Action?", required: false
	}
    
	section("Appliance Settings") {
		input "minimumWattage", "decimal", title: "Minimum running wattage", defaultValue: 10, required: false
	}
}

def installed() {
	log.debug "Installed with settings: ${settings}"
    
	initialize()
}

def updated() {
	log.debug "Updated with settings: ${settings}"
    
	unsubscribe()
	initialize()
}

def initialize() {
	subscribe(sensor1, "power", powerInputHandler)
}

def powerInputHandler(evt) {
    def latestPower = sensor1.currentValue("power")
    log.trace "Power: ${latestPower}W"
    
    if (!atomicState.isRunning && latestPower > minimumWattage) {
    	atomicState.isRunning = true
    	atomicState.startedAt = now()
        atomicState.stoppedAt = null
        log.trace "Cycle started."
    } else if (atomicState.isRunning && latestPower < minimumWattage) {
    	atomicState.isRunning = false
        atomicState.stoppedAt = now()  
        log.debug "startedAt: ${atomicState.startedAt}, stoppedAt: ${atomicState.stoppedAt}"                    
        log.info message
        
        if (phone1) {
            sendSms phone1, message
        } else {
            sendPush message
        }
        
        if (phone2) {
            sendSms phone2, message
        }
        
        if (speech) {
            speechAlert(message)
        }
        
        if (switches) {
		if (lightMode?.equals("Turn On Lights")) {
			switches.on()
		} else { 
			flashLights()
		}
        }
        
    } else {
    	// Do Nothing, no change in either direction
    }
}

private speechAlert(msg) {
  speech.speak(msg)
}

private flashLights() {
  def doFlash = true
  def onFor = onFor ?: 1001
  def offFor = offFor ?: 999
  def numFlashes = numFlashes ?: 3
  log.debug "LAST ACTIVATED IS: ${atomicState.lastActivated}"
	if (atomicState.lastActivated) {
	  def elapsed = now() - atomicState.lastActivated
	  def sequenceTime = (numFlashes + 1) * (onFor + offFor)
	  doFlash = elapsed > sequenceTime
	  // log.debug "DO FLASH: $doFlash, ELAPSED: $elapsed, LAST ACTIVATED: ${atomicState.lastActivated}"
  }
  
  if (doFlash) {
	log.debug "FLASHING $numFlashes times"
	atomicState.lastActivated = now()
	log.debug "LAST ACTIVATED SET TO: ${atomicState.lastActivated}"
	def initialActionOn = switches.collect{it.currentSwitch != "on"}
	def delay = 1L
	numFlashes.times {
		log.trace "Switch on after $delay msec"
		switches.eachWithIndex {s, i ->
			if (initialActionOn[i]) {
				s.on(delay:delay)
			} else {
				s.off(delay:delay)
			}
		}
		delay += onFor
		log.trace "Switch off after $delay msec"
		switches.eachWithIndex {s, i ->
			if (initialActionOn[i]) {
				s.off(delay: delay)
			} else {
				s.on(delay:delay)
			}
		}
		delay += offFor
	}
  }
}
1 Like

What do you have your report interval at currently? Unless you have it at 1 minute, a 1.5-2min delay isn’t going to help you at all. For what it’s worth, a 1min interval might be a little quick and part of your problem. I set mine at 5min on a brand new washer and it’s been 100% accurate on notifications.

Regardless though, it shouldn’t be too difficult to add a delay in, I’ll see if I can get to it today.

Thank you, Brandon. Yes, reporting interval is set to 1 minute. I thought of changing it to a higher value, but there’s no guarantee that the low power event wouldn’t happen near the trailing edge of whatever value was selected (unless it’s a large number… like 13 :grin: ).

Brandon, I’ve been tinkering with your app this morning, trying to add a capability.switch to it, would it be hard for you to add it while working on the delay?

You’re right, that’s why I didn’t press the issue too much, heh.

Give this a try, it is untested:

https://github.com/bmmiller/SmartThings/blob/master/smartapp.laundrymonitor/smartapp.laundrymonitor.groovy

2 Likes

capability.switch is something that goes on a device type, not a smart app.

Ok, then a capability to turn a switch on/off :slight_smile:

That isn’t the purpose of a SmartApp though, you don’t have “buttons” to interact with in a SmartApp. Liken it to a light switch, the button’s you see to turn it on and off or defined on the device type of the light switch.

You would have to go to the “Thing” to turn it on or off. If you open up the SmartApp, you’ll notice you just see a bunch of settings and configuration options. Not anything that actually directly interacts with the device.

My guess is you’re just confused a bit and I really don’t know which device type you are using for your device, but probably the default SmartThings ones. If you use the one that @Mr_Lucky mentioned, written by @jpansarasa you can turn it on/off from within the “Thing” area. Look here: Aeon Labs Smart Energy Switch DSC06

I am not talking about the meter itself. I am talking about having an option to turn a light on when the cycle ends. I use a virtual switch to notify through Sonos when the laundry is done…

Ah, I see. Yeah, that can be done pretty easily, hold please.

1 Like

Will do, but can’t test until someone is home later today. We’re expecting house guests this weekend though, so there should be plenty of opportunity!

I see what you did with the code, and it looks promising. One thing though… your second phone number notification won’t work as-written because you are calling them both “phone” (i.e., the second # overwrites the first, resulting in two notifications to the second number). See what I did in my modified code.

That functionality is already in my modified code (see the first post).

I noticed that as well, heh, and removed it when adding the switch ability for @SBDOBRESCU

I actually don’t think there would have been multiple notifications, it would have just ignored the first number set, being confusing to users.

Check again now.

1 Like

And here goes my few hours I spent this morning for nothing :slight_smile: Thanks Brandon! And @Mr_Lucky, sorry I didn’t see your app, I wouldn’t have spent time this morning tinkering with Brandon’s app, if I did…oh well…That’s what I love about this community!

As I dug into it deeper, I saw that you are correct. But in your original code that I based mine on, it did result in duplicate notifications to the second number (much to the chagrin of my wife, whose # I had entered). Either that, or I introduced an error when blending the source code from various individuals here. :flushed:

@SBDOBRESCU, welcome to the club that no one want to be a member of - the SOciety of WAsted Time (SO WAT!), of which I am a charter member. And ditto on your comment about this community!

1 Like

Brandon, just wanted to circle back and say thanks. I incorporated your changes into my modified smartapp and it works perfectly with minimumWattage set to 1.5W and minimumOffTime set to 90 secs. I don’t think I would have ever gotten all those nested if/else statements correct on my own! :raised_hand:

1 Like

Glad it worked out for you. Just a small heads up though:

if ((now() - atomicState.midCycleTime)/1000 > minimumOffTime)

I didn’t have the /1000 in the code I first posted on and added it a few hours later. Which are you using? I ask this because I had assumed the now() subtraction resulted in an answer in seconds, and later read that it might provide a result in milliseconds, thus the /1000. If it’s working for you, which are you using? If you didn’t have the /1000 in there, and it works with milliseconds, the delay would be almost instant, and thus, not effective. If you didn’t have the /1000 in there, and it does work, then maybe my information is bad and is does result in seconds. Am I making sense?

Hmm… I guess I’m using the original:

if (now() - atomicState.midCycleTime > minimumOffTime)

I.e., no /1000, and it was definitely an improvement. :confused:

Finally got a chance to test this, works great! Now I can turn my notifications on for my wife, because she didn’t like the multiple notifications that we were getting…Thank you!

Hey @bmmiller thanks for the app! I needed to make a small modification to work with with our washer, otherwise it would have false notifications when it was first turned on. This adds hysteresis to the wattage threshold.

   section("System Variables") {
    	input "offWattage", "decimal", title: "Off wattage threshold", required: true, defaultValue: 15
    	input "onWattage", "decimal", title: "On wattage threshold", required: true, defaultValue: 75
        input "minimumOffTime", "decimal", title: "Minimum amount of below wattage time to trigger off (secs)", required: true, defaultValue: 60
        input "message", "text", title: "Notification message", description: "Laundry is done!", required: true
    }

and

   if (!atomicState.isRunning && latestPower > onWattage) {
    	atomicState.isRunning = true
		atomicState.startedAt = now()
        atomicState.stoppedAt = null
        atomicState.midCycleCheck = null
        log.trace "Cycle started."
    } else if (atomicState.isRunning && latestPower < offWattage) {