# Christian Fiot Sep 2021
# RTC  -   DS1307
# RTC  -   PCF8523   2.2.28
#
# Usage:
# ds1307 = DS1307(i2c=I2C1, traceDebug=True)
# pcf8523 = PCF8523(i2c=I2C1, traceDebug=True)
# In the following example replace RTC by ds1307 or   pcf8523
#
# RTC.begin()
#
# !!! Beware ds1307 requires one additional parameter  weekday# !!!
# ds1307.datetime( datetime=(year,month,day,weekday#,hour,min,sec))   ...set time
# pcf8523.datetime( datetime=(2021,09,29,14,32,0))   ...weekday is not used
#
# RTC.datetime()     read time
#





from trap import *
from machine import I2C

class PCF8523():  # 2.2.28 same adr as DS1307

    def __init__(self, iicadr=0x068, i2c=None, sda='P22', scl='P21', traceDebug=False, sdAccess=False):
        self.trace = traceDebug
        self.adr = iicadr
        self.sdAccess = sdAccess

        self.data = bytearray(1)
        self.weekday_start = 1

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

    def begin(self):
        try:
            self.data[0] = 0x10  # reset + 24h
            self.i2c1.writeto_mem(self.adr, 0, self.data) # write to Ctrl_1
            self.data[0] = 0x00  # battery switch-over enabled in standard mode, battery low detection enabled
            self.i2c1.writeto_mem(self.adr, 2, self.data) # write to Ctrl_3
            print('Detected RTC PCF8523 Adr:' + str(self.adr))
            return True

        except Exception as e:
            scratch = 'RTC PCF8523 Adr:' + str(self.adr) + ' not found - '
            file_errLog(0, scratch + str(e), self.sdAccess)
            return False

    def _dec2bcd(self, value):  # Convert decimal to binary coded decimal (BCD) format
        return (value // 10) << 4 | (value % 10)

    def _bcd2dec(self, value): # Convert binary coded decimal (BCD) format to decimal
        return ((value >> 4) * 10) + (value & 0x0F)

    # 2.2.28  weekday# is not used     datetime=(year,month,day,hour,min,sec))
    def datetime(self, datetime=None): # Get or set datetime
        if datetime is None:
            buf = self.i2c1.readfrom_mem(self.adr, 3, 7)
            return (
                self._bcd2dec(buf[6]) + 2000, # year
                self._bcd2dec(buf[5]), # month
                # 2.2.28 self._bcd2dec(buf[4] - self.weekday_start), # weekday
                self._bcd2dec(buf[3]), # day
                self._bcd2dec(buf[2]), # hour
                self._bcd2dec(buf[1]), # minute
                self._bcd2dec(buf[0] & 0x7F), # second
                0, # microsecond
                None # Timezone
            )
        buf = bytearray(7)
        buf[0] = self._dec2bcd(datetime[5]) & 0x7F # second, msb = CH, 1=halt, 0=go
        buf[1] = self._dec2bcd(datetime[4]) # minute
        buf[2] = self._dec2bcd(datetime[3]) # hour
        buf[3] = self._dec2bcd(datetime[2]) # day
        buf[4] = 1 # 2.2.28 buf[4] = self._dec2bcd(datetime[3] + self.weekday_start) # weekday
        buf[5] = self._dec2bcd(datetime[1]) # month
        buf[6] = self._dec2bcd(datetime[0] - 2000) # year
        self.i2c1.writeto_mem(self.adr, 3, buf)
        print('Set RTC PCF8523 time')




# DS1307 internal register
DATETIME_REG = const(0) # 0x00-0x06
CHIP_HALT    = const(128)
CONTROL_REG  = const(7) # 0x07
RAM_REG      = const(8) # 0x08-0x3F

class DS1307():  # 2.2.27

    def __init__(self, iicadr=0x068, i2c=None, sda='P22', scl='P21', traceDebug=False, sdAccess=False):
        self.trace = traceDebug
        self.adr = iicadr
        self.sdAccess = sdAccess

        self.data = bytearray(1)
        self.weekday_start = 1
        self._halt = False

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

    def begin(self):
        try:
            self.halt(False) # enable RTC
            self.h12format(False) # set time format 24h
            print('Detected RTC DS1307 Adr:' + str(self.adr))
            return True

        except Exception as e:
            scratch = 'RTC DS1307 Adr:' + str(self.adr) + ' not found - '
            file_errLog(0, scratch + str(e), self.sdAccess)
            return False

    def _dec2bcd(self, value):  # Convert decimal to binary coded decimal (BCD) format
        return (value // 10) << 4 | (value % 10)

    def _bcd2dec(self, value): # Convert binary coded decimal (BCD) format to decimal
        return ((value >> 4) * 10) + (value & 0x0F)

    # datetime=(year,month,day,weekday#,hour,min,sec)
    # datetime=(2021,09,29,4,14,32,0))
    def datetime(self, datetime=None): # Get or set datetime
        if datetime is None:
            buf = self.i2c1.readfrom_mem(self.adr, DATETIME_REG, 7)
            return (
                self._bcd2dec(buf[6]) + 2000, # year
                self._bcd2dec(buf[5]), # month
                self._bcd2dec(buf[4]), # day
                self._bcd2dec(buf[3] - self.weekday_start), # weekday
                self._bcd2dec(buf[2]), # hour
                self._bcd2dec(buf[1]), # minute
                self._bcd2dec(buf[0] & 0x7F) # second
            )
        buf = bytearray(7)
        buf[0] = self._dec2bcd(datetime[6]) & 0x7F # second, msb = CH, 1=halt, 0=go
        buf[1] = self._dec2bcd(datetime[5]) # minute
        buf[2] = self._dec2bcd(datetime[4]) & 0x40 # hour, 24h mode
        buf[3] = self._dec2bcd(datetime[3] + self.weekday_start) # weekday
        buf[4] = self._dec2bcd(datetime[2]) # day
        buf[5] = self._dec2bcd(datetime[1]) # month
        buf[6] = self._dec2bcd(datetime[0] - 2000) # year
        if (self._halt):
            buf[0] |= (1 << 7)
        self.i2c1.writeto_mem(self.adr, DATETIME_REG, buf)


    def halt(self, val=None): # Power up=False, power down=True    or check status
        if val is None:
            return self._halt

        self.i2c1.readfrom_mem_into(self.adr, 0, self.data)
        data = self.data[0]
        if self.trace:
            print('RTC $00: 0x{0:04X}'.format(data) )
        if val:
            data |= CHIP_HALT
        else:
            data &= ~CHIP_HALT
        self._halt = bool(val)
        self.data[0] = data
        self.i2c1.writeto_mem(self.adr, 0, self.data)


    def h12format(self, val=False): # 1= 12h, 0= 24h mode
        self.i2c1.readfrom_mem_into(self.adr, 2, self.data)
        data = self.data[0]
        if self.trace:
            print('RTC $02: 0x{0:04X}'.format(data) )
        if val:
            data |= 64 # b6 =1   12h
        else:
            data &= ~64 # b6 =0   24h
        self.data[0] = data
        self.i2c1.writeto_mem(self.adr, 2, self.data)

    # sqw = 0,1=1hz,4=4k,8=8k,32=32k
    def square_wave(self, sqw=0, out=0):
        # Output square wave on pin SQ at 1Hz, 4.096kHz, 8.192kHz or 32.768kHz,
        # or disable the oscillator and output logic level high/low.
        data = self.data[0]   # reg = bytearray(1)
        rs0 = 1 if sqw == 4 or sqw == 32 else 0
        rs1 = 1 if sqw == 8 or sqw == 32 else 0
        out = 1 if out > 0 else 0
        sqw = 1 if sqw > 0 else 0
        data = rs0 | rs1 << 1 | sqw << 4 | out << 7
        self.data[0] = data
        self.i2c1.writeto_mem(self.adr, CONTROL_REG, self.data)
