Styrning av krypgrundsavfuktare med Domoticz

Förra året installerade jag en nytt system för att avfukta krypgrunden då det gamla systemet inte fungerade speciellt bra. Sedan dess har jag utvecklat en styrsystem för att styra avfuktaren med hjälp av “mögelindex” i syfte att minska elförbrukningen då avfuktaren drar ca 1300W när den är igång.

Mögelindex

Mögelindex är ett mått på hur stor risken för en mögelskada är. Normalt brukar man ange 75% relativ fuktighet som ett slags gränsvärde, men det är allmänt känt att det tar lång tid för mögel att börja växa vid 75%. På sidan http://www.penthon.com/faq/vad-innebar-mogelindex/ beskrivs mögelindex, vilket i denna tillämpning anger den lägsta fukthalten för att undvika tillväxt.  Denna styrning tar heller inte hänsyn till tiden, utan jobbar hela tiden mot sämsta fallet.

Mögelgräns. Skriptet följer grafen med 5% marginal
Mögelgräns. Skriptet följer grafen med 5% marginal

Fuktstyrningen använder linjen i bilden ovan med en ytterligare marginal om 5% för att vara på den säkra sidan.

Förutsättningar

Styrningen bygger på Domoticz som körs på en Raspberry PI med stöd för Razberry (zwave) och onewire. Till avfuktningsaggregatet använder jag en zwave-plugg som både styr aggregatet av och på samt mäter dess effektförbrukning. Till onewire ansluts tre stycken temperatursensorer (DS18B20) och tre stycken kombinerade temperatur och fuktgivare utspridda i krypgrunden (DS2438).

LUA-skript för onewire

Jag har valt att inte använda den inbyggda funktionen för onewire i Domoticz då jag fick problem med spikar i temperatur och fuktmätningen från DS2438. Vidare så har jag också infört en onewirehubb pga allt för långa kabellängder.

LUA-skriptet “script_time_owfs.lua” körs varje minut i Domoticz och lägger till alla värden i sk dummie-devices i Domoticz. Dummie-devices är inte kopplade till någon hårdvara, vilket gör att skriptet kan filtrera bort spikarna och sedan stoppa in värdena för temperatur och fuktighet.

Skriptet konfigurerar även vid behov onewire-hubben så att alla fyra portar fungerar.

Det bör även nämnas att jag kompilerat owfs senaste version då jag är osäker på om “apt-get install owfs” installerar en owfs-version med stöd för hubben.

Länk till script_time_owfs.lua.

print("-----OWFS TIME-----")
-- OWFS path
OWFS_PATH = "/mnt/1wire/"
OWFS_TEMP_HUM_DEVICES = {	['26.160592010000'] = 'Krypgrund_SO',
									['26.740592010000'] = 'Krypgrund_SV',
									['26.802692010000'] = 'Krypgrund_NO',
									['26.1851E2010000'] = 'Ute' }

OWFS_TEMP_DEVICES = {	['28.1C3D99030000'] = "Avfuktare_Tilluft_Temp2",
								['28.87917A060000'] = "Avfuktare_Avluft_Temp2",
								['28.A9807B060000'] = "Avfuktare_Processluft_Temp2",
								['28.DC2E7A060000'] = "Avfuktare_Uteluft_Temp2" }

-- Onewire hub file
ONEWIRE_HUB_CONFIG_FILE       = "/mnt/1wire/uncached/EF.117120150000/hub/branch.BYTE"
ONEWIRE_HUB_PORT_CONFIG_VALUE = "15"
-- Cache file dir
OWFS_CACHEPATH = '/tmp'

-- Set how many readings in buffer
OWFS_BUFFERSIZE = 10

-- FUNCTIONS START --

--integer converts a float into an integer
function integer(x)
    return x<0 and math.ceil(x) or math.floor(x)
end

 -- deviceToTime returns a device time string in a format which can be used by the os.difftime function
function deviceToTime(time)
   return os.time { year  = string.sub(time, 1, 4),
                    month = string.sub(time, 6, 7),
                    day   = string.sub(time, 9, 10),
                    hour  = string.sub(time, 12, 13),
                    min   = string.sub(time, 15, 16),
                    sec   = string.sub(time, 18, 19) }
end

local function ReadFile(path)
    local file = io.open(path, "rb") -- r read mode and b binary mode
    if not file then
      return nil
    end
    local content = file:read "*a" -- *a or *all reads the whole file
    file:close()
    return content
end

local function WriteFile(path, content)
    local file = io.open(path, "wb") -- w write mode and b binary mode
    if not file then
      return nil
    end
    file:write(content)
    file:close()
end

local function ReadOWFS(device, filename, UseCache)
	if (UseCache == false) then
		path = OWFS_PATH.."uncached/"
	else
		path = OWFS_PATH
	end

   value = ReadFile(string.format("%s/%s/%s", path, device, filename))
   if (value ~= nil) then
         return value
   else
		-- Check if hub is configured with uncorrect value
		fileContent = ReadFile(ONEWIRE_HUB_CONFIG_FILE)
		if (fileContent ~= ONEWIRE_HUB_PORT_CONFIG_VALUE) then
			WriteFile(ONEWIRE_HUB_CONFIG_FILE, ONEWIRE_HUB_PORT_CONFIG_VALUE)
			print("-------------------")
			print("Onewire hub reconfigured")
		end
	end
	return nil
end

-- calcDeHumidLevel calucates the RF level for a certain temperature
-- If the RF is higher then returned value, dehumidification shall be done
-- A safety margin of five percent is added
function calcDeHumidLevel(temp)
		local humidityThreshold
      if (temp < 0) then humidityThreshold = 100 elseif (temp > 22) then
         humidityThreshold = 79 - 5
      else
         humidityThreshold = integer(-0.0015 * temp ^ 3+0.1193 * temp ^ 2 - 2.9878 * temp + 102.96) - 5

			if (humidityThreshold > 100) then
				humidityThreshold = 100
			elseif (humidityThreshold < 0) then humidityThreshold = 0 end end return humidityThreshold end -- secsToClock return a string representing seconds as HH:MM:SS function secsToClock(seconds) if ((seconds == 0) or (seconds == nil) ) then return "00:00:00" else days = integer(seconds / (60 * 60 * 24)) hours = integer((seconds - days * 60 * 60 * 24) / (60 * 60)) mins = integer((seconds - days * 60 * 60 * 24 - hours * 60 * 60) / 60) secs = seconds - days * 60 * 60 * 24 - hours * 60 * 60 - mins * 60; if (days > 99) then
         return string.format("%03d:%02d:%02d:%02d (DDD:HH:MM:SS)", days, hours, mins, secs)
      elseif (days > 0) then
         return string.format("%02d:%02d:%02d:%02d (DD:HH:MM:SS)", days, hours, mins, secs)
      else
         return string.format("%02d:%02d:%02d (HH:MM:SS)", hours, mins, secs)
      end
   end
end

-- Implode a string into a table using delimiter
function ImplodeTable(delimiter, list)
  local len = #list
  if len == 0 then
    return ""
  end
  local string = list[1]
  for i = 2, len do
    string = string .. delimiter .. list[i]
  end
  return string
end

-- Explode a table content into a string, separate fields with a delimiter
function ExplodeTable(delimiter, text, numElements)
   local list = {}; local pos = 1
   local el = 0
   while (el < numElements) do
      el = el + 1
      local first, last = string.find(text, delimiter, pos)
      if (first) then
         table.insert(list, string.sub(text, pos, first-1))
         pos = last+1
      else
         table.insert(list, string.sub(text, pos))
      break
      end
   end
   return list
end

-- Get the mean value of a table
function CalcMeanOnTable(t)
   local sum = 0
   local count= 0
   for k,v in pairs(t) do
      if (type(v) ~= 'number') then
         v = tonumber(v)
      end
      if (type(v) == 'number') then
         sum = sum + v
         count = count + 1
      end
  end
  return (sum/count)
end

-- Get the standard deviation of a table
function CalcStandardDeviationOnTable(t)
   local m
   local vm
   local sum = 0
   local count = 0
   local result

   m = CalcMeanOnTable(t)

   for k,v in pairs(t) do
      if (type(v) ~= 'number') then
         v = tonumber(v)
      end
      if (type(v) == 'number') then
         vm = v - m
         sum = sum + (vm * vm)
         count = count + 1
      end
   end

   return math.sqrt(sum / (count-1))
end


-- Calulates values from OWFS taking care of spikes in OWFS values.
-- Using an file buffer and calculation of mean and standard deviation.
-- If the value to be inserted deviates more than the standard deviation from mean value,
-- the standard deviation from the mean value from the buffer is returned instead of the actual value.
-- The actual value is stored in buffer

function getValue(device, value)
   local cache, arr, mean, deviation, allowedDeviation, newValue, elementsInBuffer

   -- Read history from cached file content
   cache = ReadFile(string.format("%s/%s", OWFS_CACHEPATH, device))

   -- If no file found
   if (cache == nil) then
      cache = ";"
   end

   -- Explode the file content into array elements
   arr = ExplodeTable(";", cache, OWFS_BUFFERSIZE)

   -- Calculate
   deviation = tonumber(CalcStandardDeviationOnTable(arr))
   mean      = tonumber(CalcMeanOnTable(arr))

   -- The allowed deviation shall not be too small
   if (deviation < 0.75) then allowedDeviation = 0.75 else allowedDeviation = deviation end -- Limit the changes in the buffer if (value > (mean + allowedDeviation)) then
      newValue = mean + allowedDeviation
   elseif (value < (mean - allowedDeviation)) then newValue = mean - allowedDeviation else newValue = value end -- Insert calulated value last in buffer table.insert(arr, newValue) elementsInBuffer = # arr -- Remove values in front of buffer if buffer size is too big if ((elementsInBuffer) > OWFS_BUFFERSIZE) then
      table.remove(arr,1)
      elementsInBuffer = elementsInBuffer - 1
   end

   -- Write the array into file
   WriteFile(string.format("%s/%s", OWFS_CACHEPATH, device), ImplodeTable(";", arr))

   return elementsInBuffer, deviation, mean, tonumber(CalcMeanOnTable(arr))
end

-- END OF FUNCTIONS --

commandArray = {}

i = 0
for OWFS_device, name in pairs(OWFS_TEMP_HUM_DEVICES) do

   -- Read temp and humidity from OWFS
   OWFS_temp = ReadOWFS(OWFS_device, "temperature", false)
   OWFS_hum  = ReadOWFS(OWFS_device, "humidity", false)

   if ((OWFS_temp ~= nil) and (OWFS_hum ~= nil)) then

      OWFS_temp = tonumber(OWFS_temp)
      OWFS_hum  = tonumber(OWFS_hum)

      -- Correct OWFS value
      bufferSize, deviationTemp, meanTemp, tempValue = getValue(string.format("%s_%s", name, "temp"), OWFS_temp)

      print(string.format("%s:temp Bufsize = %3d, Deviation = %-3.2f, Mean = %-3.2f, OWFS =  %-3.2f, Used = %-3.2f, Diff = %-3.2f", name, bufferSize, deviationTemp, meanTemp, OWFS_temp, tempValue, math.abs(OWFS_temp - tempValue)))

      -- Correct OWFS value
      bufferSize, deviationHum, meanHum, humValue = getValue(string.format("%s_%s", name, "hum"), OWFS_hum)

      print(string.format("%s:humi Bufsize = %3d, Deviation = %-3.2f, Mean = %-3.2f, OWFS =  %-3.2f, Used = %-3.2f, Diff = %-3.2f", name, bufferSize, deviationHum, meanHum, OWFS_hum, humValue, math.abs(OWFS_hum - humValue)))

      -- Calculate the wet level
      startDehumidify = calcDeHumidLevel(tempValue)
      if (humValue > startDehumidify) then
         wet = 3
      elseif (humValue > startDehumidify * 0.8) then
         wet = 0
      else
         wet = 2
      end

      -- Insert values into Domoticz device
		if (otherdevices_idx[name] ~= nil) then
			commandArray[i] = { ['UpdateDevice'] = string.format("%s|0|%3.2f;%3.2f;%d", otherdevices_idx[name], tempValue, humValue, wet) }
			i = i + 1
		else
			print(string.format("Cannot find id number for device %s", name))
		end
   else
      print(string.format("No OWFS readings from device %s, %s: %s:%s", name, OWFS_device, OWFS_temp, OWFS_hum))
   end
end
for OWFS_device, name in pairs(OWFS_TEMP_DEVICES) do

   -- Read temp and humidity from OWFS
   OWFS_temp = ReadOWFS(OWFS_device, "temperature", true)

   if (OWFS_temp ~= nil) then

      OWFS_temp = tonumber(OWFS_temp)
      print(string.format("%s: temp = %3.2f", name, OWFS_temp))

      -- Insert values into Domoticz device
		if (otherdevices_idx[name] ~= nil) then
			commandArray[i] = { ['UpdateDevice'] = string.format("%s|0|%3.2f", otherdevices_idx[name], OWFS_temp) }
			i = i + 1
		else
			print(string.format("Cannot find id number for device %s", name))
		end
   else
      print(string.format("No OWFS readings from device %s, %s: %s:%s", name, OWFS_device, OWFS_temp, OWFS_hum))
   end
end

return commandArray

LUA-skript för fuktstyrning

LUA-skriptet för fuktstyrning består av två skript.
Länk till script_device_dehumid.lua

-- Dehumidifier device switch name in Domoticz
DEHUMID_DEVICE_SWITCH = 'SWITCH_Avfuktare_Switch'
-- User variable
DEHUMID_ISCANCELED    = 'Dehumidifier_IsCanceled'
commandArray = {}

if (devicechanged[DEHUMID_DEVICE_SWITCH] ~= nil) then
   dehumidifierState = devicechanged[DEHUMID_DEVICE_SWITCH]

   if (uservariables[DEHUMID_ISCANCELED] ~= nil) then
      dehumidifierIsCanceled = (uservariables[DEHUMID_ISCANCELED] == "1" and true or false)
   else
      dehumidifierIsCanceled = nil
      print("Warning! Cannot read user variable "..DEHUMID_ISCANCELED)
   end

   print("Dehumidifier state is "..dehumidifierState)
   print("User variable "..DEHUMID_ISCANCELED.." state is "..(dehumidifierIsCanceled == true and "true" or "false"))

   if ((dehumidifierIsCanceled == true) and (dehumidifierState == "On")) then
         dehumidifierIsCanceled = false
         commandArray['Variable:'..DEHUMID_ISCANCELED] = (dehumidifierIsCanceled == true and "1" or "0")
   end

   print("Dehumidifier is set to "..dehumidifierState)
   print("User variable "..DEHUMID_ISCANCELED.." is set to "..(dehumidifierIsCanceled == true and "true" or "false"))
end
return commandArray

script_device_dehumid.lua startar och stoppar avfuktaren och har koll på om avfuktaren är stoppad pga eventuella fel, dvs om den är “cancelerad”.

Skriptet som är själva motorn i konceptet heter script_time_dehumid.lua och körs varje minut. Länk till script_time_dehumid.lua.

print("-------------------")
-- Name of Domoticz devices for both RF and temperatur measurements
-- The devices must be of an combined type of both humidity and temperature
HUMANDTEMP_DEVICES = {'Krypgrund_SV', 'Krypgrund_SO', 'Krypgrund_NO' }

-- Outdoor air temperature measured before the dehumidifier
DEVICE_OUTDOOR_TEMP    = 'Avfuktare_Uteluft_Temp2'

-- Name of Domoticz device for both outdoor RF and outdoor temperature
HUMANDTEMP_OUTDOOR_DEVICE = 'Ute'

-- Processed wet air temperature measured after the dehumidifier
DEVICE_PROCESSAIR_TEMP = 'Avfuktare_Processluft_Temp2'

-- Inlet dry air temperature measured after the dehumidifier
DEVICE_INLETAIR_TEMP   = 'Avfuktare_Tilluft_Temp2'

-- If the air temperature increase is greater than this limit,
-- the dehumidifier will stop until it is manullay started in Domoticz
PROCESSAIR_TEMP_MAXINC = 35
INLETAIR_TEMP_MAXINC = 30

-- Max device oldest update in seconds
HUMID_DEVICE_TIMEOUT = 5*60

-- Dehumidifier minimum runtime in seconds
MIN_RUNTIME = 20*60

-- Dehumidifier minimum runtime in seconds
MAX_RUNTIME = 2*60*60

-- Dehumidifier minimum stoptime in seconds
MIN_STOPTIME = 60*15

-- Dehumidifier device switch name in Domoticz
DEHUMID_DEVICE_SWITCH = 'SWITCH_Avfuktare_Switch'

-- User variable, an integer used as an boolean (either 0 or 1)
DEHUMID_ISCANCELED = 'Dehumidifier_IsCanceled'

-- Max humidity hyesteris in percent
MAX_HUMID_HYSTERESIS = 12

-- Min humidity hyesteris in percent
MIN_HUMID_HYSTERESIS = - 2

-- Humidity hyesteris time konstant
HUMID_HYSTERESIS_KONSTANT = 20

--Name of Domoticz power meter device.
--Set to '' if no watt measure device exist
DEHUMID_POWER_DEVICE = 'SWITCH_Avfuktare_Power'

-- If runtime exceeds this value in seconds, the script checks if dehumidifier is running
NO_POWERTEST_TIME = 5*60

-- FUNCTIONS START --
--integer converts a float into an integer
function integer(x)
    return x<0 and math.ceil(x) or math.floor(x)
end

 -- deviceToTime returns a device time string in a format which can be used by the os.difftime function
function deviceToTime(time)
   return os.time { year  = string.sub(time, 1, 4),
                    month = string.sub(time, 6, 7),
                    day   = string.sub(time, 9, 10),
                    hour  = string.sub(time, 12, 13),
                    min   = string.sub(time, 15, 16),
                    sec   = string.sub(time, 18, 19) }
end

-- calcDeHumidLevel calucates the RF level for a certain temperature
-- If the RF is higher then returned value, dehumidification shall be done
-- A safety margin of five percent is added
function calcDeHumidLevel(temp)
		local humidityThreshold
      if (temp < 0) then humidityThreshold = 100 elseif (temp > 22) then
         humidityThreshold = 79 - 5
      else
         humidityThreshold = integer(-0.0015 * temp ^ 3+0.1193 * temp ^ 2 - 2.9878 * temp + 102.96) - 5

			if (humidityThreshold > 100) then
				humidityThreshold = 100
			elseif (humidityThreshold < 0) then humidityThreshold = 0 end end return humidityThreshold end -- secsToClock return a string representing seconds as (DD:)HH:MM:SS function secsToClock(seconds) if ((seconds == 0) or (seconds == nil) ) then return "00:00:00" else days = integer(seconds / (60 * 60 * 24)) hours = integer((seconds - days * 60 * 60 * 24) / (60 * 60)) mins = integer((seconds - days * 60 * 60 * 24 - hours * 60 * 60) / 60) secs = seconds - days * 60 * 60 * 24 - hours * 60 * 60 - mins * 60; if (days > 99) then
         return string.format("%03d:%02d:%02d:%02d (DDD:HH:MM:SS)", days, hours, mins, secs)
      elseif (days > 0) then
         return string.format("%02d:%02d:%02d:%02d (DD:HH:MM:SS)", days, hours, mins, secs)
      else
         return string.format("%02d:%02d:%02d (HH:MM:SS)", hours, mins, secs)
      end
   end
end

-- Print informaiton about humitiy and temperatur and other values
function printHumidity(device, currentTemperature, currentHumidity, humidityThreshold)
   delta = humidityThreshold - currentHumidity
	-- Fetch outdoor sensor humidity
   outDoorHum = getHumidityFromDevice(HUMANDTEMP_OUTDOOR_DEVICE)

	if (outDoorHum ~= nil) then
		-- OTD = OuTdoor Differense
		print(string.format("Device %s: %04.1fC, %2d%%, Th = %02d%% (%d%%), OTD = %02d%%, ThOTD = %02d%%", device, currentTemperature, currentHumidity, humidityThreshold, delta, outDoorHum-currentHumidity, outDoorHum-humidityThreshold))
	else
		print(string.format("Device %s: %04.1fC, %2d%%, Th = %02d%% (%d%%)", device, currentTemperature, currentHumidity, humidityThreshold, delta))
	end
end

-- Get humidifier switch state
function getDehumidifierSwitchState()
   if (otherdevices[DEHUMID_DEVICE_SWITCH] ~= nil) then
      return otherdevices[DEHUMID_DEVICE_SWITCH]
   else
      print("Warning! Cannot read from otherdevices['"..DEHUMID_DEVICE_SWITCH.."']")
      return "Unknown"
   end
end

-- Get dehumidifiers run time
function getRunTime()
   if (getDehumidifierSwitchState() == "On") then
      return os.difftime(os.time(), getDeviceLastUpdated(DEHUMID_DEVICE_SWITCH))
   else
      return 0
   end
end

-- Get the dehumidifer stop time
function getStopTime()
   if (getDehumidifierSwitchState() == "Off") then
      return os.difftime(os.time(), getDeviceLastUpdated(DEHUMID_DEVICE_SWITCH))
   else
      return 0
   end
end

-- Get the last time a device was updated in Domoticz
function getDeviceLastUpdated(device)
   if (otherdevices_lastupdate[device] ~= nil) then
      return deviceToTime(otherdevices_lastupdate[device])
   else
      return 0
   end
end

-- Get temperature value from a temperature device in Domoticz
function getTempFromDevice(device)
   if (otherdevices_temperature[device] ~= nil) then
      return tonumber(otherdevices_temperature[device])
   else
      print("Warning! Cannot read temp device "..device)
		return nil
   end
end

-- Get humidity value from a humidity device in Domoticz
function getHumidityFromDevice(device)
   if (otherdevices_humidity[device] ~= nil) then
      return tonumber(otherdevices_humidity[device])
   else
      print("Warning! Cannot read humid device "..device)
      return nil
   end
end

-- Get the dehumidifer power in getDehumidifierWatts()
function getDehumidifierWatts()
   if ((DEHUMID_POWER_DEVICE ~= '') and (otherdevices_svalues[DEHUMID_POWER_DEVICE] ~= nil)) then
      return integer(tonumber(otherdevices_svalues[DEHUMID_POWER_DEVICE]))
   else
      print("Warning! Cannot read power from device "..DEHUMID_POWER_DEVICE)
      return nil
   end
end
-- ca dehumidifier state as true or false
function setDehumidifierState(state)
	if ((state == true) or (state == false)) then
		commandArray['Variable:'..DEHUMID_ISCANCELED] = (state == true and "1" or "0")
	else
		print("Error in param to setDehumidifierState, unknown state")
	end
end

-- Check if dehumidfier is canceled
function isDehumidifierCanceled()
	if (commandArray['Variable:'..DEHUMID_ISCANCELED] ~= nil) then
	   return (commandArray['Variable:'..DEHUMID_ISCANCELED] == "1" and true or false)
	elseif (uservariables[DEHUMID_ISCANCELED] ~= nil) then
      return (uservariables[DEHUMID_ISCANCELED] == "1" and true or false)
   else
      print("Warning! Cannot read user variable "..DEHUMID_ISCANCELED)
      return nil
   end
end

-- Calculate the hysteris between dehumidfication start and stop
-- Hystersis is decreasing as a function of runtime
function calcHysteresis(temp)
	return (MAX_HUMID_HYSTERESIS - MIN_HUMID_HYSTERESIS) / ((getRunTime() / 60) * (1 / HUMID_HYSTERESIS_KONSTANT) + 1) + MIN_HUMID_HYSTERESIS
end

-- END OF FUNCTIONS --

commandArray = {}
now = os.time()

-- Check if dehumidifer switch is off
if (getDehumidifierSwitchState() == "Off") then

	-- Do checks if dehumidifier is not canceled
   if (isDehumidifierCanceled() == false) then

		--Check all humid devices
      local start = false
      for key,device in pairs(HUMANDTEMP_DEVICES) do

         temp = getTempFromDevice(device)
         hum  = getHumidityFromDevice(device)

         -- Only use working devices which has not old values
         if ((temp ~= nil) and (hum ~= nil) and (os.difftime(now, getDeviceLastUpdated(device)) < HUMID_DEVICE_TIMEOUT)) then startDehumidify = calcDeHumidLevel(temp) if (hum >= startDehumidify) then
               start = true
            end

            printHumidity(device, temp, hum, startDehumidify)

			else
            print("Has no updates for device "..device)
            print("Last seen "..device.." "..secsToClock(getDeviceLastUpdated(device)))
         end
      end

		-- Start if at least one device has triggered a start
      if (start == true) then

         -- Only start if MIN_STOPTIME has been reached
         if (os.difftime(now, getDeviceLastUpdated(DEHUMID_DEVICE_SWITCH)) > MIN_STOPTIME) then
            print("Starting dehumidification")
            commandArray[DEHUMID_DEVICE_SWITCH] = "On"
            return commandArray
         else
            print("Dehumidifier has not yet reached minimun stoptime which is "..secsToClock(MIN_RUNTIME)..". Time left is "..secsToClock(MIN_STOPTIME-getStopTime()))
         end
      else
			-- No devices has reached start dehumid level.
			-- Let's instead do some checks to see if humidifier is really stopped
         if (getDehumidifierWatts() ~= nil) then
            -- If device reports power after dehumidifier has been stopped, it is apperently not stopped.
            if ((getDehumidifierWatts() > 10) and  (getStopTime() > NO_POWERTEST_TIME)) then
               print("Dehumidifier shouldn't be running")
               print("Stopping Dehumidifier")
               commandArray[DEHUMID_DEVICE_SWITCH] = "Off"
               return commandArray
            else
               print("Dehumidifier is not running")
               print("Dehumidifier stoptime is "..secsToClock(getStopTime()))
            end
         end
      end
	-- Do some checks if dehumidifier is canceled (and dehumidifier switch is off)
   elseif (isDehumidifierCanceled() == true) then
      if (getDehumidifierWatts() ~= nil)  then
         -- If device reports power when it is canceled it is not stopped
         if ((getDehumidifierWatts() > 10)) then
            print("Dehumidifier is canceled and switch is turned off, but it is still running")
            print("Stopping Dehumidifier")
            commandArray[DEHUMID_DEVICE_SWITCH] = "Off"
            return commandArray
         else
            print("Dehumidifier is canceled and cannot be automatically started.")
            print("Dehumidifier stop time is "..secsToClock(getStopTime()))
         end
      end
   end
-- Check if dehumidifer switch is on
elseif (getDehumidifierSwitchState() == "On") then

	-- Do checks if dehumidifier is not canceled
   if (isDehumidifierCanceled() == false) then

      outdoorTemp = getTempFromDevice(DEVICE_OUTDOOR_TEMP)
      processAirTemp = getTempFromDevice(DEVICE_PROCESSAIR_TEMP)
      inletAirTemp = getTempFromDevice(DEVICE_INLETAIR_TEMP)

		-- First lets do some checks to see if all temperature is within boundaries
		-- If not, stop dehumidifier!
		if (outdoorTemp == nil) then
         commandArray[DEHUMID_DEVICE_SWITCH] = "Off"
         print(string.format("Temperature device %s is not available. Last value was set %s", DEVICE_OUTDOOR_TEMP, os.date("%Y-%m-%d %H:%M:%S", getDeviceLastUpdated(DEVICE_OUTDOOR_TEMP))))
			print("Canceling dehumidification. Dehumidifier must be manually started")
         setDehumidifierState(true)
         return commandArray
      elseif (processAirTemp == nil) then
         commandArray[DEHUMID_DEVICE_SWITCH] = "Off"
         print(string.format("Temperature device %s is not available. Last value was set %s", DEVICE_PROCESSAIR_TEMP,  os.date("%Y-%m-%d %H:%M:%S", getDeviceLastUpdated(DEVICE_PROCESSAIR_TEMP))))
			print("Canceling dehumidification. Dehumidifier must be manually started")
         setDehumidifierState(true)
			return commandArray
      elseif (inletAirTemp == nil) then
         commandArray[DEHUMID_DEVICE_SWITCH] = "Off"
         print(string.format("Temperature device %s is not available. Last value was set %s", DEVICE_INLETAIR_TEMP,  os.date("%Y-%m-%d %H:%M:%S", getDeviceLastUpdated(DEVICE_INLETAIR_TEMP))))
			print("Canceling dehumidification. Dehumidifier must be manually started")
			setDehumidifierState(true)
			return commandArray
      elseif ((processAirTemp - outdoorTemp) > PROCESSAIR_TEMP_MAXINC) then
         commandArray[DEHUMID_DEVICE_SWITCH] = "Off"
         print(string.format("Temperature delta, %2f, over dehumdifiers process air is greater than threshold %2f. Stopping dehumdifier for safety reasons", processAirTemp - outdoorTemp, PROCESSAIR_TEMP_MAXINC))
			print("Canceling dehumidification. Dehumidifier must be manually started")
			setDehumidifierState(true)
         return commandArray
      elseif ((inletAirTemp - outdoorTemp) > INLETAIR_TEMP_MAXINC) then
         commandArray[DEHUMID_DEVICE_SWITCH] = "Off"
         print(string.format("Temperature delta, %2f, over dehumdifiers inlet  air is greater than the threshold %2f. Stopping dehumdifier for safety reasons", inletAirTemp - outdoorTemp, INLETAIR_TEMP_MAXINC))
			print("Canceling dehumidification. Dehumidifier must be manually started")
			setDehumidifierState(true)
         return commandArray
      end

		-- Check if runtime exceeds maximum runtime
		if (getRunTime() > MAX_RUNTIME) then
         commandArray[DEHUMID_DEVICE_SWITCH] = "Off"
         print(string.format("Dehumidifier runtime %s exceed maximun runtime of %s. Stopping dehumidification.", secsToClock(getRunTime()), secsToClock(MAX_RUNTIME)))
         return commandArray
		end

      --Check all humid devices
      local stop = true
		local hysteresis = calcHysteresis()
	   print(string.format("Dehumid hysteris for runtime %s is %-3.2f", secsToClock(getRunTime()), hysteresis))

      for key,device in pairs(HUMANDTEMP_DEVICES) do
         temp = getTempFromDevice(device)
         hum  = getHumidityFromDevice(device)

         -- Only use working devices which has not old values
         if ((temp ~= nil) and (hum ~= nil) and (os.difftime(now, deviceToTime(otherdevices_lastupdate[device])) < HUMID_DEVICE_TIMEOUT)) then stopDehumidify = calcDeHumidLevel(temp) - hysteresis if (hum > stopDehumidify) then
					stop = false
				end

				printHumidity(device, temp, hum, stopDehumidify)

			else
            print("Has no updates for device "..device)
            print("Last seen "..device.." "..secsToClock(getDeviceLastUpdated(device)))
         end
      end

		-- Stop if at least one humid device has triggered a stop
      if (stop == true) then
         -- check if MIN_RUNTIME has been reached
         if (getRunTime() > MIN_RUNTIME) then
            print("Stopping dehumidification")
            commandArray[DEHUMID_DEVICE_SWITCH] = "Off"
            return commandArray
         else
            print("Dehumidifier has not yet reached minimun runtime which is "..secsToClock(MIN_RUNTIME)..". Time left is "..secsToClock(MIN_RUNTIME-getRunTime()))
         end

		-- No devices has reached stop dehumid level.
		-- Let's instead do some checks to see if humidifier is really running
      elseif (getDehumidifierWatts() ~= nil) then
      -- If device reports no power NO_POWERTEST_TIME after dehumidifier has been started, it is apperently not running
         if ((getRunTime() > NO_POWERTEST_TIME) and (getDehumidifierWatts() < 10)) then print("Dehumidifier should be running but isn't") print("Restarting dehumidifier") commandArray[DEHUMID_DEVICE_SWITCH] = "On" -- All is working as it supposed to do elseif (getDehumidifierWatts() > 10) then
			   print(string.format("Dehumidifier is running. Dehumidifier power consumption is %d W", getDehumidifierWatts()))
            print("Dehumidifier runtime is "..secsToClock(getRunTime()))
         end
      end
	-- Do some checks if dehumidifier is canceled (and dehumidifier switch is on)
   elseif (isDehumidifierCanceled() == true) then
      print("Dehumidifier is canceled and switch is turned on")
      print("Stopping Dehumidifier")
      commandArray[DEHUMID_DEVICE_SWITCH] = "Off"
      return commandArray
   end
end
-- The possibility to the script to reach down here is very low. But if it is, lets return safely
return commandArray

Beräkning av fuktgräns mha mögelindex

Funktionen calcDeHumidLevel använder temperatur som inparameter för att beräkna den nedre fuktgränsen när det finns risk för mögeltillväxt.

-- calcDeHumidLevel calucates the RF level for a certain temperature
-- If the RF is higher then returned value, dehumidification shall be done
-- A safety margin of five percent is added
function calcDeHumidLevel(temp)
		local humidityThreshold
      if (temp < 0) then humidityThreshold = 100 elseif (temp > 22) then
         humidityThreshold = 79 - 5
      else
         humidityThreshold = integer(-0.0015 * temp ^ 3+0.1193 * temp ^ 2 - 2.9878 * temp + 102.96) - 5

			if (humidityThreshold > 100) then
				humidityThreshold = 100
			elseif (humidityThreshold < 0) then
				humidityThreshold = 0
			end

		end

		return humidityThreshold
end

Ekvationen är framtagen i Excel och går att analysera i bifogad Mogelindex.xlsx. Lua-koden gör en del kontroller. Bland annat är risken för mögel noll om det är minusgrader. Likaså kan inte den relativa fukthalten överstiga 100%.
En optimering görs då temperaturen överstiger 22 grader, då returneras ett statiskt värde. Detta eftersom ekvationen efter 22 grader inte riktigt följer mögelindex.
Marginalen på 5% är också tydligt synlig i funktionen.

Beräkning av Hysteres

Skriptet använder funktionen calcHysteris för att beräkna hysteresen mellan start och stopp.

-- Calculate the hysteris between dehumidfication start and stop
-- Hystersis is decreasing as a function av runtime
function calcHysteresis(temp)
	return (MAX_HUMID_HYSTERESIS - MIN_HUMID_HYSTERESIS) / ((getRunTime() / 60) * (1 / HUMID_HYSTERESIS_KONSTANT) + 1) + MIN_HUMID_HYSTERESIS
end

Principen är att minska hysteresen allt eftersom gångtiden ökar. Syftet är att låta avfuktaren kunna stoppa om gångtiden blir lång. I värsta fall kan avfuktaren gå i all oändlighet. Med denna funktion minskar den risken, den elimineras dock inte.

Hystes som minskar med avfuktarens gångtid
Hystes som minskar med avfuktarens gångtid

Hur hysteresen minskar med tiden bestäms av tre konstanter, MAX_HUMID_HYSTERESIS, MIN_HUMID_HYSTERESIS och HUMID_HYSTERESIS_KONSTANT. Hur dessa påverkar hysteresen går att analysera i bifogad Hysteres.xlsx.

Anledningen till att jag införde denna princip är att jag har en otät krypgrund (som ska tätas) och ibland blir det väldigt långa gångtider på avfuktaren. Den sista procenten tar ibland väldigt lång tid. På detta sätt undviks detta, värdet är ju ändå under fuktgränsen.

Ändringslogg

Datum Anteckningar
2016-06-30 Första versionen publicerad
2016-07-02 Ändrade beräkningen av hysteres till en funktion som minskar hysteresen som en funktion av gångtiden. Syftet är att avfuktaren ska få en rimlig chans att kunna stoppa om dels hysteresen är stor och dels gångtiden är lång.
2016-07-03 Optimerade beräkningen av hysteres och setDehumidifierState.
Förtydligade loggutskrift från OWFS_time.lua.
Lagt till maximum runtime

Posted

in

,

by

Tags: