diff --git a/fhem/CHANGED b/fhem/CHANGED index f709f8f5d..3c54ff491 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,7 @@ # Add changes at the top of the list. Keep it in ASCII - SVN + - feature: new module I2C_BMP180 for reading I2C digital pressure sensor BMP180 + or BMP085 connected to Raspberry Pi (Dirk) - feature: new module 35_SWAP_0000002200000003 for panstamp rgb led driver board with ir and dmx support - feature: 95_remotecontrol does not require a weblink anymore (UliM) diff --git a/fhem/FHEM/51_I2C_BMP180.pm b/fhem/FHEM/51_I2C_BMP180.pm new file mode 100644 index 000000000..8f181ed27 --- /dev/null +++ b/fhem/FHEM/51_I2C_BMP180.pm @@ -0,0 +1,525 @@ +=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); + my $txtAltitude = ''; + my $pressureNN = 0; + + # if altitude given + if ($altitude != 0) { + # simple barometric height formula + $pressureNN = sprintf( + '%.' . AttrVal($hash->{NAME}, 'roundPressureDecimal', 1) . 'f', + $pressure + ($altitude / 8.5) + ); + + $txtAltitude = ' in ' . $altitude . ' m, Pressure-NN: ' . $pressureNN . ' hPa'; + } + + readingsBeginUpdate($hash); + readingsBulkUpdate( + $hash, + 'state', + 'Temp: ' . $temperature . ' °C , Pressure: ' . $pressure . ' hPa' . $txtAltitude, + 0 + ); + readingsBulkUpdate($hash, 'temperature', $temperature); + readingsBulkUpdate($hash, 'pressure', $pressure); + + # if altitude given + if ($altitude >= 0) { + readingsBulkUpdate($hash, 'pressure-nn', $pressureNN); + } + + 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 diff --git a/fhem/HISTORY b/fhem/HISTORY index 005e0f35d..e21c7bcd8 100644 --- a/fhem/HISTORY +++ b/fhem/HISTORY @@ -529,4 +529,7 @@ - Added new module "remotecontrol" - Sun Jul 14 2013 (Alexus) - - Added new module "EGPM2LAN" and "EGPM" \ No newline at end of file + - Added new module "EGPM2LAN" and "EGPM" + +- Fri Jul 26 2013 (Dirk) + - Added new module "I2C_BMP180" \ No newline at end of file diff --git a/fhem/MAINTAINER.txt b/fhem/MAINTAINER.txt index 931179fa6..5a3e3237c 100644 --- a/fhem/MAINTAINER.txt +++ b/fhem/MAINTAINER.txt @@ -99,6 +99,7 @@ FHEM/46_TRX_SECURITY.pm wherzig http://forum.fhem.de RFXTRX FHEM/46_TRX_WEATHER.pm wherzig http://forum.fhem.de RFXTRX FHEM/49_IPCAM.pm mfr69bs http://forum.fhem.de Sonstiges FHEM/50_WS300.pm tdressler http://forum.fhem.de SlowRF +FHEM/51_I2C_BMP180.pm Dirk http://forum.fhem.de/ Raspberry Pi FHEM/56_POKEYS.pm axelberner http://forum.fhem.de Sonstiges FHEM/57_Calendar.pm borisneubert http://forum.fhem.de Sonstiges FHEM/59_HCS.pm mfr69bs http://forum.fhem.de Automatisierung @@ -122,8 +123,8 @@ FHEM/70_VIERA.pm teevau http://forum.fhem.de Sonstiges FHEM/70_WS3600.pm painseeker http://forum.fhem.de Sonstiges FHEM/71_LISTENLIVE.pm betateilchen http://forum.fhem.de Multimedia FHEM/71_YAMAHA_AVR.pm markusbloch http://forum.fhem.de Multimedia -FHEM/72_FB_CALLMONITOR.pm markusbloch http://forum.fhem.de Unterstützende Dienste -FHEM/73_PRESENCE.pm markusbloch http://forum.fhem.de Unterstützende Dienste +FHEM/72_FB_CALLMONITOR.pm markusbloch http://forum.fhem.de Unterstützende Dienste +FHEM/73_PRESENCE.pm markusbloch http://forum.fhem.de Unterstützende Dienste FHEM/75_MSG.pm ruebedo http://forum.fhem.de Automatisierung FHEM/76_MSGFile.pm ruebedo http://forum.fhem.de Automatisierung FHEM/76_MSGMail.pm ruebedo http://forum.fhem.de Automatisierung