Humidity control
Purpose
This script controls the humidity in a typical bathroom setting by detecting a fast relative rise in humidity and controlling a ventilator to bring it down to normal levels.
Dependencies
This script requires a humidity sensor and a binary switch controlling the ventilator.
Installation instructions
First create the following user variables in Domoticz by going to <domoticzserver>/#/UserVariables:
- humCounter: (type=integer, value=0)
- humidityTmin5: (type=integer, value=0)
- humidityTmin10: (type=integer, value=0)
- targetFanOffHumidity: (type=integer, value=0)
- fanMaxTimer: (type=integer, value=0)
- fanFollowsProgram: (type=integer, value=0)
- TEST_MODE_HUMVAR: (type-integer, value=0)
Note: TEST_MODE_HUMVAR is needed if you use the script in test mode - then you would put your hard coded humidity value in here.
Note: the script will take care of the values of most of these variables so do not edit them yourself! It is needed to control state and carry over values between each script run.
Then create the script:
Copy and paste the code below into a new lua script inside scripts/lua
folder in your domoticz installation folder. Call the script script_time_humidity.lua
. This script will run every minute.
Design rationale
In the early days (before I had Domoticz) I had a hygrostat wirelessly controlling a ventilator. It requires a specific humidity level at which it turns on the fan and a level for when to turn it off. The problem with that is that the general humidity level varies too much. If it is rainy outside then the level goes up and when it freezes outside it goes down. At some point I had to reprogram the hygrostat once a day. A real pain in the $#&^. So... then Domoticz came around and I decided to make it smarter. Threw the hygrostat out of the window, got myself some zwave devices and started programming.
My idea was to detect a sudden rise of humidity. As soon as someone is taking a shower then it will go up within a couple of minutes so detecting that shouldn't be too hard.
Basically this is how it works: after a certain interval it takes a reading, stores it in a user variable together with the previous reading. If we detect a rise (minimum of these two readings compared to the current reading) and it is larger than some threshold then the ventilator is activated and the de-humidification program starts (fanFollowsProgram=1). At that moment we determine the target humidity for when the ventilator has to stop. And this is important: this target is different every time the script decides to turn on the ventilator! That is the whole purpose of this script. It tries to bring back the humidity to the level it was before someone started taking a shower. Of course, it is possible that it doesn't reach this level because the temperature in the room has changed or the overall humidity has risen or whatever. So there is a failsafe making sure that after a certain amount of time, the ventilator is turned off anyhow. The script is clever enough to detect manual control of the ventilator.
So, the script has a couple of constants defined at the beginning where you can set the interval period, the least amount of rise before the ventilator goes on etc. And of course you have to tell the script the exact names of your sensor and ventilator switch. There is even a test mode (create a user variable testHumidity
that acts as a sensor).
It needs the user variables because it has to maintain a state between subsequent runs of the script. After all, the script is executed once a minute and basically has no state of itself. Hence the globals.
Lua script
--[[
This script controls the humidity in a typical bathroom setting by detecting
relative rises in humidity in a short period.
Of course it requires a humidity sensor and a binary switch controlling a fan/ventilator.
(there is no provision for variable speed ventilators here!)
How it works (assuming the default constants as defined below):
Save the script as time triggered!!
Every 5 minutes a reading is done. Every reading is stored together
with the previous reading and is stored in two user variables (humidityTmin5 and humidityTmin10).
So it has two reading over the past 10 minutes.
It then takes the lowest of the two and compares it with the latest reading and
calculates a delta.
If the delta is 3 or higher (see constants) then the fan will be turned
on, it calculates the target humidity and the 'humidity-decrease program' is started (fanFollowsProgram=1).
From then on, every 5 minutes the current humidity is compared to the
stored target humidity. Basically if that target is reached, the fan is turned off
and the 'program' is ended.
Of course, it is possible that the target is never reached (might start raining outside
or whatever). Then there is a failsafe (FAN_MAX_TIME) after which the ventilator
will be turned off.
Also, it will detect if the ventilator is manually switched off during a program
or when it is switched on before the program starts.
Along the lines it prints to the log and sends notifications
but of course you can turn that off by removing those lines.
--]]
commandArray = {}
-- adjust to your specific situation
-- devices
local FAN_NAME = 'ventilatie percentage' -- exact device name of the switch turning on/off the ventilator
local SENSOR_NAME = 'luchtvochtigheid badkamer' -- exact device name of the humidity sensor
-- script constants
local SAMPLE_INTERVAL = 5 -- time in minutes when a the script logic will happen
local FAN_DELTA_TRIGGER = 3 -- rise in humidity that will trigger the fan
local FAN_MAX_TIME = 15 -- maximum amount of sample cycles the fan can be on, in case we never reach the target humidity
local TARGET_OFFSET = 5 -- ventilator goes off if target+offset is reached (maybe it takes too long to reach the true target due to wet towels etc)
local LABEL = '- Humidity control - '
-- test / debug
local TEST_MODE = false -- when true TEST_MODE_HUMVAR is used instead of the real sensor
local TEST_MODE_HUMVAR = 'testHumidity' -- fake humidity value, give it a test value in domoticz/uservars
local PRINT_MODE = true -- Any other value as false or nil will print output to log and send notifications
if PRINT_MODE then
print(LABEL)
end
-- Function added to overcome compatibility problem between Lua version 5.2 an 5.3
local function toInteger(str)
return math.floor(str)
end
-- get the global variables:
-- this script runs every minute, humCounter is used to create SAMPLE_INTERVAL periods
local current
local humCounter = toInteger(uservariables['humCounter'])
local humidityTmin5 = toInteger(uservariables['humidityTmin5']) -- youngest reading
local humidityTmin10 = toInteger(uservariables['humidityTmin10']) -- oldest reading
local targetFanOffHumidity = toInteger(uservariables['targetFanOffHumidity']) -- target humidity
local fanMaxTimer = toInteger(uservariables['fanMaxTimer'])
local fanFollowsProgram = toInteger(uservariables['fanFollowsProgram']) -- marker indicating that the decrease program is started
local target = 0 -- will hold the target humidity when the program starts
-- get the current humidity value
if (TEST_MODE) then
current = toInteger(uservariables[TEST_MODE_HUMVAR])
else
current = toInteger(otherdevices_humidity[SENSOR_NAME])
end
-- check if the sensor is on or has some weird reading
if (current == 0 or current == nil) then
print(LABEL .. 'current is 0 or nil. Skipping this reading')
return commandArray
end
if PRINT_MODE then
print(LABEL .. 'Current humidity:' .. current)
print(LABEL .. 'targetFanOffHumidity:' .. targetFanOffHumidity)
print(LABEL .. 'humidityTmin5: ' .. humidityTmin5)
print(LABEL .. 'humidityTmin10: ' .. humidityTmin10)
print(LABEL .. 'fanMaxTimer: ' .. fanMaxTimer)
print(LABEL .. 'humCounter:' .. humCounter)
print(LABEL .. 'fanFollowsProgram:' .. fanFollowsProgram)
end
-- increase cycle counter
humCounter = humCounter + 1
if (humCounter >= SAMPLE_INTERVAL) then
if (humidityTmin5 == 0) then
-- initialization, assume this is the first time
humidityTmin5 = current
humidityTmin10 = current
end
humCounter = 0 -- reset the cycle counter
-- pick the lowest history value to calculate the delta
-- this also makes sure that two relative small deltas in the past 2*interval minutes are treated as one larger rise
-- and therefore will still trigger the ventilator
-- I don't want to use a longer interval instead because I want the ventilator to start as soon as possible
-- (so rather after 5 minutes instead of after 15 minutes because the mirrors in the bathroom become kinda useless ;-)
delta = current - math.min(humidityTmin10, humidityTmin5)
if PRINT_MODE then
print(LABEL .. 'Delta: ' .. delta)
end
-- pick the lowest history value
target = math.min(humidityTmin10, humidityTmin5) + TARGET_OFFSET
-- shift the previous measurements
humidityTmin10 = humidityTmin5
-- and store the current
humidityTmin5 = current
if (otherdevices[FAN_NAME]=='Off' or (otherdevices[FAN_NAME]=='On' and fanFollowsProgram==0)) then
-- either the fan is off or it is on but the decrease program has not started
-- in that latter case we start the program anyway. This could happen if someone turns on the ventilator
-- manually because he/she is about to take a shower and doesn't like damp mirrors.
-- I don't do this because the ventilator removes heat from the bathroom and I want this to happen
-- as late as possible ;-)
if (fanFollowsProgram == 1 and otherdevices[FAN_NAME]=='Off') then
-- likely someone turned off the ventilator while the program was running
fanFollowsProgram = 1
end
-- see if we have to turn it on
if (delta >= FAN_DELTA_TRIGGER) then
-- time to start the fan
commandArray[FAN_NAME] = 'Set Level 99'
targetFanOffHumidity = target
if (fanFollowsProgram == 1) then
print('Ventilator was already on but we start the de-humidifying program')
end
fanFollowsProgram = 1
-- set the safety stop
fanMaxTimer = FAN_MAX_TIME
if PRINT_MODE then
print(LABEL .. 'Rise in humidity. Turning on the vents. Delta: ' .. delta)
print(LABEL .. 'Target humidity for turning the ventilator: ' ..targetFanOffHumidity)
commandArray['SendNotification'] = LABEL .. 'Ventilator is on#The ventilator was activated at humidity level ' .. current .. '#0'
end
end
else
if (fanMaxTimer > 0) then
-- possible that someone started the ventilator manually
fanMaxTimer = fanMaxTimer - 1
end
if (fanFollowsProgram == 1) then -- not manually started
if (delta >= FAN_DELTA_TRIGGER) then
-- ok, there is another FAN_DELTA_TRIGGER rise in humidity
-- when this happen we reset the fanMaxTimer to a new count down
-- because we have to ventilate a bit longer due to the extra humidity
if PRINT_MODE then
print(LABEL .. 'Another large increase detected, resetting max timer. Delta: ' .. delta)
end
fanMaxTimer = FAN_MAX_TIME
end
-- first see if it can be turned off
if (current <= targetFanOffHumidity or fanMaxTimer==0) then
commandArray[FAN_NAME] = 'Off'
msg = ''
if (fanMaxTimer == 0 and current > targetFanOffHumidity) then
msg = 'Target not reached but safety time-out is triggered.'
if PRINT_MODE == true then
print(msg)
end
else
msg = 'Target humidity reached'
if PRINT_MODE then
print(LABEL .. msg)
end
end
if PRINT_MODE then
print(LABEL .. 'Turning off the ventilator')
msg = msg .. '\nTurning off the ventilator'
end
targetFanOffHumidity = 0
fanMaxTimer = 0
fanFollowsProgram = 0
-- reset history in this case.. we start all over
-- Tmin10 is still in the 'ventilator=On'-zone
humidityTmin10 = humidityTmin5
if PRINT_MODE == true then
commandArray['SendNotification'] = 'Ventilator is off#' .. msg .. '#0'
end
else
-- we haven't reached the target yet
if PRINT_MODE then
print(LABEL .. 'Humidity delta: ' .. delta)
end
end
end
end
if PRINT_MODE then
print(LABEL .. 'New values >>>>>>>>>>>')
print(LABEL .. 'humidityTmin5: ' .. humidityTmin5)
print(LABEL .. 'humidityTmin10: ' .. humidityTmin10)
print(LABEL .. 'fanMaxTimer: ' .. fanMaxTimer)
print(LABEL .. 'humCounter:' .. humCounter)
print(LABEL .. 'fanFollowsProgram:' .. fanFollowsProgram)
print(LABEL .. '------ target: ' .. targetFanOffHumidity)
end
end
-- save the globals
commandArray['Variable:humCounter'] = tostring(humCounter)
commandArray['Variable:humidityTmin10'] = tostring(humidityTmin10)
commandArray['Variable:humidityTmin5'] = tostring(humidityTmin5)
commandArray['Variable:targetFanOffHumidity'] = tostring(targetFanOffHumidity)
commandArray['Variable:fanMaxTimer'] = tostring(fanMaxTimer)
commandArray['Variable:fanFollowsProgram'] = tostring(fanFollowsProgram)
return commandArray
Enjoy!
I hope it can be of some use to you and feel free to adapt it. Bug fixes, patches, and improvements are hapiliy accepted of course.
The forumtopic about this script can be found here: http://domoticz.com/forum/viewtopic.php?f=23&t=4421
Danny (dannybloe)