/*
  BMP280_DEV is an I2C/SPI compatible library for the Bosch BMP280 barometer.
	
	Copyright (C) Martin Lindupp 2019
	
	V1.0.0 -- Initial release 	
	V1.0.1 -- Added ESP32 HSPI support and change library to unique name
	V1.0.2 -- Modification to allow external creation of HSPI object on ESP32
	V1.0.3 -- Changed library name in the library.properties file
	V1.0.5 -- Fixed bug in BMP280_DEV::getTemperature() function, thanks to Jon M.
	V1.0.6 -- Merged multiple instances and initialisation pull requests by sensslen
	V1.0.8 -- Used default arguments for begin() member function and 
						added example using multiple BMP280 devices with SPI comms in NORMAL mode
	V1.0.9 -- Moved writeMask to Device class and improved measurement detection code
	V1.0.10 -- Modification to allow user-defined pins for I2C operation on the ESP8266
	V1.0.12 -- Allow sea level pressure calibration using setSeaLevelPressure() function
	V1.0.14 -- Fix uninitialised structures, thanks to David Jade investigating and 
						 flagging up this issue
	V1.0.16 -- Modification to allow user-defined pins for I2C operation on the ESP32
	V1.0.17 -- Added getCurrentTemperature(), getCurrentPressure(), getCurrentTempPres() 
						 getCurrentAltitude() and getCurrentMeasurements() functions,
						 to allow the BMP280 to be read directly without checking the measuring bit
	V1.0.18 -- Initialise "device" constructor member variables in the same order they are declared
	V1.0.19 -- Allow for additional TwoWire instances
	V1.0.20 -- Removed default parameter causing ESP32 compilation error with user defined I2C pins
	V1.0.21 -- Fixed uninitialised "Wire" pointer for ESP8266/ESP32 with user defined I2C pins 
	
	The MIT License (MIT)
	Permission is hereby granted, free of charge, to any person obtaining a copy
	of this software and associated documentation files (the "Software"), to deal
	in the Software without restriction, including without limitation the rights
	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
	copies of the Software, and to permit persons to whom the Software is
	furnished to do so, subject to the following conditions:
	The above copyright notice and this permission notice shall be included in all
	copies or substantial portions of the Software.
	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
	SOFTWARE.
*/

#include <BMP280_DEV.h>

////////////////////////////////////////////////////////////////////////////////
// BMP280_DEV Class Constructors
////////////////////////////////////////////////////////////////////////////////

BMP280_DEV::BMP280_DEV(TwoWire& twoWire) : Device(twoWire) { setI2CAddress(BMP280_I2C_ADDR); }		// Constructor for I2C communications		
#ifdef ARDUINO_ARCH_ESP8266
BMP280_DEV::BMP280_DEV(uint8_t sda, uint8_t scl, TwoWire& twoWire) : 			// Constructor for I2C comms on ESP8266
	Device(sda, scl, twoWire) { setI2CAddress(BMP280_I2C_ADDR); } 	
#endif
BMP280_DEV::BMP280_DEV(uint8_t cs) : Device(cs) {}			   								// Constructor for SPI communications
#ifdef ARDUINO_ARCH_ESP32 																			
BMP280_DEV::BMP280_DEV(uint8_t sda, uint8_t scl, TwoWire& twoWire) : 			// Constructor for I2C comms on ESP32
	Device(sda, scl, twoWire) { setI2CAddress(BMP280_I2C_ADDR); } 	
BMP280_DEV::BMP280_DEV(uint8_t cs, uint8_t spiPort, SPIClass& spiClass) : Device(cs, spiPort, spiClass) {} // Constructor for SPI communications on the ESP32
#endif

////////////////////////////////////////////////////////////////////////////////
// BMP280_DEV Public Member Functions
////////////////////////////////////////////////////////////////////////////////

uint8_t BMP280_DEV::begin(Mode mode, 															// Initialise BMP280 device settings
													Oversampling presOversampling, 
													Oversampling tempOversampling,
													IIRFilter iirFilter,
													TimeStandby timeStandby)
{
	initialise();																											// Call the Device base class "initialise" function
	if (readByte(BMP280_DEVICE_ID) != DEVICE_ID)              				// Check the device ID
  {
    return 0;                                                     	// If the ID is incorrect return 0
  }	
  reset();                                                          // Reset the BMP280 barometer
  readBytes(BMP280_TRIM_PARAMS, (uint8_t*)&params, sizeof(params)); // Read the trim parameters into the params structure
	setConfigRegister(iirFilter, timeStandby); 												// Initialise the BMP280 configuration register
	setCtrlMeasRegister(mode, presOversampling, tempOversampling);		// Initialise the BMP280 control and measurement register	
	return 1;																													// Report successful initialisation
}

uint8_t BMP280_DEV::begin(Mode mode, uint8_t addr)									// Initialise BMP280 with default settings, but selected mode and
{																																		// I2C address
	setI2CAddress(addr);
	return begin(mode);
}

uint8_t BMP280_DEV::begin(uint8_t addr)															// Initialise BMP280 with default settings and selected I2C address
{
	setI2CAddress(addr);
	return begin();
}

void BMP280_DEV::reset()																						// Reset the BMP280 barometer
{
	writeByte(BMP280_RESET, RESET_CODE);                     									
  delay(10);                                                                
}

void BMP280_DEV::startNormalConversion() { setMode(NORMAL_MODE); }	// Start continuous measurement in NORMAL_MODE

void BMP280_DEV::startForcedConversion() 														// Start a one shot measurement in FORCED_MODE
{ 
	ctrl_meas.reg = readByte(BMP280_CTRL_MEAS);											 	// Read the control and measurement register
	if (ctrl_meas.bit.mode == SLEEP_MODE)															// Only set FORCED_MODE if we're already in SLEEP_MODE
	{
		setMode(FORCED_MODE);
	}	
}			

void BMP280_DEV::stopConversion() { setMode(SLEEP_MODE); }					// Stop the conversion and return to SLEEP_MODE

void BMP280_DEV::setPresOversampling(Oversampling presOversampling)	// Set the pressure oversampling rate
{
	ctrl_meas.bit.osrs_p = presOversampling;
	writeByte(BMP280_CTRL_MEAS, ctrl_meas.reg);
}

void BMP280_DEV::setTempOversampling(Oversampling tempOversampling)	// Set the temperature oversampling rate
{
	ctrl_meas.bit.osrs_t = tempOversampling;
	writeByte(BMP280_CTRL_MEAS, ctrl_meas.reg);
}

void BMP280_DEV::setIIRFilter(IIRFilter iirFilter)									// Set the IIR filter setting
{
	config.bit.filter = iirFilter;
	writeByte(BMP280_CONFIG, config.reg);
}

void BMP280_DEV::setTimeStandby(TimeStandby timeStandby)						// Set the time standby measurement interval
{
	config.bit.t_sb = timeStandby;
	writeByte(BMP280_CONFIG, config.reg);
}

void BMP280_DEV::setSeaLevelPressure(float pressure)								// Set the sea level pressure value
{
	sea_level_pressure = pressure;
}

void BMP280_DEV::getCurrentTemperature(float &temperature)					// Get the current temperature without checking the measuring bit
{
	uint8_t data[3];                                                  // Create a data buffer
	readBytes(BMP280_TEMP_MSB, &data[0], 3);             							// Read the temperature and pressure data
	int32_t adcTemp = (int32_t)data[0] << 12 | (int32_t)data[1] << 4 | (int32_t)data[2] >> 4;  // Copy the temperature and pressure data into the adc variables
	int32_t temp = bmp280_compensate_T_int32(adcTemp);                // Temperature compensation (function from BMP280 datasheet)
	temperature = (float)temp / 100.0f;                               // Calculate the temperature in degrees Celsius
}

uint8_t BMP280_DEV::getTemperature(float &temperature)							// Get the temperature with measurement check
{
	if (!dataReady())																									// Check if a measurement is ready
	{
		return 0;
	}
	getCurrentTemperature(temperature);																// Get the current temperature
	return 1;
}

void BMP280_DEV::getCurrentPressure(float &pressure)								// Get the current pressure without checking the measuring bit
{
	float temperature;
	getCurrentTempPres(temperature, pressure);
}

uint8_t BMP280_DEV::getPressure(float &pressure)										// Get the pressure
{
	float temperature;
	return getTempPres(temperature, pressure);
}

void BMP280_DEV::getCurrentTempPres(float &temperature, float &pressure)	// Get the current temperature and pressure without checking the measuring bit
{
	uint8_t data[6];                                                  // Create a data buffer
	readBytes(BMP280_PRES_MSB, &data[0], 6);             							// Read the temperature and pressure data
	int32_t adcTemp = (int32_t)data[3] << 12 | (int32_t)data[4] << 4 | (int32_t)data[5] >> 4;  // Copy the temperature and pressure data into the adc variables
	int32_t adcPres = (int32_t)data[0] << 12 | (int32_t)data[1] << 4 | (int32_t)data[2] >> 4;
	int32_t temp = bmp280_compensate_T_int32(adcTemp);                // Temperature compensation (function from BMP280 datasheet)
	uint32_t pres = bmp280_compensate_P_int64(adcPres);               // Pressure compensation (function from BMP280 datasheet)
	temperature = (float)temp / 100.0f;                               // Calculate the temperature in degrees Celsius
	pressure = (float)pres / 256.0f / 100.0f;                         // Calculate the pressure in millibar
}

uint8_t BMP280_DEV::getTempPres(float &temperature, float &pressure)	// Get the temperature and pressure
{
	if (!dataReady())																									// Check if a measurement is ready
	{
		return 0;
	}
	getCurrentTempPres(temperature, pressure);												// Get the current temperature and pressure
	return 1;
}

void BMP280_DEV::getCurrentAltitude(float &altitude)								// Get the current altitude without checking the measuring bit
{
	float temperature, pressure;
	getCurrentMeasurements(temperature, pressure, altitude);
}

uint8_t BMP280_DEV::getAltitude(float &altitude)										// Get the altitude
{
	float temperature, pressure;
	return getMeasurements(temperature, pressure, altitude);
}

void BMP280_DEV::getCurrentMeasurements(float &temperature, float &pressure, float &altitude) // Get all the measurements without checking the measuring bit
{
	getCurrentTempPres(temperature, pressure);
	altitude = ((float)powf(sea_level_pressure / pressure, 0.190223f) - 1.0f) * (temperature + 273.15f) / 0.0065f; // Calculate the altitude in metres 
}

uint8_t BMP280_DEV::getMeasurements(float &temperature, float &pressure, float &altitude)		// Get all measurements temperature, pressue and altitude
{  
	if (getTempPres(temperature, pressure))
	{
		altitude = ((float)powf(sea_level_pressure / pressure, 0.190223f) - 1.0f) * (temperature + 273.15f) / 0.0065f; // Calculate the altitude in metres 
		return 1;
	}
	return 0;
}

////////////////////////////////////////////////////////////////////////////////
// BMP280_DEV Private Member Functions
////////////////////////////////////////////////////////////////////////////////

void BMP280_DEV::setMode(Mode mode)																	// Set the BMP280's mode
{
	ctrl_meas.bit.mode = mode;
	writeByte(BMP280_CTRL_MEAS, ctrl_meas.reg);
}

// Set the BMP280 control and measurement register 
void BMP280_DEV::setCtrlMeasRegister(Mode mode, Oversampling presOversampling, Oversampling tempOversampling)
{
	ctrl_meas.reg = tempOversampling << 5 | presOversampling << 2 | mode;
	writeByte(BMP280_CTRL_MEAS, ctrl_meas.reg);                              
}

// Set the BMP280 configuration register
void BMP280_DEV::setConfigRegister(IIRFilter iirFilter, TimeStandby timeStandby)
{
	config.reg = timeStandby << 5 | iirFilter << 2;
	writeByte(BMP280_CONFIG, config.reg);                              
}

uint8_t BMP280_DEV::dataReady()																			// Check if a measurement is ready
{			
	if (ctrl_meas.bit.mode == SLEEP_MODE)														 	// If we're in SLEEP_MODE return immediately
	{
		return 0;
	}
	status.reg = readByte(BMP280_STATUS);															// Read the status register				
	if (status.bit.measuring ^ previous_measuring)						 				// Edge detection: check if the measurement bit has been changed
	{
		previous_measuring = status.bit.measuring;											// Update the previous measuring flag
		if (!status.bit.measuring)																			// Check if the measuring bit has been cleared
		{
			if (ctrl_meas.bit.mode == FORCED_MODE)												// If we're in FORCED_MODE switch back to SLEEP_MODE
			{
				ctrl_meas.bit.mode = SLEEP_MODE;												
			}
			return 1;																											// A measurement is ready
		}
	}
	return 0;																													// A measurement is still pending
}

////////////////////////////////////////////////////////////////////////////////
// Bosch BMP280_DEV (Private) Member Functions
////////////////////////////////////////////////////////////////////////////////

// Returns temperature in DegC, resolution is 0.01 DegC. Output value of “5123” equals 51.23 DegC.
// t_fine carries fine temperature as global value
int32_t BMP280_DEV::bmp280_compensate_T_int32(int32_t adc_T)
{
  int32_t var1, var2, T;
  var1 = ((((adc_T >> 3) - ((int32_t)params.dig_T1 << 1))) * ((int32_t)params.dig_T2)) >> 11;
  var2 = (((((adc_T >> 4) - ((int32_t)params.dig_T1)) * ((adc_T >> 4) - ((int32_t)params.dig_T1))) >> 12) *
  ((int32_t)params.dig_T3)) >> 14;
  t_fine = var1 + var2;
  T = (t_fine * 5 + 128) >> 8;
  return T;
}

// Returns pressure in Pa as unsigned 32 bit integer in Q24.8 format (24 integer bits and 8 fractional bits).
// Output value of “24674867” represents 24674867/256 = 96386.2 Pa = 963.862 hPa
uint32_t BMP280_DEV::bmp280_compensate_P_int64(int32_t adc_P)
{
  int64_t var1, var2, p;
  var1 = ((int64_t)t_fine) - 128000;
  var2 = var1 * var1 * (int64_t)params.dig_P6;
  var2 = var2 + ((var1 * (int64_t)params.dig_P5) << 17);
  var2 = var2 + (((int64_t)params.dig_P4) << 35);
  var1 = ((var1 * var1 * (int64_t)params.dig_P3) >> 8) + ((var1 * (int64_t)params.dig_P2) << 12);
  var1 = (((((int64_t)1) << 47) + var1)) * ((int64_t)params.dig_P1) >> 33;
  if (var1 == 0)
  {
    return 0; // avoid exception caused by division by zero
  }
  p = 1048576 - adc_P;
  p = (((p << 31) - var2) * 3125) / var1;
  var1 = (((int64_t)params.dig_P9) * (p >> 13) * (p>>13)) >> 25;
  var2 = (((int64_t)params.dig_P8) * p) >> 19;
  p = ((p + var1 + var2) >> 8) + (((int64_t)params.dig_P7) << 4);
  return (uint32_t)p;
}
