From f5e9bb492f138cd888dc8c2eec22252b142242c4 Mon Sep 17 00:00:00 2001 From: "davitb2013@gmail.com" Date: Thu, 6 Feb 2025 16:45:53 -0800 Subject: [PATCH 1/4] add rv3028 to requirements --- lib/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/requirements.txt b/lib/requirements.txt index 631e6e61..293f961a 100644 --- a/lib/requirements.txt +++ b/lib/requirements.txt @@ -1,4 +1,5 @@ adafruit-circuitpython-asyncio @ git+https://github.com/adafruit/adafruit_circuitpython_asyncio@1.3.3 +proves-circuitpython-rv3028 @ git+https://github.com/proveskit/PROVES_CircuitPython_RV3028 adafruit-circuitpython-drv2605==1.3.4 adafruit-circuitpython-lis2mdl==2.1.23 adafruit-circuitpython-lsm6ds==4.5.13 From 3c6f3e05ae718dacb28145a2ae5ef6a2bddb3a82 Mon Sep 17 00:00:00 2001 From: "davitb2013@gmail.com" Date: Thu, 6 Feb 2025 16:55:38 -0800 Subject: [PATCH 2/4] use the new library in pysquared --- lib/pysquared/pysquared.py | 2 +- lib/pysquared/rv3028.py | 238 ----------------------- lib/rv3028/registers.py | 297 +++++++++++++++++++++++++++++ lib/rv3028/rv3028.py | 375 +++++++++++++++++++++++++++++++++++++ 4 files changed, 673 insertions(+), 239 deletions(-) delete mode 100644 lib/pysquared/rv3028.py create mode 100644 lib/rv3028/registers.py create mode 100644 lib/rv3028/rv3028.py diff --git a/lib/pysquared/pysquared.py b/lib/pysquared/pysquared.py index ab7e0155..4aa51d65 100644 --- a/lib/pysquared/pysquared.py +++ b/lib/pysquared/pysquared.py @@ -26,7 +26,7 @@ import lib.adafruit_tca9548a as adafruit_tca9548a # I2C Multiplexer import lib.neopixel as neopixel # RGB LED import lib.pysquared.nvm.register as register -import lib.pysquared.rv3028 as rv3028 # Real Time Clock +import lib.rv3028.rv3028 as rv3028 # Real Time Clock from lib.adafruit_lsm6ds.lsm6dsox import LSM6DSOX # IMU from lib.adafruit_rfm import rfm9x, rfm9xfsk # Radio from lib.pysquared.config import Config # Configs diff --git a/lib/pysquared/rv3028.py b/lib/pysquared/rv3028.py deleted file mode 100644 index 7515876b..00000000 --- a/lib/pysquared/rv3028.py +++ /dev/null @@ -1,238 +0,0 @@ -""" -This class handles communications - -Authors: Nicole Maggard, Michael Pham, and Rachel Sarmiento -""" - -import adafruit_bus_device.i2c_device as i2c_device - - -class RV3028: - # Register addresses - SECONDS = 0x00 - MINUTES = 0x01 - HOURS = 0x02 - WEEKDAY = 0x03 - DATE = 0x04 - MONTH = 0x05 - YEAR = 0x06 - STATUS = 0x0E - CONTROL1 = 0x0F - CONTROL2 = 0x10 - EVENT_CONTROL = 0x13 - TIMESTAMP_COUNT = 0x14 - TIMESTAMP_SECONDS = 0x15 - TIMESTAMP_MINUTES = 0x16 - TIMESTAMP_HOURS = 0x17 - TIMESTAMP_DATE = 0x18 - TIMESTAMP_MONTH = 0x19 - TIMESTAMP_YEAR = 0x1A - EEPROM_BACKUP = 0x37 - - def __init__(self, i2c_bus, address=0x52): - self.i2c_device = i2c_device.I2CDevice(i2c_bus, address) - - def _read_register(self, register, length=1): - with self.i2c_device as i2c: - i2c.write(bytes([register])) - result = bytearray(length) - i2c.readinto(result) - return result - - def _write_register(self, register, data): - with self.i2c_device as i2c: - i2c.write(bytes([register]) + data) - - def _bcd_to_int(self, bcd): - return (bcd & 0x0F) + ((bcd >> 4) * 10) - - def _int_to_bcd(self, value): - return ((value // 10) << 4) | (value % 10) - - def set_time(self, hours, minutes, seconds): - data = bytes( - [ - self._int_to_bcd(seconds), - self._int_to_bcd(minutes), - self._int_to_bcd(hours), - ] - ) - self._write_register(self.SECONDS, data) - - def get_time(self): - data = self._read_register(self.SECONDS, 3) - return ( - self._bcd_to_int(data[2]), # hours - self._bcd_to_int(data[1]), # minutes - self._bcd_to_int(data[0]), # seconds - ) - - def set_date(self, year, month, date, weekday): - data = bytes( - [ - self._int_to_bcd(weekday), - self._int_to_bcd(date), - self._int_to_bcd(month), - self._int_to_bcd(year), - ] - ) - self._write_register(self.WEEKDAY, data) - - def get_date(self): - data = self._read_register(self.WEEKDAY, 4) - return ( - self._bcd_to_int(data[3]), # year - self._bcd_to_int(data[2]), # month - self._bcd_to_int(data[1]), # date - self._bcd_to_int(data[0]), # weekday - ) - - def set_alarm(self, minute, hour, weekday): - # Set alarm mask to check for minute, hour, and weekday match - control2 = self._read_register(self.CONTROL2)[0] - control2 |= 0x08 # Set AIE (Alarm Interrupt Enable) bit - self._write_register(self.CONTROL2, bytes([control2])) - - data = bytes( - [ - self._int_to_bcd(minute), - self._int_to_bcd(hour), - self._int_to_bcd(weekday), - ] - ) - self._write_register(self.MINUTES, data) - - def enable_trickle_charger(self, resistance=3000): - control1 = self._read_register(self.CONTROL1)[0] - control1 |= 0x20 # Set TCE (Trickle Charge Enable) bit - - # Set TCR (Trickle Charge Resistor) bits - if resistance == 3000: - control1 |= 0x00 - elif resistance == 5000: - control1 |= 0x01 - elif resistance == 9000: - control1 |= 0x02 - elif resistance == 15000: - control1 |= 0x03 - else: - raise ValueError("Invalid trickle charger resistance") - - self._write_register(self.CONTROL1, bytes([control1])) - - def disable_trickle_charger(self): - control1 = self._read_register(self.CONTROL1)[0] - control1 &= ~0x20 # Clear TCE (Trickle Charge Enable) bit - self._write_register(self.CONTROL1, bytes([control1])) - - def configure_evi(self, enable=True): - """ - Configure EVI for rising edge detection, enable time stamping, - and enable interrupt. - - :param enable: True to enable EVI, False to disable - """ - if enable: - # Configure Event Control Register - event_control = 0x40 # EHL = 1 (rising edge), ET = 00 (no filtering) - self._write_register(self.EVENT_CONTROL, bytes([event_control])) - - # Enable time stamping and EVI interrupt - control2 = self._read_register(self.CONTROL2)[0] - control2 |= 0x84 # Set TSE (bit 7) and EIE (bit 2) - self._write_register(self.CONTROL2, bytes([control2])) - else: - # Disable time stamping and EVI interrupt - control2 = self._read_register(self.CONTROL2)[0] - control2 &= ~0x84 # Clear TSE (bit 7) and EIE (bit 2) - self._write_register(self.CONTROL2, bytes([control2])) - - def get_event_timestamp(self): - """ - Read the timestamp of the last EVI event. - - :return: Tuple of (year, month, date, hours, minutes, seconds, count) - """ - data = self._read_register(self.TIMESTAMP_COUNT, 7) - return ( - self._bcd_to_int(data[6]), # year - self._bcd_to_int(data[5]), # month - self._bcd_to_int(data[4]), # date - self._bcd_to_int(data[3]), # hours - self._bcd_to_int(data[2]), # minutes - self._bcd_to_int(data[1]), # seconds - data[0], # count (not BCD) - ) - - def clear_event_flag(self): - """ - Clear the Event Flag (EVF) in the Status Register. - """ - status = self._read_register(self.STATUS)[0] - status &= ~0x02 # Clear EVF (bit 1) - self._write_register(self.STATUS, bytes([status])) - - def is_event_flag_set(self): - """ - Check if the Event Flag (EVF) is set in the Status Register. - - :return: True if EVF is set, False otherwise - """ - status = self._read_register(self.STATUS)[0] - return bool(status & 0x02) # Check EVF (bit 1) - - def configure_backup_switchover(self, mode="level", interrupt=False): - """ - Configure the Automatic Backup Switchover function. - - :param mode: 'level' for Level Switching Mode (LSM), - 'direct' for Direct Switching Mode (DSM), - or 'disabled' to disable switchover - :param interrupt: True to enable backup switchover interrupt, False to disable - """ - backup_reg = self._read_register(self.EEPROM_BACKUP)[0] - - # Clear existing BSM bits - backup_reg &= ~0x0C - - if mode == "level": - backup_reg |= 0x0C # Set BSM to 11 for LSM - elif mode == "direct": - backup_reg |= 0x04 # Set BSM to 01 for DSM - elif mode == "disabled": - pass # BSM is already cleared to 00 - else: - raise ValueError("Invalid mode. Use 'level', 'direct', or 'disabled'.") - - # Configure backup switchover interrupt - if interrupt: - backup_reg |= 0x40 # Set BSIE bit - else: - backup_reg &= ~0x40 # Clear BSIE bit - - # Always enable fast edge detection - backup_reg |= 0x10 # Set FEDE bit - - # Write the configuration to EEPROM - self._write_register(self.EEPROM_BACKUP, bytes([backup_reg])) - - # Update EEPROM (command 0x11) - self._write_register(0x27, bytes([0x00])) # First command must be 00h - self._write_register(0x27, bytes([0x11])) # Update command - - def is_backup_switchover_occurred(self): - """ - Check if a backup switchover has occurred. - - :return: True if switchover occurred, False otherwise - """ - status = self._read_register(self.STATUS)[0] - return bool(status & 0x20) # Check BSF (bit 5) - - def clear_backup_switchover_flag(self): - """ - Clear the Backup Switchover Flag (BSF) in the Status Register. - """ - status = self._read_register(self.STATUS)[0] - status &= ~0x20 # Clear BSF (bit 5) - self._write_register(self.STATUS, bytes([status])) diff --git a/lib/rv3028/registers.py b/lib/rv3028/registers.py new file mode 100644 index 00000000..15af9e3a --- /dev/null +++ b/lib/rv3028/registers.py @@ -0,0 +1,297 @@ +""" +Register and flag definitions for the RV-3028-C7 RTC module. +https://www.microcrystal.com/fileadmin/Media/Products/RTC/App.Manual/RV-3028-C7_App-Manual.pdf + +Author: Davit Babayan +""" + + +class Reg: + SECONDS = 0x00 + MINUTES = 0x01 + HOURS = 0x02 + WEEKDAY = 0x03 + DATE = 0x04 + MONTH = 0x05 + YEAR = 0x06 + ALARM_MINUTES = 0x07 + ALARM_HOURS = 0x08 + ALARM_WEEKDAY = ALARM_DATE = 0x09 # Depends on which one is enabled + TIMER0 = 0x0A + TIMER1 = 0x0B + TIMER_STATUS0 = 0x0C # readonly + TIMER_STATUS1 = 0x0D # readonly + STATUS = 0x0E + CONTROL1 = 0x0F + CONTROL2 = 0x10 + GP_BITS = 0x11 + CLOCK_INT_MASK = 0x12 + EVENT_CONTROL = 0x13 + TIMESTAMP_COUNT = 0x14 # readonly + TIMESTAMP_SECONDS = 0x15 # readonly + TIMESTAMP_MINUTES = 0x16 # readonly + TIMESTAMP_HOURS = 0x17 # readonly + TIMESTAMP_DATE = 0x18 # readonly + TIMESTAMP_MONTH = 0x19 # readonly + TIMESTAMP_YEAR = 0x1A # readonly + UNIX_TIME0 = 0x1B + UNIX_TIME1 = 0x1C + UNIX_TIME2 = 0x1D + UNIX_TIME3 = 0x1E + RAM1 = 0x1F + RAM2 = 0x20 + EEDATA = 0x26 + EECMD = 0x27 + ID = 0x28 # readonly + EEPROM_CLKOUT = 0x35 + EEPROM_OFFSET = 0x36 + EEPROM_BACKUP = 0x37 + + +class Flag: + SET = 1 + CLEAR = 0 + + +class Alarm: + """ + Contains bit masks for the alarm registers. + The alarm registers that contain these flags are ALARM_MINUTES, ALARM_HOURS, and ALARM_DATE. + """ + + DISABLED = 0x80 # This is a flag that you should set to disable the alarm. + VALUE = 0x7F # Enable the alarm and store the value. + VALUE_SIZE = 7 # Number of bits of value + + +class Status: + """ + Contains bit masks for the status register. Generally, these should be READ, not written to. + The exception is clearing the flags, since they do not clear themselves. + """ + + PORF = 0x01 # 0: No voltage drop, 1: Voltage drop (if set to 0 after POR, default is 1) + EVENT = 0x02 # Enabled if an event is detected + ALARM = 0x04 # Enabled if an alarm is triggered + TIMER = 0x08 # Enabled if a periodic countdown timer event is triggered. + UPDATE = 0x10 # Enabled if a periodic time update event is triggered. + BACKUP_SWITCH = 0x20 # Enabled if a switch from main to backup power occurs. + CLOCK_OUTPUT = 0x40 # Enabled if there is an interrupt on the clock output pin. + EEBUSY = 0x80 # Enabled if the EEPROM is handling a read/write request. + + +class Control1: + """ + Contains bit masks for the control1 register. + - This register is used to specify the target for the Alarm Interrupt function and the Periodic Time Update Interrupt + function and to select or set operations for the Periodic Countdown Timer. + """ + + FREQ_SELECT = 0x03 # 2 bit flag. Uses the FreqSelect enum for values. + TIMER_ENABLE = 0x04 # Starts the countdown timer if 1. + EEPROM_REFRESH_DISABLE = 0x08 # Disables the automatic EEPROM refresh if 1. + UPDATE_INT_SELECT = 0x10 # 0: second updates (default). 1: minute updates. + WADA = 0x20 # 0: use weekday for alarm (default). 1: use date for alarm. + TIMER_REPEAT = 0x80 # 0: Single mode, halt countdown when it reaches 0 (default). 1: repeat timer. + + +class TimerFreq: + """ + Selections for the frequency of the timer countdown clock. + """ + + SIZE = 2 # Number of bits used for the frequency selection + FREQ_4096HZ = 0x00 # Default value + FREQ_64HZ = 0x01 + FREQ_1HZ = 0x10 + FREQ_60S = 0x11 + + +class Control2: + """ + Contains bit masks for the control2 register. + - This register is used to control the interrupt event output for the INT pin, the stop/start status of clock and calendar + operations, the interrupt controlled clock output on CLKOUT pin, the hour mode and the time stamp enable. + """ + + RESET = ( + 0x01 # 0: Normal operation. 1: software based time adjustment (see datasheet). + ) + HOUR_MODE = 0x02 # 0: 24 hour mode (default). 1: 12 hour mode. + EVENT_INT_ENABLE = ( + 0x04 # Enables the event interrupt on the INT pin (disabled by default). + ) + ALARM_INT_ENABLE = ( + 0x08 # Enables the alarm interrupt on the INT pin (disabled by default). + ) + TIMER_INT_ENABLE = ( + 0x10 # Enables the timer interrupt on the INT pin (disabled by default). + ) + UPDATE_INT_ENABLE = ( + 0x20 # Enables the update interrupt on the INT pin (disabled by default). + ) + CLOCK_OUTPUT_INT_ENABLE = ( + 0x40 # Enables the clock output interrupt on the INT pin (disabled by default). + ) + TIMESTAMP_ENABLE = 0x80 # Enables the timestamp function (disabled by default). + + +class GPBits: + GPR_MASK = 0x7F # General purpose bits. Can be used for any purpose. + + +class ClockIntOn: + """ + Contains bit masks for the clock interrupt mask register. + - This register is used to select a predefined interrupt for automatic clock output. Setting a bit to 1 selects the + corresponding interrupt. + + Example Usage: `clock_int_mask = ClockIntOn.TIMER | ClockIntOn.ALARM` + """ + + SIZE = 2 + TIME_UPDATE = 0x01 # Enables the periodic time update interrupt. + TIMER = 0x02 # Enables the countdown timer interrupt. + ALARM = 0x04 # Enables the alarm interrupt. + EVENT = 0x08 # Enables the event interrupt. + + +class EventControl: + """ + Contains bit masks for the event control register. + - This register controls the event detection on the EVI pin. Depending of the EHL bit, high or low level (or rising or + falling edge) can be detected. Moreover a digital glitch filtering can be applied to the EVI signal by selecting a + sampling period tSP in the ET field. Furthermore this register holds control functions for the Time Stamp data. And + the switching over to VBACKUP Power state can be selected as source for an event. + """ + + TIMESTAMP_SOURCE_SELECT = 0x01 # 0: Timestamp source is external event. 1: Timestamp source is the backup switchover. + TIMESTAMP_OVERWRITE = ( + 0x02 # 0: Timestamp is not overwritten. 1: Timestamp is overwritten. + ) + TIMESTAMP_RESET = ( + 0x04 # 0: Timestamp is not reset. 1: Reset all seven TS registers. + ) + EVENT_FILTER = 0x30 # 2 bit flag. Uses the EventFilter enum for values. + EVENT_HIGH_LOW_SELECT = ( + 0x40 # 0: Low level or falling edge. 1: High level or rising edge. + ) + + +class EventFilter: + """ + Selections for the event filter. + """ + + SIZE = 2 + FILTER_OFF = 0x00 # Default value + FILTER_256Hz = 0x01 + FILTER_64Hz = 0x02 + FILTER_8Hz = 0x03 + + +class EEPROMClockOut: + """ + Contains bit masks for the EEPROM clock out register. + - A programmable square wave output is available at CLKOUT pin. Clock output can be controlled by the CLKOE bit + (or by the CLKF flag) (see PROGRAMMABLE CLOCK OUTPUT). After a Power up and the first refreshment time + tPREFR = ~66 ms, the EEPROM Clkout values CLKOE, CLKSY, PORIE and FD are copied from the EEPROM to the + corresponding RAM mirror. The default values preset on delivery are: CLKOUT = enabled, synchronization enabled, + F = 32.768 kHz. + """ + + FREQ_SELECT: 0x07 # 3 bit flag. Uses the FreqSelect enum for values. + POR_INT_ENABLE = ( + 0x08 # 0: POR interrupt disabled (default). 1: POR interrupt enabled. + ) + CLKOUT_SYNC_ENABLE = 0x40 # 0: Synchronization disabled. 1: Synchronization enable/disable enabled (default). + CLKOUT_ENABLE = 0x80 # 0: Clock output pin LOW. 1: Clock output on CLKOUT pin enabled (default). + + +class FreqSelect: + """ + Selections for the frequency of the clock in CLKOUT. + - 8192 Hz to 1 Hz clock pulses and the timer interrupt pulses can be affected by compensation pulses + """ + + SIZE = 3 + FREQ_32768HZ = 0x00 # Default value + FREQ_8192HZ = 0x01 + FREQ_1024HZ = 0x02 + FREQ_64HZ = 0x03 + FREQ_32HZ = 0x04 + FREQ_1HZ = 0x05 + PREDEFINED = 0x06 # CLKSY bit has no effect + LOW = 0x07 + + +class EEPROMBackup: + """ + Contains bit masks for the EEPROM backup register. + - This register is used to control the switchover function and the trickle charger and it holds bit 0 (LSB) of the EEOffset + value. The preconfigured (Factory Calibrated) EEOffset value may be changed by the user. + After a Power up and the first refreshment time tPREFR = ~66 ms, the EEPROM Backup value is copied from the + EEPROM to the corresponding RAM mirror. + """ + + TRICKLE_CHARGE_RES = 0x03 # 2 bit flag representing trickle charge resistance. Uses the Resistance enum for values. + BACKUP_SWITCHOVER = 0x0C # 2 bit flag representing backup switchover mode. Uses the BSM enum for values. + TRICKLE_CHARGE_ENABLE = ( + 0x20 # 0: Trickle charger disabled (default). 1: Trickle charger enabled. + ) + BACKUP_SWITCHOVER_INT_ENABLE = 0x40 # 0: Backup switchover interrupt disabled (default). 1: Backup switchover interrupt enabled. + EEOFFSET_LSB = ( + 0x80 # LSB of the EEOffset value (see EEPROM OFFSET REGISTER in datasheet) + ) + FEDE = 0x10 # FOR THE LOVE OF GOD, NEVER DISABLE THIS. + + +class Resistance: + """ + Selections for the resistance of the trickle charger. + """ + + SIZE = 2 + RES_3000 = 0x00 # Default value (ohms) + RES_5000 = 0x01 + RES_9000 = 0x02 + RES_15000 = 0x03 + + +class BSM: + """ + Selections for the backup switchover mode. + """ + + SIZE = 2 + DISABLED_DEFAULT = 0x00 # Default value + DIRECT = 0x01 + DISABLED = 0x02 + LEVEL = 0x03 + + +class EECMD: + """ + Contains commands for the EEPROM. + - This register must be written with specific values, in order to Update or Refresh all (readable/writeable) Configuration + EEPROM registers or to read or write from/to a single EEPROM Memory byte. + - Before using this commands, the automatic refresh function has to be disabled (EERD = 1) and the busy status bit + EEbusy has to indicate, that the last transfer has been finished (EEbusy = 0). Before entering the command 11h, + 12h, 21h or 22h, EECMD has to be written with RESET. + """ + + RESET = 0x00 # Reset command (must always be sent first). + UPDATE = 0x11 # Sends data from configuration RAM to EEPROM. + REFRESH = 0x12 # Sends data from EEPROM to configuration RAM. + WRITE_ONE_BYTE = 0x21 # Writes one byte to the EEPROM from EEDATA. + READ_ONE_BYTE = 0x22 # Reads one byte from the EEPROM to EEDATA. + + +class ID: + """ + Contains the ID of the RTC module. + - This register holds the 4 bit Hardware Identification number (HID) and the 4 bit Version Identification number (VID). + """ + + VID = 0x0F # Version Identification number + HID = 0xF0 # Hardware Identification number diff --git a/lib/rv3028/rv3028.py b/lib/rv3028/rv3028.py new file mode 100644 index 00000000..790c64c7 --- /dev/null +++ b/lib/rv3028/rv3028.py @@ -0,0 +1,375 @@ +""" +This class handles the Rv3028 real time clock. + +Authors: Nicole Maggard, Michael Pham, and Rachel Sarmiento +""" + +from rv3028.registers import ( + BSM, + EECMD, + Alarm, + Control1, + Control2, + EEPROMBackup, + EventControl, + EventFilter, + Flag, + Reg, + Resistance, + Status, +) + +from tests.stubs.i2c_device import I2CDevice + + +class WEEKDAY: + SUNDAY = 0 + MONDAY = 1 + TUESDAY = 2 + WEDNESDAY = 3 + THURSDAY = 4 + FRIDAY = 5 + SATURDAY = 6 + + +class RV3028: + def __init__(self, i2c_device: I2CDevice): + self.i2c_device = i2c_device + + def _read_register(self, register, length=1): + with self.i2c_device as i2c: + i2c.write(bytes([register])) + result = bytearray(length) + i2c.readinto(result) + return result + + def _write_register(self, register: Reg, data: bytes): + with self.i2c_device as i2c: + i2c.write(bytes([register]) + data) + + def _set_flag(self, register, mask, value): + try: + value = int(value) + except Exception: + raise ValueError("Argument 'value' must be an integer or boolean") + + data = self._read_register(register)[0] + data &= ~mask # Clear the bits + + shift = 0 + temp_mask = mask + while (temp_mask & 0x01) == 0: + temp_mask >>= 1 + shift += 1 + + max_value = mask >> shift + if value < 0 or value > max_value: + raise ValueError(f"Value {value} does not fit in the mask {mask:#04x}") + + data |= (value << shift) & mask # Set the bits + + self._write_register(register, bytes([data])) + + def _get_flag(self, register, mask, size=0): + data = self._read_register(register)[0] + result = (data & mask) >> size + + # Automatically convert to bool if mask is a single bit + if mask & (mask - 1) == 0: + return bool(result) + return result + + def _eecommand(self, command: EECMD): + while self._get_flag(Reg.STATUS, Status.EEBUSY): + pass + self._set_flag(Reg.CONTROL1, Control1.EEPROM_REFRESH_DISABLE, Flag.SET) + self._set_flag(Reg.STATUS, Status.EEBUSY, Flag.SET) + self._write_register( + Reg.EECMD, bytes([EECMD.RESET]) + ) # First command must be 00h + self._write_register(Reg.EECMD, bytes([command])) # Update command + self._set_flag(Reg.STATUS, Status.EEBUSY, Flag.CLEAR) + self._set_flag(Reg.CONTROL1, Control1.EEPROM_REFRESH_DISABLE, Flag.CLEAR) + + def _bcd_to_int(self, bcd): + return (bcd & 0x0F) + ((bcd >> 4) * 10) + + def _int_to_bcd(self, value): + return ((value // 10) << 4) | (value % 10) + + def set_time(self, hours: int, minutes: int, seconds: int) -> None: + """ + Sets the time on the device. This method configures the device's clock. + + Args: + hours (int): The hour value to set (0-23 for 24-hour format). + minutes (int): The minute value to set (0-59). + seconds (int): The second value to set (0-59). + """ + data = bytes( + [ + self._int_to_bcd(seconds), + self._int_to_bcd(minutes), + self._int_to_bcd(hours), + ] + ) + self._write_register(Reg.SECONDS, data) + + def get_time(self) -> tuple[int, int, int]: + """ + Retrieves the current time from the device. + + Returns: + tuple: A tuple containing the current time as (hours, minutes, seconds), + where: + hours (int): The hour value (0-23 for 24-hour format). + minutes (int): The minute value (0-59). + seconds (int): The second value (0-59). + """ + data = self._read_register(Reg.SECONDS, 3) + return ( + self._bcd_to_int(data[2]), # hours + self._bcd_to_int(data[1]), # minutes + self._bcd_to_int(data[0]), # seconds + ) + + def set_date(self, year: int, month: int, date: int, weekday: int) -> None: + """ + Sets the date of the device. + + Args: + year (int): The year value to set + month (int): The month value to set (1-12). + date (int): The date value to set (1-31). + weekday (int): The day of the week to set (0-6, where 0 represents Sunday). + """ + data = bytes( + [ + self._int_to_bcd(weekday), + self._int_to_bcd(date), + self._int_to_bcd(month), + self._int_to_bcd(year), + ] + ) + self._write_register( + Reg.WEEKDAY, data + ) # this is a weird way to do it but it works + + def get_date(self) -> tuple[int, int, int, int]: + """ + Gets the date of the device. + + Returns: + tuple: A 4-tuple (year, month, date, weekday) where: + year (int): The year value (0-99). + month (int): The month value (1-12). + date (int): The date value (1-31). + weekday (int): The day of the week (0-6, where 0 represents Sunday). + """ + data = self._read_register(Reg.WEEKDAY, 4) + return ( + self._bcd_to_int(data[3]), # year + self._bcd_to_int(data[2]), # month + self._bcd_to_int(data[1]), # date + self._bcd_to_int(data[0]), # weekday + ) + + def set_alarm( + self, minute: int = None, hour: int = None, weekday: int = None + ) -> None: + """ + Set the alarm time. A value of None indicates that that portion of the alarm will not be used. + + Args: + Alarm minute (0-59) or None + Alarm hour (0-23) or None + Alarm weekday (0-6, 0=Sunday) or None + """ + # Set alarm mask to check for minute, hour, and weekday match + control2 = self._read_register(Reg.CONTROL2)[0] + self._set_flag(Reg.CONTROL2, Control2.ALARM_INT_ENABLE, Flag.SET) + self._write_register(Reg.CONTROL2, bytes([control2])) + + if minute is not None and (minute < 0 or minute > 59): + raise ValueError("Invalid minute value") + if hour is not None and (hour < 0 or hour > 23): + raise ValueError("Invalid hour value") + if weekday is not None and (weekday < 0 or weekday > 6): + raise ValueError("Invalid weekday value") + + data = bytes( + (self._int_to_bcd(param) & Alarm.VALUE) + if param is not None + else Alarm.DISABLED + for param in (minute, hour, weekday) + ) + + self._write_register(Reg.ALARM_MINUTES, data) + + def check_alarm(self, clear: bool = True) -> bool: + """ + Check if the alarm flag has been triggered. + + Args: + clear (bool): (Default: True) True to clear the alarm flag, False to leave it set. + Returns: + True if alarm flag is set, False otherwise + """ + result = self._get_flag(Reg.STATUS, Status.ALARM) + if clear and result: + self._set_flag(Reg.STATUS, Status.ALARM, Flag.CLEAR) + + return bool(result) + + def get_alarm(self) -> tuple: + """ + If an alarm has been set on the device, provides the set time. + + Returns: + A tuple representing the alarm configuration. A return value of None in any field means that that field was not set. Tuple values: + minute (int or None): the minute value of the alarm (0-59) + hour (int or None): the hour value of the alarm (0-23) + weekday (int or None): the weekday of the alarm (0-6, 0 = Sunday) + """ + + # Defining the helper in here limits its scope so outside users can't access it. + def _get_alarm_field(reg: Reg): + disabled = self._get_flag(reg, Alarm.DISABLED) + if disabled: + return None + else: + return self._get_flag(reg, Alarm.VALUE) + + return ( + _get_alarm_field(Reg.ALARM_MINUTES), + _get_alarm_field(Reg.ALARM_HOURS), + _get_alarm_field(Reg.ALARM_WEEKDAY), + ) + + def enable_trickle_charger(self, resistance=3000): + self._set_flag(Reg.EEPROM_BACKUP, EEPROMBackup.TRICKLE_CHARGE_ENABLE, Flag.SET) + self._set_flag(Reg.EEPROM_BACKUP, EEPROMBackup.TRICKLE_CHARGE_RES, Flag.CLEAR) + + # Set TCR (Trickle Charge Resistor) bits + if resistance not in [3000, 5000, 9000, 15000]: + raise ValueError("Invalid trickle charger resistance") + if resistance == 3000: + tcr_value = Resistance.RES_3000 + elif resistance == 5000: + tcr_value = Resistance.RES_5000 + elif resistance == 9000: + tcr_value = Resistance.RES_9000 + elif resistance == 15000: + tcr_value = Resistance.RES_15000 + + self._set_flag(Reg.EEPROM_BACKUP, EEPROMBackup.TRICKLE_CHARGE_RES, tcr_value) + + # Refresh the EEPROM to apply changes + self._eecommand(EECMD.REFRESH) + + def disable_trickle_charger(self): + self._set_flag( + Reg.EEPROM_BACKUP, EEPROMBackup.TRICKLE_CHARGE_ENABLE, Flag.CLEAR + ) + self._eecommand(EECMD.REFRESH) + + def configure_evi(self, enable=True): + """ + Configure EVI for rising edge detection, enable time stamping, + and enable interrupt. + + :param enable: True to enable EVI, False to disable + """ + if enable: + # Configure Event Control Register + self._set_flag( + Reg.EVENT_CONTROL, EventControl.EVENT_HIGH_LOW_SELECT, Flag.SET + ) + self._set_flag( + Reg.EVENT_CONTROL, EventControl.EVENT_FILTER, EventFilter.FILTER_OFF + ) + + # Enable time stamping and EVI interrupt + self._set_flag(Reg.CONTROL2, Control2.TIMESTAMP_ENABLE, Flag.SET) + self._set_flag(Reg.CONTROL2, Control2.EVENT_INT_ENABLE, Flag.SET) + else: + self._set_flag(Reg.CONTROL2, Control2.TIMESTAMP_ENABLE, Flag.CLEAR) + self._set_flag(Reg.CONTROL2, Control2.EVENT_INT_ENABLE, Flag.CLEAR) + + def get_event_timestamp(self): + """ + Read the timestamp of the last EVI event. + + :return: Tuple of (year, month, date, hours, minutes, seconds, count) + """ + data = self._read_register(Reg.TIMESTAMP_COUNT, 7) + return ( + self._bcd_to_int(data[6]), # year + self._bcd_to_int(data[5]), # month + self._bcd_to_int(data[4]), # date + self._bcd_to_int(data[3]), # hours + self._bcd_to_int(data[2]), # minutes + self._bcd_to_int(data[1]), # seconds + data[0], # count (not BCD) + ) + + def check_event_flag(self, clear=True): + """ + Check if the Event Flag (EVF) is set in the Status Register. + + :return: True if EVF is set, False otherwise + """ + result = self._get_flag(Reg.STATUS, Status.EVENT) + if result and clear: + self._set_flag(Reg.STATUS, Status.EVENT, Flag.CLEAR) + + return result + + def configure_backup_switchover(self, mode="level", interrupt=False): + """ + Configure the Automatic Backup Switchover function. + + :param mode: 'level' for Level Switching Mode (LSM), + 'direct' for Direct Switching Mode (DSM), + or 'disabled' to disable switchover + :param interrupt: True to enable backup switchover interrupt, False to disable + """ + + match mode: + case "level": + backup_mode = BSM.LEVEL + case "direct": + backup_mode = BSM.DIRECT + case "disabled": + backup_mode = BSM.DISABLED + case _: + raise ValueError("Invalid mode. Use 'level', 'direct', or 'disabled'.") + + self._set_flag(Reg.EEPROM_BACKUP, EEPROMBackup.BACKUP_SWITCHOVER, backup_mode) + + # Configure backup switchover interrupt + if interrupt: + self._set_flag( + Reg.EEPROM_BACKUP, EEPROMBackup.BACKUP_SWITCHOVER_INT_ENABLE, Flag.SET + ) + else: + self._set_flag( + Reg.EEPROM_BACKUP, EEPROMBackup.BACKUP_SWITCHOVER_INT_ENABLE, Flag.CLEAR + ) + + # Always enable fast edge detection + self._set_flag(Reg.EEPROM_BACKUP, EEPROMBackup.FEDE, Flag.SET) + + # Update EEPROM + self._eecommand(EECMD.REFRESH) + + def check_backup_switchover(self, clear=True): + """ + Check if a backup switchover has occurred. + + :return: True if switchover occurred, False otherwise + """ + result = self._get_flag(Reg.STATUS, Status.BACKUP_SWITCH) + if result and clear: + self._set_flag(Reg.STATUS, Status.BACKUP_SWITCH, Flag.CLEAR) + return result From d7c3176e62e1205cb3de3eb8f862bfa6acadb431 Mon Sep 17 00:00:00 2001 From: "davitb2013@gmail.com" Date: Thu, 6 Feb 2025 17:45:17 -0800 Subject: [PATCH 3/4] remove files --- lib/rv3028/registers.py | 297 ------------------------------- lib/rv3028/rv3028.py | 375 ---------------------------------------- 2 files changed, 672 deletions(-) delete mode 100644 lib/rv3028/registers.py delete mode 100644 lib/rv3028/rv3028.py diff --git a/lib/rv3028/registers.py b/lib/rv3028/registers.py deleted file mode 100644 index 15af9e3a..00000000 --- a/lib/rv3028/registers.py +++ /dev/null @@ -1,297 +0,0 @@ -""" -Register and flag definitions for the RV-3028-C7 RTC module. -https://www.microcrystal.com/fileadmin/Media/Products/RTC/App.Manual/RV-3028-C7_App-Manual.pdf - -Author: Davit Babayan -""" - - -class Reg: - SECONDS = 0x00 - MINUTES = 0x01 - HOURS = 0x02 - WEEKDAY = 0x03 - DATE = 0x04 - MONTH = 0x05 - YEAR = 0x06 - ALARM_MINUTES = 0x07 - ALARM_HOURS = 0x08 - ALARM_WEEKDAY = ALARM_DATE = 0x09 # Depends on which one is enabled - TIMER0 = 0x0A - TIMER1 = 0x0B - TIMER_STATUS0 = 0x0C # readonly - TIMER_STATUS1 = 0x0D # readonly - STATUS = 0x0E - CONTROL1 = 0x0F - CONTROL2 = 0x10 - GP_BITS = 0x11 - CLOCK_INT_MASK = 0x12 - EVENT_CONTROL = 0x13 - TIMESTAMP_COUNT = 0x14 # readonly - TIMESTAMP_SECONDS = 0x15 # readonly - TIMESTAMP_MINUTES = 0x16 # readonly - TIMESTAMP_HOURS = 0x17 # readonly - TIMESTAMP_DATE = 0x18 # readonly - TIMESTAMP_MONTH = 0x19 # readonly - TIMESTAMP_YEAR = 0x1A # readonly - UNIX_TIME0 = 0x1B - UNIX_TIME1 = 0x1C - UNIX_TIME2 = 0x1D - UNIX_TIME3 = 0x1E - RAM1 = 0x1F - RAM2 = 0x20 - EEDATA = 0x26 - EECMD = 0x27 - ID = 0x28 # readonly - EEPROM_CLKOUT = 0x35 - EEPROM_OFFSET = 0x36 - EEPROM_BACKUP = 0x37 - - -class Flag: - SET = 1 - CLEAR = 0 - - -class Alarm: - """ - Contains bit masks for the alarm registers. - The alarm registers that contain these flags are ALARM_MINUTES, ALARM_HOURS, and ALARM_DATE. - """ - - DISABLED = 0x80 # This is a flag that you should set to disable the alarm. - VALUE = 0x7F # Enable the alarm and store the value. - VALUE_SIZE = 7 # Number of bits of value - - -class Status: - """ - Contains bit masks for the status register. Generally, these should be READ, not written to. - The exception is clearing the flags, since they do not clear themselves. - """ - - PORF = 0x01 # 0: No voltage drop, 1: Voltage drop (if set to 0 after POR, default is 1) - EVENT = 0x02 # Enabled if an event is detected - ALARM = 0x04 # Enabled if an alarm is triggered - TIMER = 0x08 # Enabled if a periodic countdown timer event is triggered. - UPDATE = 0x10 # Enabled if a periodic time update event is triggered. - BACKUP_SWITCH = 0x20 # Enabled if a switch from main to backup power occurs. - CLOCK_OUTPUT = 0x40 # Enabled if there is an interrupt on the clock output pin. - EEBUSY = 0x80 # Enabled if the EEPROM is handling a read/write request. - - -class Control1: - """ - Contains bit masks for the control1 register. - - This register is used to specify the target for the Alarm Interrupt function and the Periodic Time Update Interrupt - function and to select or set operations for the Periodic Countdown Timer. - """ - - FREQ_SELECT = 0x03 # 2 bit flag. Uses the FreqSelect enum for values. - TIMER_ENABLE = 0x04 # Starts the countdown timer if 1. - EEPROM_REFRESH_DISABLE = 0x08 # Disables the automatic EEPROM refresh if 1. - UPDATE_INT_SELECT = 0x10 # 0: second updates (default). 1: minute updates. - WADA = 0x20 # 0: use weekday for alarm (default). 1: use date for alarm. - TIMER_REPEAT = 0x80 # 0: Single mode, halt countdown when it reaches 0 (default). 1: repeat timer. - - -class TimerFreq: - """ - Selections for the frequency of the timer countdown clock. - """ - - SIZE = 2 # Number of bits used for the frequency selection - FREQ_4096HZ = 0x00 # Default value - FREQ_64HZ = 0x01 - FREQ_1HZ = 0x10 - FREQ_60S = 0x11 - - -class Control2: - """ - Contains bit masks for the control2 register. - - This register is used to control the interrupt event output for the INT pin, the stop/start status of clock and calendar - operations, the interrupt controlled clock output on CLKOUT pin, the hour mode and the time stamp enable. - """ - - RESET = ( - 0x01 # 0: Normal operation. 1: software based time adjustment (see datasheet). - ) - HOUR_MODE = 0x02 # 0: 24 hour mode (default). 1: 12 hour mode. - EVENT_INT_ENABLE = ( - 0x04 # Enables the event interrupt on the INT pin (disabled by default). - ) - ALARM_INT_ENABLE = ( - 0x08 # Enables the alarm interrupt on the INT pin (disabled by default). - ) - TIMER_INT_ENABLE = ( - 0x10 # Enables the timer interrupt on the INT pin (disabled by default). - ) - UPDATE_INT_ENABLE = ( - 0x20 # Enables the update interrupt on the INT pin (disabled by default). - ) - CLOCK_OUTPUT_INT_ENABLE = ( - 0x40 # Enables the clock output interrupt on the INT pin (disabled by default). - ) - TIMESTAMP_ENABLE = 0x80 # Enables the timestamp function (disabled by default). - - -class GPBits: - GPR_MASK = 0x7F # General purpose bits. Can be used for any purpose. - - -class ClockIntOn: - """ - Contains bit masks for the clock interrupt mask register. - - This register is used to select a predefined interrupt for automatic clock output. Setting a bit to 1 selects the - corresponding interrupt. - - Example Usage: `clock_int_mask = ClockIntOn.TIMER | ClockIntOn.ALARM` - """ - - SIZE = 2 - TIME_UPDATE = 0x01 # Enables the periodic time update interrupt. - TIMER = 0x02 # Enables the countdown timer interrupt. - ALARM = 0x04 # Enables the alarm interrupt. - EVENT = 0x08 # Enables the event interrupt. - - -class EventControl: - """ - Contains bit masks for the event control register. - - This register controls the event detection on the EVI pin. Depending of the EHL bit, high or low level (or rising or - falling edge) can be detected. Moreover a digital glitch filtering can be applied to the EVI signal by selecting a - sampling period tSP in the ET field. Furthermore this register holds control functions for the Time Stamp data. And - the switching over to VBACKUP Power state can be selected as source for an event. - """ - - TIMESTAMP_SOURCE_SELECT = 0x01 # 0: Timestamp source is external event. 1: Timestamp source is the backup switchover. - TIMESTAMP_OVERWRITE = ( - 0x02 # 0: Timestamp is not overwritten. 1: Timestamp is overwritten. - ) - TIMESTAMP_RESET = ( - 0x04 # 0: Timestamp is not reset. 1: Reset all seven TS registers. - ) - EVENT_FILTER = 0x30 # 2 bit flag. Uses the EventFilter enum for values. - EVENT_HIGH_LOW_SELECT = ( - 0x40 # 0: Low level or falling edge. 1: High level or rising edge. - ) - - -class EventFilter: - """ - Selections for the event filter. - """ - - SIZE = 2 - FILTER_OFF = 0x00 # Default value - FILTER_256Hz = 0x01 - FILTER_64Hz = 0x02 - FILTER_8Hz = 0x03 - - -class EEPROMClockOut: - """ - Contains bit masks for the EEPROM clock out register. - - A programmable square wave output is available at CLKOUT pin. Clock output can be controlled by the CLKOE bit - (or by the CLKF flag) (see PROGRAMMABLE CLOCK OUTPUT). After a Power up and the first refreshment time - tPREFR = ~66 ms, the EEPROM Clkout values CLKOE, CLKSY, PORIE and FD are copied from the EEPROM to the - corresponding RAM mirror. The default values preset on delivery are: CLKOUT = enabled, synchronization enabled, - F = 32.768 kHz. - """ - - FREQ_SELECT: 0x07 # 3 bit flag. Uses the FreqSelect enum for values. - POR_INT_ENABLE = ( - 0x08 # 0: POR interrupt disabled (default). 1: POR interrupt enabled. - ) - CLKOUT_SYNC_ENABLE = 0x40 # 0: Synchronization disabled. 1: Synchronization enable/disable enabled (default). - CLKOUT_ENABLE = 0x80 # 0: Clock output pin LOW. 1: Clock output on CLKOUT pin enabled (default). - - -class FreqSelect: - """ - Selections for the frequency of the clock in CLKOUT. - - 8192 Hz to 1 Hz clock pulses and the timer interrupt pulses can be affected by compensation pulses - """ - - SIZE = 3 - FREQ_32768HZ = 0x00 # Default value - FREQ_8192HZ = 0x01 - FREQ_1024HZ = 0x02 - FREQ_64HZ = 0x03 - FREQ_32HZ = 0x04 - FREQ_1HZ = 0x05 - PREDEFINED = 0x06 # CLKSY bit has no effect - LOW = 0x07 - - -class EEPROMBackup: - """ - Contains bit masks for the EEPROM backup register. - - This register is used to control the switchover function and the trickle charger and it holds bit 0 (LSB) of the EEOffset - value. The preconfigured (Factory Calibrated) EEOffset value may be changed by the user. - After a Power up and the first refreshment time tPREFR = ~66 ms, the EEPROM Backup value is copied from the - EEPROM to the corresponding RAM mirror. - """ - - TRICKLE_CHARGE_RES = 0x03 # 2 bit flag representing trickle charge resistance. Uses the Resistance enum for values. - BACKUP_SWITCHOVER = 0x0C # 2 bit flag representing backup switchover mode. Uses the BSM enum for values. - TRICKLE_CHARGE_ENABLE = ( - 0x20 # 0: Trickle charger disabled (default). 1: Trickle charger enabled. - ) - BACKUP_SWITCHOVER_INT_ENABLE = 0x40 # 0: Backup switchover interrupt disabled (default). 1: Backup switchover interrupt enabled. - EEOFFSET_LSB = ( - 0x80 # LSB of the EEOffset value (see EEPROM OFFSET REGISTER in datasheet) - ) - FEDE = 0x10 # FOR THE LOVE OF GOD, NEVER DISABLE THIS. - - -class Resistance: - """ - Selections for the resistance of the trickle charger. - """ - - SIZE = 2 - RES_3000 = 0x00 # Default value (ohms) - RES_5000 = 0x01 - RES_9000 = 0x02 - RES_15000 = 0x03 - - -class BSM: - """ - Selections for the backup switchover mode. - """ - - SIZE = 2 - DISABLED_DEFAULT = 0x00 # Default value - DIRECT = 0x01 - DISABLED = 0x02 - LEVEL = 0x03 - - -class EECMD: - """ - Contains commands for the EEPROM. - - This register must be written with specific values, in order to Update or Refresh all (readable/writeable) Configuration - EEPROM registers or to read or write from/to a single EEPROM Memory byte. - - Before using this commands, the automatic refresh function has to be disabled (EERD = 1) and the busy status bit - EEbusy has to indicate, that the last transfer has been finished (EEbusy = 0). Before entering the command 11h, - 12h, 21h or 22h, EECMD has to be written with RESET. - """ - - RESET = 0x00 # Reset command (must always be sent first). - UPDATE = 0x11 # Sends data from configuration RAM to EEPROM. - REFRESH = 0x12 # Sends data from EEPROM to configuration RAM. - WRITE_ONE_BYTE = 0x21 # Writes one byte to the EEPROM from EEDATA. - READ_ONE_BYTE = 0x22 # Reads one byte from the EEPROM to EEDATA. - - -class ID: - """ - Contains the ID of the RTC module. - - This register holds the 4 bit Hardware Identification number (HID) and the 4 bit Version Identification number (VID). - """ - - VID = 0x0F # Version Identification number - HID = 0xF0 # Hardware Identification number diff --git a/lib/rv3028/rv3028.py b/lib/rv3028/rv3028.py deleted file mode 100644 index 790c64c7..00000000 --- a/lib/rv3028/rv3028.py +++ /dev/null @@ -1,375 +0,0 @@ -""" -This class handles the Rv3028 real time clock. - -Authors: Nicole Maggard, Michael Pham, and Rachel Sarmiento -""" - -from rv3028.registers import ( - BSM, - EECMD, - Alarm, - Control1, - Control2, - EEPROMBackup, - EventControl, - EventFilter, - Flag, - Reg, - Resistance, - Status, -) - -from tests.stubs.i2c_device import I2CDevice - - -class WEEKDAY: - SUNDAY = 0 - MONDAY = 1 - TUESDAY = 2 - WEDNESDAY = 3 - THURSDAY = 4 - FRIDAY = 5 - SATURDAY = 6 - - -class RV3028: - def __init__(self, i2c_device: I2CDevice): - self.i2c_device = i2c_device - - def _read_register(self, register, length=1): - with self.i2c_device as i2c: - i2c.write(bytes([register])) - result = bytearray(length) - i2c.readinto(result) - return result - - def _write_register(self, register: Reg, data: bytes): - with self.i2c_device as i2c: - i2c.write(bytes([register]) + data) - - def _set_flag(self, register, mask, value): - try: - value = int(value) - except Exception: - raise ValueError("Argument 'value' must be an integer or boolean") - - data = self._read_register(register)[0] - data &= ~mask # Clear the bits - - shift = 0 - temp_mask = mask - while (temp_mask & 0x01) == 0: - temp_mask >>= 1 - shift += 1 - - max_value = mask >> shift - if value < 0 or value > max_value: - raise ValueError(f"Value {value} does not fit in the mask {mask:#04x}") - - data |= (value << shift) & mask # Set the bits - - self._write_register(register, bytes([data])) - - def _get_flag(self, register, mask, size=0): - data = self._read_register(register)[0] - result = (data & mask) >> size - - # Automatically convert to bool if mask is a single bit - if mask & (mask - 1) == 0: - return bool(result) - return result - - def _eecommand(self, command: EECMD): - while self._get_flag(Reg.STATUS, Status.EEBUSY): - pass - self._set_flag(Reg.CONTROL1, Control1.EEPROM_REFRESH_DISABLE, Flag.SET) - self._set_flag(Reg.STATUS, Status.EEBUSY, Flag.SET) - self._write_register( - Reg.EECMD, bytes([EECMD.RESET]) - ) # First command must be 00h - self._write_register(Reg.EECMD, bytes([command])) # Update command - self._set_flag(Reg.STATUS, Status.EEBUSY, Flag.CLEAR) - self._set_flag(Reg.CONTROL1, Control1.EEPROM_REFRESH_DISABLE, Flag.CLEAR) - - def _bcd_to_int(self, bcd): - return (bcd & 0x0F) + ((bcd >> 4) * 10) - - def _int_to_bcd(self, value): - return ((value // 10) << 4) | (value % 10) - - def set_time(self, hours: int, minutes: int, seconds: int) -> None: - """ - Sets the time on the device. This method configures the device's clock. - - Args: - hours (int): The hour value to set (0-23 for 24-hour format). - minutes (int): The minute value to set (0-59). - seconds (int): The second value to set (0-59). - """ - data = bytes( - [ - self._int_to_bcd(seconds), - self._int_to_bcd(minutes), - self._int_to_bcd(hours), - ] - ) - self._write_register(Reg.SECONDS, data) - - def get_time(self) -> tuple[int, int, int]: - """ - Retrieves the current time from the device. - - Returns: - tuple: A tuple containing the current time as (hours, minutes, seconds), - where: - hours (int): The hour value (0-23 for 24-hour format). - minutes (int): The minute value (0-59). - seconds (int): The second value (0-59). - """ - data = self._read_register(Reg.SECONDS, 3) - return ( - self._bcd_to_int(data[2]), # hours - self._bcd_to_int(data[1]), # minutes - self._bcd_to_int(data[0]), # seconds - ) - - def set_date(self, year: int, month: int, date: int, weekday: int) -> None: - """ - Sets the date of the device. - - Args: - year (int): The year value to set - month (int): The month value to set (1-12). - date (int): The date value to set (1-31). - weekday (int): The day of the week to set (0-6, where 0 represents Sunday). - """ - data = bytes( - [ - self._int_to_bcd(weekday), - self._int_to_bcd(date), - self._int_to_bcd(month), - self._int_to_bcd(year), - ] - ) - self._write_register( - Reg.WEEKDAY, data - ) # this is a weird way to do it but it works - - def get_date(self) -> tuple[int, int, int, int]: - """ - Gets the date of the device. - - Returns: - tuple: A 4-tuple (year, month, date, weekday) where: - year (int): The year value (0-99). - month (int): The month value (1-12). - date (int): The date value (1-31). - weekday (int): The day of the week (0-6, where 0 represents Sunday). - """ - data = self._read_register(Reg.WEEKDAY, 4) - return ( - self._bcd_to_int(data[3]), # year - self._bcd_to_int(data[2]), # month - self._bcd_to_int(data[1]), # date - self._bcd_to_int(data[0]), # weekday - ) - - def set_alarm( - self, minute: int = None, hour: int = None, weekday: int = None - ) -> None: - """ - Set the alarm time. A value of None indicates that that portion of the alarm will not be used. - - Args: - Alarm minute (0-59) or None - Alarm hour (0-23) or None - Alarm weekday (0-6, 0=Sunday) or None - """ - # Set alarm mask to check for minute, hour, and weekday match - control2 = self._read_register(Reg.CONTROL2)[0] - self._set_flag(Reg.CONTROL2, Control2.ALARM_INT_ENABLE, Flag.SET) - self._write_register(Reg.CONTROL2, bytes([control2])) - - if minute is not None and (minute < 0 or minute > 59): - raise ValueError("Invalid minute value") - if hour is not None and (hour < 0 or hour > 23): - raise ValueError("Invalid hour value") - if weekday is not None and (weekday < 0 or weekday > 6): - raise ValueError("Invalid weekday value") - - data = bytes( - (self._int_to_bcd(param) & Alarm.VALUE) - if param is not None - else Alarm.DISABLED - for param in (minute, hour, weekday) - ) - - self._write_register(Reg.ALARM_MINUTES, data) - - def check_alarm(self, clear: bool = True) -> bool: - """ - Check if the alarm flag has been triggered. - - Args: - clear (bool): (Default: True) True to clear the alarm flag, False to leave it set. - Returns: - True if alarm flag is set, False otherwise - """ - result = self._get_flag(Reg.STATUS, Status.ALARM) - if clear and result: - self._set_flag(Reg.STATUS, Status.ALARM, Flag.CLEAR) - - return bool(result) - - def get_alarm(self) -> tuple: - """ - If an alarm has been set on the device, provides the set time. - - Returns: - A tuple representing the alarm configuration. A return value of None in any field means that that field was not set. Tuple values: - minute (int or None): the minute value of the alarm (0-59) - hour (int or None): the hour value of the alarm (0-23) - weekday (int or None): the weekday of the alarm (0-6, 0 = Sunday) - """ - - # Defining the helper in here limits its scope so outside users can't access it. - def _get_alarm_field(reg: Reg): - disabled = self._get_flag(reg, Alarm.DISABLED) - if disabled: - return None - else: - return self._get_flag(reg, Alarm.VALUE) - - return ( - _get_alarm_field(Reg.ALARM_MINUTES), - _get_alarm_field(Reg.ALARM_HOURS), - _get_alarm_field(Reg.ALARM_WEEKDAY), - ) - - def enable_trickle_charger(self, resistance=3000): - self._set_flag(Reg.EEPROM_BACKUP, EEPROMBackup.TRICKLE_CHARGE_ENABLE, Flag.SET) - self._set_flag(Reg.EEPROM_BACKUP, EEPROMBackup.TRICKLE_CHARGE_RES, Flag.CLEAR) - - # Set TCR (Trickle Charge Resistor) bits - if resistance not in [3000, 5000, 9000, 15000]: - raise ValueError("Invalid trickle charger resistance") - if resistance == 3000: - tcr_value = Resistance.RES_3000 - elif resistance == 5000: - tcr_value = Resistance.RES_5000 - elif resistance == 9000: - tcr_value = Resistance.RES_9000 - elif resistance == 15000: - tcr_value = Resistance.RES_15000 - - self._set_flag(Reg.EEPROM_BACKUP, EEPROMBackup.TRICKLE_CHARGE_RES, tcr_value) - - # Refresh the EEPROM to apply changes - self._eecommand(EECMD.REFRESH) - - def disable_trickle_charger(self): - self._set_flag( - Reg.EEPROM_BACKUP, EEPROMBackup.TRICKLE_CHARGE_ENABLE, Flag.CLEAR - ) - self._eecommand(EECMD.REFRESH) - - def configure_evi(self, enable=True): - """ - Configure EVI for rising edge detection, enable time stamping, - and enable interrupt. - - :param enable: True to enable EVI, False to disable - """ - if enable: - # Configure Event Control Register - self._set_flag( - Reg.EVENT_CONTROL, EventControl.EVENT_HIGH_LOW_SELECT, Flag.SET - ) - self._set_flag( - Reg.EVENT_CONTROL, EventControl.EVENT_FILTER, EventFilter.FILTER_OFF - ) - - # Enable time stamping and EVI interrupt - self._set_flag(Reg.CONTROL2, Control2.TIMESTAMP_ENABLE, Flag.SET) - self._set_flag(Reg.CONTROL2, Control2.EVENT_INT_ENABLE, Flag.SET) - else: - self._set_flag(Reg.CONTROL2, Control2.TIMESTAMP_ENABLE, Flag.CLEAR) - self._set_flag(Reg.CONTROL2, Control2.EVENT_INT_ENABLE, Flag.CLEAR) - - def get_event_timestamp(self): - """ - Read the timestamp of the last EVI event. - - :return: Tuple of (year, month, date, hours, minutes, seconds, count) - """ - data = self._read_register(Reg.TIMESTAMP_COUNT, 7) - return ( - self._bcd_to_int(data[6]), # year - self._bcd_to_int(data[5]), # month - self._bcd_to_int(data[4]), # date - self._bcd_to_int(data[3]), # hours - self._bcd_to_int(data[2]), # minutes - self._bcd_to_int(data[1]), # seconds - data[0], # count (not BCD) - ) - - def check_event_flag(self, clear=True): - """ - Check if the Event Flag (EVF) is set in the Status Register. - - :return: True if EVF is set, False otherwise - """ - result = self._get_flag(Reg.STATUS, Status.EVENT) - if result and clear: - self._set_flag(Reg.STATUS, Status.EVENT, Flag.CLEAR) - - return result - - def configure_backup_switchover(self, mode="level", interrupt=False): - """ - Configure the Automatic Backup Switchover function. - - :param mode: 'level' for Level Switching Mode (LSM), - 'direct' for Direct Switching Mode (DSM), - or 'disabled' to disable switchover - :param interrupt: True to enable backup switchover interrupt, False to disable - """ - - match mode: - case "level": - backup_mode = BSM.LEVEL - case "direct": - backup_mode = BSM.DIRECT - case "disabled": - backup_mode = BSM.DISABLED - case _: - raise ValueError("Invalid mode. Use 'level', 'direct', or 'disabled'.") - - self._set_flag(Reg.EEPROM_BACKUP, EEPROMBackup.BACKUP_SWITCHOVER, backup_mode) - - # Configure backup switchover interrupt - if interrupt: - self._set_flag( - Reg.EEPROM_BACKUP, EEPROMBackup.BACKUP_SWITCHOVER_INT_ENABLE, Flag.SET - ) - else: - self._set_flag( - Reg.EEPROM_BACKUP, EEPROMBackup.BACKUP_SWITCHOVER_INT_ENABLE, Flag.CLEAR - ) - - # Always enable fast edge detection - self._set_flag(Reg.EEPROM_BACKUP, EEPROMBackup.FEDE, Flag.SET) - - # Update EEPROM - self._eecommand(EECMD.REFRESH) - - def check_backup_switchover(self, clear=True): - """ - Check if a backup switchover has occurred. - - :return: True if switchover occurred, False otherwise - """ - result = self._get_flag(Reg.STATUS, Status.BACKUP_SWITCH) - if result and clear: - self._set_flag(Reg.STATUS, Status.BACKUP_SWITCH, Flag.CLEAR) - return result From d1b9468bb16e4b7668938e600de7247ff9936305 Mon Sep 17 00:00:00 2001 From: Nate Gay Date: Fri, 7 Feb 2025 14:31:11 -0600 Subject: [PATCH 4/4] Add release tag --- lib/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/requirements.txt b/lib/requirements.txt index 293f961a..04b29c0c 100644 --- a/lib/requirements.txt +++ b/lib/requirements.txt @@ -1,5 +1,5 @@ adafruit-circuitpython-asyncio @ git+https://github.com/adafruit/adafruit_circuitpython_asyncio@1.3.3 -proves-circuitpython-rv3028 @ git+https://github.com/proveskit/PROVES_CircuitPython_RV3028 +proves-circuitpython-rv3028 @ git+https://github.com/proveskit/PROVES_CircuitPython_RV3028@1.0.0 adafruit-circuitpython-drv2605==1.3.4 adafruit-circuitpython-lis2mdl==2.1.23 adafruit-circuitpython-lsm6ds==4.5.13