mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-01-31 06:39:11 +00:00
92bdef0832
git-svn-id: https://svn.fhem.de/fhem/trunk@28949 2b470e98-0d58-463d-a4d8-8e2adae1ed80
854 lines
35 KiB
Perl
854 lines
35 KiB
Perl
# $Id$
|
||
##############################################
|
||
#
|
||
# Dewpoint computing
|
||
#
|
||
# based / modified from 98_average.pm (C) by Rudolf Koenig
|
||
#
|
||
# Copyright (C) 2012 Willi Herzig
|
||
#
|
||
# This program is free software; you can redistribute it and/or
|
||
# modify it under the terms of the GNU General Public License
|
||
# as published by the Free Software Foundation; either version 2
|
||
# of the License, or (at your option) any later version.
|
||
#
|
||
# This program is distributed in the hope that it will be useful,
|
||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
# GNU General Public License for more details.
|
||
#
|
||
# You should have received a copy of the GNU General Public License
|
||
# along with this program; if not, write to the Free Software
|
||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||
#
|
||
# The GNU General Public License may also be found at http://www.gnu.org/licenses/gpl-2.0.html .
|
||
#
|
||
package main;
|
||
use strict;
|
||
use warnings;
|
||
|
||
use vars qw(%defs);
|
||
|
||
sub Log3($$$);
|
||
|
||
# default maximum time_diff for dewpoint
|
||
my $dewpoint_time_diff_default = 1; # 1 Second
|
||
|
||
##########################
|
||
sub
|
||
dewpoint_Initialize($)
|
||
{
|
||
my ($hash) = @_;
|
||
$hash->{DefFn} = "dewpoint_Define";
|
||
$hash->{NotifyFn} = "dewpoint_Notify";
|
||
$hash->{AttrFn} = "dewpoint_Attr";
|
||
$hash->{NotifyOrderPrefix} = "10-"; # Want to be called before the rest
|
||
$hash->{AttrList} = "disable:0,1 legacyStateHandling:0,1 verbose max_timediff absFeuchte"
|
||
. " absoluteHumidity vapourPressure";
|
||
}
|
||
|
||
|
||
##########################
|
||
sub
|
||
dewpoint_Define($$)
|
||
{
|
||
my ($hash, $def) = @_;
|
||
my @a = split("[ \t][ \t]*", $def);
|
||
|
||
return "wrong syntax: define <name> dewpoint (dewpoint|fan|alarm) devicename [options]"
|
||
if(@a < 4);
|
||
|
||
|
||
my $name = $a[0];
|
||
my $cmd_type = $a[2]; # dewpoint, fan, alarm
|
||
my $devname = $a[3];
|
||
|
||
if ($cmd_type eq "dewpoint") {
|
||
# define <name> dewpoint dewpoint devicename-regex [temp_name hum_name new_name]
|
||
if(@a == 7) {
|
||
$hash->{TEMP_NAME} = $a[4];
|
||
$hash->{HUM_NAME} = $a[5];
|
||
$hash->{NEW_NAME} = $a[6];
|
||
} elsif (@a == 4) {
|
||
$hash->{TEMP_NAME} = "temperature";
|
||
$hash->{HUM_NAME} = "humidity";
|
||
$hash->{NEW_NAME} = "dewpoint";
|
||
} else {
|
||
return "wrong syntax: define <name> dewpoint dewpoint devicename-regex [temp_name hum_name new_name]"
|
||
}
|
||
} elsif ($cmd_type eq "fan") {
|
||
# define <name> dewpoint fan devicename-regex devicename-outside min_temp
|
||
if (@a == 6 || @a == 7) {
|
||
$hash->{DEVNAME_OUT} = $a[4];
|
||
$hash->{MIN_TEMP} = $a[5];
|
||
if (@a == 6) {
|
||
$hash->{DIFF_TEMP} = 0;
|
||
} else {
|
||
$hash->{DIFF_TEMP} = $a[6];
|
||
}
|
||
} else {
|
||
return "wrong syntax: define <name> dewpoint fan devicename-regex devicename-outside min_temp [diff_temp]"
|
||
}
|
||
} elsif ($cmd_type eq "alarm") {
|
||
# define <name> dewpoint alarm devicename-regex devicename-reference diff_temp
|
||
if (@a == 6) {
|
||
$hash->{DEVNAME_REF} = $a[4];
|
||
$hash->{DIFF_TEMP} = $a[5];
|
||
} else {
|
||
return "wrong syntax: define <name> dewpoint alarm devicename-regex devicename-reference diff_temp"
|
||
}
|
||
} else {
|
||
return "wrong syntax: define <name> dewpoint (dewpoint|fan|alarm) devicename-regex [options]"
|
||
}
|
||
|
||
$hash->{CMD_TYPE} = $cmd_type;
|
||
|
||
eval { "Hallo" =~ m/^$devname$/ };
|
||
return "Bad regexp: $@" if($@);
|
||
|
||
$hash->{DEV_REGEXP} = $devname;
|
||
# set NOTIFYDEV
|
||
notifyRegexpChanged($hash, $devname);
|
||
$hash->{STATE} = "active";
|
||
return undef;
|
||
}
|
||
|
||
##########################
|
||
sub
|
||
dewpoint_Attr(@)
|
||
{
|
||
my ($cmd, $name, $a_name, $a_val) = @_;
|
||
my $hash = $defs{$name};
|
||
|
||
if ($cmd eq "set" && $a_name eq "absFeuchte") {
|
||
Log(1, "dewpoint $name: attribute 'absFeuchte' is deprecated, please use 'absoluteHumidity'");
|
||
return undef;
|
||
}
|
||
|
||
if ($cmd eq "set" && ($a_name eq "absoluteHumidity" || $a_name eq "vapourPressure")) {
|
||
if (! goodReadingName($a_val)) {
|
||
return "Value of $a_name is not a valid reading name";
|
||
}
|
||
}
|
||
|
||
return undef;
|
||
}
|
||
|
||
##########################
|
||
sub
|
||
dewpoint_Notify($$)
|
||
{
|
||
my ($hash, $dev) = @_;
|
||
|
||
my $hashName = $hash->{NAME};
|
||
my $devName = $dev->{NAME};
|
||
my $re = $hash->{DEV_REGEXP};
|
||
|
||
# fast exit
|
||
return "" if (!defined($re) || $devName !~ m/^$re$/);
|
||
|
||
return "" if (AttrVal($hashName, "disable", undef));
|
||
|
||
my $cmd_type = $hash->{CMD_TYPE};
|
||
|
||
# dewpoint
|
||
my $temp_name = "temperature";
|
||
my $hum_name = "humidity";
|
||
my $new_name = "dewpoint";
|
||
# fan
|
||
my $devname_out = "";
|
||
my $min_temp = 0;
|
||
# alarm
|
||
my $devname_ref = "";
|
||
my $diff_temp = 0;
|
||
|
||
|
||
if ($cmd_type eq "dewpoint") {
|
||
if (!defined($hash->{TEMP_NAME}) || !defined($hash->{HUM_NAME}) || !defined($hash->{NEW_NAME})) {
|
||
# should never happen!
|
||
Log3($hashName, 1, "Error dewpoint: TEMP_NAME || HUM_NAME || NEW_NAME undefined");
|
||
return "";
|
||
}
|
||
$temp_name = $hash->{TEMP_NAME};
|
||
$hum_name = $hash->{HUM_NAME};
|
||
$new_name = $hash->{NEW_NAME};
|
||
Log3($hashName, 4, "dewpoint_notify: cmd_type=$cmd_type devname=$devName dewname=$hashName, dev=$devName, "
|
||
. "dev_regex=$re temp_name=$temp_name hum_name=$hum_name");
|
||
} elsif ($cmd_type eq "fan") {
|
||
if (!defined($hash->{DEVNAME_OUT}) || !defined($hash->{MIN_TEMP})) {
|
||
# should never happen!
|
||
Log3($hashName, 1, "Error dewpoint: DEVNAME_OUT || MIN_TEMP undefined");
|
||
return "";
|
||
}
|
||
$devname_out = $hash->{DEVNAME_OUT};
|
||
$min_temp = $hash->{MIN_TEMP};
|
||
$diff_temp = $hash->{DIFF_TEMP};
|
||
Log3($hashName, 4, "dewpoint_notify: cmd_type=$cmd_type devname=$devName dewname=$hashName, dev=$devName, "
|
||
. " dev_regex=$re, devname_out=$devname_out, min_temp=$min_temp, diff_temp=$diff_temp");
|
||
|
||
} elsif ($cmd_type eq "alarm") {
|
||
if (!defined($hash->{DEVNAME_REF}) || !defined($hash->{DIFF_TEMP})) {
|
||
# should never happen!
|
||
Log3($hashName, 1, "dewpoint_notify: error: DEVNAME_REF || DIFF_TEMP undefined");
|
||
return "";
|
||
}
|
||
$devname_ref = $hash->{DEVNAME_REF};
|
||
$diff_temp = $hash->{DIFF_TEMP};
|
||
Log3($hashName, 4, "dewpoint_notify: cmd_type=$cmd_type devname=$devName dewname=$hashName, dev=$devName, "
|
||
. "dev_regex=$re, devname_ref=$devname_ref, diff_temp=$diff_temp");
|
||
} else {
|
||
# should never happen:
|
||
Log3($hashName, 1, "dewpoint_notify: error: unknown cmd_type ".$cmd_type);
|
||
return "";
|
||
}
|
||
|
||
my $nev = int(@{$dev->{CHANGED}});
|
||
|
||
# if we use the "T H" syntax we must track the index of the state event
|
||
my $i_state_ev;
|
||
|
||
my $temperature = "";
|
||
my $humidity = "";
|
||
|
||
for (my $i = 0; $i < $nev; $i++) {
|
||
my $s = $dev->{CHANGED}[$i];
|
||
|
||
Log3($hashName, 5, "dewpoint_notify: s='$s'");
|
||
|
||
################
|
||
# Filtering
|
||
next if(!defined($s));
|
||
my ($evName, $val, $rest) = split(" ", $s, 3); # resets $1
|
||
next if(!defined($evName));
|
||
next if(!defined($val));
|
||
Log3($hashName, 5, "dewpoint_notify: evName='$evName' val=$val'");
|
||
if (($evName eq "T:") && ($temp_name eq "T")) {
|
||
$i_state_ev = $i;
|
||
#my ($evName1, $val1, $evName2, $val2, $rest) = split(" ", $s, 5); # resets $1
|
||
if ($s =~ /T: ([-+]?[0-9]*\.[0-9]+|[-+]?[0-9]+)/) {
|
||
$temperature = $1;
|
||
}
|
||
if ($s =~ /H: [-+]?([0-9]*\.[0-9]+|[0-9]+)/) {
|
||
$humidity = $1;
|
||
}
|
||
Log3($hashName, 5, "dewpoint_notify: T: H:, temp=$temperature hum=$humidity");
|
||
} elsif ($evName eq $temp_name.":") {
|
||
$temperature = $val;
|
||
Log3($hashName, 5, "dewpoint_notify: temperature! dev=$devName, temp_name=$temp_name, temp=$temperature");
|
||
} elsif ($evName eq $hum_name.":") {
|
||
$humidity = $val;
|
||
Log3($hashName, 5, "dewpoint_notify: humidity! dev=$devName, hum_name=$hum_name, hum=$humidity");
|
||
}
|
||
|
||
}
|
||
|
||
#if (($temperature eq "") || ($humidity eq "")) { return undef; } # no way to calculate dewpoint!
|
||
|
||
# Check if Attribute timeout is set
|
||
my $timeout = AttrVal($hash->{NAME}, "max_timediff", $dewpoint_time_diff_default);
|
||
Log3($hashName, 5,"dewpoint_notify: max_timediff=$timeout");
|
||
|
||
if (($humidity eq "") && (($temperature eq ""))) {
|
||
return undef; # no way to calculate dewpoint!
|
||
} elsif (($humidity eq "") && (($temperature ne ""))) {
|
||
# temperature set, but humidity not. Try to use a valid value from the appropriate reading
|
||
$humidity = ReadingsNum($devName, $hum_name, undef);
|
||
my $time_diff = ReadingsAge($devName, $hum_name, undef);
|
||
|
||
if (defined($humidity) && defined($time_diff)) {
|
||
Log3($hashName, 5, ">dev=$devName, hum_name=$hum_name, reference humidity=$humidity ($time_diff),"
|
||
. " temp=$temperature");
|
||
} else { return undef; }
|
||
|
||
if ($time_diff > 0 && $time_diff > $timeout) { return undef; }
|
||
} elsif (($temperature eq "") && ($humidity ne "")) {
|
||
# humdidity set, but temperature not. Try to use a valid value from the appropriate reading
|
||
$temperature = ReadingsNum($devName, $temp_name, undef);
|
||
my $time_diff = ReadingsAge($devName, $temp_name, undef);
|
||
|
||
if (defined($temperature) && defined($time_diff)) {
|
||
Log3($hashName, 5, ">dev=$devName, temp_name=$temp_name, reference temperature=$temperature ($time_diff),"
|
||
. " hum=$humidity");
|
||
} else { return undef; }
|
||
if ($time_diff > 0 && $time_diff > $timeout) { return undef; }
|
||
}
|
||
|
||
# We found temperature and humidity. so we can calculate dewpoint first
|
||
# Pr<50>fen, ob humidity im erlaubten Bereich ist
|
||
if (($humidity <= 0) || ($humidity >= 110)){
|
||
Log3($hashName, 1, "dewpoint_notify: humidity device $devName ($hum_name) invalid: $humidity");
|
||
return undef;
|
||
}
|
||
|
||
my $dewpoint = dewpoint_dewpoint($temperature, $humidity);
|
||
Log3($hashName, 5, "dewpoint_notify: dewpoint=$dewpoint");
|
||
|
||
my $tn = TimeNow();
|
||
if ($cmd_type eq "dewpoint") {
|
||
# >define <name> dewpoint dewpoint <devicename> [<temp_name> <hum_name> <new_name>]
|
||
#
|
||
# Calculates dewpoint for device <devicename> from temperature and humidity and write it
|
||
# to new Reading dewpoint.
|
||
# If optional <temp_name>, <hum_name> and <newname> is specified
|
||
# then read temperature from reading <temp_name>, humidity from reading <hum_name>
|
||
# and write dewpoint to reading <temp_name>.
|
||
# if temp_name eq "T" then use temperature from state T: H:, add <newname> to the state
|
||
# Example:
|
||
# define dewtest1 dewpoint dewpoint .*
|
||
# define dewtest2 dewpoint dewpoint .* T H D
|
||
|
||
readingsBeginUpdate($dev);
|
||
my $rval;
|
||
my $rname;
|
||
my $abs_humidity = dewpoint_absFeuchte($temperature, $humidity);
|
||
my $aFeuchte = AttrVal($hashName, "absFeuchte", undef);
|
||
if (defined($aFeuchte)) {
|
||
$rname = "absFeuchte";
|
||
readingsBulkUpdate($dev, $rname, $abs_humidity);
|
||
Log3($hashName, 5, "dewpoint_notify: absFeuchte= $abs_humidity");
|
||
$aFeuchte = "A: " . $abs_humidity;
|
||
}
|
||
|
||
my $ah_rname = AttrVal($hashName, "absoluteHumidity", undef);
|
||
if (defined($ah_rname)) {
|
||
readingsBulkUpdate($dev, $ah_rname, $abs_humidity);
|
||
Log3($hashName, 5, "dewpoint_notify: $ah_rname= $abs_humidity");
|
||
$aFeuchte = "A: " . $abs_humidity if !defined($aFeuchte);
|
||
}
|
||
|
||
my $vp_rname = AttrVal($hashName, "vapourPressure", undef);
|
||
if (defined($vp_rname)) {
|
||
my $vp = round(10 * dewpoint_vp($temperature, $humidity), 1);
|
||
readingsBulkUpdate($dev, $vp_rname, $vp);
|
||
Log3($hashName, 5, "dewpoint_notify: $vp_rname= $vp");
|
||
}
|
||
|
||
$rname = $new_name;
|
||
|
||
my $has_state_format = defined(AttrVal($dev->{NAME}, "stateFormat", undef));
|
||
my $legacy_sh = AttrVal($hash->{NAME}, "legacyStateHandling", 0);
|
||
if ($temp_name ne "T" || ($has_state_format && ! $legacy_sh)) {
|
||
$rval = $dewpoint;
|
||
readingsBulkUpdate($dev, $rname, $rval);
|
||
readingsEndUpdate($dev, 1);
|
||
} else {
|
||
# explicit manipulation of STATE here
|
||
# first call readingsEndUpdate to finish STATE processing in the referenced device...
|
||
|
||
readingsEndUpdate($dev, 1);
|
||
|
||
# ... then update STATE
|
||
# STATE begins with "T:". append dewpoint or insert before BAT
|
||
my $lastval = $dev->{CHANGED}[$i_state_ev];
|
||
if ($lastval =~ /BAT:/) {
|
||
$rval = $lastval;
|
||
$rval =~ s/BAT:/$rname: $dewpoint BAT:/g;
|
||
} elsif ($lastval =~ /<</) {
|
||
$rval = $lastval;
|
||
$rval =~ s/<</$rname:$dewpoint <</g;
|
||
} else {
|
||
$rval = $lastval." ".$rname.": ".$dewpoint;
|
||
if (defined($aFeuchte)) {
|
||
$rval = $rval." ".$aFeuchte;
|
||
}
|
||
}
|
||
|
||
$dev->{STATE} = $rval;
|
||
# the state event must be REPLACED
|
||
$dev->{CHANGED}[$i_state_ev] = $rval;
|
||
}
|
||
|
||
# remove cached "state:..." events if any
|
||
$dev->{CHANGEDWITHSTATE} = [];
|
||
Log3($hashName, 5, "dewpoint_notify: rval=$rval");
|
||
|
||
} elsif ($cmd_type eq "fan") {
|
||
# >define <name> dewpoint fan devicename devicename-outside min-temp [diff-temp]
|
||
#
|
||
# This define may be used to turn an fan on or off if the outside air has less
|
||
# water
|
||
#
|
||
# - Generate reading/event "fan: on" if (dewpoint of <devicename-outside>) + diff_temp is lower
|
||
# than dewpoint of <devicename> and temperature of <devicename-outside> is >= min-temp
|
||
# and reading "fan" was not already "on".
|
||
# - Generate reading/event "fan: off": else and if reading "fan" was not already "off".
|
||
Log3($hashName, 5, "dewpoint_notify: fan devname_out=$devname_out, min_temp=$min_temp, diff_temp=$diff_temp");
|
||
my $rname;
|
||
my $rval;
|
||
if (exists $defs{$devname_out}{READINGS}{temperature}{VAL} && exists $defs{$devname_out}{READINGS}{humidity}{VAL}) {
|
||
my $temperature_out = $defs{$devname_out}{READINGS}{temperature}{VAL};
|
||
my $humidity_out = $defs{$devname_out}{READINGS}{humidity}{VAL};
|
||
my $dewpoint_out = dewpoint_dewpoint($temperature_out, $humidity_out);
|
||
Log3($hashName, 5, "dewpoint_notify: fan dewpoint_out=$dewpoint_out");
|
||
if (($dewpoint_out + $diff_temp) < $dewpoint && $temperature_out >= $min_temp) {
|
||
$rval = "on";
|
||
Log3($hashName, 4, "dewpoint_notify: fan ON");
|
||
} else {
|
||
$rval = "off";
|
||
Log3($hashName, 4, "dewpoint_notify: fan OFF");
|
||
}
|
||
$rname = "fan";
|
||
if (!exists $defs{$devName}{READINGS}{$rname}{VAL} || $defs{$devName}{READINGS}{$rname}{VAL} ne $rval) {
|
||
Log3($hashName, 4, "dewpoint_notify: CHANGE fan $rval");
|
||
$dev->{READINGS}{$rname}{TIME} = $tn;
|
||
$dev->{READINGS}{$rname}{VAL} = $rval;
|
||
$dev->{CHANGED}[$nev++] = $rname . ": " . $rval;
|
||
}
|
||
|
||
} else {
|
||
Log3($hashName, 1, "dewpoint_notify: fan devname_out=$devname_out no temperature or humidity available"
|
||
. " for dewpoint calculation");
|
||
}
|
||
|
||
} elsif ($cmd_type eq "alarm") {
|
||
# >define <name> dewpoint alarm devicename devicename-reference diff
|
||
#
|
||
# - Generate reading/event "alarm: on" if temperature of <devicename-reference>-<diff> is lower
|
||
# than dewpoint of <devicename> and reading "alarm" was not already "on".
|
||
# - Generate reading/event "alarm: off" if temperature of <devicename-reference>-<diff> is higher
|
||
# than dewpoint of <devicename> and reading "alarm" was not already "off".
|
||
#
|
||
# You have different options to use this define:
|
||
# * Use a temperature sensor in or on the wall (<devicename-reference>) and use a temp/hum sensor
|
||
# to measure the dewpoint of the air. Alarm if the temperature of the wall is lower than the dewpoint of the air.
|
||
# In this case the water of the air will condense on the wall because the wall is cold.
|
||
# Example: define alarmtest dewpoint alarm roomsensor wallsensor 0
|
||
# * If you do not have a temperature sensor in/on the wall, you may also compare the rooms dewpoint to the
|
||
# temperature of the same or another inside sensor. If you think that your walls are normally 5 degrees colder
|
||
# than the inside temperature, set diff to 5.
|
||
# Example: define alarmtest dewpoint alarm roomsensor roomsensor 5
|
||
Log3($hashName, 5, "dewpoint_notify: alarm devname_ref=$devname_ref, diff_temp=$diff_temp");
|
||
my $rname;
|
||
my $rval;
|
||
if (exists $defs{$devname_ref}{READINGS}{temperature}{VAL}) {
|
||
my $temperature_ref = $defs{$devname_ref}{READINGS}{temperature}{VAL};
|
||
Log3($hashName, 5, "dewpoint_notify: alarm temperature_ref=$temperature_ref");
|
||
if ($temperature_ref - $diff_temp < $dewpoint) {
|
||
$rval = "on";
|
||
Log3($hashName, 4, "dewpoint_notify: alarm ON");
|
||
} else {
|
||
$rval = "off";
|
||
Log3($hashName, 4, "dewpoint_notify: alarm OFF");
|
||
}
|
||
$rname = "alarm";
|
||
if (!exists $defs{$devName}{READINGS}{$rname}{VAL} || $defs{$devName}{READINGS}{$rname}{VAL} ne $rval) {
|
||
Log3($hashName, 5, "dewpoint_notify: CHANGE alarm $rval");
|
||
$dev->{READINGS}{$rname}{TIME} = $tn;
|
||
$dev->{READINGS}{$rname}{VAL} = $rval;
|
||
$dev->{CHANGED}[$nev++] = $rname . ": " . $rval;
|
||
}
|
||
} else {
|
||
Log3($hashName, 1, "dewpoint_notify: alarm devname_out=$devname_out no temperature or humidity available"
|
||
. " for dewpoint calculation");
|
||
}
|
||
|
||
} else {
|
||
# should never happen:
|
||
Log3($hashName, 1, "dewpoint_notify: unknown cmd_type ".$cmd_type);
|
||
return "";
|
||
}
|
||
|
||
return undef;
|
||
}
|
||
|
||
# -----------------------------
|
||
# Dewpoint calculation.
|
||
|
||
# 'Magnus formula'
|
||
#
|
||
# Parameters from https://de.wikipedia.org/wiki/Taupunkt#S.C3.A4ttigungsdampfdruck
|
||
# Good summary of formulas in http://www.wettermail.de/wetter/feuchte.html
|
||
|
||
my $E0 = 0.6112; # saturation pressure at T=0 <20>C
|
||
my @ab_gt0 = (17.62, 243.12); # T>0
|
||
my @ab_le0 = (22.46, 272.6); # T<=0 over ice
|
||
|
||
### ** Public interface ** keep stable
|
||
# vapour pressure in kPa
|
||
sub dewpoint_vp($$)
|
||
{
|
||
my ($T, $Hr) = @_;
|
||
my ($a, $b);
|
||
|
||
if ($T > 0) {
|
||
($a, $b) = @ab_gt0;
|
||
} else {
|
||
($a, $b) = @ab_le0;
|
||
}
|
||
|
||
return 0.01 * $Hr * $E0 * exp($a * $T / ($T + $b));
|
||
}
|
||
|
||
### ** Public interface ** keep stable
|
||
# dewpoint in <20>C
|
||
sub
|
||
dewpoint_dewpoint($$)
|
||
{
|
||
my ($T, $Hr) = @_;
|
||
if ($Hr == 0) {
|
||
Log(1, "Error: dewpoint() Hr==0 !: temp=$T, hum=$Hr");
|
||
return undef;
|
||
}
|
||
|
||
my ($a, $b);
|
||
|
||
if ($T > 0) {
|
||
($a, $b) = @ab_gt0;
|
||
} else {
|
||
($a, $b) = @ab_le0;
|
||
}
|
||
|
||
# solve vp($dp, 100) = vp($T,$Hr) for $dp
|
||
my $v = log(dewpoint_vp($T, $Hr) / $E0);
|
||
my $D = $a - $v;
|
||
|
||
# can this ever happen for valid input?
|
||
if ($D == 0) {
|
||
Log(1, "Error: dewpoint() D==0 !: temp=$T, hum=$Hr");
|
||
return undef;
|
||
}
|
||
|
||
return round($b * $v / $D, 1);
|
||
}
|
||
|
||
|
||
### ** Public interface ** keep stable
|
||
# absolute Feuchte in g Wasserdampf pro m3 Luft
|
||
sub
|
||
dewpoint_absFeuchte ($$)
|
||
{
|
||
my ($T, $Hr) = @_;
|
||
|
||
# 110 ?
|
||
if (($Hr < 0) || ($Hr > 110)) {
|
||
Log(1, "Error dewpoint: humidity invalid: $Hr");
|
||
return "";
|
||
}
|
||
my $DD = dewpoint_vp($T, $Hr);
|
||
my $AF = 1.0E6 * (18.016 / 8314.3) * ($DD / (273.15 + $T));
|
||
return round($AF, 1);
|
||
}
|
||
|
||
1;
|
||
|
||
|
||
=pod
|
||
=item helper
|
||
=item summary compute dewpoint and/or generate events for starting a fan
|
||
=item summary_DE berechne Taupunkt und/oder erzeuge events zum starten eines L<>fters
|
||
=begin html
|
||
|
||
<a name="dewpoint"></a>
|
||
<h3>dewpoint</h3>
|
||
<ul>
|
||
Dewpoint calculations. Offers three different ways to use dewpoint: <br>
|
||
<ul>
|
||
<li><b>dewpoint</b><br>
|
||
Compute additional reading dewpoint from a sensor offering temperature and humidity.</li>
|
||
<li><b>fan</b><br>
|
||
Generate a event to turn a fan on if the outside air has less water than the inside.</li>
|
||
<li><b>alarm</b><br>
|
||
Generate a mold alarm if a reference temperature is lower than the current dewpoint.</li>
|
||
<br>
|
||
</ul>
|
||
|
||
<a name="dewpointdefine"></a>
|
||
<b>Define</b>
|
||
<ul>
|
||
<code>define <name> dewpoint dewpoint <devicename-regex> [<temp_name> <hum_name> <new_name>]</code><br>
|
||
<br>
|
||
<ul>
|
||
Calculates dewpoint for device <devicename-regex> from temperature and humidity
|
||
and write it to a new reading named dewpoint.
|
||
If optional <temp_name>, <hum_name> and <new_name> is specified
|
||
then read temperature from reading <temp_name>, humidity from reading <hum_name>
|
||
and write the calculated dewpoint to reading <new_name>.<br>
|
||
<b>Obsolete, avoid for new definitions</b><br>
|
||
If <temp_name> is T then use temperature from state T: H:, add <new_name> to the STATE.
|
||
The addition to STATE only occurs if the target device does not define attribute "stateFormat".<br>
|
||
If the obsolete behaviour of STATE is mandatory set attribute <code>legacyStateHandling</code>.
|
||
</ul>
|
||
<br><br>
|
||
|
||
Example:<PRE>
|
||
# Compute the dewpoint for the temperature/humidity
|
||
# events of the temp1 device and generate reading dewpoint.
|
||
define dew_temp1 dewpoint dewpoint temp1
|
||
define dew_temp1 dewpoint dewpoint temp1 temperature humidity dewpoint
|
||
|
||
# Compute the dewpoint for the temperature/humidity
|
||
# events of all devices offering temperature and humidity
|
||
# and generate reading dewpoint.
|
||
define dew_all dewpoint dewpoint .*
|
||
define dew_all dewpoint dewpoint .* temperature humidity dewpoint
|
||
|
||
# Compute the dewpoint for the temperature/humidity
|
||
# events of the device Aussen_1 offering temperature and humidity
|
||
# and insert is into STATE unless Aussen_1 has attribute "stateFormat" defined.
|
||
# If "stateFormat" is defined then a reading D will be generated.
|
||
define dew_state dewpoint dewpoint Aussen_1 T H D
|
||
|
||
# Compute the dewpoint for the temperature/humidity
|
||
# events of all devices offering temperature and humidity
|
||
# and insert the result into the STATE. (See example above).
|
||
# Example STATE: "T: 10 H: 62.5" will change to
|
||
# "T: 10 H: 62.5 D: 3.2"
|
||
define dew_state dewpoint dewpoint .* T H D
|
||
|
||
</PRE>
|
||
</ul>
|
||
|
||
<ul>
|
||
<code>define <name> dewpoint fan <devicename> <devicename-outside> <min-temp> [<diff_temp>]</code><br>
|
||
<br>
|
||
<ul>
|
||
May be used to turn an fan on or off if the outside air has less water.
|
||
<ul>
|
||
<li>
|
||
Generate event "fan: on" if (dewpoint of <devicename-outside>) + <diff_temp> is lower
|
||
than dewpoint of <devicename> and temperature of <devicename-outside> is >= min-temp
|
||
and reading "fan" was not already "on". The event will be generated for <devicename>. Parameter <diff-temp> is optional</li>
|
||
<li>Generate event "fan: off": else and if reading "fan" was not already "off".</li>
|
||
</ul>
|
||
</ul>
|
||
<br>
|
||
|
||
Example:<PRE>
|
||
# Generate event "fan: on" when dewpoint of Aussen_1 is first
|
||
# time lower than basement_tempsensor and outside temperature is >= 0
|
||
# and change it to "fan: off" is this condition changes.
|
||
# Set a switch on/off (fan_switch) depending on the state.
|
||
define dew_fan1 dewpoint fan basement_tempsensor Aussen_1 0
|
||
define dew_fan1_on notify basement_tempsensor.*fan:.*on set fan_switch on
|
||
define dew_fan1_off notify basement_tempsensor.*fan:.*off set fan_switch off
|
||
|
||
</PRE>
|
||
</ul>
|
||
|
||
<ul>
|
||
<code>define <name> dewpoint alarm <devicename> <devicename-reference> <diff-temp></code><br>
|
||
<br>
|
||
<ul>
|
||
Generate a mold alarm if a reference temperature is lower than the current dewpoint.
|
||
<ul>
|
||
<li>
|
||
Generate reading/event "alarm: on" if temperature of <devicename-reference> - <diff-temp> is lower
|
||
than dewpoint of <devicename> and reading "alarm" was not already "on". The event will be generated for <devicename>.</li>
|
||
<li>Generate reading/event "alarm: off" if temperature of <devicename-reference> - <diff-temp> is higher than dewpoint of <devicename> and reading "alarm" was not already "off".</li>
|
||
</ul>
|
||
</ul>
|
||
<br>
|
||
|
||
Example:<PRE>
|
||
# Using a wall temperature sensor (wallsensor) and a temp/hum sensor
|
||
# (roomsensor) to alarm if the temperature of the wall is lower than
|
||
# the dewpoint of the air. In this case the water of the air will
|
||
# condense on the wall because the wall is cold.
|
||
# Set a switch on (alarm_siren) if alarm is on using notify.
|
||
define dew_alarm1 dewpoint alarm roomsensor wallsensor 0
|
||
define roomsensor_alarm_on notify roomsensor.*alarm:.*on set alarm_siren on
|
||
define roomsensor_alarm_off notify roomsensor.*alarm:.*off set alarm_siren off
|
||
|
||
# If you do not have a temperature sensor in/on the wall, you may also
|
||
# compare the rooms dewpoint to the temperature of the same or another
|
||
# inside sensor. Alarm is temperature is 5 degrees colder than the
|
||
# inside dewpointinside.
|
||
define dev_alarm2 dewpoint alarm roomsensor roomsensor 5
|
||
|
||
</PRE>
|
||
</ul>
|
||
|
||
<a name="dewpointset"></a>
|
||
<b>Set</b> <ul>N/A</ul><br>
|
||
|
||
<a name="dewpointget"></a>
|
||
<b>Get</b> <ul>N/A</ul><br>
|
||
|
||
<a name="dewpointattr"></a>
|
||
<b>Attributes</b>
|
||
<ul>
|
||
<li><a href="#disable">disable</a></li>
|
||
<li>absoluteHumidity <reading_name></li>
|
||
<ul>
|
||
In addition the absolute humidity in g/m³ will be computed as reading <reading_name>.
|
||
</ul><br>
|
||
<li>vapourPressure <reading_name></li>
|
||
<ul>
|
||
In addition the vapour pressure in hPa will be computed as reading <reading_name>.
|
||
</ul><br>
|
||
<li>max_timediff</li>
|
||
<ul>
|
||
Maximum time difference in seconds allowed between the temperature and humidity values for a device. dewpoint uses the Readings for temperature or humidity if they are not delivered in the event. This is necessary for using dewpoint with event-on-change-reading. Also needed for sensors that do deliver temperature and humidity in different events like for example technoline sensors TX3TH.<br>
|
||
If not set default is 1 second.
|
||
<br><br>
|
||
Examples:<PRE>
|
||
# allow maximum time difference of 60 seconds
|
||
define dew_all dewpoint dewpoint .*
|
||
attr dew_all max_timediff 60
|
||
</ul>
|
||
</ul>
|
||
</ul>
|
||
|
||
=end html
|
||
|
||
=begin html_DE
|
||
|
||
<a name="dewpoint"></a>
|
||
<h3>dewpoint</h3>
|
||
<ul>
|
||
Berechnungen des Taupunkts. Es gibt drei Varianten, das Modul dewpoint zu verwenden: <br>
|
||
<ul>
|
||
<li><b>dewpoint</b>: Taupunkt<br>
|
||
Erzeugt ein zusätzliches Ereignis "dewpoint" aus Temperatur- und Luftfeuchtewerten eines Fühlers.</li>
|
||
<li><b>fan</b>: Lüfter<br>
|
||
Erzeugt ein Ereignis, um einen Lüfter einzuschalten, wenn die Außenluft
|
||
weniger Wasser als die Raumluft enthält.</li>
|
||
<li><b>alarm</b>: Alarm<br>
|
||
Erzeugt einen Schimmel-Alarm, wenn eine Referenz-Temperatur unter den Taupunkt fällt.</li>
|
||
</ul>
|
||
<br/>
|
||
|
||
<a name="dewpointdefine"></a>
|
||
<b>Define</b>
|
||
<ul>
|
||
<code>define <name> dewpoint dewpoint <devicename-regex> [<temp_name>
|
||
<hum_name> <new_name>]</code><br>
|
||
<br/>
|
||
Berechnet den Taupunkt des Geräts <devicename-regex> basierend auf Temperatur
|
||
und Luftfeuchte und erzeugt daraus ein neues Reading namens dewpoint.<br/>
|
||
Wenn <temp_name>, <hum_name> und <new_name> angegeben sind,
|
||
werden die Temperatur aus dem Reading <temp_name>, die Luftfeuchte aus dem
|
||
Reading <hum_name> gelesen und als berechneter Taupunkt ins Reading <new_name> geschrieben.<br><br>
|
||
<b>Veraltet, für neue Definitionen nicht mehr benutzen</b><br>
|
||
Wenn <temp_name> T lautet, wird die Temperatur aus state T: H: benutzt
|
||
und <new_name> zu STATE hinzugefügt. Das hinzufügen zu STATE erfolgt nur, falls im Zielgerät
|
||
das Attribut "stateFormat" nicht definiert ist.<br>
|
||
Falls das veraltete Verhalten zum Update unbedingt gewüscht ist,
|
||
kann das Attribut <code>legacyStateHandling</code> gesetzt werden.
|
||
<br/>
|
||
Beispiele:
|
||
<pre>
|
||
# Berechnet den Taupunkt aufgrund von Temperatur und Luftfeuchte
|
||
# in Ereignissen, die vom Gerät temp1 erzeugt wurden und erzeugt ein Reading dewpoint.
|
||
define dew_temp1 dewpoint dewpoint temp1
|
||
define dew_temp1 dewpoint dewpoint temp1 temperature humidity dewpoint
|
||
|
||
# Berechnet den Taupunkt aufgrund von Temperatur und Luftfeuchte
|
||
# in Ereignissen, die von allen Geräten erzeugt wurden die diese Werte ausgeben
|
||
# und erzeugt ein Reading dewpoint.
|
||
define dew_all dewpoint dewpoint .*
|
||
define dew_all dewpoint dewpoint .* temperature humidity dewpoint
|
||
|
||
# Berechnet den Taupunkt aufgrund von Temperatur und Luftfeuchte
|
||
# in Ereignissen, die vom Gerät Aussen_1 erzeugt wurden und ergänzt
|
||
# mit diesem Wert den Status STATE, falls in Aussen_1 das Attribut "stateFormat" nicht definiert ist.
|
||
# Falls "stateFormat" definiert ist, wird das reading "D" angelegt.
|
||
define dew_state dewpoint dewpoint Aussen_1 T H D
|
||
|
||
# Berechnet den Taupunkt aufgrund von Temperatur und Luftfeuchte
|
||
# in Ereignissen, die von allen Geräten erzeugt wurden die diese Werte ausgeben
|
||
# und ergänzt mit diesem Wert den Status STATE. (Siehe Beispiel oben).
|
||
# Beispiel STATE: "T: 10 H: 62.5" wird verändert nach
|
||
# "T: 10 H: 62.5 D: 3.2"
|
||
define dew_state dewpoint dewpoint .* T H D
|
||
</pre>
|
||
|
||
<br/>
|
||
<br/>
|
||
<code>define <name> dewpoint fan <devicename> <devicename-outside> <min-temp> [<diff_temp>]</code><br>
|
||
<br>
|
||
<ul>
|
||
<li>Erzeugt ein Ereignis, um einen Lüfter einzuschalten, wenn die Außenluft
|
||
weniger Wasser als die Raumluft enthält.</li>
|
||
<li>Erzeugt das Ereignis "fan: on" wenn (Taupunkt von <devicename-outside>) +
|
||
<diff_temp> ist niedriger als der Taupunkt von <devicename> und die Temperatur
|
||
von <devicename-outside> >= min-temp ist. Das Ereignis wird nur erzeugt wenn das
|
||
Reading "fan" nicht schon "on" war. Das Ereignis wird für das Gerät <devicename> erzeugt.
|
||
Der Parameter <diff-temp> ist optional.</li>
|
||
<li>Andernfalls wird das Ereignis "fan: off" erzeugt, wenn das Reading von "fan" nicht bereits "off" war.</li>
|
||
</ul>
|
||
<br>
|
||
Beispiel:
|
||
<pre>
|
||
# Erzeugt das Ereignis "fan: on", wenn der Taupunkt des Geräts Aussen_1 zum ersten Mal
|
||
# niedriger ist als der Taupunkt des Geräts basement_tempsensor und die
|
||
# Außentemperatur >= 0 ist und wechselt nach "fan: off" wenn diese Bedingungen nicht
|
||
# mehr zutreffen.
|
||
# Schaltet den Schalter fan_switch abhängig vom Zustand ein oder aus.
|
||
define dew_fan1 dewpoint fan basement_tempsensor Aussen_1 0
|
||
define dew_fan1_on notify basement_tempsensor.*fan:.*on set fan_switch on
|
||
define dew_fan1_off notify basement_tempsensor.*fan:.*off set fan_switch off
|
||
</pre>
|
||
|
||
<code>define <name> dewpoint alarm <devicename> <devicename-reference> <diff-temp></code><br>
|
||
<br>
|
||
<ul>
|
||
<li>Erzeugt einen Schimmel-Alarm, wenn eine Referenz-Temperatur unter den Taupunkt fällt.</li>
|
||
<li>Erzeugt ein Reading/Ereignis "alarm: on" wenn die Temperatur von
|
||
<devicename-reference> - <diff-temp> unter den Taupunkt von
|
||
<devicename> fällt und das Reading "alarm" nicht bereits "on" ist.
|
||
Das Ereignis wird für <devicename> erzeugt.</li>
|
||
<li>Erzeugt ein Reading/Ereignis "alarm: off" wenn die Temperatur von
|
||
<devicename-reference> - <diff-temp> über den Taupunkt
|
||
von <devicename> steigt und das Reading "alarm" nicht bereits "off" ist.</li>
|
||
</ul>
|
||
<br>
|
||
Beispiel:
|
||
<pre>
|
||
# Es wird ein Anlegefühler (Wandsensor) und ein Thermo-/Hygrometer (Raumfühler)
|
||
# verwendet, um einen Alarm zu erzeugen, wenn die Wandtemperatur
|
||
# unter den Taupunkt der Luft fällt. In diesem Fall würde sich Wasser an der Wand
|
||
# niederschlagen (kondensieren), weil die Wand zu kalt ist.
|
||
# Der Schalter einer Sirene (alarm_siren) wird über ein notify geschaltet.
|
||
define dew_alarm1 dewpoint alarm roomsensor wallsensor 0
|
||
define roomsensor_alarm_on notify roomsensor.*alarm:.*on set alarm_siren on
|
||
define roomsensor_alarm_off notify roomsensor.*alarm:.*off set alarm_siren off
|
||
|
||
# Ohne Wandsensor lässt sich auch der Taupunkt eines Raums mit der Temperatur desselben
|
||
# (oder eines anderen) Fühlers vergleichen.
|
||
# Die Alarmtemperatur ist 5 Grad niedriger gesetzt als die des Vergleichsthermostats.
|
||
define dev_alarm2 dewpoint alarm roomsensor roomsensor 5
|
||
</pre>
|
||
</ul>
|
||
|
||
<a name="dewpointset"></a>
|
||
<b>Set</b> <ul>N/A</ul><br>
|
||
|
||
<a name="dewpointget"></a>
|
||
<b>Get</b> <ul>N/A</ul><br>
|
||
|
||
<a name="dewpointattr"></a>
|
||
<b>Attributes</b>
|
||
<ul>
|
||
<li><a href="#disable">disable</a></li>
|
||
<li>absoluteHumidity <reading_name></li>
|
||
<ul>
|
||
Zusätzlich wird die absolute Feuchte in g/m³ als Reading <reading_name> berechnet.
|
||
</ul><br>
|
||
<li>vapourPressure <reading_name></li>
|
||
<ul>
|
||
Zusätzlich wird der Dampfdruck in hPa als Reading <reading_name> berechnet.
|
||
</ul><br>
|
||
<li>max_timediff</li>
|
||
<ul>
|
||
Maximale erlaubter Zeitunterschied in Sekunden zwischen den Temperatur- und Luftfeuchtewerten eines
|
||
Geräts. dewpoint verwendet Readings von Temperatur oder Luftfeuchte wenn sie nicht im Ereignis
|
||
mitgeliefert werden. Das ist sowohl für den Betrieb mit event-on-change-reading nötig
|
||
als auch bei Sensoren die Temperatur und Luftfeuchte in getrennten Ereignissen kommunizieren
|
||
(z.B. Technoline Sensoren TX3TH).<br>
|
||
Der Standardwert ist 1 Sekunde.
|
||
<br><br>
|
||
Beispiel:
|
||
<pre>
|
||
# Maximal erlaubter Zeitunterschied soll 60 Sekunden sein
|
||
define dew_all dewpoint dewpoint .*
|
||
attr dew_all max_timediff 60
|
||
</pre>
|
||
</ul>
|
||
</ul>
|
||
</ul>
|
||
|
||
=end html_DE
|
||
|
||
=cut
|