# Christian Fiot 2020
# Current power monitor INA219, INA233 is a better part (zero offset on current reading)
# VShunt 40mV max
# V1.2  June 23 - Correct offset current
# V1.3  July 6 - Format the retuned value 0.00, if I is < 10mA report 0

# Usage:
# from INA219 import INA219
#
# IVCh1 = INA219(0x40, 0.1, traceDebug=True)
# IVCh1.begin() # return True if chip found,  display currentLSB
# IVCh1.read()  # return I in Amp, V in Volt
# IVCh1.readLSB() # return I and  P
#    I in currentLSB
#    P in powerLSB which is 20 times the currentLSB for a power value in watts

from math import trunc
from trap import * # 2.2.9b
from machine import I2C # 2.2.18


class INA219():

    def __init__(self, iicadr=0x040, sense=0.002, i2c=None, sda='P22', scl='P21', traceDebug=False, sdAccess=False): # 2.2.9b
        self.trace = traceDebug
        self.adr = iicadr
        self.sdAccess = sdAccess # 2.2.9b

        # allowed sense resistor value  0.1,  0.010,  0.002
        #    I = 40mV / 0.1 Ohm  = 400 mA   Adafruit default
        #    I = 40mV / 0.01 Ohm = 4A
        #    R = U / I  =>  40mV / 20A = 0.002 Ohm   Solar Power Distribution
        # [sense in Ohm, Current_LSB in Amp]      #   https://www.youtube.com/watch?v=AhSvKGTh28Q   4:27
        self.sense = 0
        self.currentLSB = 0
        List = [[0.1,0.0001], [0.01,0.001], [0.002,0.005], [0.001,0.01]]
        for i in range(len(List)) :
            # for j in range(len(List[i])) :
            if sense == List[i][0]: # Find sense?
                self.sense = sense
                self.currentLSB = List[i][1]


        self.data = bytearray(2)
        self.shunt = 0
        self.bus = 0
        self.power = 0
        self.current = 0

        # v1.2 current offset, read sensor when no power flows, relay openned
        self.calib = 0
        self.offcurrent = 0
        self.offshunt = 0
        self.offpower = 0


        # I2C bus declaration is extenal
        # MCP are connected on I2C bus #1   SDA-P22   SCL-P21
        #                      I2C bus #2   SDA-P18   SCL-P17
        #
        if i2c is not None: # 2.2.18
            self.i2c1 = i2c
        else:
            # from machine import I2C
            self.i2c1 = I2C(0, mode=I2C.MASTER, pins=(sda, scl))


    # Detect and init sensor
    def begin(self):
        try:
            # 8.6.2 Set ctrl register
            # Reset b15= 0, BBNG b14-13=01 32V FSR, PG b12-11=00 40mV
            # BADC b7-10 = 1001, SADC b6-3=1001   convertion time  1msec
            # Mode b2-0 = 111   Continous
            # 0010 0100 1100 1111   =   0x24CF
            self.i2c1.writeto_mem(self.adr, 0x00, bytes([0b00100100, 0b11001111]) )

            # 8.51 Set Calibration register
            # result = 0.04 / self.sense  # Max Current
            # result = result / 32768 # Current_LSB
            #
            # compute Current_LSB
            # result = 0.0001    # 100uA/bit  400mA Max    sense= 0.1 Ohm
            # result = 0.001      # 1mA/bit   4Amp max   sense=  0.01 Ohm
            # result = 0.005       # 5mA/bit  20Amp max   sense= 0.002 Ohm
            # result = 0.01     # 10mA/bit  40Amp max    sense= 0.001 Ohm
            result = self.currentLSB
            calibration = trunc(0.04096 /  ( result * self.sense))
            self.i2c1.writeto_mem(self.adr, 0x05, self.getBytes(calibration) )

            # v1.2 Calibrate offset
            self.calib = 1
            self.offcurrent, self.offpower =  self.readLSB()
            self.offshunt, u1 = self.read()
            self.calib = 0


            if self.trace :
                # result = 0.04 / self.sense  # current fullscale
                # print('INA219 RShunt: {}  currentLSB: {}A  Cal: {}'.format (self.sense, result, calibration) )
                print('INA219 OShunt: {}A  Ocurrent: {}A  '.format (self.offshunt, self.offcurrent) )
            print('Detected INA219 - power monitor   Adr:' + str(self.adr) ) # 2.2.2b
            return True

        except Exception as e: # 2.2.9b
            scratch = 'INA219 Adr:' + str(self.adr) + ' not found - '   # 2.2.9b  print('INA219 Adr:' + str(self.adr) + ' not found')
            file_errLog(0, scratch + str(e), self.sdAccess)  # 2.2.9b
            # 2.2.18 print('Begin I2C1 scan ...')
            # 2.2.18 print(self.i2c1.scan())
            return False


    def getBytes(self, U1):
        return bytearray([(U1 >> 8) & 0xFF, U1 & 0xFF])

    def getWord(self, high, low):
        return ((high & 0xFF) << 8) + (low & 0xFF)

    def read(self):
        self.i2c1.readfrom_mem_into(self.adr, 1, self.data)     # 1 = Shunt register
        self.shunt = self.getWord(self.data[0], self.data[1])   # 10uV per step
        self.shunt = ((self.shunt * 0.00001) / self.sense)  - self.offshunt  # v1.2
        if (self.shunt < 0):  self.shunt = 0   # v1.2


        self.i2c1.readfrom_mem_into(self.adr, 2, self.data)     # 2 = Bus register
        self.bus = self.getWord(self.data[0], self.data[1]) >> 3  # 4mV per step
        self.bus = self.bus * 0.004 # Value in Volt

        if (self.calib == 0):
            if (self.shunt < 0.01):
                self.shunt = 0
        return self.shunt, self.bus


    def readLSB(self):
        self.i2c1.readfrom_mem_into(self.adr, 3, self.data)     # 3 = Power register
        self.power = self.getWord(self.data[0], self.data[1])   # 20mW per step
        self.power = (self.power * 0.020) - self.offpower  #v1.2  Value in Watt
        if (self.power < 0 ):  self.power = 0  # v1.2

        self.i2c1.readfrom_mem_into(self.adr, 4, self.data)     # 4 = Current register in mA
        self.current = self.getWord(self.data[0], self.data[1]) # 1mA per step
        self.current = (self.current * self.currentLSB) - self.offcurrent  # v1.2 Value in Amp
        if (self.current < 0):  self.current = 0  # v1.2

        if (self.calib == 0):
            if (self.current < 0.01):
                self.current = 0
                self.power = 0
        return self.current, self.power
