"""
s-Sense BME680 by itbrainpower.net python library v0.3 / 20200218
BME680 [temperature, pressure, humidity and gas] sensors are manufactured by Bosch Sensortec

Compatible with:
                s-Sense BME680 I2C sensor breakout [PN: SS-BME680#I2C, SKU: ITBP-6003], info https://itbrainpower.net/sensors/BME680-BVOC-TEMPERATURE-HUMIDITY-PRESSURE-I2C-sensor-breakout
                all Raspberry PI, using Python 2.7

Reading temperature, pressure, humidity and gas resistance values example (pulling at 2sec) - based on Zanshin_BME680 Arduino library
version 1.0.2 / 2019-01-26 - https://github.com/SV-Zanshin. Thank you Zanshin! Great job! 
 
We've ported Zanshin functions into python, patch them, add some variables, functions and functionalities.                


Mandatory wiring [bellow for RPi B/B+/II/3B/3B+/4/Zero/Zero W]:
        - sensor Vin            <------> RPI pin 1 [3V3 power] *
        - sensor I2C SDA        <------> RPI pin 3 [i2c-1 SDA]
        - sensor I2C SCL        <------> RPI pin 5 [i2c-1 SCL]
        - sensor GND            <------> RPI pin 9 [GND]

Wiring notes:
        *    to spare 3V3 power - read about RPI I2C sensors 5V powering

WIRING WARNING:
        Wrong wiring may damage your RaspberryPI or your sensor! Double check what you've done.


BME680 definitions are placed in bme680_param.py


Bellow, how to set-up i2c on RPi and install requiered python packages and other utilities.

Enable I2C channel 1
        a. sudo raspi-config
                menu F5		=> 	enable I2C
                save, exit and reboot.
        
        
        b. edit /boot/config.txt and add/enable following directives:
               dtparam=i2c_arm=on
               dtparam=i2c_arm_baudrate=10000

           save and reboot.

Check i2c is loaded:
        run: ls /dev/*i2c*
        should return: /dev/i2c-1

Add i2c-tools packages:
        sudo apt-get install -y i2c-tools

Check sensor I2C connection:
        run: i2cdetect -y 1
        you should see listed the s-Sense BME680 I2C address [0x76]

Install additional python packages:
        a. sudo apt-get install python-setuptools
        b. wget https://files.pythonhosted.org/packages/6a/06/80a6928e5cbfd40c77c08e06ae9975c2a50109586ce66435bd8166ce6bb3/smbus2-0.3.0.tar.gz
        c. expand archive
        d. chdir smbus2-0.3.0
        e. sudo python setup.py install


You are legaly entitled to use this SOFTWARE ONLY IN CONJUNCTION WITH s-Sense BME680 I2C sensors DEVICES USAGE. Modifications, derivates and
redistribution of this software must include unmodified this COPYRIGHT NOTICE. You can redistribute this SOFTWARE and/or modify it under the terms 
of this COPYRIGHT NOTICE. Any other usage may be permited only after written notice of Dragos Iosub / R&D Software Solutions srl.

This SOFTWARE is distributed is provide "AS IS" in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
or FITNESS FOR A PARTICULAR PURPOSE.


itbrainpower.net invests significant time in design phase of our IoT products and in associated software and support resources.
Support us by purchasing our environmental and air quality sensors from here https://itbrainpower.net/order#s-Sense


Dragos Iosub, Bucharest 2020.
https://itbrainpower.net
"""

import smbus2 as smbus

from time import sleep

from bme680_param import *



humOSR          = BME680_OSRX16
tempOSR         = BME680_OSRX16
presOSR         = BME680_OSRX16

BME680_filter   = BME680_Filter_4

temp = 0.0
press = 0.0
hum = 0.0
gas = 0.0

_T1 = _T2 = _T3 = 0x00
_P1 = _P2 = _P3 = _P4 = _P5 = _P6 = _P7 = _P8 = _P9 = _P10 = 0x00
_H1 = _H2 = _H3 = _H4 = _H5 = _H6 = _H7 = 0x00
_G1 = _G2 = _G3 = 0x00
        
_res_heat_range = _res_heat_val = _range_sw_error = 0x00
        

def bme680SWReset():        
        bme680NWriteRegisters(BME680_SOFTRESET_REGISTER, [BME680_RESET_CODE])
        sleep(0.5)
        


def bme680ReadNRegisters(address, length):
        global bus
        global BME680_I2C_ADDRESS
        contents = bus.read_i2c_block_data(BME680_I2C_ADDRESS, address, length)
        return contents


def bme680ReadRegister(address):
        global bus
        global BME680_I2C_ADDRESS

        contents = bus.read_i2c_block_data(BME680_I2C_ADDRESS, address, 1)
        return contents[0]

def bme680NWriteRegisters(address, data):
        global bus
        global BME680_I2C_ADDRESS
        bus.write_i2c_block_data(BME680_I2C_ADDRESS, address, data)

def bme680WriteRegister(address, data):
        global bus
        global BME680_I2C_ADDRESS

        bus.write_byte_data(BME680_I2C_ADDRESS, address, data)




def bme680WriteSettings():
        global humOSR
        global tempOSR
        global presOSR
        global BME680_filter
        

        #humidity oversample
        content = bme680ReadRegister(BME680_CONTROL_HUMIDITY_REGISTER)          #get contents
        content &= 0b11111000                                                   #mask bits 3-7
        content |= humOSR                                                       #set oversampling rate  
        bme680WriteRegister(BME680_CONTROL_HUMIDITY_REGISTER, content)          #write register content
 
        #pressure oversample
        content = bme680ReadRegister(BME680_CONTROL_MEASURE_REGISTER)           #get contents
        content &= 0b11000111                                                   #mask bits 
        content |= presOSR<<2                                                   #set oversampling rate  CHECK THIS!!!!
        #content |= presOSR<<3                                                   #set oversampling rate  
        bme680WriteRegister(BME680_CONTROL_MEASURE_REGISTER, content)           #write register content
 
        #temperature oversample
        content = bme680ReadRegister(BME680_CONTROL_MEASURE_REGISTER)           #get contents
        content &= 0b00011111                                                   #mask bits 
        content |= tempOSR<<5                                                   #set oversampling rate  
        bme680WriteRegister(BME680_CONTROL_MEASURE_REGISTER, content)           #write register content

        #iirfilter
        content = bme680ReadRegister(BME680_CONFIG_REGISTER)                   #get control register byte

        content &= 0b11100011                                                   #get control reg, mask IIR bits
        content |= (BME680_filter & 0b00000111) << 2                            #use 3 bits of iirFilterSetting
        bme680WriteRegister(BME680_CONFIG_REGISTER,content)                     #write new control register value
        





def bme680SetSettings(_humOSR, _tempOSR, _presOSR, _filter):
        global humOSR
        global tempOSR
        global presOSR
        global BME680_filter

        humOSR = _humOSR
        tempOSR = _tempOSR
        presOSR = _presOSR
        BME680_filter = _filter
        
        bme680WriteSettings()


def bme680SetDefaultSettings():                         #set default settings
        bme680SetSettings(BME680_OSRX16, BME680_OSRX16, BME680_OSRX16, BME680_Filter_4)
        bme680WriteSettings()


def toSignedWord(Word):
        if (Word & (1 << 15) != 0):
                Word = Word - (1 << 16)
        return Word

def toSignedByte(Byte):
        if (Byte & (1 << 7) != 0):
                Byte = Byte - (1 << 8)
        return Byte

def bme680GetCalibration():
        global _T1, _T2, _T3
        global _P1, _P2, _P3, _P4, _P5, _P6, _P7, _P8, _P9, _P10
        global _H1, _H2, _H3, _H4, _H5, _H6, _H7
        global _G1, _G2, _G3
        
        global _res_heat_range, _res_heat_val, _range_sw_error

        BME680_COEFF_SIZE1              =    25         #First array with coefficients
        BME680_COEFF_SIZE2              =    16         #Second array with coefficients
        BME680_COEFF_START_ADDRESS1     =  0x89         #start address for array 1
        BME680_COEFF_START_ADDRESS2     =  0xE1         #start address for array 2
        BME680_HUM_REG_SHIFT_VAL        =     4         #Ambient humidity shift value
        BME680_BIT_H1_DATA_MSK          =  0x0F
        BME680_T2_LSB_REG               =     1
        BME680_T2_MSB_REG               =     2
        BME680_T3_REG		        =     3
        BME680_P1_LSB_REG   	        =     5
        BME680_P1_MSB_REG	        =     6
        BME680_P2_LSB_REG	        =     7
        BME680_P2_MSB_REG	        =     8
        BME680_P3_REG		        =     9
        BME680_P4_LSB_REG         	=    11
        BME680_P4_MSB_REG        	=    12
        BME680_P5_LSB_REG           	=    13
        BME680_P5_MSB_REG         	=    14
        BME680_P7_REG  	                =    15
        BME680_P6_REG	                =    16
        BME680_P8_LSB_REG    	        =    19
        BME680_P8_MSB_REG	        =    20
        BME680_P9_LSB_REG    	        =    21
        BME680_P9_MSB_REG	        =    22
        BME680_P10_REG		        =    23
        BME680_H2_MSB_REG	        =     0
        BME680_H2_LSB_REG	        =     1
        BME680_H1_LSB_REG    	        =     1
        BME680_H1_MSB_REG	        =     2
        BME680_H3_REG	                =     3
        BME680_H4_REG    	        =     4
        BME680_H5_REG	                =     5
        BME680_H6_REG                   =     6
        BME680_H7_REG                   =     7
        BME680_T1_LSB_REG	        =     8
        BME680_T1_MSB_REG    	        =     9
        BME680_GH2_LSB_REG              =    10
        BME680_GH2_MSB_REG              =    11
        BME680_GH1_REG	                =    12
        BME680_GH3_REG	                =    13
        BME680_ADDR_RES_HEAT_RANGE_ADDR =  0x02
        BME680_RHRANGE_MSK              =  0x30
        BME680_ADDR_RES_HEAT_VAL_ADDR   =  0x00
        BME680_ADDR_RANGE_SW_ERR_ADDR   =  0x04
        BME680_RSERROR_MSK	        =  0xF0

        coeff_array1 = [0x00] * BME680_COEFF_SIZE1
        coeff_array2 = [0x00] * BME680_COEFF_SIZE2

        coeff_array1 =  bme680ReadNRegisters(BME680_COEFF_START_ADDRESS1, BME680_COEFF_SIZE1)
        coeff_array2 =  bme680ReadNRegisters(BME680_COEFF_START_ADDRESS2, BME680_COEFF_SIZE2)

        #temperature related
        _T1  = (coeff_array2[BME680_T1_MSB_REG] << 8) | coeff_array2[BME680_T1_LSB_REG]
        _T2  = toSignedWord((coeff_array1[BME680_T2_MSB_REG] << 8) | coeff_array2[BME680_T2_LSB_REG])
        _T3  = toSignedByte(coeff_array1[BME680_T3_REG])

        #pressure related
        _P1  = (coeff_array1[BME680_P1_MSB_REG] << 8) | coeff_array1[BME680_P1_LSB_REG]
        _P2  = toSignedWord((coeff_array1[BME680_P2_MSB_REG] << 8) | coeff_array1[BME680_P2_LSB_REG])
        _P3  = coeff_array1[BME680_P3_REG]
        _P4  = toSignedWord((coeff_array1[BME680_P4_MSB_REG] << 8) | coeff_array1[BME680_P4_LSB_REG])
        _P5  = toSignedWord((coeff_array1[BME680_P5_MSB_REG] << 8) | coeff_array1[BME680_P5_LSB_REG])
        _P6  = toSignedByte(coeff_array1[BME680_P6_REG])
        _P7  = toSignedByte(coeff_array1[BME680_P7_REG])
        _P8  = toSignedWord((coeff_array1[BME680_P8_MSB_REG] << 8) | coeff_array1[BME680_P8_LSB_REG])
        _P9  = toSignedWord((coeff_array1[BME680_P9_MSB_REG] << 8) | coeff_array1[BME680_P9_LSB_REG])
        _P10 = coeff_array1[BME680_P10_REG]
        
        #humidity related
        _H1  = ((coeff_array2[BME680_H1_MSB_REG] << BME680_HUM_REG_SHIFT_VAL) | (coeff_array2[BME680_H1_LSB_REG] & BME680_BIT_H1_DATA_MSK))#ck this!
        _H2  = ((coeff_array2[BME680_H2_MSB_REG] << BME680_HUM_REG_SHIFT_VAL) | (coeff_array2[BME680_H2_LSB_REG] >> BME680_HUM_REG_SHIFT_VAL))#ck this!
        _H3  = toSignedByte(coeff_array2[BME680_H3_REG])
        _H4  = toSignedByte(coeff_array2[BME680_H4_REG])
        _H5  = toSignedByte(coeff_array2[BME680_H5_REG])
        _H6  = coeff_array2[BME680_H6_REG]
        _H7  = toSignedByte(coeff_array2[BME680_H7_REG])

        #gas related
        _G1  = toSignedByte(coeff_array2[BME680_GH1_REG])
        _G2  = toSignedWord((coeff_array2[BME680_GH2_MSB_REG]  << 8) | coeff_array2[BME680_GH2_LSB_REG])
        _G3  = toSignedByte(coeff_array2[BME680_GH3_REG])

        _res_heat_range = int((bme680ReadRegister(BME680_ADDR_RES_HEAT_RANGE_ADDR) & BME680_RHRANGE_MSK) / 16)
        _res_heat_val = bme680ReadRegister(BME680_ADDR_RES_HEAT_VAL_ADDR)
        _range_sw_error = int((bme680ReadRegister(BME680_ADDR_RANGE_SW_ERR_ADDR) & BME680_RSERROR_MSK) / 16)
        
        
        

def bme680Begin():
        global temp
        global press
        global hum
        global gas
        global DevID_BME680

        temp = 0.0
        press = 0.0
        hum = 0.0
        gas = 0.0        
        


        contents = bme680ReadRegister(BME680_ID_ADDR)           #Hardware ID should be 0x61
        print "BME680_HW_ID : 0x%02x " %contents
 

        if (contents != BME680_DevID):                          #check sensor type"
                print "BME680 not found. Please check wiring."
                return False

        print "BME680 found. Lets rock!"

        bme680SWReset()

        contents = bme680ReadRegister(BME680_ID_ADDR)                   #Hardware ID should be 0x61
        if (contents != BME680_DevID):                                  #check sensor type"
                print "BME680 not found. Wrong sw RESET."
                return False


        bme680GetCalibration()                                          # get calibration values


        bme680SetDefaultSettings()                                      #set default settings

        return 0  


        

def bme680ReadData():
        global temp
        global press
        global hum
        global gas

        global _T1, _T2, _T3
        global _P1, _P2, _P3, _P4, _P5, _P6, _P7, _P8, _P9, _P10
        global _H1, _H2, _H3, _H4, _H5, _H6, _H7
        global _G1, _G2, _G3
        
        global _res_heat_range, _res_heat_val, _range_sw_error


        #support for possible gas ranges
        lookupTable1= [2147483647,2147483647,2147483647,2147483647,2147483647,2126008810,2147483647,2130303777,2147483647,2147483647,2143188679,2136746228,2147483647,2126008810,2147483647,2147483647]
        lookupTable2= [4096000000,2048000000,1024000000,512000000,255744255,127110228,64000000,32258064,16016016,8000000,4000000,2000000,1000000,500000,250000,125000]


        buff = [0x00] * 15
        gas_range = 0
        status = 0
        var1 = var2 = var3 = var4 = var5 = var6 = 0
        temp_scaled = 0
        adc_temp = adc_pres = 0
        adc_hum = adc_gas_res = 0

        content = bme680ReadRegister(BME680_STATUS_REGISTER)
        while (content & 0b00100000 != 0):
                sleep(0.1)
                content = bme680ReadRegister(BME680_STATUS_REGISTER)

        buff = bme680ReadNRegisters(BME680_STATUS_REGISTER, 15)

        #adc_pres    = (int(buff[2] * 4096) |int(buff[3] * 16) | int(buff[4] / 16))      # put the 3 bytes of Pressure
        #adc_temp    = ((buff[5] * 4096) |(buff[6] * 16) | (buff[7] / 16))      # put the 3 bytes of Temperature
        #adc_hum     = ((buff[8] * 256) | buff[9])                              # put the 2 bytes of Humidity
        #adc_gas_res = (buff[13] * 4 | (buff[14] / 64))                         # put the 2 bytes of Gas

        adc_pres = (buff[2] << 12) | (buff[3] << 4) | (buff[4] >> 4)
        adc_temp = (buff[5] << 12) | (buff[6] << 4) | (buff[7] >> 4)
        adc_hum = (buff[8] << 8) | buff[9]
        adc_gas_res = (buff[13] << 2) | (buff[14] >> 6)

        gas_range   = buff[14] & 0x0F           # Retrieve the range

        status |= buff[14] & 0x20               #See if the gas range is valid
        status |= buff[14] & 0x10               #and the measurement is valid

        #temperature
        var1         = (adc_temp >> 3) - (_T1 << 1)                     # Perform calibration/adjustment
        var2         = (var1 * _T2) >> 11                               # of Temperature values according
        var3         = ((var1 >> 1) * (var1 >> 1)) >> 12                # to formula defined by Bosch
        var3         = (var3 * (_T3 << 4)) >> 14
        _tfine       = int(var2 + var3)
        
        temp = (((_tfine * 5) + 128) >> 8) & 0xFFFF

        #pressure
        var1      = (_tfine >> 1) - 64000
        var2      = ((((var1 >> 2) * (var1 >> 2)) >> 11) * _P6) >> 2
        var2      = var2 + ((var1 * _P5) << 1)
        var2      = (var2 >> 2) + (_P4 << 16)
        var1      = (((((var1 >> 2) * (var1 >> 2)) >> 13) * (_P3 << 5)) >> 3) + ((_P2 * var1) >> 1)
        var1      = var1 >> 18
        var1      = ((32768 + var1) * _P1) >> 15
        press = 1048576 - adc_pres
        press = (press - (var2 >> 12)) * 3125

        if (press >= 1 << 31):
                press = int(press / var1) << 1
        else:
                press = int((press << 1) / var1)

        var1 = (_P9 * (((press >> 3) * (press >> 3)) >> 13)) >> 12
        var2 = ((press >> 2) * _P8) >> 13
        var3 = ((press >> 8) * (press >> 8) * (press >> 8) * _P10) >> 17

        press = press + ((var1 + var2 + var3 + (_P7 << 7)) >> 4)
        #press = (press) + ((var1 + var2 + var3 + (_P7 << 7)) >> 4)



        #humidity
        temp_scaled = ((_tfine * 5) + 128) >> 8
        var1        = adc_hum - (_H1 * 16) - ((int(temp_scaled * _H3) / 100) >> 1)
        var2        = (_H2 * (int((temp_scaled * _H4) / 100) + int(((temp_scaled * int((temp_scaled * _H5) / 100)) >> 6) / 100) + (1 << 14))) >> 10
        var3        = var1 * var2
        var4        = _H6 << 7
        var4        = (var4 + int((temp_scaled * _H7) / 100)) >> 4
        var5        = ((var3 >> 14) * (var3 >> 14)) >> 10
        var6        = (var4 * var5) >> 1
        hum   = (((var3 + var6) >> 10) *1000) >> 12

        if (hum > 100000):
                hum = 100000
        elif (hum < 0):
                hum = 0

        #gas
        var1 = ((1340 + (5 * _range_sw_error)) * lookupTable1[gas_range]) >> 16
        uvar2 = (adc_gas_res << 15) - 16777216 + var1
        var3 = (lookupTable2[gas_range] * var1) >> 9
        gas = int((var3 + (uvar2 >> 1)) / uvar2)

        content = bme680ReadRegister(BME680_CONTROL_MEASURE_REGISTER)
        bme680WriteRegister(BME680_CONTROL_MEASURE_REGISTER, content | 0x01)            #trigger next measurement


def bme680SetGas(GasTemp, GasMillis):
        global temp
        global press
        global hum
        global gas

        global _T1, _T2, _T3
        global _P1, _P2, _P3, _P4, _P5, _P6, _P7, _P8, _P9, _P10
        global _H1, _H2, _H3, _H4, _H5, _H6, _H7
        global _G1, _G2, _G3
        
        global _res_heat_range, _res_heat_val, _range_sw_error

        gasRegister = bme680ReadRegister(BME680_CONTROL_GAS_REGISTER2)                  # Read current register values
        if (GasTemp==0 or GasMillis==0):
                bme680WriteRegister(BME680_CONTROL_GAS_REGISTER1, 0b00001000)                   # Turn off gas heater
                bme680WriteRegister(BME680_CONTROL_GAS_REGISTER2, gasRegister & 0b11101111)     # Turn off gas measurements
        else:
                bme680WriteRegister(BME680_CONTROL_GAS_REGISTER1, 0b00000000)                   # Turn on gas heater

                if (GasTemp < 200):
                        GasTemp = 200
                elif (GasTemp > 400):
                        GasTemp = 400

                var1 = int(((temp / 100) * _H3) / 1000) * 256
                var2 = (_H1 + 784) * (((((_H2 + 154009) * GasTemp * 5) / 100) + 3276800) / 10)
                var3 = var1 + (var2 / 2)
                var4 = var3 / (_res_heat_range + 4)
                var5 = (131 * _res_heat_val) + 65536

                heatr_res_x100 = int(((var4 / var5) - 250) * 34)
                heatr_res_x100 &= 0xFFFFFFFF
                heatr_res = int((heatr_res_x100 + 50) / 100)
                heatr_res &= 0xFF

                bme680WriteRegister(BME680_GAS_HEATER_REGISTER0, heatr_res)

                factor = 0
                durval = 0
                if (GasMillis >= 0xFC0):
                        durval = 0xFF   # Max duration
                else:
                        while (GasMillis > 0x3F):
                                GasMillis = GasMillis / 4
                                factor += 1
                        durval = (GasMillis + (factor * 64)) & 0xFF

                bme680WriteRegister(BME680_CONTROL_GAS_REGISTER1, 0b00000000)
                bme680WriteRegister(BME680_GAS_DURATION_REGISTER0,durval)
                bme680WriteRegister(BME680_CONTROL_GAS_REGISTER2, gasRegister | 0b00010000)



def bme680GetGas():
        global gas
        return gas /100.0

def bme680GetTemperature():
        global temp
        return temp / 100.0

def bme680GetPressure():
        global press
        return press

def bme680GetHumidity():
        global hum
        return hum / 1000.0


# Initialize I2C (SMBus)
try:
    configContents = bme680ReadRegister(BME680_DevID)
    print "I2C alredy loaded"
except:
    bus = smbus.SMBus(channel)

