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:
--[[

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:
-- 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:
-- 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: