52_I2C_SHT21.pm 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  1. ##############################################
  2. # $Id: 52_I2C_SHT21.pm 12059 2016-08-22 21:14:59Z klauswitt $
  3. package main;
  4. use strict;
  5. use warnings;
  6. use constant {
  7. SHT21_I2C_ADDRESS => '0x40',
  8. };
  9. ##################################################
  10. # Forward declarations
  11. #
  12. sub I2C_SHT21_Initialize($);
  13. sub I2C_SHT21_Define($$);
  14. sub I2C_SHT21_Attr(@);
  15. sub I2C_SHT21_Poll($);
  16. sub I2C_SHT21_Set($@);
  17. sub I2C_SHT21_Undef($$);
  18. sub I2C_SHT21_DbLog_splitFn($);
  19. my %sets = (
  20. 'readValues' => 1,
  21. );
  22. sub I2C_SHT21_Initialize($) {
  23. my ($hash) = @_;
  24. $hash->{DefFn} = 'I2C_SHT21_Define';
  25. $hash->{InitFn} = 'I2C_SHT21_Init';
  26. $hash->{AttrFn} = 'I2C_SHT21_Attr';
  27. $hash->{SetFn} = 'I2C_SHT21_Set';
  28. $hash->{UndefFn} = 'I2C_SHT21_Undef';
  29. $hash->{I2CRecFn} = 'I2C_SHT21_I2CRec';
  30. $hash->{AttrList} = 'IODev do_not_notify:0,1 showtime:0,1 poll_interval:1,2,5,10,20,30 ' .
  31. 'roundHumidityDecimal:0,1,2 roundTemperatureDecimal:0,1,2 ' .
  32. $readingFnAttributes;
  33. $hash->{DbLog_splitFn} = "I2C_SHT21_DbLog_splitFn";
  34. }
  35. sub I2C_SHT21_Define($$) {
  36. my ($hash, $def) = @_;
  37. my @a = split('[ \t][ \t]*', $def);
  38. $hash->{STATE} = "defined";
  39. if ($main::init_done) {
  40. eval { I2C_SHT21_Init( $hash, [ @a[ 2 .. scalar(@a) - 1 ] ] ); };
  41. return I2C_SHT21_Catch($@) if $@;
  42. }
  43. return undef;
  44. }
  45. sub I2C_SHT21_Init($$) {
  46. my ( $hash, $args ) = @_;
  47. my $name = $hash->{NAME};
  48. if (defined $args && int(@$args) > 1)
  49. {
  50. return "Define: Wrong syntax. Usage:\n" .
  51. "define <name> I2C_SHT21 [<i2caddress>]";
  52. }
  53. if (defined (my $address = shift @$args)) {
  54. $hash->{I2C_Address} = $address =~ /^0.*$/ ? oct($address) : $address;
  55. return "$name I2C Address not valid" unless ($address < 128 && $address > 3);
  56. } else {
  57. $hash->{I2C_Address} = hex(SHT21_I2C_ADDRESS);
  58. }
  59. my $msg = '';
  60. # create default attributes
  61. if (AttrVal($name, 'poll_interval', '?') eq '?') {
  62. $msg = CommandAttr(undef, $name . ' poll_interval 5');
  63. if ($msg) {
  64. Log3 ($hash, 1, $msg);
  65. return $msg;
  66. }
  67. }
  68. AssignIoPort($hash);
  69. $hash->{STATE} = 'Initialized';
  70. # my %sendpackage = ( i2caddress => $hash->{I2C_Address}, direction => "i2cread" );
  71. # $sendpackage{reg} = hex("AA");
  72. # $sendpackage{nbyte} = 22;
  73. # return "$name: no IO device defined" unless ($hash->{IODev});
  74. # my $phash = $hash->{IODev};
  75. # my $pname = $phash->{NAME};
  76. # CallFn($pname, "I2CWrtFn", $phash, \%sendpackage);
  77. return undef;
  78. }
  79. sub I2C_SHT21_Catch($) {
  80. my $exception = shift;
  81. if ($exception) {
  82. $exception =~ /^(.*)( at.*FHEM.*)$/;
  83. return $1;
  84. }
  85. return undef;
  86. }
  87. sub I2C_SHT21_Attr (@) {# hier noch Werteueberpruefung einfuegen
  88. my ($command, $name, $attr, $val) = @_;
  89. my $hash = $defs{$name};
  90. my $msg = '';
  91. if ($command && $command eq "set" && $attr && $attr eq "IODev") {
  92. eval {
  93. if ($main::init_done and (!defined ($hash->{IODev}) or $hash->{IODev}->{NAME} ne $val)) {
  94. main::AssignIoPort($hash,$val);
  95. my @def = split (' ',$hash->{DEF});
  96. I2C_SHT21_Init($hash,\@def) if (defined ($hash->{IODev}));
  97. }
  98. };
  99. return I2C_SHT21_Catch($@) if $@;
  100. }
  101. if ($attr eq 'poll_interval') {
  102. if ($val > 0) {
  103. RemoveInternalTimer($hash);
  104. InternalTimer(gettimeofday() + 5, 'I2C_SHT21_Poll', $hash, 0);
  105. } else {
  106. $msg = 'Wrong poll intervall defined. poll_interval must be a number > 0';
  107. }
  108. } elsif ($attr eq 'roundHumidityDecimal') {
  109. $msg = 'Wrong $attr defined. Use one of 0, 1, 2' if defined($val) && $val <= 0 && $val >= 2 ;
  110. } elsif ($attr eq 'roundTemperatureDecimal') {
  111. $msg = 'Wrong $attr defined. Use one of 0, 1, 2' if defined($val) && $val <= 0 && $val >= 2 ;
  112. }
  113. return ($msg) ? $msg : undef;
  114. }
  115. sub I2C_SHT21_Poll($) {
  116. my ($hash) = @_;
  117. my $name = $hash->{NAME};
  118. # Read values
  119. I2C_SHT21_Set($hash, ($name, 'readValues'));
  120. my $pollInterval = AttrVal($hash->{NAME}, 'poll_interval', 0);
  121. if ($pollInterval > 0) {
  122. InternalTimer(gettimeofday() + ($pollInterval * 60), 'I2C_SHT21_Poll', $hash, 0);
  123. }
  124. }
  125. sub I2C_SHT21_Set($@) {
  126. my ($hash, @a) = @_;
  127. my $name = $a[0];
  128. my $cmd = $a[1];
  129. if(!defined($sets{$cmd})) {
  130. return 'Unknown argument ' . $cmd . ', choose one of ' . join(' ', keys %sets)
  131. }
  132. if ($cmd eq 'readValues') {
  133. I2C_SHT21_triggerTemperature($hash);
  134. }
  135. }
  136. sub I2C_SHT21_Undef($$) {
  137. my ($hash, $arg) = @_;
  138. RemoveInternalTimer($hash);
  139. return undef;
  140. }
  141. sub I2C_SHT21_I2CRec ($$) {
  142. my ($hash, $clientmsg) = @_;
  143. my $name = $hash->{NAME};
  144. my $phash = $hash->{IODev};
  145. my $pname = $phash->{NAME};
  146. while ( my ( $k, $v ) = each %$clientmsg ) { #erzeugen von Internals fuer alle Keys in $clientmsg die mit dem physical Namen beginnen
  147. $hash->{$k} = $v if $k =~ /^$pname/ ;
  148. }
  149. # Bit 1 of the two LSBs indicates the measurement type (‘0’: temperature, ‘1’ humidity)
  150. if ( $clientmsg->{direction} && $clientmsg->{$pname . "_SENDSTAT"} && $clientmsg->{$pname . "_SENDSTAT"} eq "Ok" ) {
  151. if ( $clientmsg->{direction} eq "i2cread" && defined($clientmsg->{received}) ) {
  152. Log3 $hash, 5, "empfangen: $clientmsg->{received}";
  153. my @raw = split(" ",$clientmsg->{received});
  154. I2C_SHT21_GetTemp ($hash, $clientmsg->{received}) if !($raw[1] & 2) && $clientmsg->{nbyte} == 3;
  155. I2C_SHT21_GetHum ($hash, $clientmsg->{received}) if ($raw[1] & 2) && $clientmsg->{nbyte} == 3;
  156. }
  157. }
  158. }
  159. sub I2C_SHT21_GetTemp ($$) {
  160. my ($hash, $rawdata) = @_;
  161. my @raw = split(" ",$rawdata);
  162. I2C_SHT21_triggerHumidity($hash); #schnell noch Feuchtemessung anstoßen.
  163. if ( defined (my $crc = I2C_SHT21_CheckCrc(@raw)) ) { #CRC Test
  164. Log3 $hash, 2, "CRC error temperature data(MSB LSB Chechsum): $rawdata, Checksum calculated: $crc";
  165. $hash->{CRCErrorTemperature}++;
  166. return;
  167. }
  168. my $temperature = $raw[0] << 8 | $raw[1];
  169. $temperature = ( 175.72 * $temperature / 2**16 ) - 46.85;
  170. $temperature = sprintf(
  171. '%.' . AttrVal($hash->{NAME}, 'roundTemperatureDecimal', 1) . 'f',
  172. $temperature
  173. );
  174. readingsSingleUpdate($hash,"temperature", $temperature, 1);
  175. }
  176. sub I2C_SHT21_GetHum ($$) {
  177. my ($hash, $rawdata) = @_;
  178. my @raw = split(" ",$rawdata);
  179. if ( defined (my $crc = I2C_SHT21_CheckCrc(@raw)) ) { #CRC Test
  180. Log3 $hash, 2, "CRC error humidity data(MSB LSB Chechsum): $rawdata, Checksum calculated: $crc";
  181. $hash->{CRCErrorHumidity}++;
  182. return;
  183. }
  184. my $name = $hash->{NAME};
  185. my $temperature = ReadingsVal($name,"temperature","0");
  186. my $humidity = $raw[0] << 8 | $raw[1];
  187. $humidity = ( 125 * $humidity / 2**16 ) - 6;
  188. $humidity = sprintf(
  189. '%.' . AttrVal($hash->{NAME}, 'roundHumidityDecimal', 1) . 'f',
  190. $humidity
  191. );
  192. readingsBeginUpdate($hash);
  193. readingsBulkUpdate(
  194. $hash,
  195. 'state',
  196. 'T: ' . $temperature . ' H: ' . $humidity
  197. );
  198. readingsBulkUpdate($hash, 'humidity', $humidity);
  199. readingsEndUpdate($hash, 1);
  200. }
  201. sub I2C_SHT21_triggerTemperature($) {
  202. my ($hash) = @_;
  203. my $name = $hash->{NAME};
  204. return "$name: no IO device defined" unless ($hash->{IODev});
  205. my $phash = $hash->{IODev};
  206. my $pname = $phash->{NAME};
  207. # Write 0xF3 to device. This requests a "no hold master" temperature reading
  208. my $i2creq = { i2caddress => $hash->{I2C_Address}, direction => "i2cwrite" };
  209. $i2creq->{data} = hex("F3");
  210. CallFn($pname, "I2CWrtFn", $phash, $i2creq);
  211. RemoveInternalTimer($hash);
  212. InternalTimer(gettimeofday() + 1, 'I2C_SHT21_readValue', $hash, 0); #nach 1s Wert lesen (85ms sind fuer 14bit Wert notwendig)
  213. return;
  214. }
  215. sub I2C_SHT21_triggerHumidity($) {
  216. my ($hash) = @_;
  217. my $name = $hash->{NAME};
  218. return "$name: no IO device defined" unless ($hash->{IODev});
  219. my $phash = $hash->{IODev};
  220. my $pname = $phash->{NAME};
  221. # Write 0xF5 to device. This requests a "no hold master" humidity reading
  222. my $i2creq = { i2caddress => $hash->{I2C_Address}, direction => "i2cwrite" };
  223. $i2creq->{data} = hex("F5");
  224. CallFn($pname, "I2CWrtFn", $phash, $i2creq);
  225. RemoveInternalTimer($hash);
  226. InternalTimer(gettimeofday() + 1, 'I2C_SHT21_readValue', $hash, 0); #nach 1s Wert lesen (39ms sind fuer 12bit Wert notwendig)
  227. return;
  228. }
  229. sub I2C_SHT21_readValue($) {
  230. my ($hash) = @_;
  231. my $name = $hash->{NAME};
  232. return "$name: no IO device defined" unless ($hash->{IODev});
  233. my $phash = $hash->{IODev};
  234. my $pname = $phash->{NAME};
  235. # Reset Internal Timer to Poll Sub
  236. RemoveInternalTimer($hash);
  237. my $pollInterval = AttrVal($hash->{NAME}, 'poll_interval', 0);
  238. InternalTimer(gettimeofday() + ($pollInterval * 60), 'I2C_SHT21_Poll', $hash, 0) if ($pollInterval > 0);
  239. # Read the two byte result from device + 1byte CRC
  240. my $i2cread = { i2caddress => $hash->{I2C_Address}, direction => "i2cread" };
  241. $i2cread->{nbyte} = 3;
  242. CallFn($pname, "I2CWrtFn", $phash, $i2cread);
  243. return;
  244. }
  245. sub I2C_SHT21_CheckCrc(@) {
  246. my @data = @_;
  247. my $crc = 0;
  248. my $poly = 0x131; #P(x)=x^8+x^5+x^4+1 = 100110001
  249. for (my $n = 0; $n < (scalar(@data) - 1); ++$n) {
  250. $crc ^= $data[$n];
  251. for (my $bit = 8; $bit > 0; --$bit) {
  252. $crc = ($crc & 0x80 ? $poly : 0 ) ^ ($crc << 1);
  253. }
  254. }
  255. return ($crc = $data[2] ? undef : $crc);
  256. }
  257. sub I2C_SHT21_DbLog_splitFn($) {
  258. my ($event) = @_;
  259. Log3 undef, 5, "in DbLog_splitFn empfangen: $event";
  260. my ($reading, $value, $unit) = "";
  261. my @parts = split(/ /,$event);
  262. $reading = shift @parts;
  263. $reading =~ tr/://d;
  264. $value = $parts[0];
  265. $unit = "\xB0C" if(lc($reading) =~ m/temp/);
  266. $unit = "%" if(lc($reading) =~ m/humi/);
  267. return ($reading, $value, $unit);
  268. }
  269. 1;
  270. =pod
  271. =item device
  272. =item summary reads humidity and temperature from an via I2C connected SHT2x
  273. =item summary_DE lese Feuchte und Temperatur eines &uuml;ber I2C angeschlossenen SHT2x
  274. =begin html
  275. <a name="I2C_SHT21"></a>
  276. <h3>I2C_SHT21</h3>
  277. (en | <a href="commandref_DE.html#I2C_SHT21">de</a>)
  278. <ul>
  279. <a name="I2C_SHT21"></a>
  280. Provides an interface to the SHT21 I2C Humidity sensor from <a href="www.sensirion.com">Sensirion</a>.
  281. The I2C messages are send through an I2C interface module like <a href="#RPII2C">RPII2C</a>, <a href="#FRM">FRM</a>
  282. or <a href="#NetzerI2C">NetzerI2C</a> so this device must be defined first.<br>
  283. <b>attribute IODev must be set</b><br>
  284. <a name="I2C_SHT21Define"></a><br>
  285. <b>Define</b>
  286. <ul>
  287. <code>define &lt;name&gt; I2C_SHT21 [&lt;I2C Address&gt;]</code><br>
  288. where <code>&lt;I2C Address&gt;</code> is an 2 digit hexadecimal value<br>
  289. </ul>
  290. <a name="I2C_SHT21Set"></a>
  291. <b>Set</b>
  292. <ul>
  293. <code>set &lt;name&gt; readValues</code><br>
  294. Reads the current temperature and humidity values from sensor.<br><br>
  295. </ul>
  296. <a name="I2C_SHT21Attr"></a>
  297. <b>Attributes</b>
  298. <ul>
  299. <li>poll_interval<br>
  300. Set the polling interval in minutes to query data from sensor<br>
  301. Default: 5, valid values: 1,2,5,10,20,30<br><br>
  302. </li>
  303. <li>roundHumidityDecimal, roundTemperatureDecimal<br>
  304. Number of decimal places for humidity or temperature value<br>
  305. Default: 1, valid values: 0 1 2<br><br>
  306. </li>
  307. <li><a href="#IODev">IODev</a></li>
  308. <li><a href="#do_not_notify">do_not_notify</a></li>
  309. <li><a href="#showtime">showtime</a></li>
  310. </ul><br>
  311. </ul>
  312. =end html
  313. =begin html_DE
  314. <a name="I2C_SHT21"></a>
  315. <h3>I2C_SHT21</h3>
  316. (<a href="commandref.html#I2C_SHT21">en</a> | de)
  317. <ul>
  318. <a name="I2C_SHT21"></a>
  319. Erm&ouml;glicht die Verwendung eines SHT21 I2C Feuchtesensors von <a href="www.sensirion.com">Sensirion</a>.
  320. I2C-Botschaften werden &uuml;ber ein I2C Interface Modul wie beispielsweise das <a href="#RPII2C">RPII2C</a>, <a href="#FRM">FRM</a>
  321. oder <a href="#NetzerI2C">NetzerI2C</a> gesendet. Daher muss dieses vorher definiert werden.<br>
  322. <b>Das Attribut IODev muss definiert sein.</b><br>
  323. <a name="I2C_SHT21Define"></a><br>
  324. <b>Define</b>
  325. <ul>
  326. <code>define &lt;name&gt; I2C_SHT21 [&lt;I2C Address&gt;]</code><br>
  327. Der Wert <code>&lt;I2C Address&gt;</code> ist ein zweistelliger Hex-Wert<br>
  328. </ul>
  329. <a name="I2C_SHT21Set"></a>
  330. <b>Set</b>
  331. <ul>
  332. <code>set &lt;name&gt; readValues</code><br>
  333. Aktuelle Temperatur und Feuchte Werte vom Sensor lesen.<br><br>
  334. </ul>
  335. <a name="I2C_SHT21Attr"></a>
  336. <b>Attribute</b>
  337. <ul>
  338. <li>poll_interval<br>
  339. Aktualisierungsintervall aller Werte in Minuten.<br>
  340. Standard: 5, g&uuml;ltige Werte: 1,2,5,10,20,30<br><br>
  341. </li>
  342. <li>roundHumidityDecimal, roundTemperatureDecimal<br>
  343. Anzahl Dezimalstellen f&uuml;r den Feuchte-, oder Temperaturwert<br>
  344. Standard: 1, g&uuml;ltige Werte: 0 1 2<br><br>
  345. </li>
  346. <li><a href="#IODev">IODev</a></li>
  347. <li><a href="#do_not_notify">do_not_notify</a></li>
  348. <li><a href="#showtime">showtime</a></li>
  349. </ul><br>
  350. </ul>
  351. =end html_DE
  352. =cut