36_Level.pm 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. # $Id: 36_Level.pm 14513 2017-06-15 09:51:23Z HCS $
  2. #
  3. # TODO:
  4. package main;
  5. use strict;
  6. use warnings;
  7. use SetExtensions;
  8. sub Level_Parse($$);
  9. sub
  10. Level_Initialize($)
  11. {
  12. my ($hash) = @_;
  13. $hash->{Match} = "^OK\\sLS\\s";
  14. $hash->{DefFn} = "Level_Define";
  15. $hash->{UndefFn} = "Level_Undef";
  16. $hash->{FingerprintFn} = "Level_Fingerprint";
  17. $hash->{ParseFn} = "Level_Parse";
  18. $hash->{AttrList} = "IODev"
  19. ." ignore:1"
  20. ." doAverage:1"
  21. ." filterThreshold"
  22. ." litersPerCm"
  23. ." distanceToBottom"
  24. ." $readingFnAttributes";
  25. }
  26. sub
  27. Level_Define($$)
  28. {
  29. my ($hash, $def) = @_;
  30. my @a = split("[ \t][ \t]*", $def);
  31. if(@a != 3 ) {
  32. my $msg = "wrong syntax: define <name> Level <addr>";
  33. Log3 undef, 2, $msg;
  34. return $msg;
  35. }
  36. $a[2] =~ m/^([\da-f]{1})$/i;
  37. return "$a[2] is not a valid Level address" if( !defined($1) );
  38. my $name = $a[0];
  39. my $addr = $a[2];
  40. return "Level device $addr already used for $modules{Level}{defptr}{$addr}->{NAME}." if( $modules{Level}{defptr}{$addr}
  41. && $modules{Level}{defptr}{$addr}->{NAME} ne $name );
  42. $hash->{addr} = $addr;
  43. $modules{Level}{defptr}{$addr} = $hash;
  44. AssignIoPort($hash);
  45. if(defined($hash->{IODev}->{NAME})) {
  46. Log3 $name, 3, "$name: I/O device is " . $hash->{IODev}->{NAME};
  47. } else {
  48. Log3 $name, 1, "$name: no I/O device";
  49. }
  50. return undef;
  51. }
  52. #####################################
  53. sub
  54. Level_Undef($$)
  55. {
  56. my ($hash, $arg) = @_;
  57. my $name = $hash->{NAME};
  58. my $addr = $hash->{addr};
  59. delete( $modules{Level}{defptr}{$addr} );
  60. return undef;
  61. }
  62. #####################################
  63. sub
  64. Level_Get($@)
  65. {
  66. my ($hash, $name, $cmd, @args) = @_;
  67. return "\"get $name\" needs at least one parameter" if(@_ < 3);
  68. my $list = "";
  69. return "Unknown argument $cmd, choose one of $list";
  70. }
  71. sub
  72. Level_Fingerprint($$)
  73. {
  74. my ($name, $msg) = @_;
  75. return ( "", $msg );
  76. }
  77. # Format
  78. #
  79. # OK LS 1 0 5 100 4 191 60 = 38,0cm 21,5°C 6,0V
  80. # OK LS 1 0 8 167 4 251 57 = 121,5cm 27,5°C 5,7V
  81. #
  82. # 0 1 2 3 4 5 6
  83. # OK LS ID T LL LL TT TT VV
  84. # | | | | | | | | |
  85. # | | | | | | | | `--- Voltage * 10
  86. # | | | | | | | `------- Temp. * 10 + 1000 LSB
  87. # | | | | | | `----------- Temp. * 10 + 1000 MSB
  88. # | | | | | `--------------- Level * 10 + 1000 LSB
  89. # | | | | `------------------- Level * 10 + 1000 MSB
  90. # | | | `------------------------ Sensor type fix 0 at the moment
  91. # | | `--------------------------- Sensor ID ( 0 .. 15)
  92. # | `------------------------------ fix "LS"
  93. # `--------------------------------- fix "OK"
  94. sub
  95. Level_Parse($$)
  96. {
  97. my ($hash, $msg) = @_;
  98. my $name = $hash->{NAME};
  99. ###$DB::single = 1;
  100. my( @bytes, $addr, $type, $distance, $temperature, $voltage );
  101. if( $msg =~ m/^OK LS/ ) {
  102. @bytes = split( ' ', substr($msg, 5) );
  103. $addr = sprintf( "%01X", $bytes[0] );
  104. $type = $bytes[1];
  105. $distance = ($bytes[2]*256 + $bytes[3] - 1000)/10;
  106. $temperature = ($bytes[4]*256 + $bytes[5] - 1000)/10;
  107. $voltage = $bytes[6] / 10;
  108. } else {
  109. DoTrigger($name, "UNKNOWNCODE $msg");
  110. Log3 $name, 3, "$name: Unknown code $msg, help me!";
  111. return undef;
  112. }
  113. my $raddr = $addr;
  114. my $rhash = $modules{Level}{defptr}{$raddr};
  115. my $rname = $rhash?$rhash->{NAME}:$raddr;
  116. if( !$modules{Level}{defptr}{$raddr} ) {
  117. Log3 $name, 4, "Level: Unknown device $rname, please define it";
  118. return "";
  119. }
  120. my @list;
  121. push(@list, $rname);
  122. $rhash->{Level_lastRcv} = TimeNow();
  123. readingsBeginUpdate($rhash);
  124. my $litersPerCm = AttrVal( $rname, "litersPerCm", 1);
  125. my $distanceToBottom = AttrVal( $rname, "distanceToBottom", 100);
  126. my $level = $distanceToBottom - $distance;
  127. my $liters = $litersPerCm * $level;
  128. if( AttrVal( $rname, "doAverage", 0 ) && defined($rhash->{"previousLiters"}) ) {
  129. $liters = int(($rhash->{"previousLiters"}*3+$liters)/4);
  130. }
  131. if( AttrVal( $rname, "doAverage", 0 ) && defined($rhash->{"previousTemeprature"}) ) {
  132. $temperature = int(($rhash->{"previousTemeprature"}*3+$temperature)/4);
  133. }
  134. readingsBulkUpdate($rhash, "distance", $distance);
  135. readingsBulkUpdate($rhash, "level", $level);
  136. readingsBulkUpdate($rhash, "liters", $liters);
  137. readingsBulkUpdate($rhash, "temperature", $temperature);
  138. readingsBulkUpdate($rhash, "voltage", $voltage);
  139. my $state = "L: $liters";
  140. $state .= " T: $temperature";
  141. $state .= " V: $voltage";
  142. readingsBulkUpdate($rhash, "state", $state) if( Value($rname) ne $state );
  143. readingsEndUpdate($rhash,1);
  144. $rhash->{"previousLiters"} = $liters;
  145. $rhash->{"previousTemperature"} = $temperature;
  146. return @list;
  147. }
  148. sub
  149. Level_Attr(@)
  150. {
  151. my ($cmd, $name, $attrName, $attrVal) = @_;
  152. return undef;
  153. }
  154. 1;
  155. =pod
  156. =item summary IO-Device for the Levelsender.
  157. =item summary_DE IO-Device für den Levelsender.
  158. =begin html
  159. <a name="Level"></a>
  160. <h3>Level</h3>
  161. <ul>
  162. FHEM module for Level.<br><br>
  163. It can be integrated in to FHEM via a <a href="#JeeLink">JeeLink</a> as the IODevice.<br><br>
  164. The JeeNode sketch required for this module can be found in .../contrib/36_LaCrosse-LaCrosseITPlusReader.zip. It must be at least version 10.0c<br><br>
  165. For more information see: http://forum.fhem.de/index.php/topic,23217.msg165163.html#msg165163<br><br>
  166. <a name="Level_Define"></a>
  167. <b>Define</b>
  168. <ul>
  169. <code>define &lt;name&gt; Level &lt;addr&gt;</code> <br>
  170. addr is a 1 digit hex number (0 .. F) to identify the Level device.
  171. <br><br>
  172. </ul>
  173. <a name="Level_Readings"></a>
  174. <b>Readings</b>
  175. <ul>
  176. <li>liters<br>
  177. Calculated liters based on distanceToBottom, distance and litersPerCm</li>
  178. <li>temperature<br>
  179. Measured temperature</li>
  180. <li>voltage<br>
  181. Measured battery voltage</li>
  182. <li>distance<br>
  183. Measured distance from the sensor to the fluid</li>
  184. <li>level<br>
  185. Calculated level based on the distanceToBottom attribute</li>
  186. </ul><br>
  187. <a name="Level_Attr"></a>
  188. <b>Attributes</b>
  189. <ul>
  190. <li>distanceToBottom<br>
  191. Distance from the ultra sonic sensor to the bottom of the tank</li>
  192. <li>litersPerCm<br>
  193. Liters for each cm level</li>
  194. </ul><br>
  195. </ul>
  196. =end html
  197. =cut