52_I2C_BME280.pm 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632
  1. # $Id: 52_I2C_BME280.pm 12714 2016-12-04 21:34:03Z klauswitt $
  2. =head1
  3. 52_I2C_BME280.pm
  4. =head1 SYNOPSIS
  5. Modul for FHEM for reading a BME280 digital pressure/humidity sensor via I2C
  6. =cut
  7. package main;
  8. use strict;
  9. use warnings;
  10. use constant {
  11. BME280_I2C_ADDRESS => 0x76,
  12. };
  13. ##################################################
  14. # Forward declarations
  15. #
  16. sub I2C_BME280_I2CRec ($$);
  17. sub I2C_BME280_GetReadings ($$);
  18. sub I2C_BME280_GetTemp ($@);
  19. sub I2C_BME280_GetPress ($@);
  20. sub I2C_BME280_GetHum ($@);
  21. sub I2C_BME280_calcTrueTemperature($$);
  22. sub I2C_BME280_calcTrueHumidity($$);
  23. sub I2C_BME280_calcTruePressure($$);
  24. my %sets = (
  25. 'readValues' => 1,
  26. );
  27. sub I2C_BME280_Initialize($) {
  28. my ($hash) = @_;
  29. $hash->{DefFn} = 'I2C_BME280_Define';
  30. $hash->{InitFn} = 'I2C_BME280_Init';
  31. $hash->{AttrFn} = 'I2C_BME280_Attr';
  32. $hash->{SetFn} = 'I2C_BME280_Set';
  33. #$hash->{GetFn} = 'I2C_BME280_Get';
  34. $hash->{UndefFn} = 'I2C_BME280_Undef';
  35. $hash->{I2CRecFn} = 'I2C_BME280_I2CRec';
  36. $hash->{AttrList} = 'IODev do_not_notify:0,1 showtime:0,1 poll_interval:1,2,5,10,20,30 ' .
  37. 'oversampling_t:0,1,2,3,4,5 oversampling_p:0,1,2,3,4,5 oversampling_h:0,1,2,3,4,5 ' .
  38. 'roundPressureDecimal:0,1,2 roundTemperatureDecimal:0,1,2 roundHumidityDecimal:0,1,2 ' .
  39. $readingFnAttributes;
  40. $hash->{DbLog_splitFn} = "I2C_BME280_DbLog_splitFn";
  41. }
  42. sub I2C_BME280_Define($$) {
  43. my ($hash, $def) = @_;
  44. my @a = split('[ \t][ \t]*', $def);
  45. $hash->{STATE} = 'defined';
  46. my $name = $a[0];
  47. my $msg = '';
  48. if((@a < 2)) {
  49. $msg = 'wrong syntax: define <name> I2C_BME280 [I2C-Address]';
  50. }
  51. if ($msg) {
  52. Log3 ($hash, 1, $msg);
  53. return $msg;
  54. }
  55. if ($main::init_done) {
  56. eval { I2C_BME280_Init( $hash, [ @a[ 2 .. scalar(@a) - 1 ] ] ); };
  57. return I2C_BME280_Catch($@) if $@;
  58. }
  59. }
  60. sub I2C_BME280_Init($$) { # wird bei FHEM start Define oder wieder
  61. my ( $hash, $args ) = @_;
  62. my $name = $hash->{NAME};
  63. if (defined (my $address = shift @$args)) {
  64. $hash->{I2C_Address} = $address =~ /^0x.*$/ ? oct($address) : $address;
  65. return "$name: I2C Address not valid" unless ($hash->{I2C_Address} < 128 && $hash->{I2C_Address} > 3);
  66. } else {
  67. $hash->{I2C_Address} = BME280_I2C_ADDRESS;
  68. }
  69. my $msg = '';
  70. # create default attributes
  71. #if (AttrVal($name, 'poll_interval', '?') eq '?') {
  72. # $msg = CommandAttr(undef, $name . ' poll_interval 5');
  73. # if ($msg) {
  74. # Log3 ($hash, 1, $msg);
  75. # return $msg;
  76. # }
  77. #}
  78. eval {
  79. AssignIoPort($hash, AttrVal($hash->{NAME},"IODev",undef));
  80. I2C_BME280_i2cread($hash, 0xD0, 1); #get Id
  81. $hash->{STATE} = 'getCalData';
  82. I2C_BME280_i2cread($hash, 0x88, 26);
  83. I2C_BME280_i2cread($hash, 0xE1, 8);
  84. };
  85. return I2C_BME280_Catch($@) if $@;
  86. }
  87. sub I2C_BME280_Catch($) { # Fehlermeldungen formattieren
  88. my $exception = shift;
  89. if ($exception) {
  90. $exception =~ /^(.*)( at.*FHEM.*)$/;
  91. return $1;
  92. }
  93. return undef;
  94. }
  95. sub I2C_BME280_Attr (@) { # Wird beim Attribut anlegen/aendern aufgerufen
  96. my ($command, $name, $attr, $val) = @_;
  97. my $hash = $defs{$name};
  98. my $msg = '';
  99. if (defined $command && $command eq "set" && $attr eq "IODev") {
  100. eval {
  101. if ($main::init_done and (!defined ($hash->{IODev}) or $hash->{IODev}->{NAME} ne $val)) {
  102. main::AssignIoPort($hash,$val);
  103. my @def = split (' ',$hash->{DEF});
  104. I2C_BME280_Init($hash,\@def) if (defined ($hash->{IODev}));
  105. }
  106. };
  107. $msg = I2C_BME280_Catch($@) if $@;
  108. } elsif ($attr eq 'poll_interval') {
  109. if (defined($val)) {
  110. if ($val =~ m/^(0*[1-9][0-9]*)$/) {
  111. RemoveInternalTimer($hash);
  112. I2C_BME280_Poll($hash) if ($main::init_done);
  113. #InternalTimer(gettimeofday() + 5, 'I2C_BME280_Poll', $hash, 0) if ($main::init_done);
  114. } else {
  115. $msg = 'Wrong poll intervall defined. poll_interval must be a number > 0';
  116. }
  117. } else {
  118. RemoveInternalTimer($hash);
  119. }
  120. } elsif ($attr =~ m/^oversampling_.$/ && defined($val)) {
  121. $msg = 'Wrong value: $val for $attr defined. value must be a one of 0,1,2,3,4,5' unless ($val =~ m/^(0*[0-5])$/);
  122. } elsif ($attr =~ m/^round(Pressure|Temperature|Humidity)Decimal$/ && defined($val)) {
  123. $msg = 'Wrong value: $val for $attr defined. value must be a one of 0,1,2' unless ($val =~ m/^(0*[0-2])$/);
  124. }
  125. return ($msg) ? $msg : undef;
  126. }
  127. sub I2C_BME280_Poll($) { # Messwerte regelmaessig anfordern
  128. my ($hash) = @_;
  129. my $name = $hash->{NAME};
  130. I2C_BME280_Set($hash, ($name, 'readValues')); # Read values
  131. my $pollInterval = AttrVal($hash->{NAME}, 'poll_interval', 0);
  132. if ($pollInterval > 0) {
  133. InternalTimer(gettimeofday() + ($pollInterval * 60), 'I2C_BME280_Poll', $hash, 0);
  134. }
  135. }
  136. sub I2C_BME280_Set($@) { # Messwerte manuell anfordern
  137. my ($hash, @a) = @_;
  138. my $name = $a[0];
  139. my $cmd = $a[1];
  140. if(!defined($sets{$cmd})) {
  141. return 'Unknown argument ' . $cmd . ', choose one of ' . join(' ', keys %sets) . ":noArg"
  142. }
  143. if ($cmd eq 'readValues') {
  144. if (defined($hash->{calibrationData}{dig_H6})) { # query sensor
  145. I2C_BME280_i2cwrite($hash, 0xF2, AttrVal($name, 'oversampling_h', 1) & 7);
  146. my $data = ( AttrVal($name, 'oversampling_t', 1) & 7 ) << 5 | ( AttrVal($name, 'oversampling_p', 1) & 7 ) << 2 | 1; #Register 0xF4 “ctrl_meas” zusammenbasteln
  147. I2C_BME280_i2cwrite($hash, 0xF4, $data);
  148. RemoveInternalTimer($hash);
  149. InternalTimer(gettimeofday() + 1, 'I2C_BME280_UpdateReadings', $hash, 0); #nach 1s Werte auslesen
  150. } else { #..but get calibration variables first
  151. Log3 $hash, 5, "$name: in set but no calibrationData, requesting again";
  152. I2C_BME280_i2cread($hash, 0x88, 26);
  153. I2C_BME280_i2cread($hash, 0xE1, 8);
  154. }
  155. }
  156. return undef
  157. }
  158. sub I2C_BME280_Get($@) { # Messwerte manuell anfordern
  159. my ($hash, @a) = @_;
  160. my $name = $a[0];
  161. my $cmd = $a[1];
  162. if (defined($cmd) && $cmd eq 'readValues') {
  163. if (defined($hash->{calibrationData}{dig_H6})) { # query sensor
  164. I2C_BME280_i2cwrite($hash, 0xF2, AttrVal($name, 'oversampling_h', 1) & 7);
  165. my $data = ( AttrVal($name, 'oversampling_t', 1) & 7 ) << 5 | ( AttrVal($name, 'oversampling_p', 1) & 7 ) << 2 | 1; #Register 0xF4 “ctrl_meas” zusammenbasteln
  166. I2C_BME280_i2cwrite($hash, 0xF4, $data);
  167. RemoveInternalTimer($hash);
  168. InternalTimer(gettimeofday() + 1, 'I2C_BME280_UpdateReadings', $hash, 0); #nach 1s Werte auslesen
  169. } else { #..but get calibration variables first
  170. Log3 $hash, 5, "$name: in set but no calibrationData, requesting again";
  171. I2C_BME280_i2cread($hash, 0x88, 26);
  172. I2C_BME280_i2cread($hash, 0xE1, 8);
  173. }
  174. } else {
  175. return 'Unknown argument ' . $cmd . ', choose one of readValues:noArg';
  176. }
  177. return undef
  178. }
  179. sub I2C_BME280_UpdateReadings($) { # Messwerte auslesen
  180. my ($hash) = @_;
  181. I2C_BME280_i2cread($hash, 0xF7, 8); # alle Werte auslesen
  182. my $pollInterval = AttrVal($hash->{NAME}, 'poll_interval', 0); #poll_interval Timer wiederherstellen
  183. InternalTimer(gettimeofday() + ($pollInterval * 60), 'I2C_BME280_Poll', $hash, 0) if ($pollInterval > 0);
  184. }
  185. sub I2C_BME280_Undef($$) { # Device loeschen
  186. my ($hash, $arg) = @_;
  187. RemoveInternalTimer($hash);
  188. return undef;
  189. }
  190. sub I2C_BME280_I2CRec ($$) { # wird vom IODev aus aufgerufen wenn I2C Daten vorliegen
  191. my ($hash, $clientmsg) = @_;
  192. my $name = $hash->{NAME};
  193. my $pname = undef;
  194. my $phash = $hash->{IODev};
  195. $pname = $phash->{NAME};
  196. while ( my ( $k, $v ) = each %$clientmsg ) { #erzeugen von Internals fuer alle Keys in $clientmsg die mit dem physical Namen beginnen
  197. $hash->{$k} = $v if $k =~ /^$pname/ ;
  198. }
  199. if ( $clientmsg->{direction} && $clientmsg->{reg} && $clientmsg->{$pname . "_SENDSTAT"} && $clientmsg->{$pname . "_SENDSTAT"} eq "Ok" ) {
  200. if ( $clientmsg->{direction} eq "i2cread" && defined($clientmsg->{received}) ) {
  201. Log3 $hash, 5, "$name Rx, Reg: $clientmsg->{reg}, Data: $clientmsg->{received}";
  202. I2C_BME280_GetCal1 ($hash, $clientmsg->{received}) if $clientmsg->{reg} == 0x88 && $clientmsg->{nbyte} == 26;
  203. I2C_BME280_GetCal2 ($hash, $clientmsg->{received}) if $clientmsg->{reg} == 0xE1 && $clientmsg->{nbyte} >= 8;
  204. I2C_BME280_GetId ($hash, $clientmsg->{received}) if $clientmsg->{reg} == 0xD0;
  205. I2C_BME280_GetReadings ($hash, $clientmsg->{received}) if $clientmsg->{reg} == 0xF7 && $clientmsg->{nbyte} == 8;
  206. }
  207. }
  208. return undef
  209. }
  210. sub I2C_BME280_GetId ($$) { # empfangenes Id Byte auswerten
  211. my ($hash, $rawdata) = @_;
  212. if ($rawdata == hex("60")) {
  213. $hash->{DeviceType} = "BME280";
  214. } elsif ($rawdata == hex("58")) {
  215. $hash->{DeviceType} = "BMP280";
  216. } if ($rawdata == hex("56") || $rawdata == hex("57")) {
  217. $hash->{DeviceType} = "BMP280s";
  218. }
  219. }
  220. sub I2C_BME280_GetCal1 ($$) { # empfangene Cal Daten in Internals Speichern
  221. my ($hash, $rawdata) = @_;
  222. my @raw = split(" ",$rawdata);
  223. my $n = 0;
  224. $hash->{calibrationData}{dig_T1} = I2C_BME280_GetCalVar($raw[$n++], $raw[$n++], 0); # unsigned
  225. $hash->{calibrationData}{dig_T2} = I2C_BME280_GetCalVar($raw[$n++], $raw[$n++]);
  226. $hash->{calibrationData}{dig_T3} = I2C_BME280_GetCalVar($raw[$n++], $raw[$n++]);
  227. $hash->{calibrationData}{dig_P1} = I2C_BME280_GetCalVar($raw[$n++], $raw[$n++], 0); # unsigned
  228. $hash->{calibrationData}{dig_P2} = I2C_BME280_GetCalVar($raw[$n++], $raw[$n++]);
  229. $hash->{calibrationData}{dig_P3} = I2C_BME280_GetCalVar($raw[$n++], $raw[$n++]);
  230. $hash->{calibrationData}{dig_P4} = I2C_BME280_GetCalVar($raw[$n++], $raw[$n++]);
  231. $hash->{calibrationData}{dig_P5} = I2C_BME280_GetCalVar($raw[$n++], $raw[$n++]);
  232. $hash->{calibrationData}{dig_P6} = I2C_BME280_GetCalVar($raw[$n++], $raw[$n++]);
  233. $hash->{calibrationData}{dig_P7} = I2C_BME280_GetCalVar($raw[$n++], $raw[$n++]);
  234. $hash->{calibrationData}{dig_P8} = I2C_BME280_GetCalVar($raw[$n++], $raw[$n++]);
  235. $hash->{calibrationData}{dig_P9} = I2C_BME280_GetCalVar($raw[$n++], $raw[$n++]);
  236. $n++;
  237. $hash->{calibrationData}{dig_H1} = $raw[$n++]; # unsigned
  238. $hash->{STATE} = 'First calibration block received';
  239. return
  240. }
  241. sub I2C_BME280_GetCal2 ($$) { # empfangene Cal Daten in Internals Speichern Teil 2
  242. my ($hash, $rawdata) = @_;
  243. my @raw = split(" ",$rawdata);
  244. my $n = 0;
  245. $hash->{calibrationData}{dig_H2} = I2C_BME280_GetCalVar($raw[$n++], $raw[$n++]);
  246. $hash->{calibrationData}{dig_H3} = $raw[$n++]; # unsigned
  247. $hash->{calibrationData}{dig_H4} = ($raw[$n++] << 4) | $raw[$n] & 0xF; # signed word, kann aber nur positiv sein, da nur 12 bit
  248. $hash->{calibrationData}{dig_H5} = (($raw[$n++] >> 4) & 0x0F) | ($raw[$n++] << 4); # signed word, kann aber nur positiv sein, da nur 12 bit
  249. $hash->{calibrationData}{dig_H6} = I2C_BME280_GetCalVar($raw[$n++], undef); # signed 8bit #geht das? oder muss I2C_BME280_GetCalVar ($;$$) angepasst werden
  250. $hash->{STATE} = 'Initialized';
  251. I2C_BME280_Poll($hash) if defined(AttrVal($hash->{NAME}, 'poll_interval', undef)); # wenn poll_interval definiert -> timer starten
  252. return
  253. }
  254. sub I2C_BME280_GetCalVar ($$;$) { # Variablen aus Bytes zusammenbauen (signed und unsigned)
  255. my ($lsb, $msb, $returnSigned) = @_;
  256. $returnSigned = (!defined($returnSigned) || $returnSigned == 1) ? 1 : 0;
  257. my $retVal = undef;
  258. if (defined $msb) { # 16 bit Variable
  259. $retVal = $msb << 8 | $lsb;
  260. # check if we need return signed or unsigned int
  261. if ($returnSigned == 1) {
  262. $retVal = $retVal >> 15 ? $retVal - 2**16 : $retVal;
  263. }
  264. } else { # 8 bit Variable
  265. $retVal = $lsb >> 7 ? $lsb - 2 ** 8 : $lsb;
  266. }
  267. return $retVal;
  268. }
  269. sub I2C_BME280_GetReadings ($$) { # Empfangene Messwerte verarbeiten
  270. my ($hash, $rawdata) = @_;
  271. my @raw = split(" ",$rawdata);
  272. my @pres = splice(@raw,0,3);
  273. my @temp = splice(@raw,0,3);
  274. I2C_BME280_GetTemp ($hash, @temp );
  275. I2C_BME280_GetPress ($hash, @pres);
  276. I2C_BME280_GetHum ($hash, @raw );
  277. my $tem = ReadingsVal($hash->{NAME},"temperature", undef);
  278. my $hum = ReadingsVal($hash->{NAME},"humidity", undef);
  279. my $prs = ReadingsVal($hash->{NAME},"pressure", undef);
  280. readingsSingleUpdate(
  281. $hash,
  282. 'state',
  283. ((defined $tem ? "T: $tem " : "") . (defined $hum ? "H: $hum " : "") . (defined $prs ? ("P: $prs P-NN: " . ReadingsVal($hash->{NAME},"pressure-nn", 0)) : "")),
  284. 1
  285. );
  286. }
  287. sub I2C_BME280_GetTemp ($@) { # Temperatur Messwerte verarbeiten
  288. my ($hash, @raw) = @_;
  289. if ( $raw[0] == 0x80 && $raw[1] == 0 && $raw[2] == 0 ) { # 0x80000 (MSB = 0x80) wird ausgegeben, wenn Temperaturmessung deaktiviert (oversampling_t = 0)
  290. Log3 $hash, 4, "temperature reading deleted due to oversampling_t = 0";
  291. delete ($hash->{READINGS}{temperature});
  292. } else {
  293. my $ut = $raw[0] << 12 | $raw[1] << 4 | $raw[2] >> 4 ;
  294. my $temperature = sprintf(
  295. '%.' . AttrVal($hash->{NAME}, 'roundTemperatureDecimal', 1) . 'f',
  296. I2C_BME280_calcTrueTemperature($hash, $ut)
  297. );
  298. readingsSingleUpdate($hash, 'temperature', $temperature, 1);
  299. }
  300. }
  301. sub I2C_BME280_GetPress ($@) { # Luftdruck Messwerte verarbeiten
  302. my ($hash, @raw) = @_;
  303. if ( $raw[0] == 0x80 && $raw[1] == 0 && $raw[2] == 0 ) { # 0x80000 (MSB = 0x80) wird ausgegeben, wenn Luftdruckmessung deaktiviert (oversampling_p = 0)
  304. Log3 $hash, 4, "pressure readings seleted due to oversampling_p = 0";
  305. delete ($hash->{READINGS}{'pressure'});
  306. delete ($hash->{READINGS}{'pressure-nn'});
  307. } else {
  308. my $up = $raw[0] << 12 | $raw[1] << 4 | $raw[2] >> 4 ;
  309. my $pressure = sprintf(
  310. '%.' . AttrVal($hash->{NAME}, 'roundPressureDecimal', 1) . 'f',
  311. I2C_BME280_calcTruePressure($hash, $up) / 100
  312. );
  313. my $altitude = AttrVal('global', 'altitude', 0);
  314. # simple barometric height formula
  315. my $pressureNN = sprintf(
  316. '%.' . AttrVal($hash->{NAME}, 'roundPressureDecimal', 1) . 'f',
  317. $pressure + ($altitude / 8.5)
  318. );
  319. readingsBeginUpdate($hash);
  320. readingsBulkUpdate($hash, 'pressure', $pressure);
  321. readingsBulkUpdate($hash, 'pressure-nn', $pressureNN);
  322. readingsEndUpdate($hash, 1);
  323. }
  324. }
  325. sub I2C_BME280_GetHum ($@) { # Luftfeuchte Messwerte verarbeiten
  326. my ($hash, @raw) = @_;
  327. if ( $raw[0] == 0x80 && $raw[1] == 0 ) { # 0x8000 (MSB = 0x80) wird ausgegeben, wenn Feuchtemessung deaktiviert (oversampling_h = 0)
  328. Log3 $hash, 4, "humidity readings seleted due to oversampling_h = 0";
  329. delete ($hash->{READINGS}{humidity})
  330. } else {
  331. my $uh = $raw[0] << 8 | $raw[1];
  332. my $humidity = sprintf(
  333. '%.' . AttrVal($hash->{NAME}, 'roundHumidityDecimal', 1) . 'f',
  334. I2C_BME280_calcTrueHumidity($hash, $uh)
  335. );
  336. readingsSingleUpdate($hash, 'humidity', $humidity, 1);
  337. }
  338. }
  339. sub I2C_BME280_i2cread($$$) { # Lesebefehl an Hardware absetzen (antwort kommt in I2C_*****_I2CRec an)
  340. my ($hash, $reg, $nbyte) = @_;
  341. if (defined (my $iodev = $hash->{IODev})) {
  342. Log3 $hash, 5, "$hash->{NAME}: $hash->{I2C_Address} read $nbyte Byte from Register $reg";
  343. CallFn($iodev->{NAME}, "I2CWrtFn", $iodev, {
  344. direction => "i2cread",
  345. i2caddress => $hash->{I2C_Address},
  346. reg => $reg,
  347. nbyte => $nbyte
  348. });
  349. } else {
  350. return "no IODev assigned to '$hash->{NAME}'";
  351. }
  352. }
  353. sub I2C_BME280_i2cwrite($$$) { # Schreibbefehl an Hardware absetzen
  354. my ($hash, $reg, @data) = @_;
  355. if (defined (my $iodev = $hash->{IODev})) {
  356. Log3 $hash, 5, "$hash->{NAME}: $hash->{I2C_Address} write " . join (' ',@data) . " to Register $reg";
  357. CallFn($iodev->{NAME}, "I2CWrtFn", $iodev, {
  358. direction => "i2cwrite",
  359. i2caddress => $hash->{I2C_Address},
  360. reg => $reg,
  361. data => join (' ',@data),
  362. });
  363. } else {
  364. return "no IODev assigned to '$hash->{NAME}'";
  365. }
  366. }
  367. sub I2C_BME280_calcTrueTemperature($$) { # Temperatur aus Rohwerten berechnen
  368. my ($hash, $ut) = @_;
  369. my $dig_T1 = $hash->{calibrationData}{dig_T1};
  370. my $dig_T2 = $hash->{calibrationData}{dig_T2};
  371. my $dig_T3 = $hash->{calibrationData}{dig_T3};
  372. my $h1 = ( $ut / 16384.0 - $dig_T1 / 1024.0 ) * $dig_T2;
  373. my $h2 = ( ( $ut / 131072.0 - $dig_T1 / 8192.0 ) * ( $ut / 131072.0 - $dig_T1 / 8192.0 ) ) * $dig_T3;
  374. $hash->{calibrationData}{t_fine} = $h1 + $h2;
  375. my $ct = $hash->{calibrationData}{t_fine} / 5120.0;
  376. return $ct;
  377. }
  378. sub I2C_BME280_calcTruePressure($$) { # Luftdruck aus Rohwerten berechnen
  379. my ($hash, $up) = @_;
  380. my $t_fine = $hash->{calibrationData}{t_fine};
  381. my $dig_P1 = $hash->{calibrationData}{dig_P1};
  382. my $dig_P2 = $hash->{calibrationData}{dig_P2};
  383. my $dig_P3 = $hash->{calibrationData}{dig_P3};
  384. my $dig_P4 = $hash->{calibrationData}{dig_P4};
  385. my $dig_P5 = $hash->{calibrationData}{dig_P5};
  386. my $dig_P6 = $hash->{calibrationData}{dig_P6};
  387. my $dig_P7 = $hash->{calibrationData}{dig_P7};
  388. my $dig_P8 = $hash->{calibrationData}{dig_P8};
  389. my $dig_P9 = $hash->{calibrationData}{dig_P9};
  390. my $h1 = ($t_fine / 2) - 64000.0;
  391. my $h2 = $h1 * $h1 * $dig_P6 / 32768;
  392. $h2 = $h2 + $h1 * $dig_P5 * 2;
  393. $h2 = $h2 / 4 + $dig_P4 * 65536;
  394. #$h1 = $dig_P3 * $h1 * $h1 / 524288 + $dig_P2 * $h1 / 524288;
  395. $h1 = ((($dig_P3 * ((($h1/4.0) * ($h1/4.0)) / 8192)) / 8) + (($dig_P2 * $h1) / 2.0)) / 262144;
  396. #$h1 = ( 1 + $h1 / 32768) * $dig_P1;
  397. $h1 = ((32768 + $h1) * $dig_P1) / 32768;
  398. return 0 if ($h1 == 0);
  399. my $p = ((1048576 - $up) - ($h2 / 4096)) * 3125;
  400. if ($p < 0x80000000) {
  401. $p = ($p * 2) / $h1;
  402. } else {
  403. $p = ($p / $h1) * 2 ;
  404. }
  405. #$p = ( $p - $h2 / 4096 ) * 6250 / $h1;
  406. $h1 = ($dig_P9 * ((($p/8.0) * ($p/8.0)) / 8192.0)) / 4096;
  407. $h2 = (($p/4.0) * $dig_P8) / 8192.0;
  408. $p = $p + (($h1 + $h2 + $dig_P7) / 16);
  409. return $p;
  410. }
  411. sub I2C_BME280_calcTrueHumidity($$) { # Luftfeuchte aus Rohwerten berechnen
  412. my ($hash, $uh) = @_;
  413. my $t_fine = $hash->{calibrationData}{t_fine};
  414. my $dig_H1 = $hash->{calibrationData}{dig_H1};
  415. my $dig_H2 = $hash->{calibrationData}{dig_H2};
  416. my $dig_H3 = $hash->{calibrationData}{dig_H3};
  417. my $dig_H4 = $hash->{calibrationData}{dig_H4};
  418. my $dig_H5 = $hash->{calibrationData}{dig_H5};
  419. my $dig_H6 = $hash->{calibrationData}{dig_H6};
  420. my $t1 = $t_fine - 76800;
  421. $t1 = ( $uh - ( $dig_H4 * 64 + $dig_H5 / 16384 * $t1 ) ) * ( $dig_H2 / 65536 * ( 1 + $dig_H6 / 67108864 * $t1 * ( 1 + $dig_H3 / 67108864 * $t1 ) ) );
  422. $t1 = $t1 * ( 1 - $dig_H1 * $t1 / 524288);
  423. if ($t1 > 100) {
  424. $t1 = 100;
  425. } elsif ($t1 < 0) {
  426. $t1 = 0;
  427. }
  428. return $t1;
  429. }
  430. sub I2C_BME280_DbLog_splitFn($) { # Einheiten
  431. my ($event) = @_;
  432. Log3 undef, 5, "in DbLog_splitFn empfangen: $event";
  433. my ($reading, $value, $unit) = "";
  434. my @parts = split(/ /,$event);
  435. $reading = shift @parts;
  436. $reading =~ tr/://d;
  437. $value = $parts[0];
  438. $unit = "\xB0C" if(lc($reading) =~ m/temp/);
  439. $unit = "hPa" if(lc($reading) =~ m/pres/);
  440. $unit = "%" if(lc($reading) =~ m/humi/);
  441. return ($reading, $value, $unit);
  442. }
  443. 1;
  444. =pod
  445. =item device
  446. =item summary reads pressure, humidity and temperature from an via I2C connected BME280
  447. =item summary_DE lese Druck, Feuchte und Temperatur eines &uuml;ber I2C angeschlossenen BME280
  448. =begin html
  449. <a name="I2C_BME280"></a>
  450. <h3>I2C_BME280</h3>
  451. (en | <a href="commandref_DE.html#I2C_BME280">de</a>)
  452. <ul>
  453. <a name="I2C_BME280"></a>
  454. Provides an interface to the digital pressure/humidity sensor BME280
  455. The I2C messages are send through an I2C interface module like <a href="#RPII2C">RPII2C</a>, <a href="#FRM">FRM</a>
  456. or <a href="#NetzerI2C">NetzerI2C</a> so this device must be defined first.<br>
  457. <b>attribute IODev must be set</b><br>
  458. <b>Define</b>
  459. <ul>
  460. <code>define BME280 I2C_BME280 [&lt;I2C Address&gt;]</code><br><br>
  461. without defined <code>&lt;I2C Address&gt;</code> 0x76 will be used as address<br>
  462. <br>
  463. Examples:
  464. <pre>
  465. define BME280 I2C_BME280 0x77
  466. attr BME280 poll_interval 5
  467. </pre>
  468. </ul>
  469. <a name="I2C_BME280set"></a>
  470. <b>Set</b>
  471. <ul>
  472. <code>set BME280 &lt;readValues&gt;</code>
  473. <br><br>
  474. Reads current temperature, humidity and pressure values from the sensor.<br>
  475. Normaly this execute automaticly at each poll intervall. You can execute
  476. this manually if you want query the current values.
  477. <br><br>
  478. </ul>
  479. <a name="I2C_BME280attr"></a>
  480. <b>Attributes</b>
  481. <ul>
  482. <li>oversampling_t,oversampling_h,oversampling_p<br>
  483. Controls the oversampling settings of the temperature,humidity or pressure measurement in the sensor.<br>
  484. Default: 1, valid values: 0, 1, 2, 3, 4, 5<br>
  485. 0 switches the respective measurement off<br>
  486. 1 to 5 complies to oversampling value 2^value/2<br><br>
  487. </li>
  488. <li>poll_interval<br>
  489. Set the polling interval in minutes to query the sensor for new measured
  490. values.<br>
  491. Default: 5, valid values: any whole number<br><br>
  492. </li>
  493. <li>roundTemperatureDecimal,roundHumidityDecimal,roundPressureDecimal<br>
  494. Round temperature, humidity or pressure values to given decimal places.<br>
  495. Default: 1, valid values: 0, 1, 2<br><br>
  496. </li>
  497. <li>altitude<br>
  498. if set, this altitude is used for calculating the pressure related to sea level (nautic null) NN<br><br>
  499. Note: this is a global attributes, e.g<br>
  500. <code>attr global altitude 220</code>
  501. </li>
  502. <li><a href="#IODev">IODev</a></li>
  503. <li><a href="#do_not_notify">do_not_notify</a></li>
  504. <li><a href="#showtime">showtime</a></li>
  505. </ul><br>
  506. </ul>
  507. =end html
  508. =begin html_DE
  509. <a name="I2C_BME280"></a>
  510. <h3>I2C_BME280</h3>
  511. (<a href="commandref.html#I2C_BME280">en</a> | de)
  512. <ul>
  513. <a name="I2C_BME280"></a>
  514. Erm&ouml;glicht die Verwendung eines digitalen (Luft)druck/feuchtesensors BME280 &uuml;ber den I2C Bus des Raspberry Pi.<br><br>
  515. I2C-Botschaften werden &uuml;ber ein I2C Interface Modul wie beispielsweise das <a href="#RPII2C">RPII2C</a>, <a href="#FRM">FRM</a>
  516. oder <a href="#NetzerI2C">NetzerI2C</a> gesendet. Daher muss dieses vorher definiert werden.<br>
  517. <b>Das Attribut IODev muss definiert sein.</b><br>
  518. <b>Define</b>
  519. <ul>
  520. <code>define BME280 &lt;BME280_name&gt; [&lt;I2C Addresse&gt;]</code><br><br>
  521. Fehlt <code>&lt;I2C Address&gt;</code> wird 0x76 verwendet<br>
  522. <br>
  523. Beispiel:
  524. <pre>
  525. define BME280 I2C_BME280 0x77
  526. attr BME280 poll_interval 5
  527. </pre>
  528. </ul>
  529. <a name="I2C_BME280set"></a>
  530. <b>Set</b>
  531. <ul>
  532. <code>set BME280 readValues</code>
  533. <br><br>
  534. <code>set &lt;name&gt; readValues</code><br>
  535. Aktuelle Temperatur, Feuchte und Luftdruck Werte vom Sensor lesen.<br><br>
  536. </ul>
  537. <a name="I2C_BME280attr"></a>
  538. <b>Attribute</b>
  539. <ul>
  540. <li>oversampling_t,oversampling_h,oversampling_p<br>
  541. Steuert das jeweils das Oversampling der Temperatur-, Feuchte-, oder Druckmessung im Sensor.<br>
  542. Standard: 1, g&uuml;ltige Werte: 0, 1, 2, 3, 4, 5<br>
  543. 0 deaktiviert die jeweilige Messung<br>
  544. 1 to 5 entspricht einem Oversampling von 2^zahl/2<br><br>
  545. </li>
  546. <li>poll_interval<br>
  547. Definiert das Poll Intervall in Minuten f&uuml;r das Auslesen einer neuen Messung.<br>
  548. Default: 5, g&uuml;ltige Werte: 1, 2, 5, 10, 20, 30<br><br>
  549. </li>
  550. <li>roundTemperatureDecimal, roundHumidityDecimal, roundPressureDecimal<br>
  551. Rundet jeweils den Temperatur-, Feuchte-, oder Druckwert mit den angegebenen Nachkommastellen.<br>
  552. Standard: 1, g&uuml;ltige Werte: 0, 1, 2<br><br>
  553. </li>
  554. <li>altitude<br>
  555. Wenn dieser Wert definiert ist, wird diese Angabe zus&auml; f&uuml;r die Berechnung des
  556. Luftdrucks bezogen auf Meeresh&ouml;he (Normalnull) NN herangezogen.<br>
  557. Bemerkung: Dies ist ein globales Attribut.<br><br>
  558. <code>attr global altitude 220</code>
  559. </li>
  560. <li><a href="#IODev">IODev</a></li>
  561. <li><a href="#do_not_notify">do_not_notify</a></li>
  562. <li><a href="#showtime">showtime</a></li>
  563. </ul><br>
  564. </ul>
  565. =end html_DE
  566. =cut