# Christian Fiot 2020
# ADC 16bit  with PGA    4 channels   VIn max 2V
# power requirement 200uA max - 5uA low
#
# wiring hardwareOption b0=0 Pycom SOcket Va:
# Diff - PID + A0   J4-4    /    PID  - A1   J4-3
# IBattery A3   J4-1   insert 0.1 ohm   in line with -Batt
# VBattery A2   J4-2   Serie R1= 120K   R2= 100K divider to GND  J4-5
#
# wiring hardwareOption b0=1  Pycom SOcket Vb:  2.09
# Diff - PID + A2   ADS_J4-2    /    PID  - A3   ADS_J4-1     2.09
# IBattery A0   ADS_AIN-1    PyScokVb_J14-1   2.09
# VBattery A1  ADSAIN-2     PyScokVb_J14-2   2.09
#
#
# Usage:
# from ADS1115 import ADS1115
#
# pidAH2 = ADS1115(0x48)
# pidAH2.diff(chanPos=0, chanNeg=1, gain=PGA_1_024V)
# pidAH2.get_voltage(ADCount)
# pidAH2.get_voltage(pidAH2.read(channel=3, gain=PGA_0_256V))
#
#
# Pycom class for interacting with the ADS1115 ADC
# Credit  https://gist.github.com/sidwarkd/ec427aefb41e21e77919c7f52df7ea63



import ustruct
import time
from trap import * # 2.2.9b
from machine import I2C # 2.2.18


_REGISTER_MASK = const(0x03)
_REGISTER_CONVERT = const(0x00)
_REGISTER_CONFIG = const(0x01)
_REGISTER_LOWTHRESH = const(0x02)
_REGISTER_HITHRESH = const(0x03)

_OS_MASK = const(0x8000)
_OS_SINGLE = const(0x8000)  # Write: Set to start a single-conversion
_OS_BUSY = const(0x0000)  # Read: Bit=0 when conversion is in progress
_OS_NOTBUSY = const(0x8000)  # Read: Bit=1 when device is not performing a conversion

_MUX_MASK = const(0x7000)
_MUX_DIFF_0_1 = const(0x0000)  # Differential P  =  AIN0, N  =  AIN1 (default)
_MUX_DIFF_0_3 = const(0x1000)  # Differential P  =  AIN0, N  =  AIN3
_MUX_DIFF_1_3 = const(0x2000)  # Differential P  =  AIN1, N  =  AIN3
_MUX_DIFF_2_3 = const(0x3000)  # Differential P  =  AIN2, N  =  AIN3
_MUX_SINGLE_0 = const(0x4000)  # Single-ended AIN0
_MUX_SINGLE_1 = const(0x5000)  # Single-ended AIN1
_MUX_SINGLE_2 = const(0x6000)  # Single-ended AIN2
_MUX_SINGLE_3 = const(0x7000)  # Single-ended AIN3

_PGA_MASK = const(0x0E00)
PGA_6_144V = const(0x0000)  # +/-6.144V range  =  Gain 2/3
PGA_4_096V = const(0x0200)  # +/-4.096V range  =  Gain 1
PGA_2_048V = const(0x0400)  # +/-2.048V range  =  Gain 2 (default)
PGA_1_024V = const(0x0600)  # +/-1.024V range  =  Gain 4
PGA_0_512V = const(0x0800)  # +/-0.512V range  =  Gain 8
PGA_0_256V = const(0x0A00)  # +/-0.256V range  =  Gain 16

_MODE_MASK = const(0x0100)
_MODE_CONTIN = const(0x0000)  # Continuous conversion mode
_MODE_SINGLE = const(0x0100)  # Power-down single-shot mode (default)

_DR_MASK = const(0x00E0) # cf
_DR_8SPS = const(0x0000)  # 8 samples per second
_DR_16SPS = const(0x0020)  # 16 samples per second
_DR_32SPS = const(0x0040)  # 32 samples per second
_DR_64SPS = const(0x0060)  # 64 samples per second
_DR_128SPS = const(0x0080)  # 128 samples per second (default)
_DR_250SPS = const(0x00A0)  # 250 samples per second
_DR_475SPS = const(0x00C0)  # 475 samples per second
_DR_860SPS = const(0x00E0)  # 860 samples per second

_CMODE_MASK = const(0x0010)
_CMODE_TRAD = const(0x0000)  # Traditional comparator with hysteresis (default)
_CMODE_WINDOW = const(0x0010)  # Window comparator

_CPOL_MASK = const(0x0008)
_CPOL_ACTVLOW = const(0x0000)  # ALERT/RDY pin is low when active (default)
_CPOL_ACTVHI = const(0x0008)  # ALERT/RDY pin is high when active

_CLAT_MASK = const(0x0004)  # Determines if ALERT/RDY pin latches once asserted
_CLAT_NONLAT = const(0x0000)  # Non-latching comparator (default)
_CLAT_LATCH = const(0x0004)  # Latching comparator

_CQUE_MASK = const(0x0003)
_CQUE_1CONV = const(0x0000)  # Assert ALERT/RDY after one conversions
_CQUE_2CONV = const(0x0001)  # Assert ALERT/RDY after two conversions
_CQUE_4CONV = const(0x0002)  # Assert ALERT/RDY after four conversions
_CQUE_NONE = const(0x0003)  # Disable the comparator and put ALERT/RDY in high state (default)

# A mapping of gain values to rail to rail voltage range
_GAINS = {
    PGA_6_144V: 12.288, # 2/3x
    PGA_4_096V: 8.192, # 1x
    PGA_2_048V: 4.096, # 2x
    PGA_1_024V: 2.048, # 4x
    PGA_0_512V: 1.024, # 8x
    PGA_0_256V: 0.512  # 16x
}
_CHANNELS = (_MUX_SINGLE_0, _MUX_SINGLE_1, _MUX_SINGLE_2, _MUX_SINGLE_3)
_DIFFS = {
    (0, 1): _MUX_DIFF_0_1,
    (0, 3): _MUX_DIFF_0_3,
    (1, 3): _MUX_DIFF_1_3,
    (2, 3): _MUX_DIFF_2_3,
}


class ADS1115:
    def __init__(self, iicadr=0x48, i2c=None, sda='P22', scl='P21', reference='na' , traceDebug=False, sdAccess=False):  # 2.2.9b   cf  i2c, address, gain
        # cf  self.i2c = i2c
        # self.address = address
        # if gain in _GAINS:
        #    self.gain = gain
        # else:
        #    raise ValueError('gain must be one of the values in _GAINS')
        self.adr = iicadr
        self.gain = PGA_6_144V
        self.reference = reference # 2.2.3b
        self.sdAccess = sdAccess # 2.2.9b

        # 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))


    def begin(self):
        try:
            # print("\nChannel 0 voltage: {}V".format(self.get_voltage(0)))
            # print("Channel 0 ADC value: {}\n".format(self.read(0)))
            self.read_config()
            print('Detected ADS1115 ' + self.reference + ' - ADC  Adr:' + str(self.adr) )
            return True

        except Exception as e: # 2.2.9b
            scratch = 'ADS1115 ' + self.reference +  '- ADC (Adr:' + str(self.adr) + ') not found - '  # 2.2.9b  print('ADS1115 ' + self.reference +  '- ADC (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 _write_register(self, register, value):
        data = ustruct.pack('>BH', register, value)
        self.i2c1.writeto(self.adr, data)

    def _read_register(self, register):
        data = self.i2c1.readfrom_mem(self.adr, register, 2 )
        return ustruct.unpack('>h', data)[0]

    def read_config(self):
        return self.i2c1.readfrom_mem(self.adr, 1, 2)

    def get_voltage(self, value):   # cf  channel):
        return _GAINS[self.gain] * ( value / 65535) # cf  (self.read(channel) / 65535)


    def read(self, channel=0, gain=PGA_6_144V):
        self.gain = gain
        self._write_register(_REGISTER_CONFIG, _CQUE_NONE | _CLAT_NONLAT |
            _CPOL_ACTVLOW | _CMODE_TRAD | _DR_128SPS | _MODE_SINGLE |
            _OS_SINGLE | self.gain | _CHANNELS[channel])
        while not self._read_register(_REGISTER_CONFIG) & _OS_NOTBUSY:
            time.sleep_ms(1)
        return self._read_register(_REGISTER_CONVERT)


    def diff(self, chanPos=0, chanNeg=1, gain=PGA_6_144V): # default A0 +, A1 -
        self.gain = gain
        self._write_register(_REGISTER_CONFIG, _CQUE_NONE | _CLAT_NONLAT |
            _CPOL_ACTVLOW | _CMODE_TRAD | _DR_128SPS | _MODE_SINGLE |
            _OS_SINGLE | self.gain | _DIFFS[(chanPos, chanNeg)])
        while not self._read_register(_REGISTER_CONFIG) & _OS_NOTBUSY:
            time.sleep_ms(1)
        return self._read_register(_REGISTER_CONVERT)

    def alert_start(self, channel, threshold):
        self._write_register(_REGISTER_HITHRESH, threshold)
        self._write_register(_REGISTER_CONFIG, _CQUE_1CONV | _CLAT_LATCH |
            _CPOL_ACTVLOW | _CMODE_TRAD | _DR_1600SPS | _MODE_CONTIN |
            _MODE_CONTIN | self.gain | _CHANNELS[channel])

    def alert_read(self):
        return self._read_register(_REGISTER_CONVERT)
