#!/usr/bin/python
# Copyright 2014 Johan Vandewalle. All rights reserved.
#
# Redistributian and use in source and binary forms, with or without
# modification, are permitted provide that the following conditions are
# met:
#     * Redistributions of source code must retain the above copyright
#       notice, this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above
#       copyright notice, this list of conditions and the following
#       disclaimer in the documentation and/or other materials provided
#       with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import time
from Adafruit_I2C import Adafruit_I2C
import math

# ===========================================================================
# BMP085 Class
# ===========================================================================

class BMP085 :
  i2c = None

  # Operating Modes
  __BMP085_ULTRALOWPOWER     = 0
  __BMP085_STANDARD          = 1
  __BMP085_HIGHRES           = 2
  __BMP085_ULTRAHIGHRES      = 3

  # BMP085 Registers
  __BMP085_CAL_AC1           = 0xAA  # R   Calibration data (16 bits)
  __BMP085_CAL_AC2           = 0xAC  # R   Calibration data (16 bits)
  __BMP085_CAL_AC3           = 0xAE  # R   Calibration data (16 bits)
  __BMP085_CAL_AC4           = 0xB0  # R   Calibration data (16 bits)
  __BMP085_CAL_AC5           = 0xB2  # R   Calibration data (16 bits)
  __BMP085_CAL_AC6           = 0xB4  # R   Calibration data (16 bits)
  __BMP085_CAL_B1            = 0xB6  # R   Calibration data (16 bits)
  __BMP085_CAL_B2            = 0xB8  # R   Calibration data (16 bits)
  __BMP085_CAL_MB            = 0xBA  # R   Calibration data (16 bits)
  __BMP085_CAL_MC            = 0xBC  # R   Calibration data (16 bits)
  __BMP085_CAL_MD            = 0xBE  # R   Calibration data (16 bits)
  __BMP085_CONTROL           = 0xF4
  __BMP085_TEMPDATA          = 0xF6
  __BMP085_PRESSUREDATA      = 0xF6
  __BMP085_READTEMPCMD       = 0x2E
  __BMP085_READPRESSURECMD   = 0x34

  # Private Fields
  _cal_AC1 = 0
  _cal_AC2 = 0
  _cal_AC3 = 0
  _cal_AC4 = 0
  _cal_AC5 = 0
  _cal_AC6 = 0
  _cal_B1 = 0
  _cal_B2 = 0
  _cal_MB = 0
  _cal_MC = 0
  _cal_MD = 0

  # Constructor
  def __init__(self, address=0x77, mode=1, debug=False):
    self.i2c = Adafruit_I2C(address)

    self.address = address
    self.debug = debug
    # Make sure the specified mode is in the appropriate range
    if ((mode < 0) | (mode > 3)):
      if (self.debug):
        print("Invalid Mode: Using STANDARD by default")
      self.mode = self.__BMP085_STANDARD
    else:
      self.mode = mode
    # Read the calibration data
    self.readCalibrationData()

  def readS16(self, register):
    "Reads a signed 16-bit value"
    hi = self.i2c.readS8(register)
    lo = self.i2c.readU8(register+1)
    return (hi << 8) + lo

  def readU16(self, register):
    "Reads an unsigned 16-bit value"
    hi = self.i2c.readU8(register)
    lo = self.i2c.readU8(register+1)
    return (hi << 8) + lo

  def readCalibrationData(self):
    "Reads the calibration data from the IC"
    self._cal_AC1 = self.readS16(self.__BMP085_CAL_AC1)   # INT16
    self._cal_AC2 = self.readS16(self.__BMP085_CAL_AC2)   # INT16
    self._cal_AC3 = self.readS16(self.__BMP085_CAL_AC3)   # INT16
    self._cal_AC4 = self.readU16(self.__BMP085_CAL_AC4)   # UINT16
    self._cal_AC5 = self.readU16(self.__BMP085_CAL_AC5)   # UINT16
    self._cal_AC6 = self.readU16(self.__BMP085_CAL_AC6)   # UINT16
    self._cal_B1 = self.readS16(self.__BMP085_CAL_B1)     # INT16
    self._cal_B2 = self.readS16(self.__BMP085_CAL_B2)     # INT16
    self._cal_MB = self.readS16(self.__BMP085_CAL_MB)     # INT16
    self._cal_MC = self.readS16(self.__BMP085_CAL_MC)     # INT16
    self._cal_MD = self.readS16(self.__BMP085_CAL_MD)     # INT16
    if (self.debug):
      self.showCalibrationData()

  def showCalibrationData(self):
      "Displays the calibration values for debugging purposes"
      print("DBG: AC1 = %6d" % (self._cal_AC1))
      print("DBG: AC2 = %6d" % (self._cal_AC2))
      print("DBG: AC3 = %6d" % (self._cal_AC3))
      print("DBG: AC4 = %6d" % (self._cal_AC4))
      print("DBG: AC5 = %6d" % (self._cal_AC5))
      print("DBG: AC6 = %6d" % (self._cal_AC6))
      print("DBG: B1  = %6d" % (self._cal_B1))
      print("DBG: B2  = %6d" % (self._cal_B2))
      print("DBG: MB  = %6d" % (self._cal_MB))
      print("DBG: MC  = %6d" % (self._cal_MC))
      print("DBG: MD  = %6d" % (self._cal_MD))

  def readRawTemp(self):
    "Reads the raw (uncompensated) temperature from the sensor"
    self.i2c.write8(self.__BMP085_CONTROL, self.__BMP085_READTEMPCMD)
    time.sleep(0.005)  # Wait 5ms
    raw = self.readU16(self.__BMP085_TEMPDATA)
    if (self.debug):
      print("DBG: Raw Temp: 0x%04X (%d)" % (raw & 0xFFFF, raw))
    return raw

  def readRawPressure(self):
    "Reads the raw (uncompensated) pressure level from the sensor"
    self.i2c.write8(self.__BMP085_CONTROL, self.__BMP085_READPRESSURECMD + (self.mode << 6))
    if (self.mode == self.__BMP085_ULTRALOWPOWER):
      time.sleep(0.005)
    elif (self.mode == self.__BMP085_HIGHRES):
      time.sleep(0.014)
    elif (self.mode == self.__BMP085_ULTRAHIGHRES):
      time.sleep(0.026)
    else:
      time.sleep(0.008)
    msb = self.i2c.readU8(self.__BMP085_PRESSUREDATA)
    lsb = self.i2c.readU8(self.__BMP085_PRESSUREDATA+1)
    xlsb = self.i2c.readU8(self.__BMP085_PRESSUREDATA+2)
    raw = ((msb << 16) + (lsb << 8) + xlsb) >> (8 - self.mode)
    if (self.debug):
      print("DBG: Raw Pressure: 0x%04X (%d)" % (raw & 0xFFFF, raw))
    return raw

  def readTemperature(self):
    "Gets the compensated temperature in degrees celcius"
    UT = 0
    X1 = 0
    X2 = 0
    B5 = 0
    temp = 0.0

    # Read raw temp before aligning it with the calibration values
    UT = self.readRawTemp()
    X1 = ((UT - self._cal_AC6) * self._cal_AC5) >> 15
    X2 = (self._cal_MC << 11) / (X1 + self._cal_MD)
    B5 = X1 + X2
    temp = (int(B5 + 8) >> 4) / 10.0
    if (self.debug):
      print("DBG: Calibrated temperature = %f C" % temp)
    return temp

  def readPressure(self):
    "Gets the compensated pressure in pascal"
    UT = 0
    UP = 0
    B3 = 0
    B5 = 0
    B6 = 0
    X1 = 0
    X2 = 0
    X3 = 0
    p = 0
    B4 = 0
    B7 = 0

    UT = self.readRawTemp()
    UP = self.readRawPressure()

    # You can use the datasheet values to test the conversion results
    # dsValues = True
    dsValues = False

    if (dsValues):
      UT = 27898
      UP = 23843
      self._cal_AC6 = 23153
      self._cal_AC5 = 32757
      self._cal_MB = -32768;
      self._cal_MC = -8711
      self._cal_MD = 2868
      self._cal_B1 = 6190
      self._cal_B2 = 4
      self._cal_AC3 = -14383
      self._cal_AC2 = -72
      self._cal_AC1 = 408
      self._cal_AC4 = 32741
      self.mode = self.__BMP085_ULTRALOWPOWER
      if (self.debug):
        self.showCalibrationData()

    # True Temperature Calculations
    X1 = ((UT - self._cal_AC6) * self._cal_AC5) >> 15
    X2 = (self._cal_MC << 11) / (X1 + self._cal_MD)
    B5 = X1 + X2
    if (self.debug):
      print("DBG: X1 = %d" % (X1))
      print("DBG: X2 = %d" % (X2))
      print("DBG: B5 = %d" % (B5))
      print("DBG: True Temperature = %.2f C" % (((B5 + 8) >> 4) / 10.0))

    # Pressure Calculations
    B6 = B5 - 4000
    X1 = (int(self._cal_B2) * int(B6 * B6) >> 12) >> 11
    X2 = int(self._cal_AC2 * B6) >> 11
    X3 = X1 + X2
    B3 = (((self._cal_AC1 * 4 + X3) << self.mode) + 2) / 4
    if (self.debug):
      print("DBG: B6 = %d" % (B6))
      print("DBG: X1 = %d" % (X1))
      print("DBG: X2 = %d" % (X2))
      print("DBG: X3 = %d" % (X3))
      print("DBG: B3 = %d" % (B3))

    X1 = int(self._cal_AC3 * B6) >> 13
    X2 = (int(self._cal_B1) * (int(B6 * B6) >> 12)) >> 16
    X3 = ((X1 + X2) + 2) >> 2
    B4 = (self._cal_AC4 * (X3 + 32768)) >> 15
    B7 = (UP - B3) * (50000 >> self.mode)
    if (self.debug):
      print("DBG: X1 = %d" % (X1))
      print("DBG: X2 = %d" % (X2))
      print("DBG: X3 = %d" % (X3))
      print("DBG: B4 = %d" % (B4))
      print("DBG: B7 = %d" % (B7))

    if (B7 < 0x80000000):
      p = (B7 * 2) / B4
    else:
      p = (B7 / B4) * 2

    if (self.debug):
      print("DBG: X1 = %d" % (X1))

    X1 = (int(p) >> 8) * (int(p) >> 8)
    X1 = (X1 * 3038) >> 16
    X2 = (-7357 * int(p)) >> 16
    if (self.debug):
      print("DBG: p  = %d" % (p))
      print("DBG: X1 = %d" % (X1))
      print("DBG: X2 = %d" % (X2))

    p = p + ((X1 + X2 + 3791) >> 4)
    if (self.debug):
      print("DBG: Pressure = %d Pa" % (p))

    return p

  def readAltitude(self, seaLevelPressure=101325):
    "Calculates the altitude in meters"
    altitude = 0.0
    pressure = float(self.readPressure())
    temperature = float(self.readTemperature())
#    altitude = 44330.0 * (1.0 - pow(pressure / seaLevelPressure, 0.1903))
    altitude = round( -math.log( pressure / seaLevelPressure ) * 8314 * ( temperature + 273.15 ) / ( 25 * 9.81 ) , 2 )
    # this isn't completely correct. The formula uses the temperature of the sensor while it should be using the temperature
    # at sea level. At lower altitudes and close (less then 200 km) from the shore, the difference is neglectable. If you want
    # to use the script at higher locations or deeper inland, comment this line and uncomment the line above.
    if (self.debug):
      print("DBG: Altitude = %.2f m" % (altitude))
    return altitude

    return 0
