#########################################################################################
# Package        HiPi::Interface::MS5611
# Description  : Interface to MS5611_01BA03 barometric pressure sensor
# Copyright    : Copyright (c) 2013-2023 Mark Dootson
# License      : This is free software; you can redistribute it and/or modify it under
#                the same terms as the Perl 5 programming language system itself.
#########################################################################################

package HiPi::Interface::MS5611;

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

use strict;
use warnings;
use parent qw( HiPi::Interface::Common::Weather );
use HiPi qw( :i2c :rpi :ms5611);
use HiPi::RaspberryPi;
use Carp;

our $VERSION ='0.89';

__PACKAGE__->create_accessors( qw( backend crc) );

use constant {
    CMD_RESET    => 0x1E,
    CMD_ADC_READ => 0x00, # // ADC read command
    CMD_ADC_CONV => 0x40, # // ADC conversion command
    CMD_ADC_D1   => 0x00, # // ADC D1 conversion
    CMD_ADC_D2   => 0x10, # // ADC D2 conversion
    CMD_PROM_RD  => 0xA0, # // Prom read command 
};

sub new {
    my ($class, %userparams) = @_;
    my $pi = HiPi::RaspberryPi->new();
    
    my %params = (
        devicename   => ( $pi->board_type == RPI_BOARD_TYPE_1 ) ? '/dev/i2c-0' : '/dev/i2c-1',
        address     => 0x76,
        device      => undef,
        backend     => 'i2c',
    );
    
    # get user params
    foreach my $key( keys (%userparams) ) {
        $params{$key} = $userparams{$key};
    }
    
    if( $params{busmode} ) {
        $params{backend} = $params{busmode};
    }
    
    unless( defined($params{device}) ) {
        if ( $params{backend} eq 'bcm2835' ) {
            require HiPi::BCM2835::I2C;
            $params{device} = HiPi::BCM2835::I2C->new(
                address    => $params{address},
                peripheral => ( $params{devicename} eq '/dev/i2c-0' ) ? HiPi::BCM2835::I2C::BB_I2C_PERI_0() : HiPi::BCM2835::I2C::BB_I2C_PERI_1(),
            );
        } else {
            require HiPi::Device::I2C;
            $params{device} = HiPi::Device::I2C->new(
                devicename  => $params{devicename},
                address     => $params{address},
                busmode     => 'i2c', # don't smbus
            );
        }
    }
    
    my $self = $class->SUPER::new(%params);
    
    $self->_init;
    
    return $self;
}

sub reset {
    my $self = shift;
    $self->device->bus_write( CMD_RESET );
    $self->delay( 3 );
    return;
}

sub _read_prom {
    my($self, $coefnum) = @_;
    $self->device->bus_write( CMD_PROM_RD + $coefnum * 2 );
    my @ret = $self->device->bus_read( undef, 2);
    my $prom = $ret[0] * 256 + $ret[1];
    return $prom;
}

sub _crc4 {
    my( $self ) = @_;
    
    # int cnt; // simple counter
    # unsigned int n_rem; // crc reminder
    # unsigned int crc_read; // original value of the crc
    # unsigned char n_bit;
    
    my $n_rem = 0x00;
    my $crc_read = $self->crc->[7];
    $self->crc->[7] = 0;
    for (my $cnt = 0; $cnt < 16; $cnt ++) { #// operation is performed on bytes
        # // choose LSB or MSB
        if ( $cnt % 2 ==1 ) {
            $n_rem ^= (($self->crc->[$cnt>>1]) & 0x00FF);
        } else {
            $n_rem ^= ( $self->crc->[$cnt>>1] >> 8);
        }
        for (my $n_bit = 8; $n_bit > 0; $n_bit--) {
            if ($n_rem & 0x8000) {
                $n_rem = ($n_rem << 1) ^ 0x3000;
            } else {
                $n_rem = $n_rem << 1;
            }
        }
    }
    $n_rem= (0x000F & ($n_rem >> 12)); #// // final 4-bit remainder is CRC code
    $self->crc->[7] = $crc_read; # // restore the crc_read to its original place
    return $n_rem ^ 0x00;
}

sub _init {
    my $self = shift;
    
    my @promvals = ();
    
    # get callibration coeffs
    for ( my $i = 0; $i < 8 ; $i++ ) {
        my $promval = $self->_read_prom( $i );
        push @promvals, $promval;
    }
    
    $self->crc( \@promvals );
    my $n_crc = $self->_crc4( @promvals );
    # is the crc check worth it ?????
}

sub _adc_cmd {
    my( $self, $cmd ) = @_;
    $self->device->bus_write( CMD_ADC_CONV + $cmd );
    
    my $osr = $cmd &  0x0F;
    if( $osr == MS5611_OSR_256 ) {
        $self->delay(1);
    } elsif($osr == MS5611_OSR_512 ) {
        $self->delay(3);
    } elsif($osr == MS5611_OSR_1024 ) {
        $self->delay(4);
    } elsif($osr == MS5611_OSR_2048 ) {
        $self->delay(6);
    } else {
        $self->delay(10);
    }
    
    $self->device->bus_write( CMD_ADC_READ );
    
    my @ret = $self->device->bus_read( undef, 3);
    
    my $result = ($ret[0] * 65536 ) + ($ret[1] * 256 ) + $ret[2];
    return $result;
}

sub _internal_read_p_t {
    my($self, $pres_osr, $temp_osr ) = @_;
    $pres_osr //= MS5611_OSR_4096;
    $temp_osr //= MS5611_OSR_4096;
    
    my $D2 = $self->_adc_cmd( CMD_ADC_D2 + $temp_osr);
    my $D1 = $self->_adc_cmd( CMD_ADC_D1 + $pres_osr);
    
    my $dT = $D2 - $self->crc->[5] * (2**8);
    
    my $OFF  = $self->crc->[2] * (2**16) + $dT * $self->crc->[4] / (2**7);
    my $SENS = $self->crc->[1] * (2**15) + $dT * $self->crc->[3] / (2**8);

    my $TEMP = 2000 + ( $dT * $self->crc->[6]) / ( 2**23 );
        
    if( $TEMP < 2000 ) {
        my $T2 = ($dT**2) / (2**31);
        my $OFF2 = 5 * ($TEMP - 2000)**2 / 2;
        my $SENS2 = 5 * ($TEMP - 2000)**2 / 2**2;
        if( $TEMP < -1500 ) {
            $OFF2 = $OFF2 + 7 * ( $TEMP + 1500 )**2;
            $SENS2 = $SENS2 + 11 * ($TEMP + 1500)**2 / 2;
        }
        $TEMP -= $T2;
        $SENS -= $SENS2;
        $OFF -= $OFF2;
    }
    
    my $P = ( ($D1 * $SENS) / (2**21) - $OFF ) / ( 2**15 );
        
    return ( $P , $TEMP );
}

sub read_pressure_temp {
    my($self, $pres_osr, $temp_osr ) = @_;
    
    my ( $pressure, $temperature ) = $self->_internal_read_p_t( $pres_osr, $temp_osr );
            
    return ( sprintf('%.4f', $pressure / 100), sprintf('%.2f', $temperature / 100 ) );
}

sub read_pressure_pa_temp {
    my($self, $pres_osr, $temp_osr ) = @_;
    
    my ( $pressure, $temperature ) = $self->_internal_read_p_t( $pres_osr, $temp_osr );
        
    return ( sprintf('%.2f', $pressure ), sprintf('%.2f', $temperature / 100 ) );
}


1;

__END__