52_I2C_SHT3x.pm 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406
  1. ##############################################
  2. # $Id: 52_I2C_SHT3x.pm 11780 2016-07-10 19:12:34Z macs00 $
  3. #
  4. # 52_I2C_SHT3x.pm
  5. #
  6. # i2c sensor for humidity and temperature
  7. #
  8. # Basis for this module is "52_I2C_SHT21.pm" from klausw. I adapted the module so that it
  9. # is suitable for the SHT3x family from Sensirion / Switzerland. At moment the SHT3x family
  10. # consists of 3 sensors:
  11. # SHT30 (Low-Cost): sensor with less accuracy
  12. # SHT31 (Standard): sensor with good accuracy
  13. # SHT35 (High-End): sensor with best accuracy
  14. #
  15. # Via hardware pin configuration the sensor can be configured for 2 different i2c addresses.
  16. #
  17. #
  18. # If you have any questions, suggestions or like to report a failure, please feel free to cantact me:
  19. # FHEM Forum username: macs
  20. #
  21. ##############################################
  22. package main;
  23. use strict;
  24. use warnings;
  25. use constant {
  26. # 0x44 (default): ADDR (pin 2) connected to VSS (supply voltage)
  27. # 0x45 : ADDR (pin 2) connected to VDD (ground)
  28. SHT3x_I2C_ADDRESS => '0x44',
  29. };
  30. ##################################################
  31. # Forward declarations
  32. #
  33. sub I2C_SHT3x_Initialize($);
  34. sub I2C_SHT3x_Define($$);
  35. sub I2C_SHT3x_Attr(@);
  36. sub I2C_SHT3x_Poll($);
  37. sub I2C_SHT3x_Set($@);
  38. sub I2C_SHT3x_Undef($$);
  39. sub I2C_SHT3x_DbLog_splitFn($);
  40. my %sets = (
  41. 'readValues' => 1,
  42. );
  43. sub I2C_SHT3x_Initialize($) {
  44. my ($hash) = @_;
  45. $hash->{DefFn} = 'I2C_SHT3x_Define';
  46. $hash->{InitFn} = 'I2C_SHT3x_Init';
  47. $hash->{AttrFn} = 'I2C_SHT3x_Attr';
  48. $hash->{SetFn} = 'I2C_SHT3x_Set';
  49. $hash->{UndefFn} = 'I2C_SHT3x_Undef';
  50. $hash->{I2CRecFn} = 'I2C_SHT3x_I2CRec';
  51. $hash->{AttrList} = 'IODev do_not_notify:0,1 showtime:0,1 poll_interval:1,2,5,10,20,30 ' .
  52. 'roundHumidityDecimal:0,1,2 roundTemperatureDecimal:0,1,2 ' .
  53. $readingFnAttributes;
  54. $hash->{DbLog_splitFn} = "I2C_SHT3x_DbLog_splitFn";
  55. }
  56. sub I2C_SHT3x_Define($$) {
  57. my ($hash, $def) = @_;
  58. my @a = split('[ \t][ \t]*', $def);
  59. $hash->{STATE} = "defined";
  60. if ($main::init_done) {
  61. eval { I2C_SHT3x_Init( $hash, [ @a[ 2 .. scalar(@a) - 1 ] ] ); };
  62. return I2C_SHT3x_Catch($@) if $@;
  63. }
  64. return undef;
  65. }
  66. sub I2C_SHT3x_Init($$) {
  67. my ( $hash, $args ) = @_;
  68. my $name = $hash->{NAME};
  69. if (defined $args && int(@$args) > 1)
  70. {
  71. return "Define: Wrong syntax. Usage:\n" .
  72. "define <name> I2C_SHT3x [<i2caddress>]";
  73. }
  74. if (defined (my $address = shift @$args)) {
  75. $hash->{I2C_Address} = $address =~ /^0.*$/ ? oct($address) : $address;
  76. return "$name I2C Address not valid" unless (($hash->{I2C_Address} < 128) && ($hash->{I2C_Address} > 3));
  77. } else {
  78. $hash->{I2C_Address} = hex(SHT3x_I2C_ADDRESS);
  79. }
  80. my $msg = '';
  81. # create default attributes
  82. if (AttrVal($name, 'poll_interval', '?') eq '?') {
  83. $msg = CommandAttr(undef, $name . ' poll_interval 5');
  84. if ($msg) {
  85. Log3 ($hash, 1, $msg);
  86. return $msg;
  87. }
  88. }
  89. AssignIoPort($hash);
  90. $hash->{STATE} = 'Initialized';
  91. return undef;
  92. }
  93. sub I2C_SHT3x_Catch($) {
  94. my $exception = shift;
  95. if ($exception) {
  96. $exception =~ /^(.*)( at.*FHEM.*)$/;
  97. return $1;
  98. }
  99. return undef;
  100. }
  101. sub I2C_SHT3x_Attr (@) {# hier noch Werteueberpruefung einfuegen
  102. my ($command, $name, $attr, $val) = @_;
  103. my $hash = $defs{$name};
  104. my $msg = '';
  105. if ($command && $command eq "set" && $attr && $attr eq "IODev") {
  106. eval {
  107. if ($main::init_done and (!defined ($hash->{IODev}) or $hash->{IODev}->{NAME} ne $val)) {
  108. main::AssignIoPort($hash,$val);
  109. my @def = split (' ',$hash->{DEF});
  110. I2C_SHT3x_Init($hash,\@def) if (defined ($hash->{IODev}));
  111. }
  112. };
  113. return I2C_SHT3x_Catch($@) if $@;
  114. }
  115. if ($attr eq 'poll_interval') {
  116. if ($val > 0) {
  117. RemoveInternalTimer($hash);
  118. InternalTimer(gettimeofday() + 5, 'I2C_SHT3x_Poll', $hash, 0);
  119. } else {
  120. $msg = 'Wrong poll intervall defined. poll_interval must be a number > 0';
  121. }
  122. } elsif ($attr eq 'roundHumidityDecimal') {
  123. $msg = 'Wrong $attr defined. Use one of 0, 1, 2' if defined($val) && $val <= 0 && $val >= 2 ;
  124. } elsif ($attr eq 'roundTemperatureDecimal') {
  125. $msg = 'Wrong $attr defined. Use one of 0, 1, 2' if defined($val) && $val <= 0 && $val >= 2 ;
  126. }
  127. return ($msg) ? $msg : undef;
  128. }
  129. sub I2C_SHT3x_Poll($) {
  130. my ($hash) = @_;
  131. my $name = $hash->{NAME};
  132. # Read values
  133. I2C_SHT3x_Set($hash, ($name, 'readValues'));
  134. my $pollInterval = AttrVal($hash->{NAME}, 'poll_interval', 0);
  135. if ($pollInterval > 0) {
  136. InternalTimer(gettimeofday() + ($pollInterval * 60), 'I2C_SHT3x_Poll', $hash, 0);
  137. }
  138. }
  139. sub I2C_SHT3x_Set($@) {
  140. my ($hash, @a) = @_;
  141. my $name = $a[0];
  142. my $cmd = $a[1];
  143. if(!defined($sets{$cmd})) {
  144. return 'Unknown argument ' . $cmd . ', choose one of ' . join(' ', keys %sets)
  145. }
  146. if ($cmd eq 'readValues') {
  147. I2C_SHT3x_triggerTempHum($hash);
  148. }
  149. }
  150. sub I2C_SHT3x_Undef($$) {
  151. my ($hash, $arg) = @_;
  152. RemoveInternalTimer($hash);
  153. return undef;
  154. }
  155. sub I2C_SHT3x_I2CRec ($$) {
  156. my ($hash, $clientmsg) = @_;
  157. my $name = $hash->{NAME};
  158. my $phash = $hash->{IODev};
  159. my $pname = $phash->{NAME};
  160. while ( my ( $k, $v ) = each %$clientmsg ) { #erzeugen von Internals fuer alle Keys in $clientmsg die mit dem physical Namen beginnen
  161. $hash->{$k} = $v if $k =~ /^$pname/ ;
  162. }
  163. # i2c data received?
  164. if ( $clientmsg->{direction} && $clientmsg->{$pname . "_SENDSTAT"} && $clientmsg->{$pname . "_SENDSTAT"} eq "Ok" ) {
  165. if ( $clientmsg->{direction} eq "i2cread" && defined($clientmsg->{received}) ) {
  166. Log3 $hash, 5, "empfangen: $clientmsg->{received}";
  167. my @raw = split(" ",$clientmsg->{received});
  168. I2C_SHT3x_GetTempHum ($hash, $clientmsg->{received}) if $clientmsg->{nbyte} == 6;
  169. }
  170. }
  171. }
  172. sub I2C_SHT3x_GetTempHum ($$) {
  173. my ($hash, $rawdata) = @_;
  174. my @raw = split(" ",$rawdata);
  175. if ( defined (my $crc = I2C_SHT3x_CheckCrc(@raw[0..2])) ) { #CRC Test
  176. Log3 $hash, 2, "CRC error temperature data(Temp_MSB Temp_LSB Temp_Chechsum Hum_MSB Hum_LSB Hum_Chechsum): $rawdata, Checksum calculated: $crc";
  177. $hash->{CRCErrorTemperature}++;
  178. return;
  179. }
  180. if ( defined (my $crc = I2C_SHT3x_CheckCrc(@raw[3..5])) ) { #CRC Test
  181. Log3 $hash, 2, "CRC error humidity data(Temp_MSB Temp_LSB Temp_Chechsum Hum_MSB Hum_LSB Hum_Chechsum): $rawdata, Checksum calculated: $crc";
  182. $hash->{CRCErrorHumidity}++;
  183. return;
  184. }
  185. my $name = $hash->{NAME};
  186. my $temperature = ($raw[0] << 8) | $raw[1];
  187. $temperature = ( 175.0 * ($temperature / ((2**16)-1.0) )) - 45.0;
  188. $temperature = sprintf(
  189. '%.' . AttrVal($hash->{NAME}, 'roundTemperatureDecimal', 1) . 'f',
  190. $temperature
  191. );
  192. my $humidity = ($raw[3] << 8) | $raw[4];
  193. $humidity = 100.0 * ($humidity / ((2**16)-1.0) );
  194. $humidity = sprintf(
  195. '%.' . AttrVal($hash->{NAME}, 'roundHumidityDecimal', 1) . 'f',
  196. $humidity
  197. );
  198. readingsBeginUpdate($hash);
  199. readingsBulkUpdate(
  200. $hash,
  201. 'state',
  202. 'T: ' . $temperature . ' H: ' . $humidity
  203. );
  204. readingsBulkUpdate($hash, 'temperature', $temperature);
  205. readingsBulkUpdate($hash, 'humidity', $humidity);
  206. readingsEndUpdate($hash, 1);
  207. }
  208. sub I2C_SHT3x_triggerTempHum($) {
  209. my ($hash) = @_;
  210. my $name = $hash->{NAME};
  211. return "$name: no IO device defined" unless ($hash->{IODev});
  212. my $phash = $hash->{IODev};
  213. my $pname = $phash->{NAME};
  214. # Write decimal 36 00 to device. This requests a "Repeatability = High, Clock stretching = disabled" temperature and humidity reading
  215. my $i2creq = { i2caddress => $hash->{I2C_Address}, direction => "i2cwrite" };
  216. $i2creq->{data} = "36 00";
  217. CallFn($pname, "I2CWrtFn", $phash, $i2creq);
  218. RemoveInternalTimer($hash);
  219. InternalTimer(gettimeofday() + 1, 'I2C_SHT3x_readValue', $hash, 0); #nach 1s Wert lesen
  220. return;
  221. }
  222. sub I2C_SHT3x_readValue($) {
  223. my ($hash) = @_;
  224. my $name = $hash->{NAME};
  225. return "$name: no IO device defined" unless ($hash->{IODev});
  226. my $phash = $hash->{IODev};
  227. my $pname = $phash->{NAME};
  228. # Reset Internal Timer to Poll Sub
  229. RemoveInternalTimer($hash);
  230. my $pollInterval = AttrVal($hash->{NAME}, 'poll_interval', 0);
  231. InternalTimer(gettimeofday() + ($pollInterval * 60), 'I2C_SHT3x_Poll', $hash, 0) if ($pollInterval > 0);
  232. # Read 6 byte: Temp MSB, Temp LSB, CRC, Hum MSB, Hum LSB, CRC
  233. my $i2cread = { i2caddress => $hash->{I2C_Address}, direction => "i2cread" };
  234. $i2cread->{nbyte} = 6;
  235. CallFn($pname, "I2CWrtFn", $phash, $i2cread);
  236. return;
  237. }
  238. sub I2C_SHT3x_CheckCrc(@) {
  239. my @data = @_;
  240. my $crc = 0xFF;
  241. my $poly = 0x131; #P(x)=x^8+x^5+x^4+1 = 100110001
  242. for (my $n = 0; $n < (scalar(@data) - 1); ++$n) {
  243. $crc ^= $data[$n];
  244. for (my $bit = 8; $bit > 0; --$bit) {
  245. $crc = ($crc & 0x80 ? $poly : 0 ) ^ ($crc << 1);
  246. }
  247. }
  248. return ($crc == $data[scalar(@data)-1] ? undef : $crc);
  249. }
  250. sub I2C_SHT3x_DbLog_splitFn($) {
  251. my ($event) = @_;
  252. Log3 undef, 5, "in DbLog_splitFn empfangen: $event";
  253. my ($reading, $value, $unit) = "";
  254. my @parts = split(/ /,$event);
  255. $reading = shift @parts;
  256. $reading =~ tr/://d;
  257. $value = $parts[0];
  258. $unit = "\xB0C" if(lc($reading) =~ m/temp/);
  259. $unit = "%" if(lc($reading) =~ m/humi/);
  260. return ($reading, $value, $unit);
  261. }
  262. 1;
  263. =pod
  264. =begin html
  265. <a name="I2C_SHT3x"></a>
  266. <h3>I2C_SHT3x</h3>
  267. (en | <a href="commandref_DE.html#I2C_SHT3x">de</a>)
  268. <ul>
  269. <a name="I2C_SHT3x"></a>
  270. Provides an interface to the SHT30/SHT31 I2C Humidity sensor from <a href="http:\\www.sensirion.com">Sensirion</a>.
  271. The I2C messages are sent through an I2C interface module like <a href="#RPII2C">RPII2C</a>, <a href="#FRM">FRM</a>
  272. or <a href="#NetzerI2C">NetzerI2C</a> so this device must be defined first.<br>
  273. <b>attribute IODev must be set</b><br>
  274. <a name="I2C_SHT3xDefine"></a><br>
  275. <b>Define</b>
  276. <ul>
  277. <code>define &lt;name&gt; I2C_SHT3x [&lt;I2C Address&gt;]</code><br>
  278. where <code>&lt;I2C Address&gt;</code> is an 2 digit hexadecimal value:<br>
  279. ADDR (pin 2) connected to VSS (supply voltage): 0x44 (default, if <code>&lt;I2C Address&gt;</code> is not set)<br>
  280. ADDR (pin 2) connected to VDD (ground): 0x45<br>
  281. For compatible sensors also other values than 0x44 or 0x45 can be set.<br>
  282. <br>
  283. </ul>
  284. <a name="I2C_SHT3xSet"></a>
  285. <b>Set</b>
  286. <ul>
  287. <code>set &lt;name&gt; readValues</code><br>
  288. Reads the current temperature and humidity values from sensor.<br><br>
  289. </ul>
  290. <a name="I2C_SHT3xAttr"></a>
  291. <b>Attributes</b>
  292. <ul>
  293. <li>poll_interval<br>
  294. Set the polling interval in minutes to query data from sensor<br>
  295. Default: 5, valid values: 1,2,5,10,20,30<br><br>
  296. </li>
  297. <li>roundHumidityDecimal, roundTemperatureDecimal<br>
  298. Number of decimal places for humidity or temperature value<br>
  299. Default: 1, valid values: 0 1 2<br><br>
  300. </li>
  301. <li><a href="#IODev">IODev</a></li>
  302. <li><a href="#do_not_notify">do_not_notify</a></li>
  303. <li><a href="#showtime">showtime</a></li>
  304. </ul><br>
  305. </ul>
  306. =end html
  307. =begin html_DE
  308. <a name="I2C_SHT3x"></a>
  309. <h3>I2C_SHT3x</h3>
  310. (<a href="commandref.html#I2C_SHT3x">en</a> | de)
  311. <ul>
  312. <a name="I2C_SHT3x"></a>
  313. Erm&ouml;glicht die Verwendung eines SHT30/SHT31 I2C Feuchtesensors von <a href="http:\\www.sensirion.com">Sensirion</a>.
  314. I2C-Botschaften werden &uuml;ber ein I2C Interface Modul wie beispielsweise das <a href="#RPII2C">RPII2C</a>, <a href="#FRM">FRM</a>
  315. oder <a href="#NetzerI2C">NetzerI2C</a> gesendet. Daher muss dieses vorher definiert werden.<br>
  316. <b>Das Attribut IODev muss definiert sein.</b><br>
  317. <a name="I2C_SHT3xDefine"></a><br>
  318. <b>Define</b>
  319. <ul>
  320. <code>define &lt;name&gt; I2C_SHT3x [&lt;I2C Address&gt;]</code><br>
  321. <br>
  322. Der Wert <code>&lt;I2C Address&gt;</code> ist ein zweistelliger Hex-Wert:<br>
  323. ADDR (Pin 2) verbunden mit VSS (Versorgungsspannung): 0x44 (Standardwert, wenn <code>&lt;I2C Address&gt;</code> nicht angegeben)<br>
  324. ADDR (pin 2) verbunden mit VDD (Masse): 0x45<br>
  325. F&uuml;r kompatible Sensoren k&ouml;nnen auch andere Werte als 0x44 oder 0x45 angegeben werden.<br>
  326. <br>
  327. </ul>
  328. <a name="I2C_SHT3xSet"></a>
  329. <b>Set</b>
  330. <ul>
  331. <code>set &lt;name&gt; readValues</code><br>
  332. Aktuelle Temperatur und Feuchte Werte vom Sensor lesen.<br><br>
  333. </ul>
  334. <a name="I2C_SHT3xAttr"></a>
  335. <b>Attribute</b>
  336. <ul>
  337. <li>poll_interval<br>
  338. Aktualisierungsintervall aller Werte in Minuten.<br>
  339. Standard: 5, g&uuml;ltige Werte: 1,2,5,10,20,30<br><br>
  340. </li>
  341. <li>roundHumidityDecimal, roundTemperatureDecimal<br>
  342. Anzahl Dezimalstellen f&uuml;r den Feuchte- oder Temperaturwert<br>
  343. Standard: 1, g&uuml;ltige Werte: 0 1 2<br><br>
  344. </li>
  345. <li><a href="#IODev">IODev</a></li>
  346. <li><a href="#do_not_notify">do_not_notify</a></li>
  347. <li><a href="#showtime">showtime</a></li>
  348. </ul><br>
  349. </ul>
  350. =end html_DE
  351. =cut