13_KS300.pm 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455
  1. ##############################################
  2. # $Id: 13_KS300.pm 15627 2017-12-17 11:00:46Z rudolfkoenig $
  3. #
  4. # modified: 2014-02-16 - betateilchen
  5. # - added new reading for windIndex (bft)
  6. # - changed to readingFnAttributes
  7. # - some minor code cleanups
  8. #
  9. package main;
  10. use strict;
  11. use warnings;
  12. sub KS300_windIndex($);
  13. #####################################
  14. sub
  15. KS300_Initialize($)
  16. {
  17. my ($hash) = @_;
  18. # Message is like
  19. # 810d04f94027a00171212730000008
  20. # 81 0d 04 f9 4027a00171 212730000008
  21. $hash->{Match} = "^810d04..4027a001";
  22. $hash->{DefFn} = "KS300_Define";
  23. $hash->{UndefFn} = "KS300_Undef";
  24. $hash->{ParseFn} = "KS300_Parse";
  25. $hash->{AttrList} = "IODev do_not_notify:0,1 showtime:0,1 model:ks300 ".
  26. "rainadjustment:0,1 ignore:0,1 ".
  27. $readingFnAttributes;
  28. $hash->{AutoCreate}=
  29. { "KS300.*" => {
  30. GPLOT => "temp4rain10:Temp/Rain,hum6wind8:Wind/Hum,",
  31. FILTER => "%NAME:T:.*" } };
  32. }
  33. #####################################
  34. sub
  35. KS300_Define($$)
  36. {
  37. my ($hash, $def) = @_;
  38. my @a = split("[ \t][ \t]*", $def);
  39. return "wrong syntax: define <name> KS300 <code> " .
  40. "[ml/raincounter] [wind-factor]" if(int(@a) < 3 || int(@a) > 5);
  41. $a[2] = lc($a[2]);
  42. return "Define $a[0]: wrong CODE format: specify a 4 digit hex value"
  43. if($a[2] !~ m/^[a-f0-9][a-f0-9][a-f0-9][a-f0-9]$/);
  44. # $hash->{CODE} = $a[2];
  45. my $rainunit = ((int(@a) > 3) ? $a[3] : 255);
  46. my $windunit = ((int(@a) > 4) ? $a[4] : 1.0);
  47. $hash->{CODE} = $a[2];
  48. $hash->{RAINUNIT} = $rainunit;
  49. $hash->{WINDUNIT} = $windunit;
  50. $modules{KS300}{defptr}{$a[2]} = $hash;
  51. AssignIoPort($hash);
  52. readingsSingleUpdate($hash, 'state', 'defined', 0);
  53. return undef;
  54. }
  55. #####################################
  56. sub
  57. KS300_Undef($$)
  58. {
  59. my ($hash, $name) = @_;
  60. delete($modules{KS300}{defptr}{$hash->{CODE}});
  61. return undef;
  62. }
  63. #####################################
  64. # { Dispatch($defs{CUL},"810d04xx4027a00171240080009359", undef) }
  65. sub
  66. KS300_Parse($$)
  67. {
  68. my ($hash,$msg) = @_;
  69. ###############################
  70. # 1 2
  71. #0123456789012345 67890123456789
  72. #
  73. #810d04f94027a001 71212730000008
  74. ###############################
  75. my @a = split("", $msg);
  76. ##########################
  77. # I've seldom (1 out of 700) seen messages of length 10 and 11 with correct
  78. # CRC, they seem to contain partial data (e.g. temp/wind/hum but not rain)
  79. # They are suppressed as of now.
  80. if(hex($a[3]) != 13) {
  81. Log3 $hash, 4, "Strange KS300 message received, won't decode ($msg)";
  82. return "";
  83. }
  84. if(int(keys %{ $modules{KS300}{defptr} })) {
  85. my @arr = keys(%{ $modules{KS300}{defptr} }); # No code is known yet
  86. my $dev = shift(@arr);
  87. my $def = $modules{KS300}{defptr}{$dev};
  88. my $haverain = 0;
  89. my $name= $def->{NAME};
  90. return "" if(IsIgnored($name));
  91. readingsBeginUpdate($def);
  92. my @v;
  93. my @txt = ( "rain_raw", "rain", "wind", "humidity", "temperature",
  94. "israining", "checksum", "type_raw", "unknown3", "windIndex");
  95. my @sfx = ( "(counter)", "(l/m2)", "(km/h)", "(%)", "(Celsius)",
  96. "(yes/no)", "","","","");
  97. my %repchanged = ("rain"=>1, "wind"=>1, "humidity"=>1, "temperature"=>1,
  98. "israining"=>1);
  99. # time
  100. my $tm = TimeNow();
  101. my $tsecs= time(); # number of non-leap seconds since January 1, 1970, UTC
  102. # preset current $rain_raw
  103. $v[0] = hex("$a[28]$a[27]$a[26]");
  104. my $rain_raw = $v[0];
  105. # get previous rain_raw
  106. my $rain_raw_prev = ReadingsVal($name, 'rain_raw', $rain_raw);
  107. ($rain_raw_prev, undef) = split(" ", $rain_raw_prev); # cut off "(counter)"
  108. my $rain_raw_adj = $rain_raw; # unadjusted value as default
  109. my $rain_raw_adj_prev = ReadingsVal($name, 'rain_raw_adj', $rain_raw);
  110. if(AttrVal($name,"rainadjustment",0)) {
  111. # The rain values delivered by my KS300 randomly switch between two
  112. # different values. The offset between the two values follows no
  113. # identifiable principle. It is even unclear whether the problem is
  114. # caused by KS300 or by FHZ1300. ELV denies any problem with the KS300.
  115. # The problem is known to several people. For instance, see
  116. # http://www.ipsymcon.de/forum/showthread.php?t=3303&highlight=ks300+regen&page=3
  117. # The following code detects and automatically corrects these offsets.
  118. my $rain_raw_ofs_prev = ReadingsVal($name, 'rain_raw_ofs', 0);
  119. my $rain_raw_ofs = $rain_raw_ofs_prev;
  120. my $tsecs_prev = ReadingsVal($name, 'tsecs', 0);
  121. # detect error condition
  122. # delta is negative or delta is too large
  123. # see http://de.wikipedia.org/wiki/Niederschlagsintensit??t#Niederschlagsintensit.C3.A4t
  124. # during a thunderstorm in middle europe, 50l/m^2 rain may fall per hour
  125. # 50l/(m^2*h) correspond to 200 ticks/h
  126. # Since KS300 sends every 2,5 minutes, a maximum delta of 8 ticks would
  127. # be reasonable. The observed deltas are in most cases 1 or 2 orders
  128. # of magnitude larger.
  129. # The code also handles counter resets after battery replacement
  130. my $rain_raw_delta = $rain_raw - $rain_raw_prev;
  131. my $deltatsecs= ($tsecs - $tsecs_prev); # we have observed two datagrams at the same second
  132. $deltatsecs= 1 if($deltatsecs< 1);
  133. my $thours_delta = $deltatsecs/3600.0; # in hours
  134. my $rain_raw_per_hour = $rain_raw_delta/$thours_delta;
  135. if(($rain_raw_delta<0) || ($rain_raw_per_hour> 200.0)) {
  136. $rain_raw_ofs = $rain_raw_ofs_prev-$rain_raw_delta;
  137. # If the switch in the tick count occurs simultaneously with an
  138. # increase due to rain, the tick is lost. We therefore assume that
  139. # offsets between -5 and 0 are indeed rain.
  140. if(($rain_raw_ofs>=-5) && ($rain_raw_ofs<0)) {
  141. $rain_raw_ofs= 0;
  142. }
  143. readingsBulkUpdate($def, 'rain_raw_ofs', $rain_raw_ofs, 0);
  144. }
  145. $rain_raw_adj = $rain_raw + $rain_raw_ofs;
  146. }
  147. readingsBulkUpdate($def, 'tsecs', $tsecs, 0);
  148. readingsBulkUpdate($def, 'rain_raw_adj', $rain_raw_adj, 0);
  149. # KS300 has a sensor which detects any drop of rain and immediately
  150. # sends out the israining message. The sensors consists of two parallel
  151. # strips of metal separated by a small gap. The rain bridges the gap
  152. # and closes the contact. If the KS300 pole is not perfectly vertical the
  153. # drop runs along only one side and the contact is not closed. To get the
  154. # israining information anyway, the respective flag is also set when the
  155. # a positive amount of rain is detected.
  156. $haverain = 1 if($rain_raw_adj != $rain_raw_adj_prev);
  157. $v[1] = sprintf("%0.1f", $rain_raw_adj * $def->{RAINUNIT} / 1000);
  158. $v[2] = sprintf("%0.1f", ("$a[25]$a[24].$a[23]"+(hex($a[17])&0x4?100:0)) *
  159. $def->{WINDUNIT});
  160. $v[3] = "$a[22]$a[21]" + 0;
  161. $v[4] = "$a[20]$a[19].$a[18]" + 0;
  162. $v[4] = sprintf("%0.1f", $v[4]);
  163. $v[5] = ((hex($a[17]) & 0x2) || $haverain) ? "yes" : "no";
  164. $v[6] = $a[29];
  165. $v[7] = $a[16];
  166. $v[8] = $a[17];
  167. $v[9] = KS300_windIndex($v[2]);
  168. # Negative temp
  169. $v[4] = -$v[4] if(hex($v[8]) & 8);
  170. Log3 $def, 4, "KS300 $dev: $msg";
  171. my $max = int(@v);
  172. # For logging/summary
  173. my $val = "T: $v[4] H: $v[3] W: $v[2] R: $v[1] IR: $v[5] Wi: $v[9]";
  174. Log3 $def, 4, "KS300 $dev: $val";
  175. readingsBulkUpdate($def,'state', $val);
  176. for(my $i = 0; $i < $max; $i++) {
  177. readingsBulkUpdate($def, $txt[$i], $v[$i],
  178. defined($repchanged{$txt[$i]}));
  179. }
  180. ###################################
  181. # AVG computing
  182. if(!ReadingsVal($name, 'cum_day', undef)) {
  183. readingsBulkUpdate($def, 'cum_day', "$tm T: 0 H: 0 W: 0 R: $v[1]", 0);
  184. } else {
  185. my @cv = split(" ", ReadingsVal($name, 'cum_day',''));
  186. my @cd = split("[ :-]", ReadingsTimestamp($name, 'cum_day',''));
  187. my $csec = 3600*$cd[3] + 60*$cd[4] + $cd[5]; # Sec of last reading
  188. my @d = split("[ :-]", $tm);
  189. my $sec = 3600*$d[3] + 60*$d[4] + $d[5]; # Sec now
  190. my @sd = split("[ :-]", "$cv[0] $cv[1]");
  191. my $ssec = 3600*$sd[3] + 60*$sd[4] + $sd[5]; # Sec at start of day
  192. my $difft = $sec - $csec;
  193. $difft += 86400 if($d[2] != $cd[2]); # Sec since last reading
  194. my $t = $cv[3] + $difft * $v[4];
  195. my $h = $cv[5] + $difft * $v[3];
  196. my $w = $cv[7] + $difft * $v[2];
  197. my $e = $cv[9];
  198. $val = "$cv[0] $cv[1] T: $t H: $h W: $w R: $e";
  199. readingsBulkUpdate($def, 'cum_day', $val, 0);
  200. $difft = $sec - $ssec;
  201. $difft += 86400 if($d[2] != $sd[2]); # Sec since last reading
  202. $difft = 1 if(!$difft); # Don't want illegal division.
  203. $t /= $difft; $h /= $difft; $w /= $difft; $e = $v[1] - $cv[9];
  204. $val = sprintf("T: %.1f H: %d W: %.1f R: %.1f", $t, $h, $w, $e);
  205. readingsBulkUpdate($def, 'avg_day', $val, $d[2]!=$sd[2]);
  206. if($d[2] != $sd[2]) { # Day changed
  207. $val = "$tm T: 0 H: 0 W: 0 R: $v[1]";
  208. readingsBulkUpdate($def, 'cum_day', $val, 0);
  209. if(!ReadingsVal($name, 'cum_month', undef)) {
  210. $val = "1 ".ReadingsVal($name, 'avg_day','');
  211. readingsBulkUpdate($def, 'cum_month', $val, 0);
  212. } else {
  213. my @cmv = split(" ", ReadingsVal($name, 'cum_month',''));
  214. $t += $cmv[2]; $w += $cmv[4]; $h += $cmv[6];
  215. $cmv[0]++;
  216. $val = sprintf("%d T: %.1f H: %d W: %.1f R: %.1f",
  217. $cmv[0], $t, $h, $w, $cmv[8]+$e);
  218. readingsBulkUpdate($def, 'cum_month', $val, 0);
  219. $val = sprintf("T: %.1f H: %d W: %.1f R: %.1f",
  220. $t/$cmv[0], $h/$cmv[0], $w/$cmv[0], $cmv[8]+$e);
  221. readingsBulkUpdate($def, 'avg_month', $val, $d[1]!=$sd[1]);
  222. if($d[1] != $sd[1]) { # Month changed, report it
  223. $val = "0 T: 0 H: 0 W: 0 R: 0";
  224. readingsBulkUpdate($def, 'cum_month', $val, 0);
  225. }
  226. }
  227. }
  228. }
  229. # AVG computing
  230. ###################################
  231. readingsEndUpdate($def,1);
  232. return $name;
  233. } else {
  234. Log3 $hash, 4, "KS300 detected: $msg";
  235. return "UNDEFINED KS300 KS300 1234";
  236. }
  237. }
  238. sub
  239. KS300_windIndex($)
  240. {
  241. #
  242. # convert km/h to bft as described by
  243. # http://www.meteotest.ch/wetterprognosen/prognosen_schweiz/windtabelle
  244. #
  245. my ($w) = @_;
  246. return "0" if($w < 1);
  247. return "1" if($w >= 1 && $w < 6);
  248. return "2" if($w >= 6 && $w < 12);
  249. return "3" if($w >= 12 && $w < 20);
  250. return "4" if($w >= 20 && $w < 29);
  251. return "5" if($w >= 29 && $w < 39);
  252. return "6" if($w >= 39 && $w < 50);
  253. return "7" if($w >= 50 && $w < 62);
  254. return "8" if($w >= 62 && $w < 75);
  255. return "9" if($w >= 75 && $w < 89);
  256. return "10" if($w >= 89 && $w < 103);
  257. return "11" if($w >= 103 && $w <= 117);
  258. return "12" if($w > 117);
  259. }
  260. 1;
  261. =pod
  262. =item summary module for the ELV KS300 weather station
  263. =item summary_DE Anbindung der ELV KS300 Wetterstation
  264. =begin html
  265. <a name="KS300"></a>
  266. <h3>KS300</h3>
  267. <ul>
  268. Fhem can receive the KS300 or KS555 radio messages through the <a
  269. href="#FHZ">FHZ</a>, <a href="WS300">WS300</a> or <a href="#CUL">CUL</a>, so
  270. one of them must be defined first.<br> This module services messages received
  271. by the FHZ or CUL.<br> <br>
  272. <a name="KS300define"></a>
  273. <b>Define</b>
  274. <ul>
  275. <code>define &lt;name&gt; KS300 &lt;housecode&gt; [ml/raincounter [wind-factor]]</code>
  276. <br><br>
  277. <code>&lt;housecode&gt;</code> is a four digit hex number, it must be
  278. specified foir historic reasons, and it is ignored.
  279. The ml/raincounter defaults to 255 ml, and it must be specified if you wish
  280. to set the wind factor, which defaults to 1.0. <br>
  281. Examples:
  282. <ul>
  283. <code>define ks1 KS300 1234</code><br>
  284. </ul>
  285. </ul>
  286. <br>
  287. <a name="KS300set"></a>
  288. <b>Set </b>
  289. <ul>
  290. N/A
  291. </ul>
  292. <br>
  293. <a name="KS300get"></a>
  294. <b>Get</b>
  295. <ul>
  296. N/A
  297. </ul>
  298. <br>
  299. <a name="KS300attr"></a>
  300. <b>Attributes</b>
  301. <ul>
  302. <li><a href="#ignore">ignore</a></li>
  303. <li><a href="#IODev">IODev</a></li>
  304. <li><a href="#eventMap">eventMap</a></li><br>
  305. <li><a href="#do_not_notify">do_not_notify</a></li>
  306. <li><a href="#showtime">showtime</a></li>
  307. <li><a href="#model">model</a> (ks300)</li>
  308. <li>rainadjustment<br>
  309. If this attribute is set, fhem automatically considers rain counter
  310. resets after a battery change and random counter switches as
  311. experienced by some users. Default is 0 (off).</li>
  312. </ul>
  313. <br>
  314. </ul>
  315. =end html
  316. =begin html_DE
  317. <a name="KS300"></a>
  318. <h3>KS300</h3>
  319. <ul>
  320. Fhem kann KS300 bzw. KS555 Funktelegramme mit einem <a href="#FHZ">FHZ</a>,
  321. einem <a href="WS300">WS300</a> oder einem <a href="#CUL">CUL</a> empfangen.
  322. Daher muss eines von diesen zuerst definiert sein.<br> Dieses Modul behandelt
  323. Nachrichten die mittels CUL oder FHZ empfangen werden.<br>
  324. <br>
  325. <a name="KS300define"></a>
  326. <b>Define</b>
  327. <ul>
  328. <code>define &lt;name&gt; KS300 &lt;housecode&gt; [ml/raincounter [wind-factor]]</code>
  329. <br><br>
  330. <code>&lt;housecode&gt;</code> ist ein vierstelliger HEX-Wert, der aus
  331. historischen Gr&uuml;nden angegeben werden muss, es wird ignoriert. Der
  332. ml/raincounter hat einen Default-Wert von 255ml, und muss angegeben sein
  333. wenn man den Wind-Faktor setzen will. Dieser hat einen Default-Wert von
  334. 1.0.<br>
  335. Beispiele:
  336. <ul>
  337. <code>define ks1 KS300 1234</code><br>
  338. </ul>
  339. </ul>
  340. <br>
  341. <a name="KS300set"></a>
  342. <b>Set </b>
  343. <ul>
  344. N/A
  345. </ul>
  346. <br>
  347. <a name="KS300get"></a>
  348. <b>Get</b>
  349. <ul>
  350. N/A
  351. </ul>
  352. <br>
  353. <a name="KS300attr"></a>
  354. <b>Attributes</b>
  355. <ul>
  356. <li><a href="#ignore">ignore</a></li>
  357. <li><a href="#IODev">IODev</a></li>
  358. <li><a href="#eventMap">eventMap</a></li><br>
  359. <li><a href="#do_not_notify">do_not_notify</a></li>
  360. <li><a href="#showtime">showtime</a></li>
  361. <li><a href="#model">model</a> (ks300)</li>
  362. <li>rainadjustment<br>
  363. Wenn dieses Attribut gesetzt ist, Regenmesser resets werden automatisch
  364. ber&uuml;cksichtigt. Resets treten beim Wechsel der Batterie und nach
  365. Beobachtung einiger Benutzer auch nach zuf&auml;lligen Schaltzyklen
  366. auf. Die Voreinstellung ist 0 (aus).</li>
  367. </ul>
  368. <br>
  369. </ul>
  370. =end html_DE
  371. =cut