// -----------------------------------------------------------------------------
// BMP085/BMP180 Sensor over I2C
// Copyright (C) 2018 by Xose PĂ©rez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------

#if SENSOR_SUPPORT && BMP180_SUPPORT

#pragma once

#undef I2C_SUPPORT
#define I2C_SUPPORT 1 // Explicitly request I2C support.

#include "Arduino.h"
#include "I2CSensor.h"

#define BMP180_CHIP_ID                  0x55

#define BMP180_REGISTER_CHIPID          0xD0

#define BMP180_REGISTER_CAL_AC1         0xAA
#define BMP180_REGISTER_CAL_AC2         0xAC
#define BMP180_REGISTER_CAL_AC3         0xAE
#define BMP180_REGISTER_CAL_AC4         0xB0
#define BMP180_REGISTER_CAL_AC5         0xB2
#define BMP180_REGISTER_CAL_AC6         0xB4
#define BMP180_REGISTER_CAL_B1          0xB6
#define BMP180_REGISTER_CAL_B2          0xB8
#define BMP180_REGISTER_CAL_MB          0xBA
#define BMP180_REGISTER_CAL_MC          0xBC
#define BMP180_REGISTER_CAL_MD          0xBE

#define BMP180_REGISTER_VERSION         0xD1
#define BMP180_REGISTER_SOFTRESET       0xE0
#define BMP180_REGISTER_CONTROL         0xF4
#define BMP180_REGISTER_TEMPDATA        0xF6
#define BMP180_REGISTER_PRESSUREDATA    0xF6
#define BMP180_REGISTER_READTEMPCMD     0x2E
#define BMP180_REGISTER_READPRESSURECMD 0x34

class BMP180Sensor : public I2CSensor {

    public:

        static unsigned char addresses[1];

        // ---------------------------------------------------------------------
        // Public
        // ---------------------------------------------------------------------

        BMP180Sensor(): I2CSensor() {
            _sensor_id = SENSOR_BMP180_ID;
            _count = 2;
        }

        // ---------------------------------------------------------------------
        // Sensor API
        // ---------------------------------------------------------------------

        // Initialization method, must be idempotent
        void begin() {
            if (!_dirty) return;
            _init();
            _dirty = !_ready;
        }

        // Descriptive name of the sensor
        String description() {
            char buffer[20];
            snprintf(buffer, sizeof(buffer), "BMP180 @ I2C (0x%02X)", _address);
            return String(buffer);
        }

        // Type for slot # index
        unsigned char type(unsigned char index) {
            if (index == 0) return MAGNITUDE_TEMPERATURE;
            if (index == 1) return MAGNITUDE_PRESSURE;
            return MAGNITUDE_NONE;
        }

        // Pre-read hook (usually to populate registers with up-to-date data)
        virtual void pre() {

            if (_run_init) {
                i2cClearBus();
                _init();
            }

            if (_chip == 0) {
                _error = SENSOR_ERROR_UNKNOWN_ID;
                return;
            }

            _error = SENSOR_ERROR_OK;
            _error = _read();
            if (_error != SENSOR_ERROR_OK) {
                _run_init = true;
            }

        }

        // Current value for slot # index
        double value(unsigned char index) {
            if (index == 0) return _temperature;
            if (index == 1) return _pressure / 100;
            return 0;
        }

    protected:

        void _init() {

            // Make sure sensor had enough time to turn on. BMP180 requires 2ms to start up
            nice_delay(10);

            // I2C auto-discover
            _address = _begin_i2c(_address, sizeof(BMP180Sensor::addresses), BMP180Sensor::addresses);
            if (_address == 0) return;

            // Check sensor correctly initialized
            _chip = i2c_read_uint8(_address, BMP180_REGISTER_CHIPID);
            if (_chip != BMP180_CHIP_ID) {

                _chip = 0;
                i2cReleaseLock(_address);
                _previous_address = 0;
                _error = SENSOR_ERROR_UNKNOWN_ID;

                // Setting _address to 0 forces auto-discover
                // This might be necessary at this stage if there is a
                // different sensor in the hardcoded address
                _address = 0;

                return;

            }

            _readCoefficients();

            _run_init = false;
            _ready = true;

        }

        void _readCoefficients() {

            _bmp180_calib.ac1 = i2c_read_int16(_address, BMP180_REGISTER_CAL_AC1);
            _bmp180_calib.ac2 = i2c_read_int16(_address, BMP180_REGISTER_CAL_AC2);
            _bmp180_calib.ac3 = i2c_read_int16(_address, BMP180_REGISTER_CAL_AC3);
            
            _bmp180_calib.ac4 = i2c_read_uint16(_address, BMP180_REGISTER_CAL_AC4);
            _bmp180_calib.ac5 = i2c_read_uint16(_address, BMP180_REGISTER_CAL_AC5);
            _bmp180_calib.ac6 = i2c_read_uint16(_address, BMP180_REGISTER_CAL_AC6);

            _bmp180_calib.b1 = i2c_read_int16(_address, BMP180_REGISTER_CAL_B1);
            _bmp180_calib.b2 = i2c_read_int16(_address, BMP180_REGISTER_CAL_B2);
            _bmp180_calib.mb = i2c_read_int16(_address, BMP180_REGISTER_CAL_MB);
            _bmp180_calib.mc = i2c_read_int16(_address, BMP180_REGISTER_CAL_MC);
            _bmp180_calib.md = i2c_read_int16(_address, BMP180_REGISTER_CAL_MD);

        }
        
        // Compute B5 coefficient used in temperature & pressure calcs.
        // Based on Adafruit_BMP085_Unified library 
        long _computeB5(unsigned long t) {
            long X1 = (t - (long)_bmp180_calib.ac6) * ((long)_bmp180_calib.ac5) >> 15;
            long X2 = ((long)_bmp180_calib.mc << 11) / (X1+(long)_bmp180_calib.md);
            return X1 + X2;
        }

        unsigned char _read() {

            // Read raw temperature
            i2c_write_uint8(_address, BMP180_REGISTER_CONTROL, BMP180_REGISTER_READTEMPCMD);
            nice_delay(5);
            unsigned long t = i2c_read_uint16(_address, BMP180_REGISTER_TEMPDATA);

            // Compute B5 coeficient
            long b5 = _computeB5(t);

            // Final temperature
            _temperature = ((double) ((b5 + 8) >> 4)) / 10.0;

            // Read raw pressure
            i2c_write_uint8(_address, BMP180_REGISTER_CONTROL, BMP180_REGISTER_READPRESSURECMD + (_mode << 6));
            nice_delay(26);
            unsigned long p1 = i2c_read_uint16(_address, BMP180_REGISTER_PRESSUREDATA);
            unsigned long p2 = i2c_read_uint8(_address, BMP180_REGISTER_PRESSUREDATA+2);
            long p = ((p1 << 8) + p2) >> (8 - _mode);

            // Pressure compensation
            long b6 = b5 - 4000;
            long x1 = (_bmp180_calib.b2 * ((b6 * b6) >> 12)) >> 11;
            long x2 = (_bmp180_calib.ac2 * b6) >> 11;
            long x3 = x1 + x2;
            long b3 = (((((int32_t) _bmp180_calib.ac1) * 4 + x3) << _mode) + 2) >> 2;

            x1 = (_bmp180_calib.ac3 * b6) >> 13;
            x2 = (_bmp180_calib.b1 * ((b6 * b6) >> 12)) >> 16;
            x3 = ((x1 + x2) + 2) >> 2;
            unsigned long b4 = (_bmp180_calib.ac4 * (uint32_t) (x3 + 32768)) >> 15;
            unsigned long b7 = ((uint32_t) (p - b3) * (50000 >> _mode));

            if (b7 < 0x80000000) {
                p = (b7 << 1) / b4;
            } else {
                p = (b7 / b4) << 1;
            }

            x1 = (p >> 8) * (p >> 8);
            x1 = (x1 * 3038) >> 16;
            x2 = (-7357 * p) >> 16;

            _pressure = p + ((x1 + x2 + 3791) >> 4);

            return SENSOR_ERROR_OK;

        }

        // ---------------------------------------------------------------------

        unsigned char _chip;
        bool _run_init = false;
        double _temperature = 0;
        double _pressure = 0;
        unsigned int _mode = BMP180_MODE;

        typedef struct {

            int16_t  ac1;
            int16_t  ac2;
            int16_t  ac3;
            uint16_t ac4;
            uint16_t ac5;
            uint16_t ac6;

            int16_t  b1;
            int16_t  b2;
            
            int16_t  mb;
            int16_t  mc;
            int16_t  md;
            
        } bmp180_calib_t;

        bmp180_calib_t _bmp180_calib;

};

// Static inizializations

unsigned char BMP180Sensor::addresses[1] = {0x77};

#endif // SENSOR_SUPPORT && BMP180_SUPPORT