52_I2C_LM75A.pm 11 KB

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