52_I2C_EMC1001.pm 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474
  1. ##############################################
  2. #
  3. # Modul for reading a EMC1001 digital temperature sensor via I2C
  4. # (see http://ww1.microchip.com/downloads/en/DeviceDoc/20005411A.pdf)
  5. #
  6. # Copyright (C) 2018 Stephan Eisler
  7. #
  8. # This file is part of fhem.
  9. #
  10. # Fhem is free software: you can redistribute it and/or modify
  11. # it under the terms of the GNU General Public License as published by
  12. # the Free Software Foundation, either version 2 of the License, or
  13. # (at your option) any later version.
  14. #
  15. # Fhem is distributed in the hope that it will be useful,
  16. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. # GNU General Public License for more details.
  19. #
  20. # You should have received a copy of the GNU General Public License
  21. # along with fhem. If not, see <http://www.gnu.org/licenses/>.
  22. #
  23. # $Id: 52_I2C_EMC1001.pm 16082 2018-02-04 14:49:06Z eisler $
  24. #
  25. ##############################################
  26. package main;
  27. use strict;
  28. use warnings;
  29. use constant {
  30. EMC1001_I2C_ADDRESS => 0x48, # EMC1001 I2C ADDRESS
  31. Reg_TMP_HB => 0x00, # R temperature value high byte
  32. Reg_STATUS => 0x01, # RC Status
  33. Reg_TMP_LB => 0x02, # R low byte containing 1/4 deg fraction
  34. Reg_Config => 0x03, # R/W Configuration
  35. Reg_Cnv_Rate => 0x04, # R/W Conversion Rate
  36. Reg_THL_HB => 0x05, # R/W Temperature High Limit High Byte
  37. Reg_THL_LB => 0x06, # R/W Temperature High Limit Low Byte
  38. Reg_TLL_HB => 0x07, # R/W Temperature Low Limit High Byte
  39. Reg_TLL_LB => 0x08, # R/W Temperature Low Limit Low Byte
  40. Reg_One_Sht => 0x0f, # R One-Shot
  41. Reg_THM_LMT => 0x20, # R/W THERM Limit
  42. Reg_THM_HYS => 0x21, # R/W THERM Hysteresis
  43. Reg_SMB_TO => 0x22, # R/W SMBus Timeout Enable
  44. Reg_Prd_ID => 0xfd, # R Product ID Register
  45. Reg_Mnf_ID => 0xfe, # R Manufacture ID
  46. Reg_Rev_No => 0xff # R Revision Number
  47. };
  48. ##################################################
  49. # Forward declarations
  50. #
  51. sub I2C_EMC1001_I2CRec ($$);
  52. sub I2C_EMC1001_GetReadings ($$);
  53. sub I2C_EMC1001_GetTemp ($@);
  54. sub I2C_EMC1001_calcTrueTemperature($$);
  55. my %sets = (
  56. 'readValues' => 1,
  57. );
  58. sub I2C_EMC1001_Initialize($) {
  59. my ($hash) = @_;
  60. $hash->{DefFn} = 'I2C_EMC1001_Define';
  61. $hash->{InitFn} = 'I2C_EMC1001_Init';
  62. $hash->{AttrFn} = 'I2C_EMC1001_Attr';
  63. $hash->{SetFn} = 'I2C_EMC1001_Set';
  64. #$hash->{GetFn} = 'I2C_EMC1001_Get';
  65. $hash->{UndefFn} = 'I2C_EMC1001_Undef';
  66. $hash->{I2CRecFn} = 'I2C_EMC1001_I2CRec';
  67. $hash->{AttrList} = 'IODev do_not_notify:0,1 showtime:0,1 poll_interval:1,2,5,10,20,30 ' .
  68. 'roundTemperatureDecimal:0,1,2 ' .
  69. $readingFnAttributes;
  70. $hash->{DbLog_splitFn} = "I2C_EMC1001_DbLog_splitFn";
  71. }
  72. sub I2C_EMC1001_Define($$) {
  73. my ($hash, $def) = @_;
  74. my @a = split('[ \t][ \t]*', $def);
  75. $hash->{STATE} = 'defined';
  76. my $name = $a[0];
  77. my $msg = '';
  78. if((@a < 2)) {
  79. $msg = 'wrong syntax: define <name> I2C_EMC1001 [I2C-Address]';
  80. }
  81. if ($msg) {
  82. Log3 ($hash, 1, $msg);
  83. return $msg;
  84. }
  85. if ($main::init_done) {
  86. eval { I2C_EMC1001_Init( $hash, [ @a[ 2 .. scalar(@a) - 1 ] ] ); };
  87. return I2C_EMC1001_Catch($@) if $@;
  88. }
  89. }
  90. sub I2C_EMC1001_Init($$) { # wird bei FHEM start Define oder wieder
  91. my ( $hash, $args ) = @_;
  92. my $name = $hash->{NAME};
  93. if (defined (my $address = shift @$args)) {
  94. $hash->{I2C_Address} = $address =~ /^0x.*$/ ? oct($address) : $address;
  95. return "$name: I2C Address not valid" unless ($hash->{I2C_Address} < 128 && $hash->{I2C_Address} > 3);
  96. } else {
  97. $hash->{I2C_Address} = EMC1001_I2C_ADDRESS;
  98. }
  99. my $msg = '';
  100. # create default attributes
  101. #if (AttrVal($name, 'poll_interval', '?') eq '?') {
  102. # $msg = CommandAttr(undef, $name . ' poll_interval 5');
  103. # if ($msg) {
  104. # Log3 ($hash, 1, $msg);
  105. # return $msg;
  106. # }
  107. #}
  108. eval {
  109. AssignIoPort($hash, AttrVal($hash->{NAME},"IODev",undef));
  110. I2C_EMC1001_i2cread($hash, Reg_Prd_ID, 1); #get Prd Id
  111. I2C_EMC1001_i2cread($hash, Reg_Mnf_ID, 1); #get Mnf Id
  112. I2C_EMC1001_i2cread($hash, Reg_Rev_No, 1); #get Reg Rev No
  113. };
  114. return I2C_EMC1001_Catch($@) if $@;
  115. }
  116. sub I2C_EMC1001_Catch($) { # Fehlermeldungen formattieren
  117. my $exception = shift;
  118. if ($exception) {
  119. $exception =~ /^(.*)( at.*FHEM.*)$/;
  120. return $1;
  121. }
  122. return undef;
  123. }
  124. sub I2C_EMC1001_Attr (@) { # Wird beim Attribut anlegen/aendern aufgerufen
  125. my ($command, $name, $attr, $val) = @_;
  126. my $hash = $defs{$name};
  127. my $msg = '';
  128. if (defined $command && $command eq "set" && $attr eq "IODev") {
  129. eval {
  130. if ($main::init_done and (!defined ($hash->{IODev}) or $hash->{IODev}->{NAME} ne $val)) {
  131. main::AssignIoPort($hash,$val);
  132. my @def = split (' ',$hash->{DEF});
  133. I2C_EMC1001_Init($hash,\@def) if (defined ($hash->{IODev}));
  134. }
  135. };
  136. $msg = I2C_EMC1001_Catch($@) if $@;
  137. } elsif ($attr eq 'poll_interval') {
  138. if (defined($val)) {
  139. if ($val =~ m/^(0*[1-9][0-9]*)$/) {
  140. RemoveInternalTimer($hash);
  141. I2C_EMC1001_Poll($hash) if ($main::init_done);
  142. #InternalTimer(gettimeofday() + 5, 'I2C_EMC1001_Poll', $hash, 0) if ($main::init_done);
  143. } else {
  144. $msg = 'Wrong poll intervall defined. poll_interval must be a number > 0';
  145. }
  146. } else {
  147. RemoveInternalTimer($hash);
  148. }
  149. } elsif ($attr =~ m/^round(Temperature)Decimal$/ && defined($val)) {
  150. $msg = 'Wrong value: $val for $attr defined. value must be a one of 0,1,2' unless ($val =~ m/^(0*[0-2])$/);
  151. }
  152. return ($msg) ? $msg : undef;
  153. }
  154. sub I2C_EMC1001_Poll($) { # Messwerte regelmaessig anfordern
  155. my ($hash) = @_;
  156. my $name = $hash->{NAME};
  157. I2C_EMC1001_Set($hash, ($name, 'readValues')); # Read values
  158. my $pollInterval = AttrVal($hash->{NAME}, 'poll_interval', 0);
  159. if ($pollInterval > 0) {
  160. InternalTimer(gettimeofday() + ($pollInterval * 60), 'I2C_EMC1001_Poll', $hash, 0);
  161. }
  162. }
  163. sub I2C_EMC1001_Set($@) { # Messwerte manuell anfordern
  164. my ($hash, @a) = @_;
  165. my $name = $a[0];
  166. my $cmd = $a[1];
  167. if(!defined($sets{$cmd})) {
  168. return 'Unknown argument ' . $cmd . ', choose one of ' . join(' ', keys %sets) . ":noArg"
  169. }
  170. if ($cmd eq 'readValues') {
  171. I2C_EMC1001_i2cread($hash, Reg_TMP_HB, 1);
  172. I2C_EMC1001_i2cread($hash, Reg_TMP_LB, 1);
  173. RemoveInternalTimer($hash);
  174. InternalTimer(gettimeofday() + 1, 'I2C_EMC1001_UpdateReadings', $hash, 0);
  175. }
  176. return undef
  177. }
  178. sub I2C_EMC1001_Get($@) { # Messwerte manuell anfordern
  179. my ($hash, @a) = @_;
  180. my $name = $a[0];
  181. my $cmd = $a[1];
  182. if (defined($cmd) && $cmd eq 'readValues') {
  183. I2C_EMC1001_i2cread($hash, Reg_TMP_HB, 1);
  184. I2C_EMC1001_i2cread($hash, Reg_TMP_LB, 1);
  185. RemoveInternalTimer($hash);
  186. InternalTimer(gettimeofday() + 1, 'I2C_EMC1001_UpdateReadings', $hash, 0);
  187. } else {
  188. return 'Unknown argument ' . $cmd . ', choose one of readValues:noArg';
  189. }
  190. return undef
  191. }
  192. sub I2C_EMC1001_UpdateReadings($) { # Messwerte auslesen
  193. my ($hash) = @_;
  194. I2C_EMC1001_i2cread($hash, Reg_TMP_HB, 1);
  195. I2C_EMC1001_i2cread($hash, Reg_TMP_LB, 1);
  196. my $pollInterval = AttrVal($hash->{NAME}, 'poll_interval', 0); #poll_interval Timer wiederherstellen
  197. InternalTimer(gettimeofday() + ($pollInterval * 60), 'I2C_EMC1001_Poll', $hash, 0) if ($pollInterval > 0);
  198. }
  199. sub I2C_EMC1001_Undef($$) { # Device loeschen
  200. my ($hash, $arg) = @_;
  201. RemoveInternalTimer($hash);
  202. return undef;
  203. }
  204. sub I2C_EMC1001_I2CRec ($$) { # wird vom IODev aus aufgerufen wenn I2C Daten vorliegen
  205. my ($hash, $clientmsg) = @_;
  206. my $name = $hash->{NAME};
  207. my $pname = undef;
  208. my $phash = $hash->{IODev};
  209. $pname = $phash->{NAME};
  210. while ( my ( $k, $v ) = each %$clientmsg ) { #erzeugen von Internals fuer alle Keys in $clientmsg die mit dem physical Namen beginnen
  211. $hash->{$k} = $v if $k =~ /^$pname/ ;
  212. }
  213. if ( $clientmsg->{direction} && $clientmsg->{$pname . "_SENDSTAT"} && $clientmsg->{$pname . "_SENDSTAT"} eq "Ok" ) {
  214. if ( $clientmsg->{direction} eq "i2cread" && defined($clientmsg->{received}) ) {
  215. Log3 $hash, 5, "$name Rx, Reg: $clientmsg->{reg}, Data: $clientmsg->{received}";
  216. I2C_EMC1001_GetProdId ($hash, $clientmsg->{received}) if $clientmsg->{reg} == Reg_Prd_ID && $clientmsg->{nbyte} == 1;
  217. I2C_EMC1001_GetMnfId ($hash, $clientmsg->{received}) if $clientmsg->{reg} == Reg_Mnf_ID && $clientmsg->{nbyte} == 1;
  218. I2C_EMC1001_GetRevN ($hash, $clientmsg->{received}) if $clientmsg->{reg} == Reg_Rev_No && $clientmsg->{nbyte} == 1;
  219. I2C_EMC1001_GetReadingsTemperatureValueHighByte ($hash, $clientmsg->{received}) if $clientmsg->{reg} == Reg_TMP_HB && $clientmsg->{nbyte} == 1;
  220. I2C_EMC1001_GetReadingsTemperatureValueLowByte ($hash, $clientmsg->{received}) if $clientmsg->{reg} == Reg_TMP_LB && $clientmsg->{nbyte} == 1;
  221. }
  222. }
  223. return undef
  224. }
  225. sub I2C_EMC1001_GetProdId ($$) {
  226. my ($hash, $rawdata) = @_;
  227. if ($rawdata == hex("00")) {
  228. $hash->{DeviceType} = "EMC1001";
  229. } elsif ($rawdata == hex("01")) {
  230. $hash->{DeviceType} = "EMC1001-1";
  231. }
  232. readingsSingleUpdate($hash, 'DeviceType', $hash->{DeviceType}, 1);
  233. $hash->{STATE} = 'Initialized';
  234. I2C_EMC1001_Poll($hash) if defined(AttrVal($hash->{NAME}, 'poll_interval', undef)); # wenn poll_interval definiert -> timer starten
  235. }
  236. sub I2C_EMC1001_GetMnfId ($$) {
  237. my ($hash, $rawdata) = @_;
  238. readingsSingleUpdate($hash, 'DeviceManufactureId', sprintf("0x%X", $rawdata), 1);
  239. }
  240. sub I2C_EMC1001_GetRevN ($$) {
  241. my ($hash, $rawdata) = @_;
  242. readingsSingleUpdate($hash, 'DeviceRevisionNumber', sprintf("%d", $rawdata), 1);
  243. }
  244. sub I2C_EMC1001_GetReadingsTemperatureValueHighByte ($$) { # empfangenes Temperature High Byte verarbeiten
  245. my ($hash, $rawdata) = @_;
  246. Log3 $hash, 5, "ReadingsTemperatureValueHighByte: $rawdata";
  247. $hash->{TemperatureValueHighByte} = $rawdata;
  248. }
  249. sub I2C_EMC1001_GetReadingsTemperatureValueLowByte ($$) { # empfangenes Temperature Low Byte verarbeiten
  250. my ($hash, $rawdata) = @_;
  251. Log3 $hash, 5, "ReadingsTemperatureValueLowByte: $rawdata";
  252. $hash->{TemperatureValueLowByte} = $rawdata;
  253. I2C_EMC1001_GetTemp($hash, $rawdata);
  254. my $tem = ReadingsVal($hash->{NAME},"temperature", undef);
  255. readingsSingleUpdate(
  256. $hash,
  257. 'state',
  258. (defined $tem ? "T: $tem " : ""),
  259. 1
  260. );
  261. }
  262. sub I2C_EMC1001_GetTemp($@) { # Temperatur Messwerte verarbeiten
  263. my ($hash, @raw) = @_;
  264. my $temp= $hash->{TemperatureValueHighByte};
  265. my $templo= $hash->{TemperatureValueLowByte};
  266. $templo = $templo >> 6;
  267. if ($temp < 0) {
  268. $templo = 3-$templo;
  269. }
  270. my $temperature = sprintf(
  271. '%.' . AttrVal($hash->{NAME}, 'roundTemperatureDecimal', 1) . 'f',
  272. sprintf("%d.%d", $temp, $templo*25)
  273. );
  274. readingsSingleUpdate($hash, 'temperature', $temperature, 1);
  275. }
  276. sub I2C_EMC1001_i2cread($$$) { # Lesebefehl an Hardware absetzen (antwort kommt in I2C_*****_I2CRec an)
  277. my ($hash, $reg, $nbyte) = @_;
  278. if (defined (my $iodev = $hash->{IODev})) {
  279. Log3 $hash, 5, "$hash->{NAME}: $hash->{I2C_Address} read $nbyte Byte from Register $reg";
  280. CallFn($iodev->{NAME}, "I2CWrtFn", $iodev, {
  281. direction => "i2cread",
  282. i2caddress => $hash->{I2C_Address},
  283. reg => $reg,
  284. nbyte => $nbyte
  285. });
  286. } else {
  287. return "no IODev assigned to '$hash->{NAME}'";
  288. }
  289. }
  290. sub I2C_EMC1001_i2cwrite($$$) { # Schreibbefehl an Hardware absetzen
  291. my ($hash, $reg, @data) = @_;
  292. if (defined (my $iodev = $hash->{IODev})) {
  293. Log3 $hash, 5, "$hash->{NAME}: $hash->{I2C_Address} write " . join (' ',@data) . " to Register $reg";
  294. CallFn($iodev->{NAME}, "I2CWrtFn", $iodev, {
  295. direction => "i2cwrite",
  296. i2caddress => $hash->{I2C_Address},
  297. reg => $reg,
  298. data => join (' ',@data),
  299. });
  300. } else {
  301. return "no IODev assigned to '$hash->{NAME}'";
  302. }
  303. }
  304. sub I2C_EMC1001_DbLog_splitFn($) { # Einheiten
  305. my ($event) = @_;
  306. Log3 undef, 5, "in DbLog_splitFn empfangen: $event";
  307. my ($reading, $value, $unit) = "";
  308. my @parts = split(/ /,$event);
  309. $reading = shift @parts;
  310. $reading =~ tr/://d;
  311. $value = $parts[0];
  312. $unit = "\xB0C" if(lc($reading) =~ m/temp/);
  313. return ($reading, $value, $unit);
  314. }
  315. 1;
  316. =pod
  317. =item device
  318. =item summary reads temperature from an via I2C connected EMC1001
  319. =item summary_DE lese Temperatur eines &uuml;ber I2C angeschlossenen EMC1001
  320. =begin html
  321. <a name="I2C_EMC1001"></a>
  322. <h3>I2C_EMC1001</h3>
  323. (en | <a href="commandref_DE.html#I2C_EMC1001">de</a>)
  324. <ul>
  325. <a name="I2C_EMC1001"></a>
  326. Provides an interface to the digital temperature sensor EMC1001
  327. The I2C messages are send through an I2C interface module like <a href="#RPII2C">RPII2C</a>, <a href="#FRM">FRM</a>
  328. or <a href="#NetzerI2C">NetzerI2C</a> so this device must be defined first.<br>
  329. <b>attribute IODev must be set</b><br>
  330. <b>Define</b>
  331. <ul>
  332. <code>define EMC1001 I2C_EMC1001 [&lt;I2C Address&gt;]</code><br><br>
  333. without defined <code>&lt;I2C Address&gt;</code> 0x48 will be used as address<br>
  334. <br>
  335. Examples:
  336. <pre>
  337. define EMC1001 I2C_EMC1001 0x48
  338. attr EMC1001 poll_interval 5
  339. attr roundTemperatureDecimal 2
  340. </pre>
  341. </ul>
  342. <a name="I2C_EMC1001set"></a>
  343. <b>Set</b>
  344. <ul>
  345. <code>set EMC1001 &lt;readValues&gt;</code>
  346. <br><br>
  347. Reads current temperature values from the sensor.<br>
  348. Normaly this execute automaticly at each poll intervall. You can execute
  349. this manually if you want query the current values.
  350. <br><br>
  351. </ul>
  352. <a name="I2C_EMC1001attr"></a>
  353. <b>Attributes</b>
  354. <ul>
  355. <li>poll_interval<br>
  356. Set the polling interval in minutes to query the sensor for new measured
  357. values.<br>
  358. Default: 5, valid values: any whole number<br><br>
  359. </li>
  360. <li>roundTemperatureDecimal<br>
  361. Round temperature values to given decimal places.<br>
  362. Default: 1, valid values: 0, 1, 2<br><br>
  363. </li>
  364. <li><a href="#IODev">IODev</a></li>
  365. <li><a href="#do_not_notify">do_not_notify</a></li>
  366. <li><a href="#showtime">showtime</a></li>
  367. </ul><br>
  368. </ul>
  369. =end html
  370. =begin html_DE
  371. <a name="I2C_EMC1001"></a>
  372. <h3>I2C_EMC1001</h3>
  373. (<a href="commandref.html#I2C_EMC1001">en</a> | de)
  374. <ul>
  375. <a name="I2C_EMC1001"></a>
  376. Erm&ouml;glicht die Verwendung eines digitalen Temperatur EMC1001 &uuml;ber den I2C Bus des Raspberry Pi.<br><br>
  377. I2C-Botschaften werden &uuml;ber ein I2C Interface Modul wie beispielsweise das <a href="#RPII2C">RPII2C</a>, <a href="#FRM">FRM</a>
  378. oder <a href="#NetzerI2C">NetzerI2C</a> gesendet. Daher muss dieses vorher definiert werden.<br>
  379. <b>Das Attribut IODev muss definiert sein.</b><br>
  380. <b>Define</b>
  381. <ul>
  382. <code>define EMC1001 &lt;EMC1001_name&gt; [&lt;I2C Addresse&gt;]</code><br><br>
  383. Fehlt <code>&lt;I2C Address&gt;</code> wird 0x48 verwendet<br>
  384. <br>
  385. Beispiel:
  386. <pre>
  387. define EMC1001 I2C_EMC1001 0x48
  388. attr EMC1001 poll_interval 5
  389. attr roundTemperatureDecimal 2
  390. </pre>
  391. </ul>
  392. <a name="I2C_EMC1001set"></a>
  393. <b>Set</b>
  394. <ul>
  395. <code>set EMC1001 readValues</code>
  396. <br><br>
  397. <code>set &lt;name&gt; readValues</code><br>
  398. Aktuelle Temperatur Werte vom Sensor lesen.<br><br>
  399. </ul>
  400. <a name="I2C_EMC1001attr"></a>
  401. <b>Attribute</b>
  402. <ul>
  403. <li>poll_interval<br>
  404. Definiert das Poll Intervall in Minuten f&uuml;r das Auslesen einer neuen Messung.<br>
  405. Default: 5, g&uuml;ltige Werte: 1, 2, 5, 10, 20, 30<br><br>
  406. </li>
  407. <li>roundTemperatureDecimal<br>
  408. Rundet jeweils den Temperaturwert mit den angegebenen Nachkommastellen.<br>
  409. Standard: 1, g&uuml;ltige Werte: 0, 1, 2<br><br>
  410. </li>
  411. <li><a href="#IODev">IODev</a></li>
  412. <li><a href="#do_not_notify">do_not_notify</a></li>
  413. <li><a href="#showtime">showtime</a></li>
  414. </ul><br>
  415. </ul>
  416. =end html_DE
  417. =cut