Shower detection (depends on humidity)

I would like to share my rule to detect if someone is taking a shower.

Why? (Ideas)

  • play music
  • start towel heater
  • tell visitor at the door to calm down. I am taking a shower

Issues

  • delay of a few minutes
  • Inaccurate ( < 5% incorrect detections - depends on the config and envirement)

Code (TakeAShower.rules)

rule "TakeAShower"
// This rule switches shower item ON/OFF if shower is detected 
when
	Time cron "0 0/1 * 1/1 * ? *" //every minute
then
	var int nThreshold = 68
	
	if (GroundFloor_MasterBathroom_Humidity.averageSince(now.minusMinutes(5)).doubleValue > nThreshold)
	{
		logInfo("TakeAShower", "Bathroom AVG (5min) > " + nThreshold.toString() + Shower + " - detect shower")
		if (Shower.state == OFF)
		{
			logInfo("TakeAShower", "Switch Shower ON")
			Shower.sendCommand(ON)
		}
	}	
	else  
	{
		logInfo("TakeAShower", "No shower detected")
		Shower.sendCommand(OFF)
	}
	
end
2 Likes

One suggestion, to improve the sampling rate (and lower the delay slightly), would be to trigger the rule on “Item GroundFloor_MasterBathroom_Humidity changed”. This would save up to a minute (i.e. when the item state changes just after the cron triggered rule fires).

You could also activate the Shower item based on rate of change, rather than a moving average. In my experience, the humidity level rises very quickly and at a high rate (10-20% per minute), so that would lead to quicker detection than a 5 minute moving average, while still being pretty resistant to false positives.

2 Likes

Why not stick temperature sensor to the water pipe going to the shower?

Good Idea.
In my case, the water pipe divides in the wall.
A clear water pipe I will only get in the shower.
My personal goal was to detect events based on temperature or humidity.

What about placing a water leakage sensor in the shower

1 Like

I agree with Bartus, triggering the rule by changes to the humidity rather than periodically would improve its detection speed. And measuring the rate of change would probably work faster too, though that will require more calculations.

From a coding style perspective:

  • avoid primitives where possible; they cause loading delays on RPis and other SBCs
  • since nThreshold is a constant, define it as a val, maybe as a global
  • apply Design Pattern: How to Structure a Rule and you can shrink this Rule down to just 3-4 lines of code
val nThreshold = 68

rule "TakeAShower"
when
    Item GroundFloor_MasterBathroom_Humidity changed
then
    var newState = if(GroundFloor_MasterBathroom_Humidity.averageSince(now.minusMinutes(5)) > nThreshold) ON else OFF

    if(Shower.state != newState) {
        logInfo("TakeAShower", "The shower is now " + newState)
        Shower.sendCommand(newState)
    }
end
1 Like

I actually have done something like that. Works perfectly

OK, here is a way to calculate the slope. Let’s use the slope over one minute. Of course this assumes your sensor reports at least once per minute.

You will probably want to experiment with the time window.

// X-axis is the change in humidity
// Y-axis is the change in time, I'll assume the change in time is always 1
val x1 = GroundFloor_MasterBathroom_Humidity.historicState(now.minusMinutes(1)).state as Number
val x2 = GroundFloor_MasterBathroom_Humidity.state as Number
val slope  = 1 /  (x2 - x1)

val newState = if(slope > .1) ON else OFF // 10% rise

You will need to experiment with a good slope threshold as well. A negative slope will indicate the humidity is falling. A positive slope indicates it is raising. The bigger the number, the bigger the value.

3 Likes

Rich, I think you meant:

val slope  = ( (y2 - y1) / 1 ) / 100 //change in y over change in x, x = 1 minute, scaled to percentage

I’m assuming y1 = 1 and y2 = 2 which will always return 1 in the numerator.

I don’t see any benefit to converting the time to a true Y axis value since we are measuring over the same amount of time every time. So I just hard coded the change in Y to 1.

I should have added a comment that I was making a shortcut there though.

DOH! and I miss labeled my variables. They should be x1 and x2.

I know what you mean, but currently, the 1 / change formula will not result in what we want.

Given x2 is is the current humidity, and x1 is previous, if we’re looking for a positive change (rising humidity value), x2 will be larger than x1. If you divide 1 by the difference, a larger delta in the denominator will result in lower slope (the reverse of what we want).

It will result in a lower absolute slope, but the slope will still move linearly with time. Instead of a reasonable range of 0-100 we would have a reasonable range of 0.0-1.0. It’s still usable.

It probably does make more sense to swap the axises though. If we were to draw it out, the X axis would be assigned the time normally. Then the numbers will actually be closer to the same units of the y axis (i.e. percents).

Second verse, almost the same as the first: :wink:

val y1 = GroundFloor_MasterBathroom_Humidity.historicState(now.minusMinutes(1)).state as Number
val y2 = GroundFloor_MasterBathroom_Humidity.state as Number
val slope  = y2 - y1

val newState = if(slope > 10) ON else OFF // 10% rise

Yep, apparently I’m all about making things more complicated than they need to be today. It’s just a simple subtraction.

I used to be so good at math. 20 years out of school and now I’ve retained just enough trig to do wood working. Sigh… Goodbye calculus and differential equations.

1 Like

Thank you all, for the constructive ideas.
I will test both variants in parallel and compare the response times and reliability.

And thanks for the push in the direction of Design Pattern: How to Structure a Rule
It will help me to optimize my system and reminds me to spend more time to write more clean and efficient code.

In my experience OpenHAB or any automated system the is intended to promote behavior in Teenagers won’t work because of their motivated, creative attempts to NOT do whatever you want them to do…(speaking only for my kids of course). I’ve found that a small $1.50 windup alarm clock set to 10 minutes, and yelling at them through the door when it goes off works best…J

On a more topical note I have done what @Felix_Raetz was looking to do using an IR sensor in the bathroom and information taken from hot and cold water pulse sensors on the mains water and hot water. Tried using humidity as a trigger for several months but it is just too laggy no matter how you monitor it.
I was just using it to switch on the extractor fans and lights. Found that when someone is in the shower the hot water usage goes up and the IR sensor stops cycling/Triggering with the shower door closed.(Seems it doesn’t see through glass!) I use the high water usage to continue resetting a timer and hold the lights on. Not perfect but in our house we only have constant hot water usage just after the PIR sensor turns off, while showering so it works pretty well.

Nice to see exactly how much water the kids are using too…determines how loud I have to yell at them when that alarm goes off…

Been away for a long while and just noticed this topic and thought it was a cool idea and not one I’ve heard of before.

@Felix_Raetz instead of sensing if someone is having a shower why not have a waterproof switch or set of switches that remotely activate the behaviour that you want. Since the person is in the shower anyway then it’s as inconvenient as reaching for the shower gel. With a set of switches you could get granularity eg. don’t warm the towel because it’s summer; no need to warn the front door callers because I’m not alone in the house; don’t turn music on today because I don’t feel like it. Taken one step further just use Alexa in the shower room. :slightly_smiling_face:

You should also consider to put a flood sensor (like Fibaro) in the shower :wink:

1 Like

Not sure about the flod sensor, but instead of the temperature sensor, I have considered a flow sensor to track when the shower is actually on

Guys,
on that note, I’d like to share my solution to this, which I have fine-tuned over the last months and works very reliably. I want to switch on the exhaust fan for a certain amount of time whenever someone takes a shower. Sure, a flood sensor in the shower would work as well, but I want to keep humidity in the bathroom down to prevent mold, so that is the parameter I am using. The issues I was facing were:

  • Bathroom is pretty big, so the rise in humidity is not as noticeable as it would be in a small bathroom, so I placed the humidity sensor pretty close to the shower itself

  • Humdity in the house varies with the seasons, higher in summer and lower in the winter

  • Whenever our cleaning lady mops the floors, humidity throughout the house rises, and I don’t want it to trigger then

So I use other humidity sensors I have throughout the house (I use multisensors for motion detection, and the thermostats have humidity sensors as well) to establish a baseline humidity and calculate on that. So the script basically is triggered by a change in humidity in the bathroom and looks for a rise that is not happening in the rest of the home, and detects that rise by comparing to historical data from 15min minutes ago. I use Influx for that, but rrd would work as well. As it gets triggered on the humidity rise, it also works if the next person showers half an hour later and then cancels and re-sets the timer.
The only situation I have found where it does not work is when the 2nd person showers exactly 15min after the first one started, because then the rise now and 15min ago do not show a delta.

So here it is, ask me if you have any questions. I think most of the variables are self-explanatory. As you can see, the rise in humidity has to be at least 4% and it has to be more than 7% above baseline to trigger. Those are the values that work for me, but YMMV.

var logname = "FanRule"
var Timer t_mbr_fan = null
val hdelta = 4 // 4% Humidity
val humdiff = 7 // 7% Difference to avg. in Home
val int fanTime = 60


rule "Fan Bathroom"
when

    Item Sensor_Multi_Master_Bathroom_Humidity received update

then
        val pastHum = Sensor_Multi_Master_Bathroom_Humidity.historicState(now.minusMinutes(15),"influxdb").state as Number
        val currHum = Sensor_Multi_Master_Bathroom_Humidity.state as Number
        val delta = currHum - pastHum
        val avgHum = (((Sensor_Multi_Hallway_Office_Humidity.state as Number) + (Sensor_Multi_Hallway_Garage_Humidity.state as Number) + (ecobee_off_actual_humidity.state as Number) + (ecobee_mbr_actual_humidity.state as Number)) / 4 )
        val hdiff = currHum - avgHum
//              logInfo(logname, "avgHum: " + avgHum "- currHum: " + currHum "- pastHum: " + pastHum; "- delta: " + delta)

        logInfo(logname, "avgHum: {}, hdiff:{}, currHum: {}, pastHum: {}, delta: {}", avgHum, hdiff, currHum, pastHum, delta)

        if(pastHum < currHum) {
            if(delta > hdelta) {
                if(hdiff > humdiff) {
                    if(Switch_Fan_Light_Master_Bathroom_Fan.state != ON) Switch_Fan_Light_Master_Bathroom_Fan.sendCommand(ON)  // only send ON if OFF
                        if (t_mbr_fan !== null) t_mbr_fan.cancel        // cancel timer if started
                                t_mbr_fan = createTimer(now.plusMinutes(fanTime), [ |      // start timer
                                Switch_Fan_Light_Master_Bathroom_Fan.sendCommand(OFF)     // when elapsed send OFF
                                 t_mbr_fan = null                            // reinitialize Timer
                         ])
                }
            }
        }

end

2 Likes

can i please see your items for this rule i have been locking for something like this for a while thanks for this nice rule.

Nothing special, really.
Sensor_Multi_Master_Bathroom_Humidity is a humidity sensor
Switch_Fan_Light_Master_Bathroom_Fan is the fan switch
Both are z-wave, but that’s rather unrelated to this rule.

Please note that getting historic states from influxdb is broken as of now with OH3. But it works with rrd4j.

I have to say that I’ve since moved away from this approach as it didn’t work that reliably when more than one person took a shower within a short timeframe. I now use a door contact for the shower door, which is much easier and a lot simpler.

1 Like