98_dewpoint.pm 34 KB

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