=head1 51_I2C_BMP180.pm =head1 SYNOPSIS Modul for FHEM for reading a BMP180 or BMP085 digital pressure sensor via I2C connected to the Raspberry Pi. contributed by Dirk Hoffmann 2013 $Id$ =head1 DESCRIPTION 51_I2C_BMP180.pm reads the air pressure of the digital pressure sensor BMP180 or BMP085 via i2c bus connected to the Raspberry Pi. This module needs the HiPi Perl Modules see: http://raspberrypi.znix.com/hipidocs/ For a simple automated installation:
wget http://raspberry.znix.com/hipifiles/hipi-install perl hipi-install Example: define BMP180 I2C_BMP180 /dev/i2c-0 attr BMP180 poll_iterval 5 attr BMP180 oversampling_settings 3 =head1 AUTHOR - Dirk Hoffmann dirk@FHEM_Forum (forum.fhem.de) =cut package main; use strict; use warnings; use HiPi::Device::I2C; use Time::HiRes qw(usleep); use Scalar::Util qw(looks_like_number); use Error qw(:try); use constant { BMP180_I2C_ADDRESS => '0x77', }; ################################################## # Forward declarations # sub I2C_BMP180_Initialize($); sub I2C_BMP180_Define($$); sub I2C_BMP180_Attr(@); sub I2C_BMP180_Poll($); sub I2C_BMP180_Set($@); sub I2C_BMP180_Undef($$); sub I2C_BMP180_ReadInt($$;$); sub I2C_BMP180_readUncompensatedTemperature($); sub I2C_BMP180_readUncompensatedPressure($$); sub I2C_BMP180_calcTrueTemperature($$); sub I2C_BMP180_calcTruePressure($$$); my %sets = ( 'readValues' => 1, ); =head2 I2C_BMP180_Initialize Title: I2C_BMP180_Initialize Function: Implements the initialize function. Returns: - Args: named arguments: -argument1 => hash =cut sub I2C_BMP180_Initialize($) { my ($hash) = @_; $hash->{DefFn} = 'I2C_BMP180_Define'; $hash->{AttrFn} = 'I2C_BMP180_Attr'; $hash->{SetFn} = 'I2C_BMP180_Set'; $hash->{UndefFn} = 'I2C_BMP180_Undef'; $hash->{AttrList} = 'do_not_notify:0,1 showtime:0,1 model:BMP180,BMP085 ' . 'loglevel:0,1,2,3,4,5,6 poll_interval:1,2,5,10,20,30 ' . 'oversampling_settings:0,1,2,3 roundPressureDecimal:0,1,2 ' . 'roundTemperatureDecimal:0,1,2 ' . $readingFnAttributes; } =head2 I2C_BMP180_Define Title: I2C_BMP180_Define Function: Implements the define function. Returns: string|undef Args: named arguments: -argument1 => hash -argument2 => string =cut sub I2C_BMP180_Define($$) { my ($hash, $def) = @_; my @a = split('[ \t][ \t]*', $def); my $name = $a[0]; my $dev = $a[2]; my $msg = ''; if( (@a < 3)) { $msg = 'wrong syntax: define I2C_BMP180 devicename'; } # create default attributes $msg = CommandAttr(undef, $name . ' poll_interval 5'); $msg = CommandAttr(undef, $name . ' oversampling_settings 3'); if ($msg) { Log (1, $msg); return $msg; } # check for existing i2c device my $i2cModulesLoaded = 0; $i2cModulesLoaded = 1 if -e $dev; if ($i2cModulesLoaded) { $hash->{devBPM180} = HiPi::Device::I2C->new( devicename => $dev, address => hex(BMP180_I2C_ADDRESS), busmode => 'i2c', ); # read calibration data from sensor $hash->{calibrationData}{ac1} = I2C_BMP180_ReadInt($hash, 0xAA); if ( defined($hash->{calibrationData}{ac1}) ) { $hash->{calibrationData}{ac2} = I2C_BMP180_ReadInt($hash, 0xAC); $hash->{calibrationData}{ac3} = I2C_BMP180_ReadInt($hash, 0xAE); $hash->{calibrationData}{ac4} = I2C_BMP180_ReadInt($hash, 0xB0, 0); $hash->{calibrationData}{ac5} = I2C_BMP180_ReadInt($hash, 0xB2, 0); $hash->{calibrationData}{ac6} = I2C_BMP180_ReadInt($hash, 0xB4, 0); $hash->{calibrationData}{b1} = I2C_BMP180_ReadInt($hash, 0xB6); $hash->{calibrationData}{b2} = I2C_BMP180_ReadInt($hash, 0xB8); $hash->{calibrationData}{mb} = I2C_BMP180_ReadInt($hash, 0xBA); $hash->{calibrationData}{mc} = I2C_BMP180_ReadInt($hash, 0xBC); $hash->{calibrationData}{md} = I2C_BMP180_ReadInt($hash, 0xBE); } else { return $name . ': Error! I2C failure: Please check your i2c bus ' . $dev . ' and the connected device address: ' . BMP180_I2C_ADDRESS; } $hash->{STATE} = 'Initialized'; } else { return $name . ': Error! I2C device not found: ' . $dev . '. Please check kernelmodules must loaded: i2c_bcm2708, i2c_dev'; } return undef; } =head2 I2C_BMP180_Attr Title: I2C_BMP180_Attr Function: Implements AttrFn function. Returns: string|undef Args: named arguments: -argument1 => array =cut sub I2C_BMP180_Attr (@) { my (undef, $name, $attr, $val) = @_; my $hash = $defs{$name}; my $msg = ''; if ($attr eq 'poll_interval') { my $pollInterval = (defined($val) && looks_like_number($val) && $val > 0) ? $val : 0; if ($val > 0) { RemoveInternalTimer($hash); InternalTimer(1, 'I2C_BMP180_Poll', $hash, 0); } else { $msg = 'Wrong poll intervall defined. poll_interval must be a number > 0'; } } return ($msg) ? $msg : undef; } =head2 I2C_BMP180_Poll Title: I2C_BMP180_Poll Function: Start polling the sensor at interval defined in attribute Returns: - Args: named arguments: -argument1 => hash =cut sub I2C_BMP180_Poll($) { my ($hash) = @_; my $name = $hash->{NAME}; # Read values I2C_BMP180_Set($hash, ($name, 'readValues')); my $pollInterval = AttrVal($hash->{NAME}, 'poll_interval', 0); if ($pollInterval > 0) { InternalTimer(gettimeofday() + ($pollInterval * 60), 'I2C_BMP180_Poll', $hash, 0); } } =head2 I2C_BMP180_Set Title: I2C_BMP180_Set Function: Implements SetFn function. Returns: string|undef Args: named arguments: -argument1 => hash: $hash hash of device -argument2 => array: @a argument array =cut sub I2C_BMP180_Set($@) { my ($hash, @a) = @_; my $name =$a[0]; my $cmd = $a[1]; if(!defined($sets{$cmd})) { return 'Unknown argument ' . $cmd . ', choose one of ' . join(' ', keys %sets) } if ($cmd eq 'readValues') { my $overSamplingSettings = AttrVal($hash->{NAME}, 'oversampling_settings', 3); # query sensor my $ut = I2C_BMP180_readUncompensatedTemperature($hash); my $up = I2C_BMP180_readUncompensatedPressure($hash, $overSamplingSettings); my $temperature = sprintf( '%.' . AttrVal($hash->{NAME}, 'roundTemperatureDecimal', 1) . 'f', I2C_BMP180_calcTrueTemperature($hash, $ut) / 10 ); my $pressure = sprintf( '%.' . AttrVal($hash->{NAME}, 'roundPressureDecimal', 1) . 'f', I2C_BMP180_calcTruePressure($hash, $up, $overSamplingSettings) / 100 ); my $altitude = AttrVal('global', 'altitude', 0); # simple barometric height formula my $pressureNN = sprintf( '%.' . AttrVal($hash->{NAME}, 'roundPressureDecimal', 1) . 'f', $pressure + ($altitude / 8.5) ); readingsBeginUpdate($hash); readingsBulkUpdate( $hash, 'state', 'T: ' . $temperature . ' P: ' . $pressure . ' P-NN: ' . $pressureNN ); readingsBulkUpdate($hash, 'temperature', $temperature); readingsBulkUpdate($hash, 'pressure', $pressure); readingsBulkUpdate($hash, 'pressure-nn', $pressureNN); readingsBulkUpdate($hash, 'altitude', $altitude, 0); readingsEndUpdate($hash, 1); } } =head2 I2C_BMP180_Undef Title: I2C_BMP180_Undef Function: Implements UndefFn function. Returns: undef Args: named arguments: -argument1 => hash: $hash hash of device -argument2 => array: @a argument array =cut sub I2C_BMP180_Undef($$) { my ($hash, $arg) = @_; RemoveInternalTimer($hash); return undef; } =head2 I2C_BMP180_ReadInt Title: I2C_BMP180_ReadInt Function: Read 2 bytes from i2c device from given register. Returns: number Args: named arguments: -argument1 => hash: $hash hash of device -argument2 => number: $register -argument3 => boolean: $returnSigned 1, if number returned signed (optional) =cut sub I2C_BMP180_ReadInt($$;$) { my ($hash, $register, $returnSigned) = @_; my $name = $hash->{NAME}; $returnSigned = (!defined($returnSigned) || $returnSigned == 1) ? 1 : 0; my $retVal = undef; try { my @values = $hash->{devBPM180}->bus_read($register, 2); $retVal = $values[0] << 8 | $values[1]; # check if we need return signed or unsigned int if ($returnSigned == 1) { $retVal = $retVal >> 15 ? $retVal - 2**16 : $retVal; } } catch Error with { Log (1, $name . ': ERROR: I2C_BMP180: i2c-bus_read failure'); }; return $retVal; } =head2 I2C_BMP180_readUncompensatedTemperature Title: I2C_BMP180_readUncompensatedTemperature Function: Read the uncompensated temperature value. Returns: number Args: named arguments: -argument1 => hash: $hash hash of device =cut sub I2C_BMP180_readUncompensatedTemperature($) { my ($hash) = @_; # Write 0x2E into Register 0xF4. This requests a temperature reading $hash->{devBPM180}->bus_write( (0xF4, 0x2E) ); usleep(4500); # Read the two byte result from address 0xF6 my @values = $hash->{devBPM180}->bus_read(0xF6, 2); my $retVal = $values[0] << 8 | $values[1]; return $retVal; } =head2 I2C_BMP180_readUncompensatedPressure Title: I2C_BMP180_readUncompensatedPressure Function: Read the uncompensated pressure value. Returns: number Args: named arguments: -argument1 => hash: $hash hash of device -argument2 => number: $overSamplingSettings =cut sub I2C_BMP180_readUncompensatedPressure($$) { my ($hash, $overSamplingSettings) = @_; # Write 0x34+($overSamplingSettings << 6) into register 0xF4 # Request a pressure reading with oversampling setting $hash->{devBPM180}->bus_write( (0xF4, 0x34 + ($overSamplingSettings << 6)) ); # Wait for conversion, delay time dependent on oversampling setting usleep( (2 + (3 << $overSamplingSettings)) * 1000 ); # Read the three byte result from 0xF6. 0xF6 = MSB, 0xF7 = LSB and 0xF8 = XLSB my @values = $hash->{devBPM180}->bus_read(0xF6, 3); my $retVal = ( ( ($values[0] << 16) | ($values[1] << 8) | $values[2] ) >> (8 - $overSamplingSettings) ); return $retVal; } =head2 I2C_BMP180_calcTrueTemperature Title: I2C_BMP180_calcTrueTemperature Function: Calculate temperature from given uncalibrated temperature Returns: number Args: named arguments: -argument1 => hash: $hash hash of device -argument2 => number: $ut uncalibrated temperature =cut sub I2C_BMP180_calcTrueTemperature($$) { my ($hash, $ut) = @_; my $x1 = ($ut - $hash->{calibrationData}{ac6}) * $hash->{calibrationData}{ac5} / 32768; my $x2 = ($hash->{calibrationData}{mc} * 2048) / ($x1 + $hash->{calibrationData}{md}); $hash->{calibrationData}{b5} = $x1 + $x2; my $retVal = (($hash->{calibrationData}{b5} + 8) / 16); return $retVal; } =head2 I2C_BMP180_calcTruePressure Title: I2C_BMP180_calcTruePressure Function: Calculate the pressure from given uncalibrated pressure Returns: number Args: named arguments: -argument1 => hash: $hash hash of device -argument2 => number: $up uncalibrated pressure -argument3 => number: $overSamplingSettings =cut sub I2C_BMP180_calcTruePressure($$$) { my ($hash, $up, $overSamplingSettings) = @_; my $b6 = $hash->{calibrationData}{b5} - 4000; my $x1 = ($hash->{calibrationData}{b2} * ($b6 * $b6 / 4096)) / 2048; my $x2 = ($hash->{calibrationData}{ac2} * $b6) / 2048; my $x3 = $x1 + $x2; my $b3 = ((($hash->{calibrationData}{ac1} * 4 + $x3) << $overSamplingSettings) + 2) / 4; $x1 = $hash->{calibrationData}{ac3} * $b6 / 8192; $x2 = ($hash->{calibrationData}{b1} * ($b6 * $b6 / 4096)) / 65536; $x3 = (($x1 + $x2) + 2) / 4; my $b4 = $hash->{calibrationData}{ac4} * ($x3 + 32768) / 32768; my $b7 = ($up - $b3) * (50000 >> $overSamplingSettings); my $p = ($b7 < 0x80000000) ? (($b7 * 2) / $b4) : (($b7 / $b4) * 2); $x1 = ($p / 256) * ($p / 256); $x1 = ($x1 * 3038) / 65536; $x2 = (-7357 * $p) / 65536; $p += (($x1 + $x2 + 3791) / 16); return $p; } 1; =pod =begin html

I2C_BMP180

=end html =cut