import time
import mariadb

from smbus2 import SMBus

import sys
import json

from ctypes import c_short
from ctypes import c_byte
from ctypes import c_ubyte



class bcolors:
    MAGENTA = '\033[95m'
    BLUE = '\033[94m'
    CYAN = '\033[96m'
    GREEN = '\033[92m'
    YELLOW = '\033[93m'
    RED = '\033[91m'
    ENDC = '\033[0m'
    BOLD = '\033[1m'
    UNDERLINE = '\033[4m'




# -------------- SDP810 ------------------------------------------

def init_sen_sdp810(addr, bus_nr):
    
    bus = SMBus(bus_nr)

    bus.write_i2c_block_data(addr, 0x3F, [0xF9]) #Stop any cont measurement of the sensor
    time.sleep(0.1)
    bus.write_i2c_block_data(addr, 0x36, [0x1E])
    time.sleep(0.1)

    bus.close()


def query_sen_sdp810(addr, bus_nr):
    
    bus = SMBus(bus_nr)
    data = bus.read_i2c_block_data(addr, 0, 9)

    press = -twos_comp((256 * data[0] + data[1]), 16) / 240.0
    temp = twos_comp((256 * data[3] + data[4]), 16) / 200
  
    bus.close()
  
    return press


def twos_comp(val, bits):
    if (val & (1 << (bits - 1))) != 0: # if sign bit is set e.g., 8bit: 128-255#
        val = val - (1 << bits)        # compute negative value
    return val   


# -------------- BMP280 ------------------------------------------

def init_sen_bmp280(addr, bus_nr):
    pass


def query_sen_bmp280(addr, bus_nr):

    # print(f'     * {addr}, {bus_nr}')

    bus = SMBus(bus_nr) 

    REG_DATA = 0xF7
    REG_CONTROL = 0xF4
    REG_CONFIG  = 0xF5

    REG_CONTROL_HUM = 0xF2
    REG_HUM_MSB = 0xFD
    REG_HUM_LSB = 0xFE

    # Oversample setting - page 27
    OVERSAMPLE_TEMP = 2
    OVERSAMPLE_PRES = 2
    MODE = 1

    # Oversample setting for humidity register - page 26
    OVERSAMPLE_HUM = 2
    bus.write_byte_data(addr, REG_CONTROL_HUM, OVERSAMPLE_HUM)

    control = OVERSAMPLE_TEMP<<5 | OVERSAMPLE_PRES<<2 | MODE
    bus.write_byte_data(addr, REG_CONTROL, control)

    # Read blocks of calibration data from EEPROM
    # See Page 22 data sheet
    cal1 = bus.read_i2c_block_data(addr, 0x88, 24)
    cal2 = bus.read_i2c_block_data(addr, 0xA1, 1)
    cal3 = bus.read_i2c_block_data(addr, 0xE1, 7)

    # Convert byte data to word values
    dig_T1 = getUShort(cal1, 0)
    dig_T2 = getShort(cal1, 2)
    dig_T3 = getShort(cal1, 4)

    dig_P1 = getUShort(cal1, 6)
    dig_P2 = getShort(cal1, 8)
    dig_P3 = getShort(cal1, 10)
    dig_P4 = getShort(cal1, 12)
    dig_P5 = getShort(cal1, 14)
    dig_P6 = getShort(cal1, 16)
    dig_P7 = getShort(cal1, 18)
    dig_P8 = getShort(cal1, 20)
    dig_P9 = getShort(cal1, 22)

    dig_H1 = getUChar(cal2, 0)
    dig_H2 = getShort(cal3, 0)
    dig_H3 = getUChar(cal3, 2)

    dig_H4 = getChar(cal3, 3)
    dig_H4 = (dig_H4 << 24) >> 20
    dig_H4 = dig_H4 | (getChar(cal3, 4) & 0x0F)

    dig_H5 = getChar(cal3, 5)
    dig_H5 = (dig_H5 << 24) >> 20
    dig_H5 = dig_H5 | (getUChar(cal3, 4) >> 4 & 0x0F)

    dig_H6 = getChar(cal3, 6)

    # Wait in ms (Datasheet Appendix B: Measurement time and current calculation)
    wait_time = 1.25 + (2.3 * OVERSAMPLE_TEMP) + ((2.3 * OVERSAMPLE_PRES) + 0.575) + ((2.3 * OVERSAMPLE_HUM)+0.575)
    time.sleep(wait_time/1000)  # Wait the required time  

    # Read temperature/pressure/humidity
    data = bus.read_i2c_block_data(addr, REG_DATA, 8)
    pres_raw = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4)
    temp_raw = (data[3] << 12) | (data[4] << 4) | (data[5] >> 4)
    hum_raw = (data[6] << 8) | data[7]

    #Refine temperature
    var1 = ((((temp_raw>>3)-(dig_T1<<1)))*(dig_T2)) >> 11
    var2 = (((((temp_raw>>4) - (dig_T1)) * ((temp_raw>>4) - (dig_T1))) >> 12) * (dig_T3)) >> 14
    t_fine = var1+var2
    temperature = float(((t_fine * 5) + 128) >> 8)

    # Refine pressure and adjust for temperature
    var1 = t_fine / 2.0 - 64000.0
    var2 = var1 * var1 * dig_P6 / 32768.0
    var2 = var2 + var1 * dig_P5 * 2.0
    var2 = var2 / 4.0 + dig_P4 * 65536.0
    var1 = (dig_P3 * var1 * var1 / 524288.0 + dig_P2 * var1) / 524288.0
    var1 = (1.0 + var1 / 32768.0) * dig_P1
    if var1 == 0:
        pressure=0
    else:
        pressure = 1048576.0 - pres_raw
        pressure = ((pressure - var2 / 4096.0) * 6250.0) / var1
        var1 = dig_P9 * pressure * pressure / 2147483648.0
        var2 = pressure * dig_P8 / 32768.0
        pressure = pressure + (var1 + var2 + dig_P7) / 16.0

    # Refine humidity
    humidity = t_fine - 76800.0
    humidity = (hum_raw - (dig_H4 * 64.0 + dig_H5 / 16384.0 * humidity)) * (dig_H2 / 65536.0 * (1.0 + dig_H6 / 67108864.0 * humidity * (1.0 + dig_H3 / 67108864.0 * humidity)))
    humidity = humidity * (1.0 - dig_H1 * humidity / 524288.0)
   
    if humidity > 100:
        humidity = 100
    elif humidity < 0:
        humidity = 0

    bus.close()

    return temperature / 100.0, pressure / 100.0, humidity


def getShort(data, index):
    return c_short((data[index+1] << 8) + data[index]).value

def getUShort(data, index):
    return (data[index+1] << 8) + data[index]

def getChar(data,index):
    result = data[index]
    if result > 127:
        result -= 256
    return result

def getUChar(data,index):
    result =  data[index] & 0xFF
    return result


# -----------------------------------------------------------------

def write_val_to_db(session_pid, sensor_pid, value):
    global conn

    with conn.cursor(buffered = True) as cur:
        cur.execute('INSERT INTO data.sensor_data(sensor, meas_session, val) VALUES(?, ?, ?)', (sensor_pid, session_pid, value))



# -----------------------------------------------------------------


sensors = []

try:
    conn = mariadb.connect(user='meas_www', password='6yWmVAw+a70wGToAFoJnWA', host='localhost', port=3306)      
    conn.autocommit = True  

    with conn.cursor(buffered = True) as cur:
    # with conn.cursor(buffered = True, named_tuple = True) as cur:
        cur.execute('SELECT s_phys.pid, s_type.pid, s_phys.i2c_address, loc.pid, loc.i2c_bus_nr, s_type.query_func_info FROM sensor.sensor_physical s_phys, sensor.sensor_type s_type, sensor.sensor_location loc WHERE s_phys.sensor_type = s_type.pid AND s_phys.location = loc.pid AND s_phys.is_enabled = 1')
        
        # for (sen) in cur:
            # sensors.append(dict(sen))
            # print(dict(sen))

        for (sen_pyhs_pid, sen_type_pid, i2c_address, loc_pid, i2c_bus_nr, query_func_info) in cur:

            with conn.cursor(buffered = True) as cur2:
                cur2.execute('SELECT pid, meas_type FROM sensor.sensor WHERE sensor_physical = ? ORDER BY meas_type', (sen_pyhs_pid, ))
                sens = []
                for (sen_pid, sen_type) in cur2:
                    sens.append({'sen_pid' : sen_pid, 'sen_type' : sen_type})
                    # print(f'   {sen_pid} - {sen_type}')

            sensors.append({'sen_pyhs_pid' : sen_pyhs_pid, 'sens' : sens, 'sen_type_pid' : sen_type_pid, 'i2c_address' : i2c_address, 'loc_pid' : loc_pid, 'i2c_bus_nr' : i2c_bus_nr, 'query_func_info' : query_func_info })
            print(f'{sen_pyhs_pid}, {sen_type_pid} : {loc_pid} ({i2c_bus_nr}, {i2c_address}) - {query_func_info}')

except mariadb.Error as e:
    print(f'Error: {e}')




print('>> initializing sensors...')

for sen in sensors:
    
    init_func_name = 'init_' + sen['query_func_info']
    meas_data = locals()[init_func_name](int('0x' + sen['i2c_address'], 16), sen['i2c_bus_nr'])
    print(f'  initializing sensor {sen["sen_pyhs_pid"]} ({init_func_name})')
    # print(f'  {sen["sens"]}')


print('')
print('>> starting continuous measurement...')
meas_session_id = 0


# while (False):
while (True):

    with conn.cursor(buffered = True) as cur:
        cur.execute('INSERT INTO data.meas_session() VALUES()')
        meas_session_id = cur.lastrowid
        
    meas_start_time = time.time()
    print(f'---- {meas_session_id} -----------------------')

    for sen in sensors:
        
        query_func_name = 'query_' + sen['query_func_info']
        meas_data = locals()[query_func_name](int('0x' + sen['i2c_address'], 16), sen['i2c_bus_nr'])
        print(f'  {sen["sen_pyhs_pid"]} ({query_func_name})  >  {meas_data}')

        if sen['sen_type_pid'] == 2:        # bmp280
            write_val_to_db(meas_session_id, sen['sens'][1]['sen_pid'], meas_data[0])   # Temp
            write_val_to_db(meas_session_id, sen['sens'][0]['sen_pid'], meas_data[1])   # Druck abs
            write_val_to_db(meas_session_id, sen['sens'][2]['sen_pid'], meas_data[2])   # Feuchte rel
            pass
        elif sen['sen_type_pid'] == 3:      # sdp810
            write_val_to_db(meas_session_id, sen['sens'][0]['sen_pid'], meas_data)


    meas_end_time = time.time()
    meas_duration = meas_end_time - meas_start_time
    meas_pause = 1 - meas_duration
    if meas_pause < 0:
        meas_pause = 0

    print(f'* took {(meas_duration * 1000):.1f} ms - pausing {(meas_pause * 1000):.1f} ms')


    time.sleep(meas_pause)
    # time.sleep(1)
    # meas_session_id += 1