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.

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.

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 |