98_average.pm 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. ##############################################
  2. # $Id: 98_average.pm 16293 2018-02-28 21:33:57Z rudolfkoenig $
  3. # Average computing
  4. package main;
  5. use strict;
  6. use warnings;
  7. ##########################
  8. sub
  9. average_Initialize($)
  10. {
  11. my ($hash) = @_;
  12. $hash->{DefFn} = "average_Define";
  13. $hash->{NotifyFn} = "average_Notify";
  14. $hash->{NotifyOrderPrefix} = "10-"; # Want to be called before the rest
  15. $hash->{AttrList} = "disable:0,1 " .
  16. "disabledForIntervals " .
  17. "computeMethod:integral,counter " .
  18. "noaverage:0,1 " .
  19. "nominmax:0,1 " .
  20. "floatformat:%0.1f,%0.2f";
  21. }
  22. ##########################
  23. sub
  24. average_Define($$$)
  25. {
  26. my ($hash, $def) = @_;
  27. my ($name, $type, $re, $rest) = split("[ \t]+", $def, 4);
  28. if(!$re || $rest) {
  29. my $msg = "wrong syntax: define <name> average device[:event]";
  30. return $msg;
  31. }
  32. # Checking for misleading regexps
  33. eval { "Hallo" =~ m/^$re$/ };
  34. return "Bad regexp: $@" if($@);
  35. $hash->{REGEXP} = $re;
  36. $hash->{STATE} = "active";
  37. return undef;
  38. }
  39. sub
  40. avg_setValTime($$$$)
  41. {
  42. my ($r, $rname, $val, $tn) = @_;
  43. $r->{$rname}{VAL} = $val;
  44. $r->{$rname}{TIME} = $tn;
  45. }
  46. ##########################
  47. sub
  48. average_Notify($$)
  49. {
  50. my ($avg, $dev) = @_;
  51. my $myName = $avg->{NAME};
  52. return "" if(IsDisabled($myName));
  53. my $devName = $dev->{NAME};
  54. my $re = $avg->{REGEXP};
  55. my $max = int(@{$dev->{CHANGED}});
  56. my $tn;
  57. my $myIdx = $max;
  58. my $doCounter = (AttrVal($myName, "computeMethod", "integral") eq "counter");
  59. my $doMMx = (AttrVal($myName, "nominmax", "0") eq "0");
  60. my $doAvg = (AttrVal($myName, "noaverage", "0") eq "0");
  61. my $ffmt = AttrVal($myName, "floatformat", "%0.1f");
  62. my $r = $dev->{READINGS};
  63. for (my $i = 0; $i < $max; $i++) {
  64. my $s = $dev->{CHANGED}[$i];
  65. ################
  66. # Filtering
  67. next if(!defined($s));
  68. my ($evName, $val) = split(" ", $s, 2); # resets $1
  69. next if($devName !~ m/^$re$/ && "$devName:$s" !~ m/^$re$/ ||
  70. $s =~ m/(_avg_|_cum_|_min_|_max_|_cnt_)/);
  71. if(defined($1)) {
  72. my $reArg = $1;
  73. if(defined($2)) {
  74. $evName = $1;
  75. $reArg = $2;
  76. }
  77. $val = $reArg if(defined($reArg) && $reArg =~ m/^(-?\d+\.?\d*)/);
  78. }
  79. next if(!defined($val) || $val !~ m/^(-?\d+\.?\d*)/);
  80. $val = $1;
  81. ################
  82. # Avg computing
  83. $evName =~ s/[^A-Za-z\d_\.\-\/]//g;
  84. $tn = TimeNow() if(!$tn);
  85. my @dNow = split("[ :-]", $tn);
  86. for(my $idx = 0; $idx <= 1; $idx++) { # 0:day 1:month
  87. my $secNow = 3600*$dNow[3] + 60*$dNow[4] + $dNow[5];
  88. $secNow += $dNow[2]*86400 if($idx);
  89. my $cumName = "${evName}_cum_" . ($idx ? "month" : "day");
  90. my $avgName = "${evName}_avg_" . ($idx ? "month" : "day");
  91. my $minName = "${evName}_min_" . ($idx ? "month" : "day");
  92. my $maxName = "${evName}_max_" . ($idx ? "month" : "day");
  93. my $cntName = "${evName}_cnt_" . ($idx ? "month" : "day");
  94. if($doCounter && !defined($r->{$cntName})) {
  95. avg_setValTime($r, $cntName, 1, $tn);
  96. delete $r->{$cumName}; # Reset when switching to counter-mode
  97. delete $r->{$avgName};
  98. }
  99. if($doMMx && (!defined($r->{$maxName}) || !defined($r->{$minName}))) {
  100. avg_setValTime($r, $maxName, $val, $tn);
  101. avg_setValTime($r, $minName, $val, $tn);
  102. }
  103. if(!defined($r->{$cumName}) || ($doAvg && !defined($r->{$avgName}))) {
  104. my $cum = ($doCounter ? $val : $secNow*$val);
  105. avg_setValTime($r, $cumName, $cum, $tn);
  106. avg_setValTime($r, $avgName, $val, $tn) if ($doAvg);
  107. next;
  108. }
  109. my @dLast = split("[ :-]", $r->{$cumName}{TIME});
  110. my $secLast = 3600*$dLast[3] + 60*$dLast[4] + $dLast[5];
  111. $secLast += $dLast[2]*86400 if($idx);
  112. if($idx == 0 && ($dLast[2] == $dNow[2]) ||
  113. $idx == 1 && ($dLast[1] == $dNow[1])) { # same day or month
  114. my $cVal = $r->{$cumName}{VAL};
  115. $cVal += ($doCounter ? $val : ($secNow-$secLast) * $val);
  116. avg_setValTime($r, $cumName, $cVal, $tn);
  117. if($doAvg) {
  118. my $div = ($secNow ? $secNow : 1);
  119. if($doCounter) {
  120. $div = $r->{$cntName}{VAL}+1;
  121. avg_setValTime($r, $cntName, $div, $tn);
  122. }
  123. my $lVal = sprintf($ffmt, $r->{$cumName}{VAL}/$div);
  124. avg_setValTime($r, $avgName, $lVal, $tn);
  125. }
  126. if($doMMx) {
  127. avg_setValTime($r, $maxName, sprintf($ffmt,$val), $tn)
  128. if($r->{$maxName}{VAL} < $val);
  129. avg_setValTime($r, $minName, sprintf($ffmt,$val), $tn)
  130. if($r->{$minName}{VAL} > $val);
  131. }
  132. } else { # day or month changed: create events and reset values
  133. if($doAvg) {
  134. $dev->{CHANGED}[$myIdx++] = "$avgName: ".$r->{$avgName}{VAL};
  135. avg_setValTime($r, $cumName, $secNow*$val, $tn);
  136. avg_setValTime($r, $avgName, $val, $tn);
  137. }
  138. if($doCounter) {
  139. $dev->{CHANGED}[$myIdx++] = "$cumName: ".$r->{$cumName}{VAL};
  140. avg_setValTime($r, $cumName, 0, $tn);
  141. avg_setValTime($r, $cntName, 0, $tn) if($doAvg);
  142. } else {
  143. avg_setValTime($r, $cumName, $secNow*$val, $tn);
  144. }
  145. if($doMMx) {
  146. $dev->{CHANGED}[$myIdx++] = "$maxName: ".$r->{$maxName}{VAL};
  147. $dev->{CHANGED}[$myIdx++] = "$minName: ".$r->{$minName}{VAL};
  148. avg_setValTime($r, $maxName, sprintf($ffmt, $val), $tn);
  149. avg_setValTime($r, $minName, sprintf($ffmt, $val), $tn);
  150. }
  151. }
  152. }
  153. }
  154. return undef;
  155. }
  156. 1;
  157. =pod
  158. =item helper
  159. =item summary add avarage Readings to arbitrary devices
  160. =item summary_DE berechnet Durchschnittswerte (als Readings)
  161. =begin html
  162. <a name="average"></a>
  163. <h3>average</h3>
  164. <ul>
  165. Compute additional average, minimum and maximum values for current day and
  166. month.
  167. <br>
  168. <a name="averagedefine"></a>
  169. <b>Define</b>
  170. <ul>
  171. <code>define &lt;name&gt; average &lt;regexp&gt;</code><br>
  172. <br>
  173. <ul>
  174. The syntax for &lt;regexp&gt; is the same as the
  175. regexp for <a href="#notify">notify</a>.<br>
  176. If it matches, and the event is of the form "eventname number", then this
  177. module computes the daily and monthly average, maximum and minimum values
  178. and sums depending on attribute settings and generates events of the form
  179. <ul>
  180. &lt;device&gt; &lt;eventname&gt;_avg_day: &lt;computed_average&gt;
  181. </ul>
  182. <ul>
  183. &lt;device&gt; &lt;eventname&gt;_min_day: &lt;minimum day value&gt;
  184. </ul>
  185. <ul>
  186. &lt;device&gt; &lt;eventname&gt;_max_day: &lt;maximum day value&gt;
  187. </ul>
  188. <ul>
  189. &lt;device&gt; &lt;eventname&gt;_cum_day: &lt;sum of the values during the day&gt;
  190. </ul>
  191. and
  192. <ul>
  193. &lt;device&gt; &lt;eventname&gt;_avg_month: &lt;computed_average&gt;
  194. </ul>
  195. <ul>
  196. &lt;device&gt; &lt;eventname&gt;_min_month: &lt;minimum month value&gt;
  197. </ul>
  198. <ul>
  199. &lt;device&gt; &lt;eventname&gt;_max_month: &lt;maximum month value&gt;
  200. </ul>
  201. <ul>
  202. &lt;device&gt; &lt;eventname&gt;_cum_month: &lt;sum of the values during the month&gt;
  203. </ul>
  204. at the beginning of the next day or month respectively depending on attributes defined.<br>
  205. The current average, minimum, maximum and the cumulated values are stored
  206. in the device readings depending on attributes defined.
  207. </ul>
  208. <br>
  209. Example:<PRE>
  210. # Compute the average, minimum and maximum for the temperature events of
  211. # the ws1 device
  212. define avg_temp_ws1 average ws1:temperature.*
  213. # Compute the average, minimum and maximum for each temperature event
  214. define avg_temp_ws1 average .*:temperature.*
  215. # Compute the average, minimum and maximum for all temperature and humidity events
  216. # Events:
  217. # ws1 temperature: 22.3
  218. # ws1 humidity: 67.4
  219. define avg_temp_ws1 average .*:(temperature|humidity).*
  220. # Compute the same from a combined event. Note: we need two average
  221. # definitions here, each of them defining the name with the first
  222. # paranthesis, and the value with the second.
  223. #
  224. # Event: ws1 T: 52.3 H: 67.4
  225. define avg_temp_ws1_t average ws1:(T):.([-\d\.]+).*
  226. define avg_temp_ws1_h average ws1:.*(H):.([-\d\.]+).*
  227. </PRE>
  228. </ul>
  229. <a name="averageset"></a>
  230. <b>Set</b> <ul>N/A</ul><br>
  231. <a name="averageget"></a>
  232. <b>Get</b> <ul>N/A</ul><br>
  233. <a name="averageattr"></a>
  234. <b>Attributes</b>
  235. <ul>
  236. <li><a href="#disable">disable</a></li>
  237. <li><a href="#disabledForIntervals">disabledForIntervals</a></li>
  238. <li>computeMethod</li>
  239. defines how values are added up for the average calculation. This
  240. attribute can be set to integral or counter.
  241. The integral mode is meant for measuring continuous values like
  242. temperature, counter is meant for adding up values, e.g. from a
  243. feeding unit. In the first case, the time between the events plays an
  244. important role, in the second case not. Default is integral.
  245. <li>nominmax</li>
  246. don't compute min and max values. Default is 0 (compute min &amp; max).
  247. <li>noaverage</li>
  248. don't compute average values. Default is 0 (compute avarage).
  249. </ul>
  250. <a name="averageevents"></a>
  251. <b>Generated events:</b>
  252. <ul>
  253. <li>&lt;eventname&gt;_avg_day: $avg_day</li>
  254. <li>&lt;eventname&gt;_avg_month: $avg_month</li>
  255. <li>&lt;eventname&gt;_cum_day: $cum_day (only if cumtype is set to raw)</li>
  256. <li>&lt;eventname&gt;_cum_month: $cum_month (only if cumtype is set to raw)</li>
  257. <li>&lt;eventname&gt;_min_day: $min_day</li>
  258. <li>&lt;eventname&gt;_min_month: $min_month</li>
  259. <li>&lt;eventname&gt;_max_day: $max_day</li>
  260. <li>&lt;eventname&gt;_max_month: $max_month</li>
  261. </ul>
  262. </ul>
  263. =end html
  264. =cut