# Christian Fiot June 2021
# LED driver PCA9955B   16bits
# Sample catcher controller VA
#
# Usage:
# from PCA9955B import PCA9955B
# LEDIO = PCA9955B(traceDebug=True)
# LEDIO.begin()  # return True if chip is found
# LEDIO.ledON(7)
# LEDIO.ledOFF(7)
# LEDIO.ledBlink(7)
# LEDIO.allON()
# LEDIO.allOFF()
#

# $18  MCP9808 skyhook - main
# $19  LIS3DH skyhook
# $1A  MCP9808 skyhook - main
# $21  MCP23017 skyhook - main
# 54=$36  MAX17048  - fuelguage  boot
# 56=$38  PCF8574 - weathershield main
# 64=$40  HM3300 / HM3300 - dust skyhook - main
# 65=$41  PCA9536  - boot
# $48  ADS1115 / INA219 skyhook - main
# 73=$49  ADS1115 / INA219 skyhook - main
# 74=$4A  ADS1115 / INA219 skyhook - main
# 75=$4B  ADS1115 / INA219 skyhook - main
# $4C  INA219 skyhook - main
# $4D  INA219 skyhook - main
# $4E  INA219 skyhook - main
# $4F  INA219 skyhook - main
# $5E  PCA9955B - LEDctrl smpcatcher
# $50  M24C02 - EEPROM smpctacher ch#0
# $51  M24C02 - EEPROM smpctacher ch#1
# $52  M24C02 - EEPROM smpctacher ch#2
# $53  M24C02 - EEPROM smpctacher ch#3
# $54  M24C02 - EEPROM smpctacher ch#4
# $55  M24C02 - EEPROM smpctacher ch#5
# $56  M24C02 - EEPROM smpctacher ch#6
# $57  M24C02 - EEPROM smpctacher ch#7
# 88=$58  SGP30 - VOC - main
# $60 ($70 at reset) PCA9955B - LEDctrl smpcatcher
# $61 ($76 at reset) PCA9955B - LEDctrl smpcatcher
# $68  DS1307 - RTC
# 112=$70  PI4M / PCA9955B - I2Cswitch - boot
# 118=$76  BME280 / PCA9955B at reset - main
# $77  BMP280 - skyhook

# pin2  WS2812  -  sensors/_init_.py




import time # utime
import ustruct
from trap import *
from machine import I2C # 2.2.18


class PCA9955B():

    def __init__(self, iicadr=0x05E, i2c=None, sda='P22', scl='P21', traceDebug=False, sdAccess=False):
        self.trace = traceDebug
        self.adr = iicadr
        self.sdAccess = sdAccess
        self.detected = False
        self.data = bytearray(1)

        # I2C bus declaration is extenal
        # PCA is connected on I2C bus #1   SDA-P22   SCL-P21
        #
        #
        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 write(self, adreg, data, paused=1):
        self.data[0] = data
        self.i2c1.writeto_mem(self.adr, adreg, self.data)
        time.sleep_ms(paused)

    def read(self, adreg):
        self.i2c1.readfrom_mem_into(self.adr, adreg, self.data)
        return self.data[0]

    # see: LED Driver PCA9955B.PDF   page 12
    def begin(self):
        try:
            '''
            self.data[0] = 0b00000000 # disable autoinc, allcall and sub1-2-3
            self.i2c1.writeto_mem(self.adr, 0x00, self.data)  # Write MODE1  pg 17
            self.data[0] = 0b00100001 # group ctrl blinking
            self.i2c1.writeto_mem(self.adr, 0x01, self.data)  # Write MODE2  pg 18
            self.data[0] = 0x0FF # output Imax
            self.i2c1.writeto_mem(self.adr, 0x045, self.data)  # Write IREFALL  pg 32
            self.data[0] = 0x0F # 1sec blinking
            self.i2c1.writeto_mem(self.adr, 0x07, self.data)  # Write GRPFREQ  pg 20
            self.data[0] = 0x0C0 # duty cycle 75%
            self.i2c1.writeto_mem(self.adr, 0x044, self.data)  # Write PWMALL  pg 32
            '''
            self.detected = True
            # 2.2.18 no communication on $0 allowed ...
            # self.write(0x00, 0x0) # MODE1: disable autoinc & allcall & sub1-2-3
            #
            # $5E  PCA9955B smpcatcher
            # $60 ($70 at reset) PCA9955B smpcatcher
            # $61 ($76 at reset) PCA9955B smpcatcher
            line = "1"
            c = self.read(0x00) # does read locks i2c when part chip not found ?
            line = "2" # 2.2.18
            print('PCA9955B MODE1:' + str(c))  # does read locks i2c when part chip not found ?
            self.data[0] = 0x060 * 2
            self.i2c1.writeto_mem(self.adr, 0x043, self.data)  # Write to $43 = ALLCALLADR pg 31
            line = "3"
            self.data[0] = 0x061 * 2
            self.i2c1.writeto_mem(self.adr, 0x040, self.data)  # Write to $40 = SUBADR1  pg 31

            self.allOFF() # all output OFF

            self.write(0x01, 0b00101001, 2) # MODE2: group blinking
            self.write(0x045, 0x0FF) # IREFALL: Imax
            self.write(0x044, 255) # 100% ON for motor pump      127)  # PWMALL duty cycle 50% (ON state)

            # self.write(0x06, 192) # GRPPWM: duty cycle 75%  (1/3OFF 2/3ON)
            # pump 8sec ON - 5sec OFF   Total: 13sec    Time ON 60%
            self.write(0x06, 153) # GRPPWM: duty cycle 60 * 255 / 100

            # pump 8sec ON - 5sec OFF   Total: 13sec
            # self.write(0x07, 45) # GRPFREQ:  (sec x 15.26)   ( 45 => 3sec)
            self.write(0x07, 198) # GRPFREQ:  13 x 15.26 = 198

            # print('Detected DIO PCA9955B Adr: 96, 97,' + str(self.adr))
            # print('Detected DIO PCA9955B Adr: ' + str(self.adr))
            print('Detected Sample Catcher - PCA9955B Adr: ' + str(self.adr))
            if self.trace:
                # 2.2.18 print('Begin I2C1 scan ...')
                # 2.2.18 print(self.i2c1.scan())
                pass
            return True

        except Exception as e:
            self.detected = False
            scratch =  'Sample Catcher Err' + line + '- PCA9955B Adr:' + str(self.adr) + ' not found - '
            file_errLog(0, scratch + str(e), self.sdAccess)
            if self.trace:
                # 2.2.18 print('Begin I2C1 scan ...')
                # 2.2.18 print(self.i2c1.scan())
                pass
            return False

    # page 19
    def ledWrite(self, channel, command):  # channel  led# from 0 to 15
        if self.detected == False: return
        adreg = 0x02 # adr register
        rank = channel # 0, 1, 2, 3
        if channel > 3:
            adreg = 0x03
            rank = channel - 4
        if channel > 7:
            adreg = 0x04
            rank = channel - 8
        if channel > 11:
            adreg = 0x05
            rank = channel - 12
        mask = 0b11 << (rank * 2) # ON mask, shifted to LED rank
        if self.trace:
            # print('led#' + str(channel) + ' adr:' + str(adreg) + ' rank:' + str(rank) + ' mask:' + str(mask))
            print('led#' + str(channel) + ' adr:' + str(adreg) + ' rank:' + str(rank) + ' mask:0b{0:08b}'.format(mask))

        self.i2c1.readfrom_mem_into(self.adr, adreg, self.data) # read LED status
        data = self.data[0]
        if self.trace:
            print('read:0b{0:08b}'.format(data))

        maskcomp = (mask ^ 0x0FF) # ~mask
        # print('maskcomp:0b{0:08b}'.format(maskcomp) )
        data &= maskcomp
        # print('data:0b{0:08b}'.format(data) )
        fullON = (command & mask) # (0b01010101 & mask)
        # print('fullON:0b{0:08b}'.format(fullON) )
        data |= fullON
        # print('data:0b{0:08b}'.format(data) )
        if self.trace:
            # print('data:0b{0:08b} fullON:0b{0:08b} maskcomp:0b{0:08b}'.format(data, fullON, maskcomp) )
            # print('maskcomp:0b{0:08b}'.format(maskcomp) )
            # print('fullON:0b{0:08b}'.format(fullON) )
            print('data:0b{0:08b}'.format(data) )

        self.data[0] = data
        self.i2c1.writeto_mem(self.adr, adreg, self.data)

    def ledON(self, channel):
        self.ledWrite(channel, 0b01010101)

    def ledOFF(self, channel):
        self.ledWrite(channel, 0b00000000)

    def ledBlink(self, channel):
        self.ledWrite(channel, 0b11111111)

    def ledDim(self, channel):
        self.ledWrite(channel, 0b10101010)

    def allON(self):
        for i in range(16):
            self.ledON(i)
            if self.trace:
                print('')

    def allOFF(self):
        for i in range(16):
            self.ledOFF(i)
            if self.trace:
                print('')

    def ledCmd(self, channel, Cmd):
        if Cmd == 0: self.ledOFF(channel)
        if Cmd == 1: self.ledON(channel)
        if Cmd == 2: self.ledBlink(channel)
