LUA Script - Control humidity in bathroom - tigi - 19.02.2021
This script will control the fan and if present the air valve when humidity rises in a certain amount of time.
I found this script on a domoticz forum and ported it for use with LogicMachine and knx.
Hopefully this will be helpfull for other people looking for something like this.
If anyone feels the need to optimize, rewrite or change, feel free!
Variables
- Change the group addresses for the fan_name, sensor_name and fan_valve if present, else place empty brackets ''
For testing purposes you can use group addresses from light switches to see what happens.
Fan_name_hispeed is for 2-speed fans, however this is not tested thoroughly.
- Script constants should not be changed apart from fan_startup_delay (the time it takes the air valve to open before the fan starts) and fan_valve_close_delay (the time to wait for the air valve to close after fan is turned off)
- For testing purposes set test_mode to true and give test_mode_humvar a test value for humidity of 50, run the script a couple of times and then change that value to 60 and run a couple of times.
- For feedback purposes set print_mode to true for generating data in the log
- For those interested, this script is also usefull to control a ventilator for heating purposes by changing the variable values, group addresses. Certainly set fan_max_timer > 120 (2 hours)
I use it to start a ventilator once the air in a ventilation pipe gets to a certain temperature.
If you run 2 scripts of those, you need to change the table name also
Script placement
Place the script in Scripting > Scheduled and let it repeat every minute.
Final version with all suggested changes
Code: --[[
Source: https://www.domoticz.com/wiki/Humidity_control
Credit: Danny (dannybloe)
Adapt to LogicMachine KNX: Tigi
v1 - ported code for use in logicmachine knx
v2 - rremoved unnecessary function toInteger, fixed test_mode to work for logicmachine knx
v3 - placed globals in table, load and save table to storage for logicmachine knx
v4 - changed --> if next(HumidDB) == nil then --> to --> if type(HumidDB) ~= 'table' or next(HumidDB) == nil then
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 Scheduled script. This script will run every minute.
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!)
There is however added functionality for an extra air valve that closes and opens before the fan.
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
but of course you can turn that off by removing those lines.
--]]
-- adjust to your specific situation
-- devices
local FAN_NAME = '0/0/12' -- group address or name of the switch turning on/off the ventilator at low-speed
local FAN_NAME_HISPEED = '0/0/12' -- group address or name of the switch turning on/off the ventilator at hi-speed, if not in use clear value and set as ''
local SENSOR_NAME = '8/0/0' -- group address or name of the humidity sensor
local FAN_VALVE = '' -- group address or name of fan valve to open before fan start, if not in use clear value and set as ''
-- script constants
local FAN_STARTUP_DELAY = 15 -- time in seconds it takes to open the valve before fan starts
local FAN_VALVE_CLOSE_DELAY = 2 -- time in seconds to wait for the valve to close after fan stops
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 Bathroom - '
-- test / debug
local TEST_MODE = false -- when true TEST_MODE_HUMVAR is used instead of the real sensor
local TEST_MODE_HUMVAR = 50 -- fake humidity value, give it a value above 55 to start fan, set it to 50 to stop the fan, let script repeat till fan goes on or off
local PRINT_MODE = false -- Any other value as false or nil will print output to log and send notifications
log(LABEL .. 'Script Executed')
-- Creating database for global variables and initialized to ZERO only first time of script use
HumidDB = {}
HumidDB = storage.get('Humid')
if type(HumidDB) ~= 'table' or next(HumidDB) == nil then
if PRINT_MODE then
log(LABEL .. 'Initializing Global Variables to ZERO')
end
HumidDB["humCounter"] = 0
HumidDB["humidityTmin5"] = 0
HumidDB["humidityTmin10"] = 0
HumidDB["targetFanOffHumidity"] = 0
HumidDB["fanMaxTimer"] = 0
HumidDB["fanFollowsProgram"] = 0
-- save table with globals to storage
storage.set('Humid',HumidDB)
else
if PRINT_MODE then
log(LABEL .. 'Global Variables already initialized or in use')
end
end
-- Function to get status of fan and air valve
local function FanValveState()
if grp.getvalue(FAN_NAME) then
msg_fan = 'Running at LOW Speed'
else
msg_fan = 'Not Running'
end
if FAN_NAME_HISPEED ~= '' then
if grp.getvalue(FAN_NAME_HISPEED) then
msg_fan = 'Running at HI Speed'
end
end
if FAN_VALVE ~= '' then
if grp.getvalue(FAN_VALVE) then
msg_valve = 'Opened'
else
msg_valve = 'Closed'
end
else
msg_valve = 'Not Present'
end
end
-- get the global variables:
-- this script runs every minute, humCounter is used to create SAMPLE_INTERVAL periods
local current
local humCounter = HumidDB["humCounter"]
local humidityTmin5 = HumidDB["humidityTmin5"] -- youngest reading
local humidityTmin10 = HumidDB["humidityTmin10"] -- oldest reading
local targetFanOffHumidity = HumidDB["targetFanOffHumidity"] -- target humidity
local fanMaxTimer = HumidDB["fanMaxTimer"]
local fanFollowsProgram = HumidDB["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
if humCounter >= 1 and TEST_MODE_HUMVAR ~= 50 then
current = TEST_MODE_HUMVAR
else
current = 50
end
else
current = grp.getvalue(SENSOR_NAME)
end
-- check if the sensor is on or has some weird reading
if (current == 0 or current == nil) then
if PRINT_MODE then
log(LABEL .. 'current is 0 or nil. Skipping this reading')
end
end
if PRINT_MODE then
-- Get Fan and Air Valve states
FanValveState()
log(LABEL .. 'Fan State : ' .. msg_fan)
log(LABEL .. 'Air Valve State : ' .. msg_valve)
log(LABEL .. 'Current humidity :' .. current)
log(LABEL .. 'targetFanOffHumidity:' .. targetFanOffHumidity)
log(LABEL .. 'humidityTmin5 : ' .. humidityTmin5)
log(LABEL .. 'humidityTmin10 : ' .. humidityTmin10)
log(LABEL .. 'fanMaxTimer : ' .. fanMaxTimer)
log(LABEL .. 'humCounter :' .. humCounter)
log(LABEL .. 'fanFollowsProgram :' .. fanFollowsProgram)
end
-- increase cycle counter
humCounter = humCounter + 1
--storage.set('humCounter',humCounter)
if (humCounter >= SAMPLE_INTERVAL) then
if (humidityTmin5 == 0) then
-- initialization, assume this is the first time
humidityTmin5 = current
humidityTmin10 = current
end
-- reset the cycle counter
humCounter = 0
-- 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
log(LABEL .. 'Current humidity - lowest historic humidity (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 (not grp.getvalue(FAN_NAME) or (grp.getvalue(FAN_NAME) 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 not grp.getvalue(FAN_NAME)) 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
-- check if valve is present
if FAN_VALVE ~= '' then
-- opening valve first, then waiting xx seconds before fan start
grp.write(FAN_VALVE, true)
sleep(FAN_STARTUP_DELAY)
end
grp.write(FAN_NAME, true)
targetFanOffHumidity = target
if (fanFollowsProgram == 1) then
if PRINT_MODE then
log('Ventilator was already on but we start the de-humidifying program')
end
end
fanFollowsProgram = 1
-- set the safety stop
fanMaxTimer = FAN_MAX_TIME
if PRINT_MODE then
log(LABEL .. 'Rise in humidity. Turning on the vents. Delta: ' .. delta)
log(LABEL .. 'Target humidity for turning the ventilator: ' ..targetFanOffHumidity)
log(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
log(LABEL .. 'Another large increase detected, resetting max timer. Delta: ' .. delta)
end
fanMaxTimer = FAN_MAX_TIME
-- if the fan has a higher speed setting, we activate it here
if FAN_NAME_HISPEED ~= '' then
grp.write(FAN_NAME, false)
grp.write(FAN_NAME_HISPEED, true)
end
end
-- first see if it can be turned off
if (current <= targetFanOffHumidity or fanMaxTimer==0) then
-- turning off fan
grp.write(FAN_NAME, false)
grp.write(FAN_NAME_HISPEED, false)
-- check if seperate valve is present
if FAN_VALVE ~= '' then
-- wait xx seconds before valve to close
sleep(FAN_VALVE_CLOSE_DELAY)
-- closing fan valve
grp.write(FAN_VALVE, false)
end
msg = ''
if (fanMaxTimer == 0 and current > targetFanOffHumidity) then
msg = 'Target not reached but safety time-out is triggered.'
if PRINT_MODE == true then
log(LABEL .. msg)
end
else
msg = 'Target humidity reached'
if PRINT_MODE then
log(LABEL .. msg)
end
end
if PRINT_MODE then
log(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
log('Ventilator is off#' .. msg .. '#0')
end
else
-- we haven't reached the target yet
if PRINT_MODE then
log(LABEL .. 'Humidity delta: ' .. delta)
end
end
end
end
if PRINT_MODE then
log(LABEL .. 'target: ' .. target)
end
if PRINT_MODE then
-- Get Fan and Air Valve states
FanValveState()
log(LABEL .. 'New values >>>>>>>>>>>')
log(LABEL .. 'Fan State : ' .. msg_fan)
log(LABEL .. 'Air Valve State : ' .. msg_valve)
log(LABEL .. 'HumidityTmin5 : ' .. humidityTmin5)
log(LABEL .. 'HumidityTmin10 : ' .. humidityTmin10)
log(LABEL .. 'FanMaxTimer : ' .. fanMaxTimer)
log(LABEL .. 'HumCounter : ' .. humCounter)
log(LABEL .. 'FanFollowsProgram: ' .. fanFollowsProgram)
log(LABEL .. '---->>> Target : ' .. targetFanOffHumidity)
end
end
-- update the table with the new globals
HumidDB["humCounter"] = humCounter
HumidDB["humidityTmin5"] = humidityTmin5
HumidDB["humidityTmin10"] = humidityTmin10
HumidDB["targetFanOffHumidity"] = targetFanOffHumidity
HumidDB["fanMaxTimer"] = fanMaxTimer
HumidDB["fanFollowsProgram"] = fanFollowsProgram
-- save the table to storage
storage.set('Humid',HumidDB)
RE: LUA Script - Control humidity in bathroom - admin - 19.02.2021
For storage I recommend using a single table with values instead of multiple storage entries. You also have TEST_MODE_HUMVAR in quotes which means it will try to access "TEST_MODE_HUMVAR" storage item not an item that is contained in the TEST_MODE_HUMVAR variable. toInteger calls are not needed.
RE: LUA Script - Control humidity in bathroom - tigi - 19.02.2021
(19.02.2021, 12:18)admin Wrote: For storage I recommend using a single table with values instead of multiple storage entries. You also have TEST_MODE_HUMVAR in quotes which means it will try to access "TEST_MODE_HUMVAR" storage item not an item that is contained in the TEST_MODE_HUMVAR variable. toInteger calls are not needed.
Thank you admin for the quick review!
I've changed the TEST_MODE_HUMVAR part. I didn't quite test that part out, now I did.
As it seems, it doesn't need to be in storage anyway. On top of that it didn't work like it was for this Logicmachine knx script, I've tweaked it a little bit and now it works. Value 50 will stop the fan, any value above 55 will start the fan.
I've removed the toInterger calls and the function. Very strange it works without, I removed it initially but because at the time of porting and testing I encountered a strange behaviour in the calculated delta value (e.g. when delta should be 0 I got like 7.1...) which disappeared when re-adding the initial toInterger function. Better if it isn't needed indeed and it works without!
I've also placed the FanValveState() in the if-then PRINT_MODE statement, so it only is executed when logging is enabled.
As of now I haven't changed the storage part into a single table because of the lack of necessary knowledge. I'll get there eventually I hope and will change it then.
RE: LUA Script - Control humidity in bathroom - tigi - 22.02.2021
I've changed the script so the global variables are now stored in a table in storage.
RE: LUA Script - Control humidity in bathroom - admin - 22.02.2021
You should also check if the storage value is not a table otherwise the script will produce an error. Corrected version:
Code: -- Creating database for global variables and initialized to ZERO only first time of script use
HumidDB = storage.get('Humid')
if type(HumidDB) ~= 'table' or next(HumidDB) == nil then
RE: LUA Script - Control humidity in bathroom - tigi - 22.02.2021
(22.02.2021, 11:20)admin Wrote: You should also check if the storage value is not a table otherwise the script will produce an error. Corrected version:
Code: -- Creating database for global variables and initialized to ZERO only first time of script use
HumidDB = storage.get('Humid')
if type(HumidDB) ~= 'table' or next(HumidDB) == nil then
Thank you very much admin, I've changed the code in my script.
I assume it isn't necessary to post the complete code again or do I change my initial post?
For those interested, this script is also usefull to control a ventilator for heating purposes by changing the variable values, group addresses. Certainly set fan_max_timer > 120 (2 hours)
I will use it to start a ventilator once the air in a ventilation pipe gets to a certain temperature.
If you run 2 scripts of those, you need to change the table name also.
RE: LUA Script - Control humidity in bathroom - admin - 22.02.2021
Just update your first post with the latest code and remove all others if possible so only the newest code is available.
|