#########################################################################################
# Package       HiPi::Interface::MPL3115A2
# Description:  Interface to MPL3115A2 precision Altimeter
# Created       Wed Mar 13 08:56:53 2013
# SVN Id        $Id: MPL3115A2.pm 87 2016-04-04 12:18:15Z Mark Dootson $
# Copyright:    Copyright (c) 2013 Mark Dootson
# Licence:      This work is free software; you can redistribute it and/or modify it 
#               under the terms of the GNU General Public License as published by the 
#               Free Software Foundation; either version 3 of the License, or any later 
#               version.
#########################################################################################

package HiPi::Interface::MPL3115A2;

#########################################################################################

use strict;
use warnings;
use parent qw( HiPi::Interface );
use HiPi::Constant qw( :raspberry :i2c_readmode );
use Carp;

our $VERSION = '0.26';

__PACKAGE__->create_accessors( qw( address peripheral osdelay backend readmode ) );

our @EXPORT = ();
our @EXPORT_OK = ();
our %EXPORT_TAGS = ( all => \@EXPORT_OK );

use constant {
    MPL_REG_STATUS              => 0x00,
    MPL_REG_OUT_P_MSB           => 0x01,
    MPL_REG_OUT_P_CSB           => 0x02,
    MPL_REG_OUT_P_LSB           => 0x03,
    MPL_REG_OUT_T_MSB           => 0x04,
    MPL_REG_OUT_T_LSB           => 0x05,
    MPL_REG_DR_STATUS           => 0x06,
    MPL_REG_OUT_P_DELTA_MSB     => 0x07,
    MPL_REG_OUT_P_DELTA_CSB     => 0x08,
    MPL_REG_OUT_P_DELTA_LSB     => 0x09,
    MPL_REG_OUT_T_DELTA_MSB     => 0x0A,
    MPL_REG_OUT_T_DELTA_LSB     => 0x0B,
    MPL_REG_WHO_AM_I            => 0x0C,
    MPL_REG_F_STATUS            => 0x0D,
    MPL_REG_F_DATA              => 0x0E,
    MPL_REG_F_SETUP             => 0x0F,
    MPL_REG_TIME_DLY            => 0x10,
    MPL_REG_SYSMOD              => 0x11,
    MPL_REG_INT_SOURCE          => 0x12,
    MPL_REG_PT_DATA_CFG         => 0x13,
    MPL_REG_BAR_IN_MSB          => 0x14,
    MPL_REG_MAR_IN_LSB          => 0x15,
    MPL_REG_P_TGT_MSB           => 0x16,
    MPL_REG_P_TGT_LSB           => 0x17,
    MPL_REG_T_TGT               => 0x18,
    MPL_REG_P_WND_MSB           => 0x19,
    MPL_REG_P_WND_LSB           => 0x1A,
    MPL_REG_T_WND               => 0x1B,
    MPL_REG_P_MIN_MSB           => 0x1C,
    MPL_REG_P_MIN_CSB           => 0x1D,
    MPL_REG_P_MIN_LSB           => 0x1E,
    MPL_REG_T_MIN_MSB           => 0x1F,
    MPL_REG_T_MIN_LSB           => 0x20,
    MPL_REG_P_MAX_MSB           => 0x21,
    MPL_REG_P_MAX_CSB           => 0x22,
    MPL_REG_P_MAX_LSB           => 0x23,
    MPL_REG_T_MAX_MSB           => 0x24,
    MPL_REG_T_MAX_LSB           => 0x25,
    MPL_REG_CTRL_REG1           => 0x26,
    MPL_REG_CTRL_REG2           => 0x27,
    MPL_REG_CTRL_REG3           => 0x28,
    MPL_REG_CTRL_REG4           => 0x29,
    MPL_REG_CTRL_REG5           => 0x2A,
    MPL_REG_OFF_P               => 0x2B,
    MPL_REG_OFF_T               => 0x2C,
    MPL_REG_OFF_H               => 0x2D,
    
    MPL_CTRL_REG1_SBYB          => 0x01,
    MPL_CTRL_REG1_OST           => 0x02,
    MPL_CTRL_REG1_RST           => 0x04,
    MPL_CTRL_REG1_OS0           => 0x08,
    MPL_CTRL_REG1_OS1           => 0x10,
    MPL_CTRL_REG1_OS2           => 0x20,
    MPL_CTRL_REG1_RAW           => 0x40,
    MPL_CTRL_REG1_ALT           => 0x80,
    
    MPL_CTRL_REG1_MASK          => 0xFF,
    
    MPL_CTRL_REG2_ST0           => 0x01,
    MPL_CTRL_REG2_ST1           => 0x02,
    MPL_CTRL_REG2_ST2           => 0x04,
    MPL_CTRL_REG2_ST3           => 0x08,
    MPL_CTRL_REG2_ALARM_SEL     => 0x10,
    MPL_CTRL_REG2_LOAD_OUTPUT   => 0x20,
    
    MPL_CTRL_REG2_MASK          => 0x3F,
    
    MPL_CTRL_REG3_PP_0D2        => 0x01,
    MPL_CTRL_REG3_IPOL2         => 0x02,
    MPL_CTRL_REG3_PP_OD1        => 0x10,
    MPL_CTRL_REG3_IPOL1         => 0x20,
  
    MPL_CTRL_REG3_MASK          => 0x33,
    
    MPL_CTRL_REG4_INT_EN_DRDY   => 0x80,
    MPL_CTRL_REG4_INT_EN_FIFO   => 0x40,
    MPL_CTRL_REG4_INT_EN_PW     => 0x20,
    MPL_CTRL_REG4_INT_EN_TW     => 0x10,
    MPL_CTRL_REG4_INT_EN_PTH    => 0x08,
    MPL_CTRL_REG4_INT_EN_TTH    => 0x04,
    MPL_CTRL_REG4_INT_EN_PCHG   => 0x02,
    MPL_CTRL_REG4_INT_EN_TCHG   => 0x01,
    
    MPL_CTRL_REG4_MASK          => 0xFF,
    
    MPL_INTREGS_DRDY  => 0x80,
    MPL_INTREGS_FIFO  => 0x40,
    MPL_INTREGS_PW    => 0x20,
    MPL_INTREGS_TW    => 0x10,
    MPL_INTREGS_PTH   => 0x08,
    MPL_INTREGS_TTH   => 0x04,
    MPL_INTREGS_PCHG  => 0x02,
    MPL_INTREGS_TCHG  => 0x01,
    
    MPL_INTREGS_MASK          => 0xFF,
    
    MPL_DR_STATUS_PTOW          => 0x80,
    MPL_DR_STATUS_POW           => 0x40,
    MPL_DR_STATUS_TOW           => 0x20,
    MPL_DR_STATUS_PTDR          => 0x08,
    MPL_DR_STATUS_PDR           => 0x04,
    MPL_DR_STATUS_TDR           => 0x02,
    
    MPL_DR_STATUS_MASK          => 0xEE,
    
    MPL_F_STATUS_F_OVF          => 0x80,
    MPL_F_STATUS_F_WMRK_FLAG    => 0x40,
    MPL_F_STATUS_F_CNT5         => 0x20,
    MPL_F_STATUS_F_CNT4         => 0x10,
    MPL_F_STATUS_F_CNT3         => 0x08,
    MPL_F_STATUS_F_CNT2         => 0x04,
    MPL_F_STATUS_F_CNT1         => 0x02,
    MPL_F_STATUS_F_CNT0         => 0x01,
    
    MPL_F_STATUS_MASK           => 0xFF,
    
    MPL_PT_DATA_CFG_DREM        => 0x04,
    MPL_PT_DATA_CFG_PDEFE       => 0x02,
    MPL_PT_DATA_CFG_TDEFE       => 0x01,
    
    MPL_PT_DATA_CFG_MASK        => 0x07,
    
    MPL_BIT_SBYB          => 0,
    MPL_BIT_OST           => 1,
    MPL_BIT_RST           => 2,
    MPL_BIT_OS0           => 3,
    MPL_BIT_OS1           => 4,
    MPL_BIT_OS2           => 5,
    MPL_BIT_RAW           => 6,
    MPL_BIT_ALT           => 7,
    
    MPL_BIT_ST0           => 0,
    MPL_BIT_ST1           => 1,
    MPL_BIT_ST2           => 2,
    MPL_BIT_ST3           => 3,
    MPL_BIT_ALARM_SEL     => 4,
    MPL_BIT_LOAD_OUTPUT   => 5,
    
    MPL_BIT_PP_0D2        => 0,
    MPL_BIT_IPOL2         => 1,
    MPL_BIT_PP_OD1        => 4,
    MPL_BIT_IPOL1         => 5,
    
    # interrupt bits for CTRL_REG5,
    # INT_SOURCE
    
    MPL_BIT_DRDY          => 7,
    MPL_BIT_FIFO          => 6,
    MPL_BIT_PW            => 5,
    MPL_BIT_TW            => 4,
    MPL_BIT_PTH           => 3,
    MPL_BIT_TTH           => 2,
    MPL_BIT_PCHG          => 1,
    MPL_BIT_TCHG          => 0,
    
    MPL_BIT_PTOW          => 7,
    MPL_BIT_POW           => 6,
    MPL_BIT_TOW           => 5,
    MPL_BIT_PTDR          => 3,
    MPL_BIT_PDR           => 2,
    MPL_BIT_TDR           => 1,
    
    MPL_BIT_F_OVF        => 7,
    MPL_BIT_F_WMRK_FLAG  => 6,
    MPL_BIT_F_CNT5       => 5,
    MPL_BIT_F_CNT4       => 4,
    MPL_BIT_F_CNT3       => 3,
    MPL_BIT_F_CNT2       => 2,
    MPL_BIT_F_CNT1       => 1,
    MPL_BIT_F_CNT0       => 0,
    
    MPL_BIT_DREM         => 2,
    MPL_BIT_PDEFE        => 1,
    MPL_BIT_TDEFE        => 0,
    
    
    MPL_OSREAD_DELAY     => 1060, # left for compatibility with code that uses it.
                                  
    MPL_FUNC_ALTITUDE    => 1,
    MPL_FUNC_PRESSURE    => 2,
    MPL3115A2_ID         => 0xC4,
    
    
    MPL_CONTROL_MASK     => 0b00111000, #128 oversampling
    MPL_BYTE_MASK        => 0xFF,
    MPL_WORD_MASK        => 0xFFFF,
    
    MPL_OVERSAMPLE_1     => 0b00000000,
    MPL_OVERSAMPLE_2     => 0b00001000,
    MPL_OVERSAMPLE_4     => 0b00010000,
    MPL_OVERSAMPLE_8     => 0b00011000,
    MPL_OVERSAMPLE_16    => 0b00100000,
    MPL_OVERSAMPLE_32    => 0b00101000,
    MPL_OVERSAMPLE_64    => 0b00110000,
    MPL_OVERSAMPLE_128   => 0b00111000,
    
    MPL_OVERSAMPLE_MASK  => 0b00111000,
    
    MPL_BB_I2C_PERI_0    => 0x10,
    MPL_BB_I2C_PERI_1    => 0x20,
    
};

{
    my @const = qw(
        MPL_REG_STATUS 
        MPL_REG_OUT_P_MSB  
        MPL_REG_OUT_P_CSB 
        MPL_REG_OUT_P_LSB 
        MPL_REG_OUT_T_MSB 
        MPL_REG_OUT_T_LSB 
        MPL_REG_DR_STATUS  
        MPL_REG_OUT_P_DELTA_MSB 
        MPL_REG_OUT_P_DELTA_CSB 
        MPL_REG_OUT_P_DELTA_LSB 
        MPL_REG_OUT_T_DELTA_MSB 
        MPL_REG_OUT_T_DELTA_LSB 
        MPL_REG_WHO_AM_I        
        MPL_REG_F_STATUS             
        MPL_REG_F_DATA               
        MPL_REG_F_SETUP              
        MPL_REG_TIME_DLY             
        MPL_REG_SYSMOD               
        MPL_REG_INT_SOURCE           
        MPL_REG_PT_DATA_CFG          
        MPL_REG_BAR_IN_MSB           
        MPL_REG_MAR_IN_LSB           
        MPL_REG_P_TGT_MSB            
        MPL_REG_P_TGT_LSB            
        MPL_REG_T_TGT                
        MPL_REG_P_WND_MSB            
        MPL_REG_P_WND_LSB            
        MPL_REG_T_WND                
        MPL_REG_P_MIN_MSB            
        MPL_REG_P_MIN_CSB            
        MPL_REG_P_MIN_LSB            
        MPL_REG_T_MIN_MSB            
        MPL_REG_T_MIN_LSB            
        MPL_REG_P_MAX_MSB            
        MPL_REG_P_MAX_CSB            
        MPL_REG_P_MAX_LSB            
        MPL_REG_T_MAX_MSB            
        MPL_REG_T_MAX_LSB            
        MPL_REG_CTRL_REG1            
        MPL_REG_CTRL_REG2            
        MPL_REG_CTRL_REG3            
        MPL_REG_CTRL_REG4            
        MPL_REG_CTRL_REG5            
        MPL_REG_OFF_P                
        MPL_REG_OFF_T                
        MPL_REG_OFF_H                
        
        MPL_CTRL_REG1_SBYB           
        MPL_CTRL_REG1_OST            
        MPL_CTRL_REG1_RST            
        MPL_CTRL_REG1_OS0            
        MPL_CTRL_REG1_OS1            
        MPL_CTRL_REG1_OS2            
        MPL_CTRL_REG1_RAW            
        MPL_CTRL_REG1_ALT            
        
        MPL_CTRL_REG1_MASK           
        
        MPL_CTRL_REG2_ST0            
        MPL_CTRL_REG2_ST1            
        MPL_CTRL_REG2_ST2            
        MPL_CTRL_REG2_ST3            
        MPL_CTRL_REG2_ALARM_SEL      
        MPL_CTRL_REG2_LOAD_OUTPUT    
        
        MPL_CTRL_REG2_MASK           
        
        MPL_CTRL_REG3_PP_0D2         
        MPL_CTRL_REG3_IPOL2          
        MPL_CTRL_REG3_PP_OD1         
        MPL_CTRL_REG3_IPOL1          
      
        MPL_CTRL_REG3_MASK           
        
        MPL_CTRL_REG4_INT_EN_DRDY    
        MPL_CTRL_REG4_INT_EN_FIFO    
        MPL_CTRL_REG4_INT_EN_PW      
        MPL_CTRL_REG4_INT_EN_TW      
        MPL_CTRL_REG4_INT_EN_PTH     
        MPL_CTRL_REG4_INT_EN_TTH     
        MPL_CTRL_REG4_INT_EN_PCHG    
        MPL_CTRL_REG4_INT_EN_TCHG    
        
        MPL_CTRL_REG4_MASK           
        
        MPL_INTREGS_DRDY   
        MPL_INTREGS_FIFO   
        MPL_INTREGS_PW     
        MPL_INTREGS_TW     
        MPL_INTREGS_PTH    
        MPL_INTREGS_TTH    
        MPL_INTREGS_PCHG   
        MPL_INTREGS_TCHG   
        
        MPL_INTREGS_MASK           
        
        MPL_DR_STATUS_PTOW           
        MPL_DR_STATUS_POW            
        MPL_DR_STATUS_TOW            
        MPL_DR_STATUS_PTDR           
        MPL_DR_STATUS_PDR            
        MPL_DR_STATUS_TDR            
        
        MPL_DR_STATUS_MASK           
        
        MPL_F_STATUS_F_OVF           
        MPL_F_STATUS_F_WMRK_FLAG     
        MPL_F_STATUS_F_CNT5          
        MPL_F_STATUS_F_CNT4          
        MPL_F_STATUS_F_CNT3          
        MPL_F_STATUS_F_CNT2          
        MPL_F_STATUS_F_CNT1          
        MPL_F_STATUS_F_CNT0          
        
        MPL_F_STATUS_MASK            
        
        MPL_PT_DATA_CFG_DREM         
        MPL_PT_DATA_CFG_PDEFE        
        MPL_PT_DATA_CFG_TDEFE        
        
        MPL_PT_DATA_CFG_MASK
        
        MPL_OSREAD_DELAY
        MPL_FUNC_ALTITUDE
        MPL_FUNC_PRESSURE
        MPL3115A2_ID
    
        MPL_CONTROL_MASK
        MPL_BYTE_MASK
        MPL_WORD_MASK
        
        MPL_OVERSAMPLE_1 
        MPL_OVERSAMPLE_2
        MPL_OVERSAMPLE_4 
        MPL_OVERSAMPLE_8 
        MPL_OVERSAMPLE_16 
        MPL_OVERSAMPLE_32
        MPL_OVERSAMPLE_64 
        MPL_OVERSAMPLE_128
        
        MPL_OVERSAMPLE_MASK
    );
    push @EXPORT_OK, @const;

}

sub new {
    my ($class, %userparams) = @_;
    my %params = (
        peripheral  => ( RPI_BOARD_REVISION == 1 ) ? MPL_BB_I2C_PERI_0 : MPL_BB_I2C_PERI_1,
        devicename  => ( RPI_BOARD_REVISION == 1 ) ? '/dev/i2c-0' : '/dev/i2c-1',
        address     => 0x60,
        device      => undef,
        osdelay     => MPL_OSREAD_DELAY,
        backend     => 'i2c',
        _function_mode => 'hipi',
        readmode    => I2C_READMODE_REPEATED_START,
    );
    
    # get user params
    foreach my $key( keys (%userparams) ) {
        $params{$key} = $userparams{$key};
    }
    
    unless( defined($params{device}) ) {
        if ( $params{backend} && $params{backend} =~ /^i2c|smbus$/) {
            require HiPi::Device::I2C;
            $params{device} = HiPi::Device::I2C->new(
                devicename  => $params{devicename},
                address     => $params{address},
                busmode     => $params{backend},
                readmode    => $params{readmode},
            );
        } elsif ( $params{backend} && $params{backend} eq 'bcm2835') {
            require HiPi::BCM2835::I2C;
            $params{device} = HiPi::BCM2835::I2C->new(
                peripheral  => $params{peripheral},
                address     => $params{address},
                readmode    => $params{readmode},
                _function_mode  => $params{_function_mode},
            );
        } else {
            croak q(invalid backend specified);
        }
    }
    
    my $self = $class->SUPER::new(%params);
    # init
    {
        my $maxloop = 0;
        while ( $maxloop++ < 20 ) {
            $self->sysmod;
            last if( $self->who_am_i && $self->who_am_i == 0xC4);
            $self->device->delay(100);
        }
        $self->device->delay(100);
        $self->sysmod;
    }
    
    return $self;
}

sub unpack_altitude {
    my( $self, $msb, $csb, $lsb ) =@_;
    my $alt = $msb << 8;
    $alt += $csb;
    if( $msb > 127 ) {
        $alt = 0xFFFF &~$alt;
        $alt ++;
        $alt *= -1;
    }
    $alt += 0.5    if( $lsb & 0b10000000 );
    $alt += 0.25   if( $lsb & 0b01000000 );
    $alt += 0.125  if( $lsb & 0b00100000 );
    $alt += 0.0625 if( $lsb & 0b00010000 );
    return $alt;
}

sub pack_altitude {
    my($self, $alt) = @_;
    my $mint = int( $alt );
    my $lsb =  0b1111 & int(0.5 + ( 15.0 * (abs($alt) - abs($mint))));
    $lsb <<= 4;
    
    if( $alt < 0 ) {
        $mint *= -1;
        $mint --;
        $mint = 0xFFFF &~$mint;
    }
    
    my $msb = $mint >> 8;
    my $csb = $mint & 0xFF;
    return($msb, $csb, $lsb);
}

sub unpack_temperature {
    my( $self, $msb, $lsb ) =@_;
    if( $msb > 127 ) {
        $msb = 0xFFFF &~$msb;
        $msb ++;
        $msb *= -1;
    }
    $msb += 0.5    if( $lsb & 0b10000000 );
    $msb += 0.25   if( $lsb & 0b01000000 );
    $msb += 0.125  if( $lsb & 0b00100000 );
    $msb += 0.0625 if( $lsb & 0b00010000 );
    return $msb;
}

sub pack_temperature {
    my($self, $temp) = @_;
    my $mint = int( $temp );
    my $lsb =  0b1111 & int(0.495 + ( 15.0 * (abs($temp) - abs($mint))));
    $lsb <<= 4;
    if( $temp < 0 ) {
        $mint *= -1;
        $mint --;
        $mint = 0xFF &~$mint;
    }
    my $msb = $mint & 0xFF;
    return($msb, $lsb);
}

sub unpack_pressure {
    my( $self, $msb, $csb, $lsb ) =@_;
    my $alt = $msb << 10;
    $alt += $csb << 2;
    $alt += 0b11 & ( $lsb >> 6 );
    $alt += 0.5  if( $lsb & 0b00100000 );
    $alt += 0.25 if( $lsb & 0b00010000 );
    return $alt;
}

sub pack_pressure {
    my($self, $alt) = @_;
    my $mint = int( $alt );
    my $lsb =  0b1111 & int(0.495 + ( 3.0 * (abs($alt) - abs($mint))));
    $lsb <<= 4;
    my $msb = $mint & 0x3FC00;
    $msb >>= 10;
    my $csb = $mint & 0x3FC;
    $csb >>= 2;
    my $extra = $mint & 0x03;
    $lsb += ($extra << 6);
    return($msb, $csb, $lsb);
}

sub sysmod {
    my $self = shift;
    ( $self->device->bus_read(MPL_REG_SYSMOD, 1))[0];
}

sub who_am_i {
    my $self = shift;
    ( $self->device->bus_read(MPL_REG_WHO_AM_I, 1))[0];
}

sub active {
    my ($self, $set) = @_;
    my ( $curreg ) = $self->device->bus_read(MPL_REG_CTRL_REG1, 1);
    my $rval = $curreg & MPL_CTRL_REG1_SBYB;
    if (defined($set)) {
        my $setmask = ( $set ) ? MPL_CTRL_REG1_SBYB | $curreg : $curreg &~MPL_CTRL_REG1_SBYB;
        $self->device->bus_write(MPL_REG_CTRL_REG1, $setmask);
        $rval = $setmask & MPL_CTRL_REG1_SBYB;
    }
    return $rval;
}

sub reboot {
    my $self = shift;
    $self->device->bus_write_error(MPL_REG_CTRL_REG1, MPL_CTRL_REG1_RST);
    $self->device->delay(100);
}


sub oversample {
    my($self, $newval) = @_;
    my ( $curreg ) = $self->device->bus_read(MPL_REG_CTRL_REG1, 1);
    my $currentval = $curreg & MPL_OVERSAMPLE_MASK;
    if(defined($newval)) {
        $newval &= MPL_OVERSAMPLE_MASK;
        unless( $currentval == $newval ) {
            if( $curreg & MPL_CTRL_REG1_SBYB ) {
                croak('cannot set oversample rate while system is active');
            }
            $self->device->bus_write(MPL_REG_CTRL_REG1, $curreg | $newval );
            ( $curreg ) = $self->device->bus_read(MPL_REG_CTRL_REG1, 1);
            $currentval = $curreg & MPL_OVERSAMPLE_MASK;
        }
    }
    return $currentval;
}

sub delay_from_oversample {
    my ($self, $oversample) = @_;
    # calculate delay needed for oversample to complete.
    # spec sheet says 60ms at oversample 1 and 1000ms at oversample 128
    # so if we range at 100ms to 1100ms and the oversample register bits
    # contain a value of 0 through 7 representing 1 to 128
    # delay = 100 + 2^$oversample * 1000/128
    $oversample >>= 3;
    return int(100.5 + 2**$oversample * 1000/128);
}

sub raw {
    my($self, $newval) = @_;
    my ( $curreg ) = $self->device->i2c_read_register_rs(MPL_REG_CTRL_REG1, 1);
    my $currentval = $curreg & MPL_CTRL_REG1_RAW;
    if(defined($newval)) {
        $newval &= MPL_CTRL_REG1_RAW;
        unless( $currentval == $newval ) {
            if( $curreg & MPL_CTRL_REG1_SBYB ) {
                croak('cannot set raw mode while system is active');
            }
            $self->device->i2c_write(MPL_REG_CTRL_REG1, $curreg | $newval );
            ( $curreg ) = $self->device->i2c_read_register_rs(MPL_REG_CTRL_REG1, 1);
            $currentval = $curreg & MPL_CTRL_REG1_RAW;
        }
    }
    return $currentval;
}

sub mode {
    my($self, $newmode) = @_;
    my ( $curreg ) = $self->device->bus_read(MPL_REG_CTRL_REG1, 1);
    my $currentmode = ( $curreg & MPL_CTRL_REG1_ALT ) ? MPL_FUNC_ALTITUDE : MPL_FUNC_PRESSURE;
    if(defined($newmode)) {
        unless( $currentmode == $newmode ) {
            if( $curreg & MPL_CTRL_REG1_SBYB ) {
                croak('cannot set altitude / pressure mode while system is active');
            }
            my $setmask = ($newmode == MPL_FUNC_ALTITUDE) ? $curreg | MPL_CTRL_REG1_ALT : $curreg &~MPL_CTRL_REG1_ALT;
            $self->device->bus_write(MPL_REG_CTRL_REG1, $setmask );
            ( $curreg ) = $self->device->bus_read(MPL_REG_CTRL_REG1, 1);
            $currentmode = ( $curreg & MPL_CTRL_REG1_ALT ) ? MPL_FUNC_ALTITUDE : MPL_FUNC_PRESSURE;
        }
    }
    return $currentmode;
}

sub os_temperature {
    my $self = shift;
    my ( $pvalue, $tvalue ) = $self->os_any_data; 
    return  $tvalue;    
}

sub os_pressure {
    my $self = shift;
    my($pdata, $tdata) = $self->os_both_data( MPL_FUNC_PRESSURE );
    return $pdata;
}

sub os_altitude {
    my $self = shift;
    my($pdata, $tdata) = $self->os_both_data( MPL_FUNC_ALTITUDE );
    return $pdata;
}

sub os_any_data {
    my $self = shift;
    my ( $curreg ) = $self->device->bus_read(MPL_REG_CTRL_REG1, 1);
    
    my $currentmode = ( $curreg & MPL_CTRL_REG1_ALT ) ? MPL_FUNC_ALTITUDE : MPL_FUNC_PRESSURE;
    my $oversample  = ( $curreg & MPL_OVERSAMPLE_MASK );
    
    # whatever the original state of CTRL_REG1, we want to restore it with
    # one shot bit cleared
    my $restorereg = $curreg &~MPL_CTRL_REG1_OST;
    
    my $delayms = $self->delay_from_oversample($oversample);
        
    # clear any one shot bit
    $self->device->bus_write(MPL_REG_CTRL_REG1, $curreg &~MPL_CTRL_REG1_OST );
    # set one shot bit
    $self->device->bus_write(MPL_REG_CTRL_REG1, $curreg | MPL_CTRL_REG1_OST );
    
    # wait before read
    $self->device->delay($delayms);
        
    # read data       
    my( $pmsb, $pcsb, $plsb, $tmsb, $tlsb)
        = $self->device->bus_read(MPL_REG_OUT_P_MSB, 5);
    
    # convert pressure / altitude data
    my $pdata;
    if( $currentmode == MPL_FUNC_ALTITUDE ) {
        $pdata = $self->unpack_altitude( $pmsb, $pcsb, $plsb );
    } else {
        $pdata = $self->unpack_pressure( $pmsb, $pcsb, $plsb );
    }
    
    # convert temperature data
    my $tdata = $self->unpack_temperature( $tmsb, $tlsb );
    
    # restore REG1 clearing any one shot bit
    $self->device->bus_write(MPL_REG_CTRL_REG1, $restorereg );
    
    # return both
    return ( $pdata, $tdata );    
}

sub os_both_data {
    my($self, $function) = @_;
    $function //= MPL_FUNC_PRESSURE; # default it not defined
    
    my ( $curreg ) = $self->device->bus_read(MPL_REG_CTRL_REG1, 1);
    
    my $currentmode   = ( $curreg & MPL_CTRL_REG1_ALT ) ? MPL_FUNC_ALTITUDE : MPL_FUNC_PRESSURE;
    my $currentactive = $curreg & 0x01;
    
    # we can't change datamodes if system is currently active
    if($currentactive && ( $currentmode !=  $function )) {
        croak('cannot switch between pressure and altitude modes when system is active');
    }
    
    my $ctrlmask = ( $function == MPL_FUNC_ALTITUDE )
        ? $curreg | MPL_CTRL_REG1_ALT
        : $curreg &~MPL_CTRL_REG1_ALT;
    
    $self->device->bus_write(MPL_REG_CTRL_REG1, $ctrlmask );
    $self->os_any_data;
}

sub os_all_data {
    my($self ) = @_;
    
    my( $altitude, $discard ) = $self->os_both_data( MPL_FUNC_ALTITUDE );
    my( $pressure, $tempert ) = $self->os_both_data( MPL_FUNC_PRESSURE );
    
    return ( $altitude, $pressure, $tempert );    
}

sub sea_level_pressure {
    my( $class, $pressure, $altitude, $temperature, $gravity) = @_;
    $gravity ||= 9.81;   # acceleratio due to gravity
    my $dgc    = 287.0; # dry gas constant
    
    # Po = ((P * 1000) * Math.exp((g*Zg)/(Rd *  (Tv_avg + 273.15))))/1000;
    
    my $result = (($pressure * 10) * exp(($gravity * $altitude)/($dgc *  ($temperature + 273.15))))/10;
    
    $result = sprintf("%.2f", $result);
    return $result;
}


1;
