This forum uses cookies
This forum makes use of cookies to store your login information if you are registered, and your last visit if you are not. Cookies are small text documents stored on your computer; the cookies set by this forum can only be used on this website and pose no security risk. Cookies on this forum also track the specific topics you have read and when you last read them. Please confirm that you accept these cookies being set.

LUA Script - Control humidity in bathroom
#1
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:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
--[[ 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)
Reply
#2
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.
Reply
#3
(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! Cool

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.
Reply
#4
I've changed the script so the global variables are now stored in a table in storage.
Reply
#5
You should also check if the storage value is not a table otherwise the script will produce an error. Corrected version:
Code:
1234
-- 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
Reply
#6
(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:
1234
-- 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.
Reply
#7
Just update your first post with the latest code and remove all others if possible so only the newest code is available.
Reply


Forum Jump: