98_dewpoint.pm 33 KB

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