98_dewpoint.pm 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855
  1. # $Id: 98_dewpoint.pm 17027 2018-07-24 11:53:15Z hotbso $
  2. ##############################################
  3. #
  4. # Dewpoint computing
  5. #
  6. # based / modified from 98_average.pm (C) by Rudolf Koenig
  7. #
  8. # Copyright (C) 2012 Willi Herzig
  9. #
  10. # This program is free software; you can redistribute it and/or
  11. # modify it under the terms of the GNU General Public License
  12. # as published by the Free Software Foundation; either version 2
  13. # of the License, or (at your option) any later version.
  14. #
  15. # This program is distributed in the hope that it will be useful,
  16. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. # GNU General Public License for more details.
  19. #
  20. # You should have received a copy of the GNU General Public License
  21. # along with this program; if not, write to the Free Software
  22. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  23. #
  24. # The GNU General Public License may also be found at http://www.gnu.org/licenses/gpl-2.0.html .
  25. #
  26. package main;
  27. use strict;
  28. use warnings;
  29. use vars qw(%defs);
  30. sub Log3($$$);
  31. # default maximum time_diff for dewpoint
  32. my $dewpoint_time_diff_default = 1; # 1 Second
  33. ##########################
  34. sub
  35. dewpoint_Initialize($)
  36. {
  37. my ($hash) = @_;
  38. $hash->{DefFn} = "dewpoint_Define";
  39. $hash->{NotifyFn} = "dewpoint_Notify";
  40. $hash->{AttrFn} = "dewpoint_Attr";
  41. $hash->{NotifyOrderPrefix} = "10-"; # Want to be called before the rest
  42. $hash->{AttrList} = "disable:0,1 legacyStateHandling:0,1 verbose max_timediff absFeuchte"
  43. . " absoluteHumidity vapourPressure";
  44. }
  45. ##########################
  46. sub
  47. dewpoint_Define($$)
  48. {
  49. my ($hash, $def) = @_;
  50. my @a = split("[ \t][ \t]*", $def);
  51. return "wrong syntax: define <name> dewpoint (dewpoint|fan|alarm) devicename [options]"
  52. if(@a < 4);
  53. my $name = $a[0];
  54. my $cmd_type = $a[2]; # dewpoint, fan, alarm
  55. my $devname = $a[3];
  56. if ($cmd_type eq "dewpoint") {
  57. # define <name> dewpoint dewpoint devicename-regex [temp_name hum_name new_name]
  58. if(@a == 7) {
  59. $hash->{TEMP_NAME} = $a[4];
  60. $hash->{HUM_NAME} = $a[5];
  61. $hash->{NEW_NAME} = $a[6];
  62. } elsif (@a == 4) {
  63. $hash->{TEMP_NAME} = "temperature";
  64. $hash->{HUM_NAME} = "humidity";
  65. $hash->{NEW_NAME} = "dewpoint";
  66. } else {
  67. return "wrong syntax: define <name> dewpoint dewpoint devicename-regex [temp_name hum_name new_name]"
  68. }
  69. } elsif ($cmd_type eq "fan") {
  70. # define <name> dewpoint fan devicename-regex devicename-outside min_temp
  71. if (@a == 6 || @a == 7) {
  72. $hash->{DEVNAME_OUT} = $a[4];
  73. $hash->{MIN_TEMP} = $a[5];
  74. if (@a == 6) {
  75. $hash->{DIFF_TEMP} = 0;
  76. } else {
  77. $hash->{DIFF_TEMP} = $a[6];
  78. }
  79. } else {
  80. return "wrong syntax: define <name> dewpoint fan devicename-regex devicename-outside min_temp [diff_temp]"
  81. }
  82. } elsif ($cmd_type eq "alarm") {
  83. # define <name> dewpoint alarm devicename-regex devicename-reference diff_temp
  84. if (@a == 6) {
  85. $hash->{DEVNAME_REF} = $a[4];
  86. $hash->{DIFF_TEMP} = $a[5];
  87. } else {
  88. return "wrong syntax: define <name> dewpoint alarm devicename-regex devicename-reference diff_temp"
  89. }
  90. } else {
  91. return "wrong syntax: define <name> dewpoint (dewpoint|fan|alarm) devicename-regex [options]"
  92. }
  93. $hash->{CMD_TYPE} = $cmd_type;
  94. eval { "Hallo" =~ m/^$devname$/ };
  95. return "Bad regexp: $@" if($@);
  96. $hash->{DEV_REGEXP} = $devname;
  97. # set NOTIFYDEV
  98. notifyRegexpChanged($hash, $devname);
  99. $hash->{STATE} = "active";
  100. return undef;
  101. }
  102. ##########################
  103. sub
  104. dewpoint_Attr(@)
  105. {
  106. my ($cmd, $name, $a_name, $a_val) = @_;
  107. my $hash = $defs{$name};
  108. if ($cmd eq "set" && $a_name eq "absFeuchte") {
  109. Log(1, "dewpoint $name: attribute 'absFeuchte' is deprecated, please use 'absoluteHumidity'");
  110. return undef;
  111. }
  112. if ($cmd eq "set" && ($a_name eq "absoluteHumidity" || $a_name eq "vapourPressure")) {
  113. if (! goodReadingName($a_val)) {
  114. return "Value of $a_name is not a valid reading name";
  115. }
  116. }
  117. return undef;
  118. }
  119. ##########################
  120. sub
  121. dewpoint_Notify($$)
  122. {
  123. my ($hash, $dev) = @_;
  124. my $hashName = $hash->{NAME};
  125. my $devName = $dev->{NAME};
  126. my $re = $hash->{DEV_REGEXP};
  127. # fast exit
  128. return "" if (!defined($re) || $devName !~ m/^$re$/);
  129. return "" if (AttrVal($hashName, "disable", undef));
  130. my $cmd_type = $hash->{CMD_TYPE};
  131. # dewpoint
  132. my $temp_name = "temperature";
  133. my $hum_name = "humidity";
  134. my $new_name = "dewpoint";
  135. # fan
  136. my $devname_out = "";
  137. my $min_temp = 0;
  138. # alarm
  139. my $devname_ref = "";
  140. my $diff_temp = 0;
  141. if ($cmd_type eq "dewpoint") {
  142. if (!defined($hash->{TEMP_NAME}) || !defined($hash->{HUM_NAME}) || !defined($hash->{NEW_NAME})) {
  143. # should never happen!
  144. Log3($hashName, 1, "Error dewpoint: TEMP_NAME || HUM_NAME || NEW_NAME undefined");
  145. return "";
  146. }
  147. $temp_name = $hash->{TEMP_NAME};
  148. $hum_name = $hash->{HUM_NAME};
  149. $new_name = $hash->{NEW_NAME};
  150. Log3($hashName, 4, "dewpoint_notify: cmd_type=$cmd_type devname=$devName dewname=$hashName, dev=$devName, "
  151. . "dev_regex=$re temp_name=$temp_name hum_name=$hum_name");
  152. } elsif ($cmd_type eq "fan") {
  153. if (!defined($hash->{DEVNAME_OUT}) || !defined($hash->{MIN_TEMP})) {
  154. # should never happen!
  155. Log3($hashName, 1, "Error dewpoint: DEVNAME_OUT || MIN_TEMP undefined");
  156. return "";
  157. }
  158. $devname_out = $hash->{DEVNAME_OUT};
  159. $min_temp = $hash->{MIN_TEMP};
  160. $diff_temp = $hash->{DIFF_TEMP};
  161. Log3($hashName, 4, "dewpoint_notify: cmd_type=$cmd_type devname=$devName dewname=$hashName, dev=$devName, "
  162. . " dev_regex=$re, devname_out=$devname_out, min_temp=$min_temp, diff_temp=$diff_temp");
  163. } elsif ($cmd_type eq "alarm") {
  164. if (!defined($hash->{DEVNAME_REF}) || !defined($hash->{DIFF_TEMP})) {
  165. # should never happen!
  166. Log3($hashName, 1, "Error dewpoint: DEVNAME_REF || DIFF_TEMP undefined");
  167. return "";
  168. }
  169. $devname_ref = $hash->{DEVNAME_REF};
  170. $diff_temp = $hash->{DIFF_TEMP};
  171. Log3($hashName, 4, "dewpoint_notify: cmd_type=$cmd_type devname=$devName dewname=$hashName, dev=$devName, "
  172. . "dev_regex=$re, devname_ref=$devname_ref, diff_temp=$diff_temp");
  173. } else {
  174. # should never happen:
  175. Log3($hashName, 1, "Error notify_dewpoint: <1> unknown cmd_type ".$cmd_type);
  176. return "";
  177. }
  178. my $nev = int(@{$dev->{CHANGED}});
  179. # if we use the "T H" syntax we must track the index of the state event
  180. my $i_state_ev;
  181. my $temperature = "";
  182. my $humidity = "";
  183. for (my $i = 0; $i < $nev; $i++) {
  184. my $s = $dev->{CHANGED}[$i];
  185. Log3($hashName, 5, "dewpoint_notify: s='$s'");
  186. ################
  187. # Filtering
  188. next if(!defined($s));
  189. my ($evName, $val, $rest) = split(" ", $s, 3); # resets $1
  190. next if(!defined($evName));
  191. next if(!defined($val));
  192. Log3($hashName, 5, "dewpoint_notify: evName='$evName' val=$val'");
  193. if (($evName eq "T:") && ($temp_name eq "T")) {
  194. $i_state_ev = $i;
  195. #my ($evName1, $val1, $evName2, $val2, $rest) = split(" ", $s, 5); # resets $1
  196. if ($s =~ /T: ([-+]?[0-9]*\.[0-9]+|[-+]?[0-9]+)/) {
  197. $temperature = $1;
  198. }
  199. if ($s =~ /H: [-+]?([0-9]*\.[0-9]+|[0-9]+)/) {
  200. $humidity = $1;
  201. }
  202. Log3($hashName, 5, "dewpoint_notify T: H:, temp=$temperature hum=$humidity");
  203. } elsif ($evName eq $temp_name.":") {
  204. $temperature = $val;
  205. Log3($hashName, 5, "dewpoint_notify temperature! dev=$devName, temp_name=$temp_name, temp=$temperature");
  206. } elsif ($evName eq $hum_name.":") {
  207. $humidity = $val;
  208. Log3($hashName, 5, "dewpoint_notify humidity! dev=$devName, hum_name=$hum_name, hum=$humidity");
  209. }
  210. }
  211. #if (($temperature eq "") || ($humidity eq "")) { return undef; } # no way to calculate dewpoint!
  212. # Check if Attribute timeout is set
  213. my $timeout = AttrVal($hash->{NAME}, "max_timediff", $dewpoint_time_diff_default);
  214. Log3($hashName, 5,"dewpoint max_timediff=$timeout");
  215. if (($humidity eq "") && (($temperature eq ""))) {
  216. return undef; # no way to calculate dewpoint!
  217. } elsif (($humidity eq "") && (($temperature ne ""))) {
  218. # temperature set, but humidity not. Try to use a valid value from the appropriate reading
  219. $humidity = ReadingsNum($devName, $hum_name, undef);
  220. my $time_diff = ReadingsAge($devName, $hum_name, undef);
  221. if (defined($humidity) && defined($time_diff)) {
  222. Log3($hashName, 5, ">dev=$devName, hum_name=$hum_name, reference humidity=$humidity ($time_diff),"
  223. . " temp=$temperature");
  224. } else { return undef; }
  225. if ($time_diff > 0 && $time_diff > $timeout) { return undef; }
  226. } elsif (($temperature eq "") && ($humidity ne "")) {
  227. # humdidity set, but temperature not. Try to use a valid value from the appropriate reading
  228. $temperature = ReadingsNum($devName, $temp_name, undef);
  229. my $time_diff = ReadingsAge($devName, $temp_name, undef);
  230. if (defined($temperature) && defined($time_diff)) {
  231. Log3($hashName, 5, ">dev=$devName, temp_name=$temp_name, reference temperature=$temperature ($time_diff),"
  232. . " hum=$humidity");
  233. } else { return undef; }
  234. if ($time_diff > 0 && $time_diff > $timeout) { return undef; }
  235. }
  236. # We found temperature and humidity. so we can calculate dewpoint first
  237. # Prüfen, ob humidity im erlaubten Bereich ist
  238. if (($humidity <= 0) || ($humidity >= 110)){
  239. Log3($hashName, 1, "Error dewpoint: humidity invalid: $humidity");
  240. return undef;
  241. }
  242. my $dewpoint = dewpoint_dewpoint($temperature, $humidity);
  243. Log3($hashName, 5, "dewpoint_notify: dewpoint=$dewpoint");
  244. my $tn = TimeNow();
  245. if ($cmd_type eq "dewpoint") {
  246. # >define <name> dewpoint dewpoint <devicename> [<temp_name> <hum_name> <new_name>]
  247. #
  248. # Calculates dewpoint for device <devicename> from temperature and humidity and write it
  249. # to new Reading dewpoint.
  250. # If optional <temp_name>, <hum_name> and <newname> is specified
  251. # then read temperature from reading <temp_name>, humidity from reading <hum_name>
  252. # and write dewpoint to reading <temp_name>.
  253. # if temp_name eq "T" then use temperature from state T: H:, add <newname> to the state
  254. # Example:
  255. # define dewtest1 dewpoint dewpoint .*
  256. # define dewtest2 dewpoint dewpoint .* T H D
  257. readingsBeginUpdate($dev);
  258. my $rval;
  259. my $rname;
  260. my $abs_hunidity = dewpoint_absFeuchte($temperature, $humidity);
  261. my $aFeuchte = AttrVal($hashName, "absFeuchte", undef);
  262. if (defined($aFeuchte)) {
  263. $rname = "absFeuchte";
  264. readingsBulkUpdate($dev, $rname, $abs_hunidity);
  265. Log3($hashName, 5, "dewpoint absFeuchte= $abs_hunidity");
  266. $aFeuchte = "A: " . $abs_hunidity;
  267. }
  268. my $ah_rname = AttrVal($hashName, "absoluteHumidity", undef);
  269. if (defined($ah_rname)) {
  270. readingsBulkUpdate($dev, $ah_rname, $abs_hunidity);
  271. Log3($hashName, 5, "dewpoint $ah_rname= $abs_hunidity");
  272. $aFeuchte = "A: " . $abs_hunidity if !defined($aFeuchte);
  273. }
  274. my $vp_rname = AttrVal($hashName, "vapourPressure", undef);
  275. if (defined($vp_rname)) {
  276. my $vp = round(10 * dewpoint_vp($temperature, $humidity), 1);
  277. readingsBulkUpdate($dev, $vp_rname, $vp);
  278. Log3($hashName, 5, "dewpoint $vp_rname= $vp");
  279. }
  280. $rname = $new_name;
  281. my $has_state_format = defined(AttrVal($dev->{NAME}, "stateFormat", undef));
  282. my $legacy_sh = AttrVal($hash->{NAME}, "legacyStateHandling", 0);
  283. if ($temp_name ne "T" || ($has_state_format && ! $legacy_sh)) {
  284. $rval = $dewpoint;
  285. readingsBulkUpdate($dev, $rname, $rval);
  286. readingsEndUpdate($dev, 1);
  287. } else {
  288. # explicit manipulation of STATE here
  289. # first call readingsEndUpdate to finish STATE processing in the referenced device...
  290. readingsEndUpdate($dev, 1);
  291. # ... then update STATE
  292. # STATE begins with "T:". append dewpoint or insert before BAT
  293. my $lastval = $dev->{CHANGED}[$i_state_ev];
  294. if ($lastval =~ /BAT:/) {
  295. $rval = $lastval;
  296. $rval =~ s/BAT:/$rname: $dewpoint BAT:/g;
  297. } elsif ($lastval =~ /<</) {
  298. $rval = $lastval;
  299. $rval =~ s/<</$rname:$dewpoint <</g;
  300. } else {
  301. $rval = $lastval." ".$rname.": ".$dewpoint;
  302. if (defined($aFeuchte)) {
  303. $rval = $rval." ".$aFeuchte;
  304. }
  305. }
  306. $dev->{STATE} = $rval;
  307. # the state event must be REPLACED
  308. $dev->{CHANGED}[$i_state_ev] = $rval;
  309. }
  310. # remove cached "state:..." events if any
  311. $dev->{CHANGEDWITHSTATE} = [];
  312. Log3($hashName, 5, "dewpoint_notify: rval=$rval");
  313. } elsif ($cmd_type eq "fan") {
  314. # >define <name> dewpoint fan devicename devicename-outside min-temp [diff-temp]
  315. #
  316. # This define may be used to turn an fan on or off if the outside air has less
  317. # water
  318. #
  319. # - Generate reading/event "fan: on" if (dewpoint of <devicename-outside>) + diff_temp is lower
  320. # than dewpoint of <devicename> and temperature of <devicename-outside> is >= min-temp
  321. # and reading "fan" was not already "on".
  322. # - Generate reading/event "fan: off": else and if reading "fan" was not already "off".
  323. Log3($hashName, 5, "dewpoint_notify: fan devname_out=$devname_out, min_temp=$min_temp, diff_temp=$diff_temp");
  324. my $rname;
  325. my $rval;
  326. if (exists $defs{$devname_out}{READINGS}{temperature}{VAL} && exists $defs{$devname_out}{READINGS}{humidity}{VAL}) {
  327. my $temperature_out = $defs{$devname_out}{READINGS}{temperature}{VAL};
  328. my $humidity_out = $defs{$devname_out}{READINGS}{humidity}{VAL};
  329. my $dewpoint_out = dewpoint_dewpoint($temperature_out, $humidity_out);
  330. Log3($hashName, 5, "dewpoint_notify: fan dewpoint_out=$dewpoint_out");
  331. if (($dewpoint_out + $diff_temp) < $dewpoint && $temperature_out >= $min_temp) {
  332. $rval = "on";
  333. Log3($hashName, 4, "dewpoint_notify: fan ON");
  334. } else {
  335. $rval = "off";
  336. Log3($hashName, 4, "dewpoint_notify: fan OFF");
  337. }
  338. $rname = "fan";
  339. if (!exists $defs{$devName}{READINGS}{$rname}{VAL} || $defs{$devName}{READINGS}{$rname}{VAL} ne $rval) {
  340. Log3($hashName, 4, "dewpoint_notify: CHANGE fan $rval");
  341. $dev->{READINGS}{$rname}{TIME} = $tn;
  342. $dev->{READINGS}{$rname}{VAL} = $rval;
  343. $dev->{CHANGED}[$nev++] = $rname . ": " . $rval;
  344. }
  345. } else {
  346. Log3($hashName, 1, "dewpoint_notify: fan devname_out=$devname_out no temperature or humidity available"
  347. . " for dewpoint calculation");
  348. }
  349. } elsif ($cmd_type eq "alarm") {
  350. # >define <name> dewpoint alarm devicename devicename-reference diff
  351. #
  352. # - Generate reading/event "alarm: on" if temperature of <devicename-reference>-<diff> is lower
  353. # than dewpoint of <devicename> and reading "alarm" was not already "on".
  354. # - Generate reading/event "alarm: off" if temperature of <devicename-reference>-<diff> is higher
  355. # than dewpoint of <devicename> and reading "alarm" was not already "off".
  356. #
  357. # You have different options to use this define:
  358. # * Use a temperature sensor in or on the wall (<devicename-reference>) and use a temp/hum sensor
  359. # to measure the dewpoint of the air. Alarm if the temperature of the wall is lower than the dewpoint of the air.
  360. # In this case the water of the air will condense on the wall because the wall is cold.
  361. # Example: define alarmtest dewpoint alarm roomsensor wallsensor 0
  362. # * If you do not have a temperature sensor in/on the wall, you may also compare the rooms dewpoint to the
  363. # temperature of the same or another inside sensor. If you think that your walls are normally 5 degrees colder
  364. # than the inside temperature, set diff to 5.
  365. # Example: define alarmtest dewpoint alarm roomsensor roomsensor 5
  366. Log3($hashName, 5, "dewpoint_notify: alarm devname_ref=$devname_ref, diff_temp=$diff_temp");
  367. my $rname;
  368. my $rval;
  369. if (exists $defs{$devname_ref}{READINGS}{temperature}{VAL}) {
  370. my $temperature_ref = $defs{$devname_ref}{READINGS}{temperature}{VAL};
  371. Log3($hashName, 5, "dewpoint_notify: alarm temperature_ref=$temperature_ref");
  372. if ($temperature_ref - $diff_temp < $dewpoint) {
  373. $rval = "on";
  374. Log3($hashName, 4, "dewpoint_notify: alarm ON");
  375. } else {
  376. $rval = "off";
  377. Log3($hashName, 4, "dewpoint_notify: alarm OFF");
  378. }
  379. $rname = "alarm";
  380. if (!exists $defs{$devName}{READINGS}{$rname}{VAL} || $defs{$devName}{READINGS}{$rname}{VAL} ne $rval) {
  381. Log3($hashName, 5, "dewpoint_notify: CHANGE alarm $rval");
  382. $dev->{READINGS}{$rname}{TIME} = $tn;
  383. $dev->{READINGS}{$rname}{VAL} = $rval;
  384. $dev->{CHANGED}[$nev++] = $rname . ": " . $rval;
  385. }
  386. } else {
  387. Log3($hashName, 1, "dewpoint_notify: alarm devname_out=$devname_out no temperature or humidity available"
  388. . " for dewpoint calculation");
  389. }
  390. } else {
  391. # should never happen:
  392. Log3($hashName, 1, "Error notify_dewpoint: <2> unknown cmd_type ".$cmd_type);
  393. return "";
  394. }
  395. return undef;
  396. }
  397. # -----------------------------
  398. # Dewpoint calculation.
  399. # 'Magnus formula'
  400. #
  401. # Parameters from https://de.wikipedia.org/wiki/Taupunkt#S.C3.A4ttigungsdampfdruck
  402. # Good summary of formulas in http://www.wettermail.de/wetter/feuchte.html
  403. my $E0 = 0.6112; # saturation pressure at T=0 °C
  404. my @ab_gt0 = (17.62, 243.12); # T>0
  405. my @ab_le0 = (22.46, 272.6); # T<=0 over ice
  406. ### ** Public interface ** keep stable
  407. # vapour pressure in kPa
  408. sub dewpoint_vp($$)
  409. {
  410. my ($T, $Hr) = @_;
  411. my ($a, $b);
  412. if ($T > 0) {
  413. ($a, $b) = @ab_gt0;
  414. } else {
  415. ($a, $b) = @ab_le0;
  416. }
  417. return 0.01 * $Hr * $E0 * exp($a * $T / ($T + $b));
  418. }
  419. ### ** Public interface ** keep stable
  420. # dewpoint in °C
  421. sub
  422. dewpoint_dewpoint($$)
  423. {
  424. my ($T, $Hr) = @_;
  425. if ($Hr == 0) {
  426. Log(1, "Error: dewpoint() Hr==0 !: temp=$T, hum=$Hr");
  427. return undef;
  428. }
  429. my ($a, $b);
  430. if ($T > 0) {
  431. ($a, $b) = @ab_gt0;
  432. } else {
  433. ($a, $b) = @ab_le0;
  434. }
  435. # solve vp($dp, 100) = vp($T,$Hr) for $dp
  436. my $v = log(dewpoint_vp($T, $Hr) / $E0);
  437. my $D = $a - $v;
  438. # can this ever happen for valid input?
  439. if ($D == 0) {
  440. Log(1, "Error: dewpoint() D==0 !: temp=$T, hum=$Hr");
  441. return undef;
  442. }
  443. return round($b * $v / $D, 1);
  444. }
  445. ### ** Public interface ** keep stable
  446. # absolute Feuchte in g Wasserdampf pro m3 Luft
  447. sub
  448. dewpoint_absFeuchte ($$)
  449. {
  450. my ($T, $Hr) = @_;
  451. # 110 ?
  452. if (($Hr < 0) || ($Hr > 110)) {
  453. Log(1, "Error dewpoint: humidity invalid: $Hr");
  454. return "";
  455. }
  456. my $DD = dewpoint_vp($T, $Hr);
  457. my $AF = 1.0E6 * (18.016 / 8314.3) * ($DD / (273.15 + $T));
  458. return round($AF, 1);
  459. }
  460. 1;
  461. =pod
  462. =item helper
  463. =item summary compute dewpoint and/or generate events for starting a fan
  464. =item summary_DE berechne Taupunkt und/oder erzeuge events zum starten eines Lüfters
  465. =begin html
  466. <a name="dewpoint"></a>
  467. <h3>dewpoint</h3>
  468. <ul>
  469. Dewpoint calculations. Offers three different ways to use dewpoint: <br>
  470. <ul>
  471. <li><b>dewpoint</b><br>
  472. Compute additional event dewpoint from a sensor offering temperature and humidity.</li>
  473. <li><b>fan</b><br>
  474. Generate a event to turn a fan on if the outside air has less water than the inside.</li>
  475. <li><b>alarm</b><br>
  476. Generate a mold alarm if a reference temperature is lower that the current dewpoint.</li>
  477. <br>
  478. </ul>
  479. <a name="dewpointdefine"></a>
  480. <b>Define</b>
  481. <ul>
  482. <code>define &lt;name&gt; dewpoint dewpoint &lt;devicename-regex&gt; [&lt;temp_name&gt; &lt;hum_name&gt; &lt;new_name&gt;]</code><br>
  483. <br>
  484. <ul>
  485. Calculates dewpoint for device &lt;devicename-regex&gt; from temperature and humidity
  486. and write it to a new reading named dewpoint.
  487. If optional &lt;temp_name&gt;, &lt;hum_name&gt; and &lt;new_name&gt; is specified
  488. then read temperature from reading &lt;temp_name&gt;, humidity from reading &lt;hum_name&gt;
  489. and write the calculated dewpoint to reading &lt;new_name&gt;.<br>
  490. <b>Obsolete, avoid for new definitions</b><br>
  491. &nbsp;&nbsp;If &lt;temp_name&gt; is T then use temperature from state T: H:, add &lt;new_name&gt; to the STATE.
  492. The addition to STATE only occurs if the target device does not define attribute "stateFormat".<br>
  493. If the obsolete behaviour of STATE is mandatory set attribute <code>legacyStateHandling</code>
  494. should be set.
  495. </ul>
  496. <br><br>
  497. Example:<PRE>
  498. # Compute the dewpoint for the temperature/humidity
  499. # events of the temp1 device and generate reading dewpoint.
  500. define dew_temp1 dewpoint dewpoint temp1
  501. define dew_temp1 dewpoint dewpoint temp1 temperature humidity dewpoint
  502. # Compute the dewpoint for the temperature/humidity
  503. # events of all devices offering temperature and humidity
  504. # and generate reading dewpoint.
  505. define dew_all dewpoint dewpoint .*
  506. define dew_all dewpoint dewpoint .* temperature humidity dewpoint
  507. # Compute the dewpoint for the temperature/humidity
  508. # events of the device Aussen_1 offering temperature and humidity
  509. # and insert is into STATE unless Aussen_1 has attribute "stateFormat" defined.
  510. # If "stateFormat" is defined then a reading D will be generated.
  511. define dew_state dewpoint dewpoint Aussen_1 T H D
  512. # Compute the dewpoint for the temperature/humidity
  513. # events of all devices offering temperature and humidity
  514. # and insert the result into the STATE. (See example above).
  515. # Example STATE: "T: 10 H: 62.5" will change to
  516. # "T: 10 H: 62.5 D: 3.2"
  517. define dew_state dewpoint dewpoint .* T H D
  518. </PRE>
  519. </ul>
  520. <ul>
  521. <code>define &lt;name&gt; dewpoint fan &lt;devicename-regex&gt; &lt;devicename-outside&gt; &lt;min-temp&gt; [&lt;diff_temp&gt;]</code><br>
  522. <br>
  523. <ul>
  524. May be used to turn an fan on or off if the outside air has less water.
  525. <ul>
  526. <li>
  527. Generate event "fan: on" if (dewpoint of &lt;devicename-outside&gt;) + &lt;diff_temp&gt; is lower
  528. than dewpoint of &lt;devicename&gt; and temperature of &lt;devicename-outside&gt; is &gt;= min-temp
  529. and reading "fan" was not already "on". The event will be generated for &lt;devicename&gt;. Parameter &lt;diff-temp&gt; is optional</li>
  530. <li>Generate event "fan: off": else and if reading "fan" was not already "off".</li>
  531. </ul>
  532. </ul>
  533. <br>
  534. Example:<PRE>
  535. # Generate event "fan: on" when dewpoint of Aussen_1 is first
  536. # time lower than basement_tempsensor and outside temperature is &gt;= 0
  537. # and change it to "fan: off" is this condition changes.
  538. # Set a switch on/off (fan_switch) depending on the state.
  539. define dew_fan1 dewpoint fan basement_tempsensor Aussen_1 0
  540. define dew_fan1_on notify basement_tempsensor.*fan:.*on set fan_switch on
  541. define dew_fan1_off notify basement_tempsensor.*fan:.*off set fan_switch off
  542. </PRE>
  543. </ul>
  544. <ul>
  545. <code>define &lt;name&gt; dewpoint alarm &lt;devicename-regex&gt; &lt;devicename-reference&gt; &lt;diff-temp&gt;</code><br>
  546. <br>
  547. <ul>
  548. Generate a mold alarm if a reference temperature is lower that the current dewpoint.
  549. <ul>
  550. <li>
  551. Generate reading/event "alarm: on" if temperature of &lt;devicename-reference&gt; - &lt;diff-temp&gt; is lower
  552. than dewpoint of &lt;devicename&gt; and reading "alarm" was not already "on". The event will be generated for &lt;devicename&gt;.</li>
  553. <li>Generate reading/event "alarm: off" if temperature of &lt;devicename-reference&gt; - &lt;diff-temp&gt; is higher than dewpoint of &lt;devicename&gt; and reading "alarm" was not already "off".</li>
  554. </ul>
  555. </ul>
  556. <br>
  557. Example:<PRE>
  558. # Using a wall temperature sensor (wallsensor) and a temp/hum sensor
  559. # (roomsensor) to alarm if the temperature of the wall is lower than
  560. # the dewpoint of the air. In this case the water of the air will
  561. # condense on the wall because the wall is cold.
  562. # Set a switch on (alarm_siren) if alarm is on using notify.
  563. define dew_alarm1 dewpoint alarm roomsensor wallsensor 0
  564. define roomsensor_alarm_on notify roomsensor.*alarm:.*on set alarm_siren on
  565. define roomsensor_alarm_off notify roomsensor.*alarm:.*off set alarm_siren off
  566. # If you do not have a temperature sensor in/on the wall, you may also
  567. # compare the rooms dewpoint to the temperature of the same or another
  568. # inside sensor. Alarm is temperature is 5 degrees colder than the
  569. # inside dewpointinside.
  570. define dev_alarm2 dewpoint alarm roomsensor roomsensor 5
  571. </PRE>
  572. </ul>
  573. <a name="dewpointset"></a>
  574. <b>Set</b> <ul>N/A</ul><br>
  575. <a name="dewpointget"></a>
  576. <b>Get</b> <ul>N/A</ul><br>
  577. <a name="dewpointattr"></a>
  578. <b>Attributes</b>
  579. <ul>
  580. <li><a href="#disable">disable</a></li>
  581. <li>absoluteHumidity &lt;reading_name&gt;</li>
  582. <ul>
  583. In addition the absolute humidity in g/m&sup3; will be computed as reading &lt;reading_name&gt;.
  584. </ul><br>
  585. <li>vapourPressure &lt;reading_name&gt;</li>
  586. <ul>
  587. In addition the vapour pressure in hPa will be computed as reading &lt;reading_name&gt;.
  588. </ul><br>
  589. <li>max_timediff</li>
  590. <ul>
  591. 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>
  592. If not set default is 1 second.
  593. <br><br>
  594. Examples:<PRE>
  595. # allow maximum time difference of 60 seconds
  596. define dew_all dewpoint dewpoint .*
  597. attr dew_all max_timediff 60
  598. </ul>
  599. </ul>
  600. </ul>
  601. =end html
  602. =begin html_DE
  603. <a name="dewpoint"></a>
  604. <h3>dewpoint</h3>
  605. <ul>
  606. Berechnungen des Taupunkts. Es gibt drei Varianten, das Modul dewpoint zu verwenden: <br>
  607. <ul>
  608. <li><b>dewpoint</b>: Taupunkt<br>
  609. Erzeugt ein zus&auml;tzliches Ereignis "dewpoint" aus Temperatur- und Luftfeuchtewerten eines F&uuml;hlers.</li>
  610. <li><b>fan</b>: L&uuml;fter<br>
  611. Erzeugt ein Ereignis, um einen L&uuml;fter einzuschalten, wenn die Au&szlig;enluft
  612. weniger Wasser als die Raumluft enth&auml;lt.</li>
  613. <li><b>alarm</b>: Alarm<br>
  614. Erzeugt einen Schimmel-Alarm, wenn eine Referenz-Temperatur unter den Taupunkt f&auml;llt.</li>
  615. </ul>
  616. <br/>
  617. <a name="dewpointdefine"></a>
  618. <b>Define</b>
  619. <ul>
  620. <code>define &lt;name&gt; dewpoint dewpoint &lt;devicename-regex&gt; [&lt;temp_name&gt;
  621. &lt;hum_name&gt; &lt;new_name&gt;]</code><br>
  622. <br/>
  623. Berechnet den Taupunkt des Ger&auml;ts &lt;devicename-regex&gt; basierend auf Temperatur
  624. und Luftfeuchte und erzeugt daraus ein neues Reading namens dewpoint.<br/>
  625. Wenn &lt;temp_name&gt;, &lt;hum_name&gt; und &lt;new_name&gt; angegeben sind,
  626. werden die Temperatur aus dem Reading &lt;temp_name&gt;, die Luftfeuchte aus dem
  627. Reading &lt;hum_name&gt; gelesen und als berechneter Taupunkt ins Reading &lt;new_name&gt; geschrieben.<br><br>
  628. <b>Veraltet, f&uuml;r neue Definitionen nicht mehr benutzen</b><br>
  629. &nbsp;&nbsp;Wenn &lt;temp_name&gt; T lautet, wird die Temperatur aus state T: H: benutzt
  630. und &lt;new_name&gt; zu STATE hinzugef&uuml;gt. Das hinzuf&uuml;gen zu STATE erfolgt nur, falls im Zielger&auml;t
  631. das Attribut "stateFormat" nicht definiert ist.<br>
  632. Falls das veraltete Verhalten zum Update unbedingt gew&uuml;scht ist,
  633. kann das Attribut <code>legacyStateHandling</code> gesetzt werden.
  634. <br/>
  635. Beispiele:
  636. <pre>
  637. # Berechnet den Taupunkt aufgrund von Temperatur und Luftfeuchte
  638. # in Ereignissen, die vom Ger&auml;t temp1 erzeugt wurden und erzeugt ein Reading dewpoint.
  639. define dew_temp1 dewpoint dewpoint temp1
  640. define dew_temp1 dewpoint dewpoint temp1 temperature humidity dewpoint
  641. # Berechnet den Taupunkt aufgrund von Temperatur und Luftfeuchte
  642. # in Ereignissen, die von allen Ger&auml;ten erzeugt wurden die diese Werte ausgeben
  643. # und erzeugt ein Reading dewpoint.
  644. define dew_all dewpoint dewpoint .*
  645. define dew_all dewpoint dewpoint .* temperature humidity dewpoint
  646. # Berechnet den Taupunkt aufgrund von Temperatur und Luftfeuchte
  647. # in Ereignissen, die vom Ger&auml;t Aussen_1 erzeugt wurden und erg&auml;nzt
  648. # mit diesem Wert den Status STATE, falls in Aussen_1 das Attribut "stateFormat" nicht definiert ist.
  649. # Falls "stateFormat" definiert ist, wird das reading "D" angelegt.
  650. define dew_state dewpoint dewpoint Aussen_1 T H D
  651. # Berechnet den Taupunkt aufgrund von Temperatur und Luftfeuchte
  652. # in Ereignissen, die von allen Ger&auml;ten erzeugt wurden die diese Werte ausgeben
  653. # und erg&auml;nzt mit diesem Wert den Status STATE. (Siehe Beispiel oben).
  654. # Beispiel STATE: "T: 10 H: 62.5" wird ver&auml;ndert nach
  655. # "T: 10 H: 62.5 D: 3.2"
  656. define dew_state dewpoint dewpoint .* T H D
  657. </pre>
  658. <br/>
  659. <br/>
  660. <code>define &lt;name&gt; dewpoint fan &lt;devicename-regex&gt; &lt;devicename-outside&gt; &lt;min-temp&gt; [&lt;diff_temp&gt;]</code><br>
  661. <br>
  662. <ul>
  663. <li>Erzeugt ein Ereignis, um einen L&uuml;fter einzuschalten, wenn die Au&szlig;enluft
  664. weniger Wasser als die Raumluft enth&auml;lt.</li>
  665. <li>Erzeugt das Ereignis "fan: on" wenn (Taupunkt von &lt;devicename-outside&gt;) +
  666. &lt;diff_temp&gt; ist niedriger als der Taupunkt von &lt;devicename&gt; und die Temperatur
  667. von &lt;devicename-outside&gt; &gt;= min-temp ist. Das Ereignis wird nur erzeugt wenn das
  668. Reading "fan" nicht schon "on" war. Das Ereignis wird f&uuml;r das Ger&auml;t &lt;devicename&gt; erzeugt.
  669. Der Parameter &lt;diff-temp&gt; ist optional.</li>
  670. <li>Andernfalls wird das Ereignis "fan: off" erzeugt, wenn das Reading von "fan" nicht bereits "off" war.</li>
  671. </ul>
  672. <br>
  673. Beispiel:
  674. <pre>
  675. # Erzeugt das Ereignis "fan: on", wenn der Taupunkt des Ger&auml;ts Aussen_1 zum ersten Mal
  676. # niedriger ist als der Taupunkt des Ger&auml;ts basement_tempsensor und die
  677. # Au&szlig;entemperatur &gt;= 0 ist und wechselt nach "fan: off" wenn diese Bedingungen nicht
  678. # mehr zutreffen.
  679. # Schaltet den Schalter fan_switch abh&auml;ngig vom Zustand ein oder aus.
  680. define dew_fan1 dewpoint fan basement_tempsensor Aussen_1 0
  681. define dew_fan1_on notify basement_tempsensor.*fan:.*on set fan_switch on
  682. define dew_fan1_off notify basement_tempsensor.*fan:.*off set fan_switch off
  683. </pre>
  684. <code>define &lt;name&gt; dewpoint alarm &lt;devicename-regex&gt; &lt;devicename-reference&gt; &lt;diff-temp&gt;</code><br>
  685. <br>
  686. <ul>
  687. <li>Erzeugt einen Schimmel-Alarm, wenn eine Referenz-Temperatur unter den Taupunkt f&auml;llt.</li>
  688. <li>Erzeugt ein Reading/Ereignis "alarm: on" wenn die Temperatur von
  689. &lt;devicename-reference&gt; - &lt;diff-temp&gt; unter den Taupunkt von
  690. &lt;devicename&gt; f&auml;llt und das Reading "alarm" nicht bereits "on" ist.
  691. Das Ereignis wird f&uuml;r &lt;devicename&gt; erzeugt.</li>
  692. <li>Erzeugt ein Reading/Ereignis "alarm: off" wenn die Temperatur von
  693. &lt;devicename-reference&gt; - &lt;diff-temp&gt; &uuml;ber den Taupunkt
  694. von &lt;devicename&gt; steigt und das Reading "alarm" nicht bereits "off" ist.</li>
  695. </ul>
  696. <br>
  697. Beispiel:
  698. <pre>
  699. # Es wird ein Anlegef&uuml;hler (Wandsensor) und ein Thermo-/Hygrometer (Raumf&uuml;hler)
  700. # verwendet, um einen Alarm zu erzeugen, wenn die Wandtemperatur
  701. # unter den Taupunkt der Luft f&auml;llt. In diesem Fall w&uuml;rde sich Wasser an der Wand
  702. # niederschlagen (kondensieren), weil die Wand zu kalt ist.
  703. # Der Schalter einer Sirene (alarm_siren) wird &uuml;ber ein notify geschaltet.
  704. define dew_alarm1 dewpoint alarm roomsensor wallsensor 0
  705. define roomsensor_alarm_on notify roomsensor.*alarm:.*on set alarm_siren on
  706. define roomsensor_alarm_off notify roomsensor.*alarm:.*off set alarm_siren off
  707. # Ohne Wandsensor l&auml;sst sich auch der Taupunkt eines Raums mit der Temperatur desselben
  708. # (oder eines anderen) F&uuml;hlers vergleichen.
  709. # Die Alarmtemperatur ist 5 Grad niedriger gesetzt als die des Vergleichsthermostats.
  710. define dev_alarm2 dewpoint alarm roomsensor roomsensor 5
  711. </pre>
  712. </ul>
  713. <a name="dewpointset"></a>
  714. <b>Set</b> <ul>N/A</ul><br>
  715. <a name="dewpointget"></a>
  716. <b>Get</b> <ul>N/A</ul><br>
  717. <a name="dewpointattr"></a>
  718. <b>Attributes</b>
  719. <ul>
  720. <li><a href="#disable">disable</a></li>
  721. <li>absoluteHumidity &lt;reading_name&gt;</li>
  722. <ul>
  723. Zus&auml;tzlich wird die absolute Feuchte in g/m&sup3; als Reading &lt;reading_name&gt; berechnet.
  724. </ul><br>
  725. <li>vapourPressure &lt;reading_name&gt;</li>
  726. <ul>
  727. Zus&auml;tzlich wird der Dampfdruck in hPa als Reading &lt;reading_name&gt; berechnet.
  728. </ul><br>
  729. <li>max_timediff</li>
  730. <ul>
  731. Maximale erlaubter Zeitunterschied in Sekunden zwischen den Temperatur- und Luftfeuchtewerten eines
  732. Ger&auml;ts. dewpoint verwendet Readings von Temperatur oder Luftfeuchte wenn sie nicht im Ereignis
  733. mitgeliefert werden. Das ist sowohl f&uuml;r den Betrieb mit event-on-change-reading n&ouml;tig
  734. als auch bei Sensoren die Temperatur und Luftfeuchte in getrennten Ereignissen kommunizieren
  735. (z.B. Technoline Sensoren TX3TH).<br>
  736. Der Standardwert ist 1 Sekunde.
  737. <br><br>
  738. Beispiel:
  739. <pre>
  740. # Maximal erlaubter Zeitunterschied soll 60 Sekunden sein
  741. define dew_all dewpoint dewpoint .*
  742. attr dew_all max_timediff 60
  743. </pre>
  744. </ul>
  745. </ul>
  746. </ul>
  747. =end html_DE
  748. =cut