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