# mPy - RTC driver for DS3231
# Copyright by Anthony Vogelaar, Perth Scotland
# rev 0.10  2026-08-13  initial release

# DS3231 datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/ds3231.pdf

# microPython RTC usage:
#	from machine import RTC
# 	rtc = RTC()
# 	rtc.datatime((YYYY, M, D, DOW, h, m, s, 0))		# set datetime, DOW=0 for monday
# 	rtc.datatime()									# get datetime (YYYY, M, D, DOW, h, m, s, 0)

from	machine	import	Pin, RTC, I2C

# Implemented:
# RtcEx()
#    RtcEx.get()		-->  6-list
#    RtcEx.set(t)
# Ds3231(i2c, addr=0x68)
#   +Ds3231.fromBcd(x)	-->  int
#   +Ds3231.toBcd(x)
#    Ds3231.get()		-->  6-list
#    Ds3231.set(t)

# ========================== RtcEx ===========================

# Class RTC extended
class RtcEx(RTC):
    
    # get datetime as [YYYY, M, D, h, m, s]
    def get(self):
        dt = list(self.datetime())
        return dt[:3] + dt[4:7]				# remove DOW and subseconds
        
    
    # set RTC from [YYYY, M, D, h, m, s]
    def set(self, t):
        dt = t[:3] + [0] + t[3:] + [0]		# insert DOW and subseconds
        self.datetime(dt)
  
# ========================== Ds3231 ==========================

# Battery backedup I2C Real Time clock
class Ds3231:
    
    # convert bcd byte into int
    @staticmethod
    def fromBcd(x):
        return (x & 0x0F) + ((x & 0x70) >> 4) * 10
        
    
    # convert 0..99 int into bcd byte
    @staticmethod
    def toBcd(x):
        d, r = divmod(x, 10)
        return (d << 4) + r


    # default DS3231 address = 0x68
    def __init__(self, i2c, addr=0x68):
        self.i2c  = i2c
        self.addr = addr
        
        
    # return datetime as [YYYY, M, D, h, m, s]
    def get(self):
        x  = list(self.i2c.readfrom_mem(self.addr, 0, 7))
        x.pop(3)							# remove DOW
        dt = [self.fromBcd(t) for t in x]
        dt.reverse()
        dt[0] += 2000
        return dt


    # set datetime from [YYYY, M, D, h, m, s]
    def set(self, t):
        t[0] %= 100							# remove century
        t.insert(3, 0)						# insert dummy DOW
        t.reverse()
        dt = [self.toBcd(x) for x in t]
        self.i2c.writeto_mem(self.addr, 0, bytes(dt))
        
# ========================== tests ===========================

if __name__ == '__main__':
    
    import	time
    import	json
    from	drivers	import	nbInput
    
    # set RTC to 2026-08-06 Thursday 01:02:03 (8-list) and print it
    def test1():
        rtc = RTC()
        rtc.datetime((2026, 8, 6, 3, 1, 2, 3, 0))
        print(rtc.datetime())
        
        
    # set RTC to 2026-08-06 01:02:03 (6-list) and print it
    def test2():
        rtc = RtcEx()
        rtc.set([2026, 8, 6, 1, 2, 3])
        print(rtc.get())
        
        
    # CLI interface with RTC
    # get datetime: time<cr>
    # set datetime: time=[YYYY, M, D, h, m, s] ex. [2026, 8, 7, 10, 11, 12]  (6-list)
    def test3():
        rtc = RtcEx()
        while True:
            if s := nbInput():						# ex: [2026, 8, 7, 10, 11, 12]
                if   s == 'time': print(rtc.get())
                elif s.startswith('time='):
                    dt = json.loads(s[5:])
                    rtc.set(dt)
            time.sleep_ms(100)
            print('*', end='')
        
      
    # check if DS3231 address is 0x68
    def test4():
        i2c0 = I2C(0, scl=Pin(22), sda=Pin(21), freq=400_000)
        addr = [hex(a) for a in i2c0.scan()]
        print('used I2C addresses:', addr)
        
        
    # write dt (6-list) to DS3231 and print DS3231 and RTC  
    def test5(dt=[2025, 8, 6, 15, 37, 0]):
        rtc    = RtcEx()
        i2c0   = I2C(0, scl=Pin(22), sda=Pin(21), freq=400000)
        ds3231 = Ds3231(i2c0)
        ds3231.set(dt)
        print('DS3231:', ds3231.get())
        print('RTC   :', rtc.get())
        

    # synchronise RTC with DS3231 and print DS3231 and RTC ten times @ 1 sec
    def test6():
        rtc    = RtcEx()
        i2c0   = I2C(0, scl=Pin(22), sda=Pin(21), freq=400000)
        ds3231 = Ds3231(i2c0)
        dt     = ds3231.get()
        rtc.set(dt)
        for _ in range(10):
            print('DS3231:', ds3231.get())
            print('RTC   :', rtc.get(), '\n')
            time.sleep(1)


    test6()

# =========================== end ===========================
