// -----------------------------------------------------------------------------
// BME280/BMP280 Sensor over I2C
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------

#if SENSOR_SUPPORT && BMX280_SUPPORT

#pragma once

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

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

#define BMX280_CHIP_BMP280              0x58
#define BMX280_CHIP_BME280              0x60

#define BMX280_REGISTER_DIG_T1          0x88
#define BMX280_REGISTER_DIG_T2          0x8A
#define BMX280_REGISTER_DIG_T3          0x8C

#define BMX280_REGISTER_DIG_P1          0x8E
#define BMX280_REGISTER_DIG_P2          0x90
#define BMX280_REGISTER_DIG_P3          0x92
#define BMX280_REGISTER_DIG_P4          0x94
#define BMX280_REGISTER_DIG_P5          0x96
#define BMX280_REGISTER_DIG_P6          0x98
#define BMX280_REGISTER_DIG_P7          0x9A
#define BMX280_REGISTER_DIG_P8          0x9C
#define BMX280_REGISTER_DIG_P9          0x9E

#define BMX280_REGISTER_DIG_H1          0xA1
#define BMX280_REGISTER_DIG_H2          0xE1
#define BMX280_REGISTER_DIG_H3          0xE3
#define BMX280_REGISTER_DIG_H4          0xE4
#define BMX280_REGISTER_DIG_H5          0xE5
#define BMX280_REGISTER_DIG_H6          0xE7

#define BMX280_REGISTER_CHIPID          0xD0
#define BMX280_REGISTER_VERSION         0xD1
#define BMX280_REGISTER_SOFTRESET       0xE0

#define BMX280_REGISTER_CAL26           0xE1

#define BMX280_REGISTER_CONTROLHUMID    0xF2
#define BMX280_REGISTER_CONTROL         0xF4
#define BMX280_REGISTER_CONFIG          0xF5
#define BMX280_REGISTER_PRESSUREDATA    0xF7
#define BMX280_REGISTER_TEMPDATA        0xFA
#define BMX280_REGISTER_HUMIDDATA       0xFD

class BMX280Sensor : public I2CSensor {

    public:

        static unsigned char addresses[2];

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

        BMX280Sensor(): I2CSensor() {
            _sensor_id = SENSOR_BMX280_ID;
        }

        // ---------------------------------------------------------------------
        // 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), "%s @ I2C (0x%02X)", _chip == BMX280_CHIP_BME280 ? "BME280" : "BMP280",  _address);
            return String(buffer);
        }

        // Type for slot # index
        unsigned char type(unsigned char index) {
            unsigned char i = 0;
            #if BMX280_TEMPERATURE > 0
                if (index == i++) return MAGNITUDE_TEMPERATURE;
            #endif
            #if BMX280_PRESSURE > 0
                if (index == i++) return MAGNITUDE_PRESSURE;
            #endif
            #if BMX280_HUMIDITY > 0
                if (_chip == BMX280_CHIP_BME280) {
                    if (index == i) return MAGNITUDE_HUMIDITY;
                }
            #endif
            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;

            #if BMX280_MODE == 1
                _forceRead();
            #endif

            _error = _read();

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

        }

        // Current value for slot # index
        double value(unsigned char index) {
            unsigned char i = 0;
            #if BMX280_TEMPERATURE > 0
                if (index == i++) return _temperature;
            #endif
            #if BMX280_PRESSURE > 0
                if (index == i++) return _pressure / 100;
            #endif
            #if BMX280_HUMIDITY > 0
                if (_chip == BMX280_CHIP_BME280) {
                    if (index == i) return _humidity;
                }
            #endif
            return 0;
        }

        // Load the configuration manifest
        static void manifest(JsonArray& sensors) {

            char buffer[10];

            JsonObject& sensor = sensors.createNestedObject();
            sensor["sensor_id"] = SENSOR_BMX280_ID;
            JsonArray& fields = sensor.createNestedArray("fields");

            {
                JsonObject& field = fields.createNestedObject();
                field["tag"] = UI_TAG_SELECT;
                field["name"] = "address";
                field["label"] = "Address";
                JsonArray& options = field.createNestedArray("options");
                {
                    JsonObject& option = options.createNestedObject();
                    option["name"] = "auto";
                    option["value"] = 0;
                }
                for (unsigned char i=0; i< sizeof(BMX280Sensor::addresses); i++) {
                    JsonObject& option = options.createNestedObject();
                    snprintf(buffer, sizeof(buffer), "0x%02X", BMX280Sensor::addresses[i]);
                    option["name"] = String(buffer);
                    option["value"] = BMX280Sensor::addresses[i];
                }
            }

        };

        void getConfig(JsonObject& root) {
            root["sensor_id"] = _sensor_id;
            root["address"] = _address;
        };

        void setConfig(JsonObject& root) {
            if (root.containsKey("address")) setAddress(root["address"]);
        };

    protected:

        void _init() {

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

            // No chip ID by default
            _chip = 0;

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

            // Check sensor correctly initialized
            _chip = i2c_read_uint8(_address, BMX280_REGISTER_CHIPID);
            if ((_chip != BMX280_CHIP_BME280) && (_chip != BMX280_CHIP_BMP280)) {

                _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;

            }

            _count = 0;
            #if BMX280_TEMPERATURE > 0
                ++_count;
            #endif
            #if BMX280_PRESSURE > 0
                ++_count;
            #endif
            #if BMX280_HUMIDITY > 0
                if (_chip == BMX280_CHIP_BME280) ++_count;
            #endif

            _readCoefficients();

            unsigned char data = 0;
            i2c_write_uint8(_address, BMX280_REGISTER_CONTROL, data);

        	data =  (BMX280_STANDBY << 0x5) & 0xE0;
        	data |= (BMX280_FILTER << 0x02) & 0x1C;
        	i2c_write_uint8(_address, BMX280_REGISTER_CONFIG, data);

            data =  (BMX280_HUMIDITY) & 0x07;
            i2c_write_uint8(_address, BMX280_REGISTER_CONTROLHUMID, data);

            data =  (BMX280_TEMPERATURE << 5) & 0xE0;
            data |= (BMX280_PRESSURE << 2) & 0x1C;
            data |= (BMX280_MODE) & 0x03;
            i2c_write_uint8(_address, BMX280_REGISTER_CONTROL, data);

            _measurement_delay = _measurementTime();
            _run_init = false;
            _ready = true;

        }

        void _readCoefficients() {
            _bmx280_calib.dig_T1 = i2c_read_uint16_le(_address, BMX280_REGISTER_DIG_T1);
            _bmx280_calib.dig_T2 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_T2);
            _bmx280_calib.dig_T3 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_T3);

            _bmx280_calib.dig_P1 = i2c_read_uint16_le(_address, BMX280_REGISTER_DIG_P1);
            _bmx280_calib.dig_P2 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_P2);
            _bmx280_calib.dig_P3 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_P3);
            _bmx280_calib.dig_P4 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_P4);
            _bmx280_calib.dig_P5 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_P5);
            _bmx280_calib.dig_P6 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_P6);
            _bmx280_calib.dig_P7 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_P7);
            _bmx280_calib.dig_P8 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_P8);
            _bmx280_calib.dig_P9 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_P9);

            _bmx280_calib.dig_H1 = i2c_read_uint8(_address, BMX280_REGISTER_DIG_H1);
            _bmx280_calib.dig_H2 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_H2);
            _bmx280_calib.dig_H3 = i2c_read_uint8(_address, BMX280_REGISTER_DIG_H3);
            _bmx280_calib.dig_H4 = (i2c_read_uint8(_address, BMX280_REGISTER_DIG_H4) << 4) | (i2c_read_uint8(_address, BMX280_REGISTER_DIG_H4+1) & 0xF);
            _bmx280_calib.dig_H5 = (i2c_read_uint8(_address, BMX280_REGISTER_DIG_H5+1) << 4) | (i2c_read_uint8(_address, BMX280_REGISTER_DIG_H5) >> 4);
            _bmx280_calib.dig_H6 = (int8_t) i2c_read_uint8(_address, BMX280_REGISTER_DIG_H6);
        }

        unsigned long _measurementTime() {

            // Measurement Time (as per BMX280 datasheet section 9.1)
            // T_max(ms) = 1.25
            //  + (2.3 * T_oversampling)
            //  + (2.3 * P_oversampling + 0.575)
            //  + (2.4 * H_oversampling + 0.575)
            //  ~ 9.3ms for current settings

            double t = 1.25;
            #if BMX280_TEMPERATURE > 0
                t += (2.3 * BMX280_TEMPERATURE);
            #endif
            #if BMX280_PRESSURE > 0
                t += (2.3 * BMX280_PRESSURE + 0.575);
            #endif
            #if BMX280_HUMIDITY > 0
                if (_chip == BMX280_CHIP_BME280) {
                    t += (2.4 * BMX280_HUMIDITY + 0.575);
                }
            #endif

            return round(t + 1); // round up

        }

        void _forceRead() {

            // We set the sensor in "forced mode" to force a reading.
            // After the reading the sensor will go back to sleep mode.
            uint8_t value = i2c_read_uint8(_address, BMX280_REGISTER_CONTROL);
            value = (value & 0xFC) + 0x01;
            i2c_write_uint8(_address, BMX280_REGISTER_CONTROL, value);

            nice_delay(_measurement_delay);

        }

        unsigned char _read() {

            #if BMX280_TEMPERATURE > 0
                int32_t adc_T = i2c_read_uint16(_address, BMX280_REGISTER_TEMPDATA);
                if (0xFFFF == adc_T) return SENSOR_ERROR_I2C;
                adc_T <<= 8;
                adc_T |= i2c_read_uint8(_address, BMX280_REGISTER_TEMPDATA+2);
                adc_T >>= 4;

                int32_t var1t = ((((adc_T>>3) -
                    ((int32_t)_bmx280_calib.dig_T1 <<1))) *
                    ((int32_t)_bmx280_calib.dig_T2)) >> 11;

                int32_t var2t = (((((adc_T>>4) -
                    ((int32_t)_bmx280_calib.dig_T1)) *
                    ((adc_T>>4) - ((int32_t)_bmx280_calib.dig_T1))) >> 12) *
                    ((int32_t)_bmx280_calib.dig_T3)) >> 14;

                int32_t t_fine = var1t + var2t;

                double T  = (t_fine * 5 + 128) >> 8;
                _temperature = T / 100;
            #else
                int32_t t_fine = 102374; // ~20ºC
            #endif

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

            #if BMX280_PRESSURE > 0
                int64_t var1, var2, p;

                int32_t adc_P = i2c_read_uint16(_address, BMX280_REGISTER_PRESSUREDATA);
                if (0xFFFF == adc_P) return SENSOR_ERROR_I2C;
                adc_P <<= 8;
                adc_P |= i2c_read_uint8(_address, BMX280_REGISTER_PRESSUREDATA+2);
                adc_P >>= 4;

                var1 = ((int64_t)t_fine) - 128000;
                var2 = var1 * var1 * (int64_t)_bmx280_calib.dig_P6;
                var2 = var2 + ((var1*(int64_t)_bmx280_calib.dig_P5)<<17);
                var2 = var2 + (((int64_t)_bmx280_calib.dig_P4)<<35);
                var1 = ((var1 * var1 * (int64_t)_bmx280_calib.dig_P3)>>8) +
                    ((var1 * (int64_t)_bmx280_calib.dig_P2)<<12);
                var1 = (((((int64_t)1)<<47)+var1))*((int64_t)_bmx280_calib.dig_P1)>>33;
                if (var1 == 0) return SENSOR_ERROR_I2C;  // avoid exception caused by division by zero

                p = 1048576 - adc_P;
                p = (((p<<31) - var2)*3125) / var1;
                var1 = (((int64_t)_bmx280_calib.dig_P9) * (p>>13) * (p>>13)) >> 25;
                var2 = (((int64_t)_bmx280_calib.dig_P8) * p) >> 19;

                p = ((p + var1 + var2) >> 8) + (((int64_t)_bmx280_calib.dig_P7)<<4);
                _pressure = (double) p / 256;
            #endif

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

            #if BMX280_HUMIDITY > 0
            if (_chip == BMX280_CHIP_BME280) {

                int32_t adc_H = i2c_read_uint16(_address, BMX280_REGISTER_HUMIDDATA);
                if (0xFFFF == adc_H) return SENSOR_ERROR_I2C;

                int32_t v_x1_u32r;

                v_x1_u32r = (t_fine - ((int32_t)76800));

                v_x1_u32r = (((((adc_H << 14) - (((int32_t)_bmx280_calib.dig_H4) << 20) -
                    (((int32_t)_bmx280_calib.dig_H5) * v_x1_u32r)) + ((int32_t)16384)) >> 15) *
                    (((((((v_x1_u32r * ((int32_t)_bmx280_calib.dig_H6)) >> 10) *
                    (((v_x1_u32r * ((int32_t)_bmx280_calib.dig_H3)) >> 11) + ((int32_t)32768))) >> 10) +
                    ((int32_t)2097152)) * ((int32_t)_bmx280_calib.dig_H2) + 8192) >> 14));

                v_x1_u32r = (v_x1_u32r - (((((v_x1_u32r >> 15) * (v_x1_u32r >> 15)) >> 7) *
                    ((int32_t)_bmx280_calib.dig_H1)) >> 4));

                v_x1_u32r = (v_x1_u32r < 0) ? 0 : v_x1_u32r;
                v_x1_u32r = (v_x1_u32r > 419430400) ? 419430400 : v_x1_u32r;
                double h = (v_x1_u32r >> 12);
                _humidity = h / 1024.0;

            }
            #endif

            return SENSOR_ERROR_OK;

        }

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

        unsigned char _chip;
        unsigned long _measurement_delay;
        bool _run_init = false;
        double _temperature = 0;
        double _pressure = 0;
        double _humidity = 0;

        typedef struct {

            uint16_t dig_T1;
            int16_t  dig_T2;
            int16_t  dig_T3;

            uint16_t dig_P1;
            int16_t  dig_P2;
            int16_t  dig_P3;
            int16_t  dig_P4;
            int16_t  dig_P5;
            int16_t  dig_P6;
            int16_t  dig_P7;
            int16_t  dig_P8;
            int16_t  dig_P9;

            uint8_t  dig_H1;
            int16_t  dig_H2;
            uint8_t  dig_H3;
            int16_t  dig_H4;
            int16_t  dig_H5;
            int8_t   dig_H6;

        } bmx280_calib_t;

        bmx280_calib_t _bmx280_calib;

};

// Static inizializations

unsigned char BMX280Sensor::addresses[2] = {0x76, 0x77};

#endif // SENSOR_SUPPORT && BMX280_SUPPORT