Detect if somebody is taking a shower

Magic words will be sonoff, tasmota, espEasy ...
and then Node-RED, MQTT, some kind threshold node and you are done

I wasn't clear, sorry. It is always on, but I can only adjust the speed (1,2,3)

Seems to give me false positives. So it wont work. I have no clue what to change.

Which of the sensor was showing higher humidity value (when they are close to each other)? Is it the one in the living room?

I let them settle for a while and the results were quite similar. Temperature was almost the same. Humidity was 54 on the living room and 56 from the bathroom.

This could be what I'm looking for:

[{"id":"60a9f589.a5e6ac","type":"change","z":"d11dfeaa.8b609","name":"","rules":[{"t":"set","p":"test","pt":"global","to":"payload.humidity","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":3400,"y":1060,"wires":[["524cb7e8.509af8"]]},{"id":"b8a5bfcf.6d0b7","type":"switch","z":"d11dfeaa.8b609","name":"","property":"payload.humidity","propertyType":"msg","rules":[{"t":"lte","v":"test","vt":"global"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":3350,"y":900,"wires":[["37cd6123.98630e","2394744d.44af5c","7bddd6ef.b31788"],["754a812d.2bbe6","2394744d.44af5c","fe12dde1.42cd5"]]},{"id":"37cd6123.98630e","type":"debug","z":"d11dfeaa.8b609","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":3640,"y":940,"wires":[]},{"id":"754a812d.2bbe6","type":"debug","z":"d11dfeaa.8b609","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":3640,"y":1000,"wires":[]},{"id":"524cb7e8.509af8","type":"debug","z":"d11dfeaa.8b609","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":3670,"y":1100,"wires":[]},{"id":"2394744d.44af5c","type":"delay","z":"d11dfeaa.8b609","name":"","pauseType":"delay","timeout":"5","timeoutUnits":"milliseconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":3390,"y":1000,"wires":[["60a9f589.a5e6ac"]]},{"id":"fe12dde1.42cd5","type":"change","z":"d11dfeaa.8b609","name":"","rules":[{"t":"set","p":"value","pt":"global","to":"up","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":3640,"y":1040,"wires":[[]]},{"id":"7bddd6ef.b31788","type":"change","z":"d11dfeaa.8b609","name":"","rules":[{"t":"set","p":"value","pt":"global","to":"down","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":3620,"y":900,"wires":[[]]},{"id":"31a7ff05.3472e","type":"mqtt in","z":"d11dfeaa.8b609","name":"","topic":"zigbee2mqtt/Temperatuur_badkamer","qos":"2","datatype":"json","broker":"7a291092.162ac","x":3440,"y":580,"wires":[["269fd53b.b20a2a","1ad6b5b1.da226a","b8a5bfcf.6d0b7"]]},{"id":"a785ad6a.27f9f","type":"change","z":"d11dfeaa.8b609","name":"Set global.badkamer TRUE","rules":[{"t":"set","p":"badkamer","pt":"global","to":"true","tot":"bool"}],"action":"","property":"","from":"","to":"","reg":false,"x":4420,"y":560,"wires":[["e11598bf.9416b8"]]},{"id":"11078b0f.3a1d25","type":"change","z":"d11dfeaa.8b609","name":"Set global.badkamer FALSE","rules":[{"t":"set","p":"badkamer","pt":"global","to":"false","tot":"bool"}],"action":"","property":"","from":"","to":"","reg":false,"x":4420,"y":600,"wires":[["9d16613c.14739"]]},{"id":"da6a977.3c01f68","type":"inject","z":"d11dfeaa.8b609","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":true,"onceDelay":0.1,"x":4110,"y":640,"wires":[["11078b0f.3a1d25"]]},{"id":"e643f5f5.df6fb8","type":"rbe","z":"d11dfeaa.8b609","name":"Humidity is omhoog gegaan?","func":"deadband","gap":"4","start":"","inout":"out","property":"payload.humidity","x":4060,"y":560,"wires":[["a8717c51.9d4d5","a785ad6a.27f9f"]]},{"id":"269fd53b.b20a2a","type":"switch","z":"d11dfeaa.8b609","name":"Badkamer bezet?","property":"badkamer","propertyType":"global","rules":[{"t":"false"},{"t":"true"}],"checkall":"false","repair":false,"outputs":2,"x":3710,"y":580,"wires":[["5cbe85b.169387c","d8b52fad.261f7"],["e5a06c0.07ac798","28c86f18.45b19"]]},{"id":"d8b52fad.261f7","type":"switch","z":"d11dfeaa.8b609","name":"Up?","property":"value","propertyType":"global","rules":[{"t":"cont","v":"up","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":3850,"y":500,"wires":[["e643f5f5.df6fb8","763de58d.1cf6bc"]]},{"id":"28c86f18.45b19","type":"switch","z":"d11dfeaa.8b609","name":"Down?","property":"value","propertyType":"global","rules":[{"t":"cont","v":"down","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":3810,"y":660,"wires":[["7cd4f1e7.86faf","4a67da70.657674"]]},{"id":"7cd4f1e7.86faf","type":"rbe","z":"d11dfeaa.8b609","name":"Humidity is omhoog gegaan?","func":"deadband","gap":"4","start":"","inout":"out","property":"payload.humidity","x":4060,"y":600,"wires":[["11078b0f.3a1d25","9a0556c3.f206f8"]]},{"id":"7a291092.162ac","type":"mqtt-broker","z":"","name":"MQTT","broker":"localhost","port":"1883","clientid":"","usetls":false,"compatmode":true,"keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","closeTopic":"","closeQos":"0","closePayload":"","willTopic":"","willQos":"0","willPayload":""}]

Made a flow to detect if the value is higher or lower than the previous value. And the use RBE to detect if the change was big enough.
Now I have to test it in real life!

1 Like

First real life test went well!

Good to hear!

Also my test went fine with my flow, when my wife took a shower this morning, "occupancy" was detected and when humidity later dropped, status was changed to "vacant" (however, my sensors are not that fast reporting as yours)

Yesterday we talked about having a reference humidity value. I still cannot let go, I think it is important to have. But instead of using two sensors, I have experimented with a different approach just using one sensor. The idea is to build a reference value; I create one smoothed value with the last 30 readings, this becomes an average mean value for the last 30 minutes if you get a reading per minute from the sensor

This is how it looks in my bathroom right now
image

  1. In normal operation, the sensor value will be the same as the reference value and the bathroom status is "vacant"

  2. When someone starts the shower, the sensor value will rise and become higher than the reference. Once it has reached a specified level above the reference, the bathroom status is changed to "occupied"

  3. After the shower, the sensor value will drop and when below the level, the bathroom status is changed back to "vacant"

  4. After "a while" with no showering the sensor and reference values will be the same

I have also added a chart that allows you to study how the sensor and reference values are changing during time and during showering

If you wan't, you can run this flow in parallel to your existing one, just import it to a new flow/tab and check the settings for the mqtt node that it is correct. Soon you should see readings coming into the chart

[{"id":"cab7df7f.32cb2","type":"function","z":"8951a2c1.96fe9","name":"","func":"var hum_ref = flow.get('hum_ref')||30;\nvar hum_shower = flow.get('hum_shower')||30;\nvar sensor = msg.sensor;\nvar delta = 2; //set to the desired threshold level\n\nswitch(sensor){\n    case \"reference\":\n        flow.set(\"hum_ref\", msg.payload.humidity);\n        hum_ref = msg.payload.humidity;\n        break;\n        \n    case \"shower\":\n        flow.set(\"hum_shower\", msg.payload.humidity);\n        hum_shower = msg.payload.humidity;\n        break;\n}\n\nif (hum_shower > hum_ref + delta){\n    msg.payload = \"occupied\";\n}\nelse{\n    msg.payload = \"vacant\";\n}\n\nnode.status({\n\ttext : \"Bathroom RH%: \"+hum_shower.toString()+' '+\"Reference RH%: \"+hum_ref.toString()\n});\n\nmsg.topic = '';\nreturn msg;","outputs":1,"noerr":0,"x":750,"y":440,"wires":[["e90842eb.ee96a","8891cbb5.27fee8"]]},{"id":"2cfb89ab.229286","type":"smooth","z":"8951a2c1.96fe9","name":"","property":"payload.humidity","action":"mean","count":"30","round":"","mult":"single","x":550,"y":370,"wires":[["a9ea86e2.b3b3d8"]]},{"id":"f43fac24.f2d4","type":"smooth","z":"8951a2c1.96fe9","name":"","property":"payload.humidity","action":"mean","count":"1","round":"","mult":"single","x":550,"y":510,"wires":[["8fc793a4.9567b"]]},{"id":"a9ea86e2.b3b3d8","type":"change","z":"8951a2c1.96fe9","name":"","rules":[{"t":"set","p":"sensor","pt":"msg","to":"reference","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":570,"y":420,"wires":[["cab7df7f.32cb2"]]},{"id":"8fc793a4.9567b","type":"change","z":"8951a2c1.96fe9","name":"","rules":[{"t":"set","p":"sensor","pt":"msg","to":"shower","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":570,"y":460,"wires":[["cab7df7f.32cb2"]]},{"id":"c7b7055.a070cf8","type":"debug","z":"8951a2c1.96fe9","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":1060,"y":440,"wires":[]},{"id":"76d76521.baaa0c","type":"json","z":"8951a2c1.96fe9","name":"","property":"payload","action":"","pretty":false,"x":330,"y":440,"wires":[["2cfb89ab.229286","f43fac24.f2d4"]]},{"id":"e90842eb.ee96a","type":"rbe","z":"8951a2c1.96fe9","name":"","func":"rbe","gap":"","start":"","inout":"out","property":"payload","x":890,"y":440,"wires":[["c7b7055.a070cf8"]]},{"id":"bbc1e848.813eb8","type":"ui_chart","z":"8951a2c1.96fe9","name":"","group":"e8cbbd46.f3f46","order":0,"width":0,"height":0,"label":"chart","chartType":"line","legend":"false","xformat":"HH:mm:ss","interpolate":"linear","nodata":"","dot":false,"ymin":"","ymax":"","removeOlder":1,"removeOlderPoints":"","removeOlderUnit":"86400","cutout":0,"useOneColor":false,"colors":["#1f77b4","#aec7e8","#ff7f0e","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"useOldStyle":false,"x":1040,"y":370,"wires":[[],[]]},{"id":"8891cbb5.27fee8","type":"function","z":"8951a2c1.96fe9","name":"","func":"var hum_ref = flow.get('hum_ref')||30;\nvar hum_shower = flow.get('hum_shower')||30;\n\nmsg.topic = 'shower';\nmsg.payload = hum_shower;\nnode.send(msg);\n\nmsg.topic = 'reference';\nmsg.payload = hum_ref;\nnode.send(msg);\n","outputs":1,"noerr":0,"x":890,"y":370,"wires":[["bbc1e848.813eb8"]]},{"id":"217f9c09.e31ac4","type":"mqtt in","z":"8951a2c1.96fe9","name":"","topic":"zigbee2mqtt/Temperatuur_badkamer","qos":"2","broker":"414976e5.691688","x":230,"y":370,"wires":[["76d76521.baaa0c"]]},{"id":"e8cbbd46.f3f46","type":"ui_group","z":"","name":"Humidity","tab":"cbdbd298.da297","disp":false,"width":"16","collapse":false},{"id":"414976e5.691688","type":"mqtt-broker","z":"","name":"MQTT","broker":"localhost","port":"1883","clientid":"","usetls":false,"compatmode":true,"keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","closeTopic":"","closeQos":"0","closePayload":"","willTopic":"","willQos":"0","willPayload":""},{"id":"cbdbd298.da297","type":"ui_tab","z":"","name":"Humidity","icon":"dashboard"}]

Eventually, the JSON node is not needed

You obviously do not have teenagers !! Their showers go for longer than 1/2 hour !!

Craig

2 Likes

Nope, they have moved out! But I remember slightly what you are mentioning. I think my solution will "automagically" adjust also to teenager habits :smiley:

What is missing is support for cutting off the water (or just provide the cold variant) after a certain predefined time slot. Or to have a node accepting credit cards for extended showers...

Sounds like you are using older sensors such as the DHT11 or DHT22. Do yourself a favour and ditch them as they are rubbish. Switch to something like a BME280 or HTU21D.

Just remember that measuring humidity accurately is immensely difficult. Particularly at extremes. The DHT sensors for example not only drift wildly over time, they take a long time to recover from high humidity levels. Relative humidity also depends very heavily on temperature so you get a double issue with a sensor that isn't so accurate. A decent sensor will still show a variance of at least +/- 3% between devices - that is a lot if you are trying to measure differences in that range but will only impact you if trying to compare two sensors.


As an aside, that variance is also a quite lot if you are trying to measure safe levels of internal humidity since the gap considered "safe" generally for living conditions is only around 40% to 60%.

https://www.epa.gov/mold/mold-course-chapter-2

Sorry, as you will realise, this is a bit of a sore subject for me. Living in the North of the UK presents "interesting" issues with humidity, condensation and mold, especially living in an old Victorian house.

I like the approach.

Here is the data in my house from this morning:

It includes external data as well both from a sensor outside our bedroom window and from local weather API's.

This was a teenager. Something strange here though. The shower was probably 40min but the humidity didn't start dropping until well after that. Probably when my wife opened a window. I suspect that my son didn't turn on the fan.

Although the sensor is just one built into the stand-alone thermostat of our smart heating system and only gets updates about every 50s, you can easily see the changes. You can even see this when someone has a bath instead of a shower:

Also interesting here with 2 showers one after the other:

This is a more significant chart, not as beautiful as the one made by @TotallyInformation :wink: but anyway...

Myself taking a short 6 min shower. In the chart the dark blue line is the raw sensor value and the light blue the smoothed 30-readings. After the shower ended, the humidity is still there for a while but starts decreasing. The raw value "meets" the smoothed and when the difference is less than the defined delta (hardcoded in the function node) the bathroom status changes back again

My sensor is positioned some meters away from the shower, close to the ventilation outlet going to the heat pump. It is most likely not that accurate, a simple Oregon thing, but since I just care about the delta, the actual accuracy isn't that important in this case I guess. To have a faster reaction I think decreasing the delta and smoothing factor would be the way to go. Eventually having one slower and one faster smoothing curve and use them for comparison when shower begins and ends respectively

Anyway, kind of funny excercis this little project, demonstrating the power of Node-RED, not much coding needed there

Kind words but I can't take credit as Grafana does the hard work for me with InfluxDB holding the data.

From my data, it looks like it would take about 2-3 minutes to reliably detect a high enough change in humidity. Anything less than that might be normal variations. That would be enough to change fan speeds but not sure I would bother with that as a presence indicator.


How about this as another thought - a sound sensor might be better, you could tune the sensitivity and it would react much quicker. I have one of these in our hallway, I don't really do anything useful with it but it does provide a decent indicator of activity.

2 Likes

Or a camera with AI - but it would not be that popular....

1 Like

I let them settle for a longer period and the results were practically the same. Temperature was about equal. Slightly different on humidity. About 2%. So not too bad.

I'm gonna check it out this weekend. Quite busy now. Thanks for sharing!

Made some changes to my flow, and this works a lot better.

[{"id":"e4a4bb20.6693f8","type":"change","z":"d11dfeaa.8b609","name":"","rules":[{"t":"set","p":"oldhumidity","pt":"global","to":"payload.humidity","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1960,"y":580,"wires":[["999d6844.50a308"]]},{"id":"ca83de6.c34892","type":"switch","z":"d11dfeaa.8b609","name":"","property":"payload.humidity","propertyType":"msg","rules":[{"t":"lte","v":"oldhumidity","vt":"global"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":1750,"y":500,"wires":[["6cd501d7.1ea9c","882aa2f9.4ff07"],["6cd501d7.1ea9c","9893d4a2.b41448"]]},{"id":"6cd501d7.1ea9c","type":"delay","z":"d11dfeaa.8b609","name":"","pauseType":"delay","timeout":"1","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":1760,"y":580,"wires":[["e4a4bb20.6693f8"]]},{"id":"9893d4a2.b41448","type":"change","z":"d11dfeaa.8b609","name":"global updown UP","rules":[{"t":"set","p":"updown","pt":"global","to":"up","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":1950,"y":540,"wires":[["d4749ad2.4a0bd8"]]},{"id":"882aa2f9.4ff07","type":"change","z":"d11dfeaa.8b609","name":"global updown DOWN","rules":[{"t":"set","p":"updown","pt":"global","to":"down","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":1960,"y":500,"wires":[["d4749ad2.4a0bd8"]]},{"id":"67bc3aee.a2cd54","type":"mqtt in","z":"d11dfeaa.8b609","name":"","topic":"zigbee2mqtt/Temperatuur_badkamer","qos":"2","datatype":"json","broker":"7a291092.162ac","x":1800,"y":400,"wires":[["ca83de6.c34892","28983548.f10b8a","9de05449.bb1e28"]]},{"id":"8bbf10c8.c83f","type":"change","z":"d11dfeaa.8b609","name":"Set global.badkamer TRUE","rules":[{"t":"set","p":"badkamer","pt":"global","to":"true","tot":"bool"}],"action":"","property":"","from":"","to":"","reg":false,"x":2800,"y":340,"wires":[["8cfd579f.546768"]]},{"id":"a205049c.6aac28","type":"change","z":"d11dfeaa.8b609","name":"Set global.badkamer FALSE","rules":[{"t":"set","p":"badkamer","pt":"global","to":"false","tot":"bool"}],"action":"","property":"","from":"","to":"","reg":false,"x":2800,"y":460,"wires":[["8cfd579f.546768"]]},{"id":"c1d8efd1.0405f","type":"inject","z":"d11dfeaa.8b609","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":true,"onceDelay":0.1,"x":2390,"y":440,"wires":[["a205049c.6aac28"]]},{"id":"69bcd415.58602c","type":"rbe","z":"d11dfeaa.8b609","name":"Humidity is flink veranderd?","func":"deadband","gap":"4","start":"","inout":"out","property":"payload.humidity","x":2260,"y":400,"wires":[["ad8e1096.127c5"]]},{"id":"ad8e1096.127c5","type":"switch","z":"d11dfeaa.8b609","name":"Up or down?","property":"updown","propertyType":"global","rules":[{"t":"cont","v":"up","vt":"str"},{"t":"else"}],"checkall":"false","repair":false,"outputs":2,"x":2470,"y":400,"wires":[["8bbf10c8.c83f","ddeffb7c.d26498"],["9dba1b96.8f3968"]]},{"id":"53e26d1c.bd4a04","type":"inject","z":"d11dfeaa.8b609","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":2400,"y":360,"wires":[["8bbf10c8.c83f"]]},{"id":"28983548.f10b8a","type":"delay","z":"d11dfeaa.8b609","name":"","pauseType":"delay","timeout":"2","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":2040,"y":400,"wires":[["69bcd415.58602c"]]},{"id":"9de05449.bb1e28","type":"function","z":"d11dfeaa.8b609","name":"payload humidity","func":"const lastValue = msg.payload.humidity\n\nnode.status({text: lastValue.toString()})\n\nreturn msg","outputs":1,"noerr":0,"x":2070,"y":340,"wires":[[]]},{"id":"999d6844.50a308","type":"function","z":"d11dfeaa.8b609","name":"global oldhumidity","func":"var a = global.get('oldhumidity'); \n\nnode.status({text: a.toString()});\n\nreturn msg;","outputs":1,"noerr":0,"x":2170,"y":580,"wires":[[]]},{"id":"8cfd579f.546768","type":"function","z":"d11dfeaa.8b609","name":"Check global.badkamer","func":"var a = global.get('badkamer');          \nif (a === true)\n{\n    node.status({fill:\"green\",shape:\"dot\",text:\"Bezet\"});\n}\nelse{\n    node.status({fill:\"red\",shape:\"ring\",text:\"Niet bezet\"});\n}\nreturn msg;","outputs":1,"noerr":0,"x":3050,"y":400,"wires":[[]]},{"id":"d4749ad2.4a0bd8","type":"function","z":"d11dfeaa.8b609","name":"Check global.badkamer","func":"var a = global.get('value');          \nif (a == 'up')\n{\n    node.status({fill:\"green\",shape:\"dot\",text:\"Up\"});\n}\nelse{\n    node.status({fill:\"red\",shape:\"ring\",text:\"Down\"});\n}\nreturn msg;","outputs":1,"noerr":0,"x":2190,"y":520,"wires":[[]]},{"id":"ddeffb7c.d26498","type":"change","z":"d11dfeaa.8b609","name":"stop","rules":[{"t":"set","p":"payload","pt":"msg","to":"stop","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":2570,"y":300,"wires":[["9dba1b96.8f3968"]]},{"id":"9dba1b96.8f3968","type":"stoptimer","z":"d11dfeaa.8b609","duration":"5","units":"Minute","payloadtype":"num","payloadval":"0","name":"","x":2740,"y":400,"wires":[["a205049c.6aac28"],[]]},{"id":"7a291092.162ac","type":"mqtt-broker","z":"","name":"MQTT","broker":"localhost","port":"1883","clientid":"","usetls":false,"compatmode":true,"keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","closeTopic":"","closeQos":"0","closePayload":"","willTopic":"","willQos":"0","willPayload":""}]

I tried this, and it looks great. But it is kinda slow to report that it is vacant. But I like the idea. I didn't experiment any further though.