36_LaCrosse.pm 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683
  1. # $Id: 36_LaCrosse.pm 16168 2018-02-13 21:01:41Z HCS $
  2. package main;
  3. use strict;
  4. use warnings;
  5. use SetExtensions;
  6. sub LaCrosse_Parse($$);
  7. sub LaCrosse_Initialize($) {
  8. my ($hash) = @_;
  9. $hash->{Match} = "^(\\S+\\s+9 | OK\\sWS\\s)";
  10. $hash->{SetFn} = "LaCrosse_Set";
  11. ###$hash->{GetFn} = "LaCrosse_Get";
  12. $hash->{DefFn} = "LaCrosse_Define";
  13. $hash->{UndefFn} = "LaCrosse_Undef";
  14. $hash->{FingerprintFn} = "LaCrosse_Fingerprint";
  15. $hash->{ParseFn} = "LaCrosse_Parse";
  16. ###$hash->{AttrFn} = "LaCrosse_Attr";
  17. $hash->{AttrList} = "IODev"
  18. ." ignore:1,0"
  19. ." doAverage:1,0"
  20. ." doDewpoint:1,0"
  21. ." filterThreshold"
  22. ." resolution"
  23. ." $readingFnAttributes";
  24. $hash->{AutoCreate} = { "LaCrosse.*" => { autocreateThreshold => "2:120", FILTER => "%NAME" }};
  25. }
  26. sub LaCrosse_Define($$) {
  27. my ($hash, $def) = @_;
  28. my @a = split("[ \t][ \t]*", $def);
  29. if(int(@a) < 3 || int(@a) > 5) {
  30. my $msg = "wrong syntax: define <name> LaCrosse <addr> [corr1...corr2]";
  31. Log3 undef, 2, $msg;
  32. return $msg;
  33. }
  34. $a[2] =~ m/^([\da-f]{2})$/i;
  35. return "$a[2] is not a valid LaCrosse address" if( !defined($1) );
  36. my $name = $a[0];
  37. my $addr = $a[2];
  38. $hash->{corr1} = ((int(@a) > 3) ? $a[3] : 0);
  39. $hash->{corr2} = ((int(@a) > 4) ? $a[4] : 0);
  40. return "LaCrosse device $addr already used for $modules{LaCrosse}{defptr}{$addr}->{NAME}." if( $modules{LaCrosse}{defptr}{$addr} && $modules{LaCrosse}{defptr}{$addr}->{NAME} ne $name );
  41. $hash->{addr} = $addr;
  42. $modules{LaCrosse}{defptr}{$addr} = $hash;
  43. AssignIoPort($hash);
  44. if(defined($hash->{IODev}->{NAME})) {
  45. Log3 $name, 3, "$name: I/O device is " . $hash->{IODev}->{NAME};
  46. }
  47. else {
  48. Log3 $name, 1, "$name: no I/O device";
  49. }
  50. return undef;
  51. }
  52. #-----------------------------------#
  53. sub LaCrosse_Undef($$) {
  54. my ($hash, $arg) = @_;
  55. my $name = $hash->{NAME};
  56. my $addr = $hash->{addr};
  57. delete( $modules{LaCrosse}{defptr}{$addr} );
  58. return undef;
  59. }
  60. #-----------------------------------#
  61. sub LaCrosse_Get($@) {
  62. my ($hash, $name, $cmd, @args) = @_;
  63. return "\"get $name\" needs at least one parameter" if(@_ < 3);
  64. my $list = "";
  65. return "Unknown argument $cmd, choose one of $list";
  66. }
  67. #-----------------------------------#
  68. sub LaCrosse_Attr(@) {
  69. my ($cmd, $name, $attrName, $attrVal) = @_;
  70. return undef;
  71. }
  72. #-----------------------------------#
  73. sub LaCrosse_Fingerprint($$) {
  74. my ($name, $msg) = @_;
  75. return ( "", $msg );
  76. }
  77. #-----------------------------------#
  78. sub LaCrosse_CalcDewpoint (@) {
  79. my ($temp,$hum) = @_;
  80. my($SDD, $DD, $a, $b, $v, $DP);
  81. if($temp>=0) {
  82. $a = 7.5;
  83. $b = 237.3;
  84. }
  85. else {
  86. $a = 7.6;
  87. $b = 240.7;
  88. }
  89. $SDD = 6.1078*10**(($a*$temp)/($b+$temp));
  90. $DD = $hum/100 * $SDD;
  91. $v = log($DD/6.1078)/log(10);
  92. $DP = ($b*$v)/($a-$v);
  93. return $DP;
  94. }
  95. #-----------------------------------#
  96. sub LaCrosse_RemoveReplaceBattery($) {
  97. my $hash = shift;
  98. delete($hash->{replaceBattery});
  99. }
  100. sub LaCrosse_Set($@) {
  101. my ($hash, $name, $cmd, $arg, $arg2) = @_;
  102. my $list = "replaceBatteryForSec";
  103. if( $cmd eq "replaceBatteryForSec" ) {
  104. foreach my $d (sort keys %defs) {
  105. next if (!defined($defs{$d}) );
  106. next if ($defs{$d}->{TYPE} ne "LaCrosse" );
  107. LaCrosse_RemoveReplaceBattery{$defs{$d}};
  108. }
  109. return "Usage: set $name replaceBatteryForSec <seconds_active> [ignore_battery]" if(!$arg || $arg !~ m/^\d+$/ || ($arg2 && $arg2 ne "ignore_battery"));
  110. $hash->{replaceBattery} = $arg2?2:1;
  111. InternalTimer(gettimeofday()+$arg, "LaCrosse_RemoveReplaceBattery", $hash, 0);
  112. }
  113. else {
  114. return "Unknown argument $cmd, choose one of ".$list;
  115. }
  116. return undef;
  117. }
  118. #-----------------------------------#
  119. sub LaCrosse_Parse($$) {
  120. my ($hash, $msg) = @_;
  121. my $name = $hash->{NAME};
  122. my( @bytes, $addr, $typeNumber, $typeName, $battery_new, $battery_low, $error, $type, $channel, $temperature, $humidity, $windDirection, $windSpeed, $windGust, $rain, $pressure, $gas1, $gas2, $lux, $version, $voltage, $debug );
  123. $temperature = 0xFFFF;
  124. $humidity = 0xFF;
  125. $windDirection = 0xFFFF;
  126. $windSpeed = 0xFFFF;
  127. $windGust = 0xFFFF;
  128. $rain = 0xFFFF;
  129. $pressure = 0xFFFF;
  130. $gas1 = 0xFFFFFF;
  131. $gas2 = 0xFFFFFF;
  132. $lux = 0xFFFFFF;
  133. $version = 0xFF;
  134. $voltage = 0xFF;
  135. $debug = 0xFFFFFF;
  136. $error = 0;
  137. if( $msg =~ m/^OK 9/ ) {
  138. # Temperature sensor - Format:
  139. # 0 1 2 3 4
  140. # -------------------------
  141. # OK 9 56 1 4 156 37 ID = 56 T: 18.0 H: 37 no NewBatt
  142. # OK 9 49 1 4 182 54 ID = 49 T: 20.6 H: 54 no NewBatt
  143. # OK 9 55 129 4 192 56 ID = 55 T: 21.6 H: 56 WITH NewBatt
  144. # OK 9 2 1 4 212 106 ID = 2 T: 23.6 H: -- Channel: 1
  145. # OK 9 2 130 4 225 125 ID = 2 T: 24.9 H: -- Channel: 2
  146. # OK 9 ID XXX XXX XXX XXX
  147. # | | | | | | |
  148. # | | | | | | --- Humidity incl. WeakBatteryFlag
  149. # | | | | | |------ Temp * 10 + 1000 LSB
  150. # | | | | |---------- Temp * 10 + 1000 MSB
  151. # | | | |-------------- Sensor type (1 or 2) +128 if NewBatteryFlag
  152. # | | |----------------- Sensor ID
  153. # | |------------------- fix "9"
  154. # |---------------------- fix "OK"
  155. @bytes = split( ' ', substr($msg, 5) );
  156. return "" if(@bytes != 5);
  157. $addr = sprintf( "%02X", $bytes[0] );
  158. $battery_new = ($bytes[1] & 0x80) >> 7;
  159. $battery_low = ($bytes[4] & 0x80) >> 7;
  160. $typeNumber = 0;
  161. $typeName = "T(H)";
  162. $type = ($bytes[1] & 0x70) >> 4;
  163. $channel = $bytes[1] & 0x0F;
  164. $temperature = ($bytes[2]*256 + $bytes[3] - 1000)/10;
  165. $humidity = $bytes[4] & 0x7f;
  166. }
  167. elsif ($msg =~ m/^OK WS/) {
  168. # Weather station - Format:
  169. # 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
  170. # -------------------------------------------------------------------
  171. # OK WS 14 1 4 208 53 0 0 7 8 0 29 0 31 1 4 1 I D=0E 23.2°C 52%rH 0mm Dir.: 180.0° Wind:2.9m/s Gust:3.1m/s new Batt. 1025 hPa
  172. # OK WS ID XXX TTT TTT HHH RRR RRR DDD DDD SSS SSS GGG GGG FFF PPP PPP
  173. # | | | | | | | | | | | | | | | | | |-- Pressure LSB
  174. # | | | | | | | | | | | | | | | | |------ Pressure MSB
  175. # | | | | | | | | | | | | | | | |---------- Flags *
  176. # | | | | | | | | | | | | | | |-------------- WindGust * 10 LSB (0.0 ... 50.0 m/s) FF/FF = none
  177. # | | | | | | | | | | | | | |------------------ WindGust * 10 MSB
  178. # | | | | | | | | | | | | |---------------------- WindSpeed * 10 LSB(0.0 ... 50.0 m/s) FF/FF = none
  179. # | | | | | | | | | | | |-------------------------- WindSpeed * 10 MSB
  180. # | | | | | | | | | | |------------------------------ WindDirection * 10 LSB (0.0 ... 365.0 Degrees) FF/FF = none
  181. # | | | | | | | | | |---------------------------------- WindDirection * 10 MSB
  182. # | | | | | | | | |-------------------------------------- Rain * 0.5mm LSB (0 ... 9999 mm) FF/FF = none
  183. # | | | | | | | |------------------------------------------ Rain * 0.5mm MSB
  184. # | | | | | | |---------------------------------------------- Humidity (1 ... 99 %rH) FF = none
  185. # | | | | | |-------------------------------------------------- Temp * 10 + 1000 LSB (-40 ... +60 °C) FF/FF = none
  186. # | | | | |------------------------------------------------------ Temp * 10 + 1000 MSB
  187. # | | | |---------------------------------------------------------- Sensor type (1=TX22, 2=NodeSensor)
  188. # | | |-------------------------------------------------------------- Sensor ID (0 ... 63)
  189. # | |----------------------------------------------------------------- fix "WS"
  190. # |-------------------------------------------------------------------- fix "OK"
  191. #
  192. # * Flags: 128 64 32 16 8 4 2 1
  193. # | | |
  194. # | | |-- New battery
  195. # | |------ ERROR
  196. # |---------- Low battery
  197. @bytes = split( ' ', substr($msg, 5) );
  198. return "" if(@bytes < 14);
  199. $addr = sprintf( "%02X", $bytes[0] );
  200. $typeNumber = $bytes[1];
  201. if($typeNumber == 1) {
  202. $typeName = "TX22";
  203. }
  204. elsif($typeNumber == 2) {
  205. $typeName = "NodeSensor";
  206. }
  207. elsif($typeNumber == 3) {
  208. $typeName = "WS1080";
  209. }
  210. elsif($typeNumber == 4) {
  211. $typeName = "LaCrosseGateway";
  212. }
  213. elsif($typeNumber == 5) {
  214. $typeName = "UniversalSensor";
  215. }
  216. else {
  217. $typeName = "unknown";
  218. }
  219. $battery_new = $bytes[13] & 0x01;
  220. $battery_low = $bytes[13] & 0x04;
  221. $error = $bytes[13] & 0x02;
  222. $type = 0;
  223. $channel = 1;
  224. my $rh = $modules{LaCrosse}{defptr}{$addr};
  225. if($bytes[2] != 0xFF) {
  226. $temperature = ($bytes[2]*256 + $bytes[3] - 1000)/10;
  227. $rh->{"bufferedT"} = $temperature;
  228. }
  229. else {
  230. if(defined($rh->{"bufferedT"})) {
  231. $temperature = $rh->{"bufferedT"};
  232. }
  233. }
  234. if($bytes[4] != 0xFF) {
  235. $humidity = $bytes[4];
  236. if (defined($rh)) {
  237. $rh->{"bufferedH"} = $humidity;
  238. }
  239. }
  240. else {
  241. if(defined($rh->{"bufferedH"})) {
  242. $humidity = $rh->{"bufferedH"};
  243. }
  244. }
  245. if($bytes[5] != 0xFF) {
  246. $rain = ($bytes[5]*256 + $bytes[6]) * 0.5;
  247. }
  248. if($bytes[7] != 0xFF) {
  249. $windDirection = ($bytes[7]*256 + $bytes[8]) / 10;
  250. }
  251. if($bytes[9] != 0xFF) {
  252. $windSpeed = ($bytes[9] * 256 + $bytes[10]) / 10;
  253. }
  254. if($bytes[11] != 0xFF) {
  255. $windGust = ($bytes[11] * 256 + $bytes[12]) / 10;
  256. }
  257. if(@bytes > 15 && $bytes[14] != 0xFF) {
  258. $pressure = $bytes[14] * 256 + $bytes[15];
  259. $pressure /= 10.0 if $pressure > 5000;
  260. }
  261. if(@bytes > 18 && $bytes[16] != 0xFF) {
  262. $gas1 = $bytes[16] * 65536 + $bytes[17] * 256 + $bytes[18];
  263. }
  264. if(@bytes > 21 && $bytes[19] != 0xFF) {
  265. $gas2 = $bytes[19] * 65536 + $bytes[20] * 256 + $bytes[21];
  266. }
  267. if(@bytes > 24 && $bytes[22] != 0xFF) {
  268. $lux = $bytes[22] * 65536 + $bytes[23] * 256 + $bytes[24];
  269. }
  270. if(@bytes > 25 && $bytes[25] != 0xFF) {
  271. $version = $bytes[25] / 10;
  272. }
  273. if(@bytes > 26 && $bytes[26] != 0xFF) {
  274. $voltage = $bytes[26] / 10;
  275. }
  276. if(@bytes > 29 && $bytes[27] != 0xFF) {
  277. $debug = $bytes[27] * 65536 + $bytes[28] * 256 + $bytes[29];
  278. }
  279. }
  280. else {
  281. DoTrigger($name, "UNKNOWNCODE $msg");
  282. Log3 $name, 3, "$name: Unknown code $msg, help me!";
  283. return "";
  284. }
  285. my $raddr = $addr;
  286. my $rhash = $modules{LaCrosse}{defptr}{$raddr};
  287. my $rname = $rhash?$rhash->{NAME}:$raddr;
  288. return "" if( IsIgnored($rname) );
  289. if( !$modules{LaCrosse}{defptr}{$raddr} ) {
  290. foreach my $d (sort keys %defs) {
  291. next if( !defined($defs{$d}) );
  292. next if( !defined($defs{$d}->{TYPE}) );
  293. next if( $defs{$d}->{TYPE} ne "LaCrosse" );
  294. next if( !$defs{$d}->{replaceBattery} );
  295. if( $battery_new || $defs{$d}->{replaceBattery} == 2 ) {
  296. $rhash = $defs{$d};
  297. $raddr = $rhash->{addr};
  298. Log3 $name, 3, "LaCrosse: Changing device $rname from $raddr to $addr";
  299. delete $modules{LaCrosse}{defptr}{$raddr};
  300. $rhash->{DEF} = $addr;
  301. $rhash->{addr} = $addr;
  302. $modules{LaCrosse}{defptr}{$addr} = $rhash;
  303. LaCrosse_RemoveReplaceBattery($rhash);
  304. CommandSave(undef,undef) if( AttrVal( "autocreate", "autosave", 1 ) );
  305. return "";
  306. }
  307. }
  308. # get info about autocreate
  309. my $autoCreateState = 0;
  310. my $laCrosseInIgnoreTypes = 0;
  311. foreach my $d (keys %defs) {
  312. next if($defs{$d}{TYPE} ne "autocreate");
  313. $autoCreateState = 1;
  314. $autoCreateState = 2 if(!AttrVal($defs{$d}{NAME}, "disable", undef));
  315. my $it = AttrVal($defs{$d}{NAME}, "ignoreTypes", "");
  316. if("LaCrosse" =~ m/$it/i) {
  317. $laCrosseInIgnoreTypes = 1
  318. }
  319. }
  320. # $autoCreateState
  321. # ----------------
  322. # 0 = autoreate not defined
  323. # 1 = autocreate defined
  324. # 2 = autocreate active
  325. #$laCrosseInIgnoreTypes
  326. #----------------------
  327. # 0 = no
  328. # 1 = yes
  329. # decide how to log
  330. my $loglevel = 4;
  331. if($autoCreateState < 2 && $laCrosseInIgnoreTypes == 0) {
  332. $loglevel = 3;
  333. }
  334. Log3 $name, $loglevel, "LaCrosse: Unknown device $rname, please define it";
  335. Log3 $name, $loglevel, "LaCrosse: check commandref on usage of LaCrossePairForSec" if( !$hash->{LaCrossePair} && !defined($modules{LaCrosse}{defptr}) );
  336. return "" if( !$hash->{LaCrossePair} );
  337. return "UNDEFINED LaCrosse_$rname LaCrosse $raddr" if( $battery_new || $hash->{LaCrossePair} == 2 );
  338. return "";
  339. }
  340. $rhash->{battery_new} = $battery_new;
  341. my @list;
  342. push(@list, $rname);
  343. $rhash->{LaCrosse_lastRcv} = TimeNow();
  344. $rhash->{"sensorType"} = "$typeNumber=$typeName";
  345. if( $type == 0x00) {
  346. $channel = "" if( $channel == 1 );
  347. # Correction
  348. $temperature += $rhash->{corr1};
  349. $humidity += $rhash->{corr2};
  350. my $previousT = $temperature;
  351. my $previousH = $humidity;
  352. # Check filterThreshold
  353. if(!defined($rhash->{"previousT$channel"})
  354. || (defined($rhash->{"previousT$channel"})
  355. && abs($rhash->{"previousH$channel"} - $humidity) <= AttrVal( $rname, "filterThreshold", 10 )
  356. && abs($rhash->{"previousT$channel"} - $temperature) <= AttrVal( $rname, "filterThreshold", 10 ) )) {
  357. # remove unwanted Battery2 readings
  358. if (defined ($rhash->{READINGS}{battery2})) {
  359. delete $rhash->{READINGS}{battery2}
  360. }
  361. # Calculate average
  362. if (AttrVal( $rname, "doAverage", 0 ) && defined($rhash->{"previousT$channel"}) && $temperature != 0xFFFF) {
  363. $temperature = ($rhash->{"previousT$channel"} * 3 + $temperature) / 4;
  364. }
  365. if (AttrVal( $rname, "doAverage", 0 ) && defined($rhash->{"previousH$channel"}) && $humidity != 0xFF) {
  366. $humidity = ($rhash->{"previousH$channel"} * 3 + $humidity) / 4;
  367. }
  368. # Calculate dew point
  369. my $dewpoint = undef;
  370. if( AttrVal( $rname, "doDewpoint", 0 ) && $humidity && $humidity <= 99 && $temperature != 0xFFFF ) {
  371. $dewpoint = LaCrosse_CalcDewpoint($temperature, $humidity);
  372. }
  373. # Handle resolution
  374. my $resolution = AttrVal( $rname, "resolution", 1);
  375. if ($temperature != 0xFFFF) {
  376. $temperature = int($temperature * 10 / $resolution + ($temperature < 0 ? -0.5 : 0.5)) * $resolution / 10
  377. }
  378. if ($humidity != 0xFF) {
  379. $humidity = int($humidity * 10 / $resolution + ($humidity < 0 ? -0.5 : 0.5)) * $resolution / 10
  380. }
  381. if ($dewpoint) {
  382. $dewpoint = int($dewpoint * 10 / $resolution + ($dewpoint < 0 ? -0.5 : 0.5)) * $resolution / 10
  383. }
  384. readingsBeginUpdate($rhash);
  385. if ($typeNumber > 0) {
  386. readingsBulkUpdate($rhash, "error", $error ? "1" : "0");
  387. }
  388. readingsBulkUpdate($rhash, "battery", $battery_low ? "low" : "ok");
  389. # write temperature, humidity, ...
  390. if ($temperature != 0xFFFF) {
  391. readingsBulkUpdate($rhash, "temperature$channel", $temperature);
  392. }
  393. if ($humidity && $humidity <= 100) {
  394. readingsBulkUpdate($rhash, "humidity$channel", $humidity);
  395. }
  396. if ($dewpoint) {
  397. readingsBulkUpdate($rhash, "dewpoint$channel", $dewpoint);
  398. }
  399. # STATE
  400. if( !$channel ) {
  401. my $state = "T: ". $temperature;
  402. $state .= " H: ". ($humidity) if( $humidity && $humidity <= 99 );
  403. $state .= " D: $dewpoint" if( $dewpoint );
  404. readingsBulkUpdate($rhash, "state", $state) if( Value($rname) ne $state );
  405. }
  406. readingsEndUpdate($rhash,1);
  407. }
  408. else {
  409. $rhash->{"bufferedT"} = undef;
  410. $rhash->{"bufferedH"} = undef;
  411. }
  412. $rhash->{"previousT$channel"} = int($previousT*10 + 0.5) / 10;
  413. $rhash->{"previousH$channel"} = int($previousH*10 + 0.5) / 10;
  414. readingsBeginUpdate($rhash);
  415. if ($typeNumber > 0 && $windSpeed != 0xFFFF) {
  416. readingsBulkUpdate($rhash, "windSpeed", $windSpeed );
  417. }
  418. if ($typeNumber > 0 && $windGust != 0xFFFF) {
  419. readingsBulkUpdate($rhash, "windGust", $windGust );
  420. }
  421. if ($typeNumber > 0 && $rain != 0xFFFF) {
  422. if(!defined($rhash->{"previousR"}) || (defined($rhash->{"previousR"}) && abs($rhash->{"previousR"} - $rain) <= AttrVal( $rname, "filterThreshold", 10 ))){
  423. readingsBulkUpdate($rhash, "rain", $rain );
  424. }
  425. $rhash->{"previousR"} = $rain;
  426. }
  427. if ($typeNumber > 0 && $windDirection != 0xFFFF) {
  428. readingsBulkUpdate($rhash, "windDirectionDegree", $windDirection );
  429. my $windDirectionText = "---";
  430. if ($windDirection >= 0 && $windDirection <= 11.2) { $windDirectionText = "N"; }
  431. elsif ($windDirection > 11.2 && $windDirection <= 33.7) { $windDirectionText = "NNE"; }
  432. elsif ($windDirection > 33.7 && $windDirection <= 56.2) { $windDirectionText = "NE"; }
  433. elsif ($windDirection > 56.2 && $windDirection <= 78.7) { $windDirectionText = "ENE"; }
  434. elsif ($windDirection > 78.7 && $windDirection <= 101.2) { $windDirectionText = "E"; }
  435. elsif ($windDirection > 101.2 && $windDirection <= 123.7) { $windDirectionText = "ESE"; }
  436. elsif ($windDirection > 123.7 && $windDirection <= 146.2) { $windDirectionText = "SE"; }
  437. elsif ($windDirection > 146.2 && $windDirection <= 168.7) { $windDirectionText = "SSE"; }
  438. elsif ($windDirection > 168.7 && $windDirection <= 191.2) { $windDirectionText = "S"; }
  439. elsif ($windDirection > 191.2 && $windDirection <= 213.7) { $windDirectionText = "SSW"; }
  440. elsif ($windDirection > 213.7 && $windDirection <= 236.2) { $windDirectionText = "SW"; }
  441. elsif ($windDirection > 236.2 && $windDirection <= 258.7) { $windDirectionText = "WSW"; }
  442. elsif ($windDirection > 258.7 && $windDirection <= 281.2) { $windDirectionText = "W"; }
  443. elsif ($windDirection > 281.2 && $windDirection <= 303.7) { $windDirectionText = "WNW"; }
  444. elsif ($windDirection > 303.7 && $windDirection <= 326.2) { $windDirectionText = "NW"; }
  445. elsif ($windDirection > 326.2 && $windDirection <= 348.7) { $windDirectionText = "NNW"; };
  446. readingsBulkUpdate($rhash, "windDirectionText", $windDirectionText );
  447. }
  448. if ($typeNumber > 1 && $pressure != 0xFFFF) {
  449. readingsBulkUpdate($rhash, "pressure", $pressure );
  450. }
  451. if ($typeNumber > 1 && $gas1 != 0xFFFFFF) {
  452. readingsBulkUpdate($rhash, "gas1", $gas1 );
  453. }
  454. if ($typeNumber > 1 && $gas2 != 0xFFFFFF) {
  455. readingsBulkUpdate($rhash, "gas2", $gas2 );
  456. }
  457. if ($typeNumber == 5 && $lux != 0xFFFFFF) {
  458. readingsBulkUpdate($rhash, "lux", $lux );
  459. }
  460. if ($typeNumber == 5 && $version != 0xFF) {
  461. readingsBulkUpdate($rhash, "version", $version );
  462. }
  463. if ($typeNumber = 5 && $voltage != 0xFF) {
  464. readingsBulkUpdate($rhash, "voltage", $voltage );
  465. }
  466. if ($typeNumber > 1 && $debug != 0xFFFFFF) {
  467. readingsBulkUpdate($rhash, "debug", $debug );
  468. }
  469. readingsEndUpdate($rhash,1);
  470. }
  471. return @list;
  472. }
  473. 1;
  474. =pod
  475. =item summary LaCrosse Temperature and Humidity sensors
  476. =item summary_DE LaCrosse Temperature und Luftfeuchtigkeitssensoren
  477. =begin html
  478. <a name="LaCrosse"></a>
  479. <h3>LaCrosse</h3>
  480. <ul>
  481. FHEM module for LaCrosse Temperature and Humidity sensors and weather stations like WS 1600 (TX22 sensor).<br><br>
  482. It can be integrated in to FHEM via a <a href="#JeeLink">JeeLink</a> as the IODevice.<br><br>
  483. The JeeNode sketch required for this module can be found in .../contrib/36_LaCrosse-pcaSerial.zip.<br><br>
  484. <a name="LaCrosseDefine"></a>
  485. <b>Define</b>
  486. <ul>
  487. <code>define &lt;name&gt; LaCrosse &lt;addr&gt; [corr1...corr2]</code> <br>
  488. <br>
  489. addr is a 2 digit hex number to identify the LaCrosse device.<br>
  490. corr1..corr2 are up to 2 numerical correction factors (corr1 for the temperature and corr2 for the humidity), which will be added to the respective value to calibrate the device.<br><br>
  491. Note: devices are autocreated only if LaCrossePairForSec is active for the <a href="#JeeLink">JeeLink</a> IODevice device.<br>
  492. </ul>
  493. <br>
  494. <a name="LaCrosse_Set"></a>
  495. <b>Set</b>
  496. <ul>
  497. <li>replaceBatteryForSec &lt;sec&gt; [ignore_battery]<br>
  498. sets the device for &lt;sec&gt; seconds into replace battery mode. the first unknown address that is
  499. received will replace the current device address. this can be partly automated with a readings group configured
  500. to show the battery state of all LaCrosse devices and a link/command to set replaceBatteryForSec on klick.
  501. </li>
  502. </ul><br>
  503. <a name="LaCrosse_Get"></a>
  504. <b>Get</b>
  505. <ul>
  506. </ul><br>
  507. <a name="LaCrosse_Readings"></a>
  508. <b>Readings</b>
  509. <ul>
  510. <li>battery[]<br>
  511. ok or low</li>
  512. <li>temperature (°C)<br>
  513. Notice: see the filterThreshold attribute.</li>
  514. <li>humidity (%rH)</li>
  515. <li>Wind speed (m/s), gust (m/s) and direction (degree)</li>
  516. <li>Rain (mm)</li>
  517. </ul><br>
  518. <a name="LaCrosse_Attr"></a>
  519. <b>Attributes</b>
  520. <ul>
  521. <li>doAverage<br>
  522. use an average of the last 4 values for temperature and humidity readings</li>
  523. <li>doDewpoint<br>
  524. calculate dewpoint</li>
  525. <li>filterThreshold<br>
  526. if the difference between the current and previous temperature is greater than filterThreshold degrees
  527. the readings for this channel are not updated. the default is 10.</li>
  528. <li>resolution<br>
  529. the resolution in 1/10 degree for the temperature reading</li>
  530. <li>ignore<br>
  531. 1 -> ignore this device.</li>
  532. </ul><br>
  533. <b>Logging and autocreate</b><br>
  534. <ul>
  535. <li>If autocreate is not active (not defined or disabled) and LaCrosse is not contained in the ignoreTypes attribute of autocreate then
  536. the <i>Unknown device xx, please define it</i> messages will be logged with loglevel 3. In all other cases they will be logged with loglevel 4. </li>
  537. <li>The autocreateThreshold attribute of the autocreate module (see <a href="#autocreate">autocreate</a>) is respected. The default is 2:120, means, that
  538. autocreate will create a device for a sensor only, if the sensor was received at least two times within two minutes.</li>
  539. </ul>
  540. </ul>
  541. =end html
  542. =cut