diff --git a/pyproject.toml b/pyproject.toml index 292fdd9..e44e05d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,13 +1,13 @@ [project] name = "proves-circuitpython-rv3028" -version = "1.0.0" +version = "1.0.1" description = "RV3028 device driver" readme = "README.md" requires-python = ">=3.12.3" dependencies = [ "coverage==7.6.10", "pre-commit==4.0.1", - "pytest==8.3.2", + "pytest==8.3.2" ] [tool.ruff.format] @@ -27,6 +27,7 @@ skip_covered = false include = [ "rv3028/registers.py", "rv3028/rv3028.py", + "rv3028/rtc_datetime.py", ] [tool.coverage.html] diff --git a/rv3028/rtc_datetime.py b/rv3028/rtc_datetime.py new file mode 100644 index 0000000..97ec60e --- /dev/null +++ b/rv3028/rtc_datetime.py @@ -0,0 +1,170 @@ +""" +Basic date and time formatting library for RV-3028-C7 RTC module. +""" + +_MONTH_DAYS = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) + + +def _is_leap_year(year: int) -> bool: + "year -> True if leap year, else False." + return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0) + + +def _validate_times(hour: int, minute: int, second: int) -> None: + if not (0 <= hour <= 23): + raise ValueError("Hour must be in 0..24") + if not (0 <= minute <= 59): + raise ValueError("Minute must be in 0..59") + if not (0 <= second <= 59): + raise ValueError("Second must be in 0..59") + + +def _validate_dates(year: int, month: int, day: int) -> None: + if not (2000 <= year <= 2099): + raise ValueError("Year must be in 2000..2099") + if not (1 <= month <= 12): + raise ValueError("Month must be in 1..12") + if month == 2 and _is_leap_year(year): + if not (1 <= day <= 29): + raise ValueError("Day must be in 1..29") + elif not (1 <= day <= _MONTH_DAYS[month - 1]): + raise ValueError(f"Day must be in 1..{_MONTH_DAYS[month - 1]}") + + +class time: + """ + Class that represents time in hours, minutes and seconds. + + Attributes: + hour (int): The hour of the time. + minute (int): The minute of the time. + second (int): The second of the time. + """ + + def __new__(cls, hour: int, minute: int, second: int) -> "time": + _validate_times(hour, minute, second) + self = object.__new__(cls) + self._hour = hour + self._minute = minute + self._second = second + return self + + def __repr__(self) -> str: + return f"{self.hour:2d}:{self.minute:2d}:{self.second:2d}" + + __str__ = __repr__ + + @property + def hour(self) -> int: + return self._hour + + @property + def minute(self) -> int: + return self._minute + + @property + def second(self) -> int: + return self._second + + +class date: + """ + Class that represents date in year, month and day. + + Attributes: + year (int): The year of the date. + month (int): The month of the date. + day (int): The day of the date. + """ + + def __new__(cls, year: int, month: int, day: int) -> "date": + _validate_dates(year, month, day) + self = object.__new__(cls) + self._year = year + self._month = month + self._day = day + return self + + def __repr__(self) -> str: + return f"{self.year}-{self.month}-{self.day}" + + __str__ = __repr__ + + @property + def year(self) -> int: + return self._year + + @property + def month(self) -> int: + return self._month + + @property + def day(self) -> int: + return self._day + + +class datetime: + """ + Class that represents date and time combined. + + Attributes: + year (int): The year of the datetime. + month (int): The month of the datetime. + day (int): The day of the datetime. + hour (int): The hour of the datetime. + minute (int): The minute of the datetime. + second (int): The second of the datetime. + """ + + def __new__( + cls, year: int, month: int, day: int, hour: int, minute: int, second: int + ) -> "datetime": + _validate_dates(year, month, day) + _validate_times(hour, minute, second) + self = object.__new__(cls) + self._year = year + self._month = month + self._day = day + self._second = second + self._minute = minute + self._hour = hour + return self + + @classmethod + def combine(cls, date: date, time: time) -> "datetime": + return cls(date.year, date.month, date.day, time.hour, time.minute, time.second) + + def __repr__(self) -> str: + return f"{self.year}-{self.month:02d}-{self.day:02d} {self.hour:02d}:{self.minute:02d}:{self.second:02d}" + + __str__ = __repr__ + + @property + def year(self) -> int: + return self._year + + @property + def month(self) -> int: + return self._month + + @property + def day(self) -> int: + return self._day + + @property + def hour(self) -> int: + return self._hour + + @property + def minute(self) -> int: + return self._minute + + @property + def second(self) -> int: + return self._second + + def time(self) -> time: + return time(self.hour, self.minute, self.second) + + def date(self) -> date: + return date(self.year, self.month, self.day) diff --git a/rv3028/rv3028.py b/rv3028/rv3028.py index d1b47d4..97b5c4d 100755 --- a/rv3028/rv3028.py +++ b/rv3028/rv3028.py @@ -4,6 +4,7 @@ Authors: Nicole Maggard, Michael Pham, and Rachel Sarmiento """ +import rv3028.rtc_datetime as dt from rv3028.registers import ( BSM, EECMD, @@ -25,17 +26,8 @@ from adafruit_bus_device.i2c_device import I2CDevice from busio import I2C -_RV3028_DEFAULT_ADDRESS = 0x52 - -class WEEKDAY: - SUNDAY = 0 - MONDAY = 1 - TUESDAY = 2 - WEDNESDAY = 3 - THURSDAY = 4 - FRIDAY = 5 - SATURDAY = 6 +_RV3028_DEFAULT_ADDRESS = 0x52 class RV3028: @@ -108,98 +100,94 @@ def _bcd_to_int(self, bcd): def _int_to_bcd(self, value): return ((value // 10) << 4) | (value % 10) - def set_time(self, hours: int, minutes: int, seconds: int) -> None: + @property + def time(self) -> dt.time: + """ + Retrieves the current time from the device. + + Returns: + An rtc_datetime.time object representing the time to set. + """ + data = self._read_register(Reg.SECONDS, 3) + return dt.time( + hour=self._bcd_to_int(data[2]), + minute=self._bcd_to_int(data[1]), + second=self._bcd_to_int(data[0]), + ) + + @time.setter + def time(self, time: dt.time) -> 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). + time: A rtc_datetime.time object representing the time to set. """ - if hours < 0 or hours > 23: - raise ValueError("Hour value must be between 0 and 23") - if minutes < 0 or minutes > 59: - raise ValueError("Minute value must be between 0 and 59") - if seconds < 0 or seconds > 59: - raise ValueError("Second vaue must be between 0 and 59") - data = bytes( [ - self._int_to_bcd(seconds), - self._int_to_bcd(minutes), - self._int_to_bcd(hours), + self._int_to_bcd(time.second), + self._int_to_bcd(time.minute), + self._int_to_bcd(time.hour), ] ) self._write_register(Reg.SECONDS, data) - def get_time(self) -> tuple[int, int, int]: + @property + def date(self) -> dt.date: """ - Retrieves the current time from the device. + Gets the date of 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). + An rtc_datetime.date object representing the date. """ - 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 + data = self._read_register(Reg.DATE, 3) + return dt.date( + year=self._bcd_to_int(data[2]) + 2000, + month=self._bcd_to_int(data[1]), + day=self._bcd_to_int(data[0]), ) - def set_date(self, year: int, month: int, date: int, weekday: int) -> None: + @date.setter + def date(self, date: dt.date) -> 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). + date: A rtc_datetime.date object representing the date to set. """ - if year < 0 or year > 99: - raise ValueError("Year value must be between 0 and 99") - if month < 1 or month > 12: - raise ValueError("Month value must be between 1 and 12") - if date < 1 or date > 31: - raise ValueError("Date value must be between 1 and 31") - if weekday < 0 or weekday > 6: - raise ValueError("Weekday value must be between 0 and 6") data = bytes( [ - self._int_to_bcd(weekday), - self._int_to_bcd(date), - self._int_to_bcd(month), - self._int_to_bcd(year), + self._int_to_bcd(date.day), + self._int_to_bcd(date.month), + self._int_to_bcd(date.year - 2000), ] ) self._write_register( - Reg.WEEKDAY, data + Reg.DATE, data ) # this is a weird way to do it but it works - def get_date(self) -> tuple[int, int, int, int]: + @property + def datetime(self) -> dt.datetime: """ - Gets the date of the device. + Get the current date and time as a combined datetime object. 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). + rtc_datetime.datetime: The current date and time. """ - 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 - ) + return dt.datetime.combine(self.date, self.time) + + @datetime.setter + def datetime(self, datetime: dt.datetime | str) -> None: + """ + Set the date and time for the RV3028. + + Args: + datetime (rtc_datetime.datetime): A datetime object + """ + + self.time = datetime.time() + self.date = datetime.date() def set_alarm( self, minute: int = None, hour: int = None, weekday: int = None diff --git a/tests/test_RV3028.py b/tests/test_RV3028.py index a5dd7d0..6447d6a 100644 --- a/tests/test_RV3028.py +++ b/tests/test_RV3028.py @@ -1,6 +1,7 @@ import pytest from mocks.i2cMock import MockI2C, MockI2CDevice +import rv3028.rtc_datetime as dt from rv3028.registers import ( BSM, Alarm, @@ -25,20 +26,46 @@ def rtc(): # Test functions def test_set_and_get_time(rtc): - rtc.set_time(23, 59, 58) - hours, minutes, seconds = rtc.get_time() - assert hours == 23 - assert minutes == 59 - assert seconds == 58 + rtc.time = dt.time(hour=23, minute=59, second=58) + + time_to_check = rtc.time + assert time_to_check.hour == 23 + assert time_to_check.minute == 59 + assert time_to_check.second == 58 def test_set_and_get_date(rtc): - rtc.set_date(21, 12, 31, 5) # Year, month, date, weekday - year, month, date, weekday = rtc.get_date() - assert year == 21 - assert month == 12 - assert date == 31 - assert weekday == 5 + rtc.date = dt.date(year=2021, month=12, day=31) + + date_to_check = rtc.date + assert date_to_check.year == 2021 + assert date_to_check.month == 12 + assert date_to_check.day == 31 + + +def test_year_bounds_on_set_date(rtc): + # Test setting a date with a year below the lower bound (2000) + with pytest.raises(ValueError): + rtc.date = dt.date(year=1999, month=12, day=31) + + # Test setting a date with a year above the upper bound (2099) + with pytest.raises(ValueError): + rtc.date = dt.date(year=2100, month=1, day=1) + + +def test_set_and_get_datetime(rtc): + datetime_to_set = dt.datetime( + year=2028, month=11, day=30, hour=11, minute=11, second=12 + ) + rtc.datetime = datetime_to_set + + datetime_to_check = rtc.datetime + assert datetime_to_check.year == 2028 + assert datetime_to_check.month == 11 + assert datetime_to_check.day == 30 + assert datetime_to_check.hour == 11 + assert datetime_to_check.minute == 11 + assert datetime_to_check.second == 12 def test_set_flag(rtc): diff --git a/tests/test_rtc_datetime.py b/tests/test_rtc_datetime.py new file mode 100644 index 0000000..59832eb --- /dev/null +++ b/tests/test_rtc_datetime.py @@ -0,0 +1,85 @@ +import pytest +from mocks.i2cMock import MockI2C, MockI2CDevice + +import rv3028.rtc_datetime as dt +from rv3028.rv3028 import RV3028 + + +@pytest.fixture +def rtc(): + i2c_bus = MockI2C() + i2c_device = MockI2CDevice(i2c_bus, 0x52) + rtc = RV3028(i2c_device) + return rtc + + +# Test functions +def test_leap_year(): + assert dt._is_leap_year(2000) + assert not dt._is_leap_year(2001) + assert not dt._is_leap_year(2100) + assert dt._is_leap_year(2004) + + +def test_validate_times(): + with pytest.raises(ValueError): + dt._validate_times(24, 0, 0) + with pytest.raises(ValueError): + dt._validate_times(0, 60, 0) + with pytest.raises(ValueError): + dt._validate_times(0, 0, 60) + + +def test_validate_dates(): + with pytest.raises(ValueError): + dt._validate_dates(1999, 1, 1) # year to small + with pytest.raises(ValueError): + dt._validate_dates(2100, 1, 1) # year too big + with pytest.raises(ValueError): + dt._validate_dates(2000, 0, 1) # month too small + with pytest.raises(ValueError): + dt._validate_dates(2000, 13, 1) # month too large + with pytest.raises(ValueError): + dt._validate_dates(2000, 4, 31) # day too big + with pytest.raises(ValueError): + dt._validate_dates(2001, 2, 29) # Not a leap year + try: + dt._validate_dates(2000, 2, 29) # Valid leap year date + except ValueError: + pytest.fail("Unexpected ValueError raised") + + +def test_time(): + t = dt.time(12, 20, 14) + assert t.hour == 12 + assert t.minute == 20 + assert t.second == 14 + assert str(t) == "12:20:14" + + +def test_date(): + d = dt.date(2002, 11, 14) + assert d.year == 2002 + assert d.month == 11 + assert d.day == 14 + assert str(d) == "2002-11-14" + + +def test_datetime(): + dt1 = dt.datetime(2002, 11, 14, 12, 20, 14) + assert dt1.year == 2002 + assert dt1.month == 11 + assert dt1.day == 14 + assert dt1.hour == 12 + assert dt1.minute == 20 + assert dt1.second == 14 + assert str(dt1) == "2002-11-14 12:20:14" + + +def test_combine_and_split_datetime(): + t = dt.time(12, 20, 14) + d = dt.date(2002, 11, 14) + dt2 = dt.datetime.combine(d, t) + assert str(dt2) == "2002-11-14 12:20:14" + assert str(dt2.time()) == "12:20:14" + assert str(dt2.date()) == "2002-11-14" diff --git a/uv.lock b/uv.lock index 3173c3b..a1da050 100644 --- a/uv.lock +++ b/uv.lock @@ -1,6 +1,118 @@ version = 1 requires-python = ">=3.12.3" +[[package]] +name = "adafruit-blinka" +version = "8.54.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "adafruit-circuitpython-typing" }, + { name = "adafruit-platformdetect" }, + { name = "adafruit-pureio" }, + { name = "binho-host-adapter" }, + { name = "pyftdi" }, + { name = "sysv-ipc", marker = "platform_machine != 'mips' and sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/69/33/4f5f0074cacbd779fd35936356a40bc515e7b68d934564886f7ab5d21c80/adafruit_blinka-8.54.0.tar.gz", hash = "sha256:3c9184626e952dcd805638cd7f630b68783fd03f779bbfc9b8f1fafac8819beb", size = 253957 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4f/47/fc186df8528ae06626f3f08d669675c2adfe97b866e31015b9e2a2b85485/Adafruit_Blinka-8.54.0-py3-none-any.whl", hash = "sha256:d658ad49976b5c96c5c4583ffd29f7fe8b7b282b22835e23c1ae3a5c784fde11", size = 369441 }, +] + +[[package]] +name = "adafruit-circuitpython-busdevice" +version = "5.2.11" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "adafruit-blinka" }, + { name = "adafruit-circuitpython-typing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a2/c0/f6347ab32f077413c20f55bc4b0f1592f35affd4d26753394c5ed6c36c4c/adafruit_circuitpython_busdevice-5.2.11.tar.gz", hash = "sha256:a9a1310bee7021703ccc247bb3ff04d0873573948a6c7bee9016361cd6707a71", size = 27627 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/c7/9f0e2b2674cb5b1fb35d067a7585a2a76596a36044264eb390980d428ccf/adafruit_circuitpython_busdevice-5.2.11-py3-none-any.whl", hash = "sha256:d4379c9ae86a15f7044dea815a94525ca9eda6a7c0b2fa0e75cf9e700c9384b8", size = 7539 }, +] + +[[package]] +name = "adafruit-circuitpython-connectionmanager" +version = "3.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "adafruit-blinka" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/69/8b/8316002905f97a7f7e9c3a53dd9bb5a17889033ec55c403a6e55077f6298/adafruit_circuitpython_connectionmanager-3.1.3.tar.gz", hash = "sha256:0f133bdedf454ede0c0a866ed605fe166cc85f75cfcea74758e3622ae403e5f9", size = 37381 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/55/7d/896b31bd31eff89e5cab5d3acec9d3d34f5a0654ceab25e01865e628d9f9/adafruit_circuitpython_connectionmanager-3.1.3-py3-none-any.whl", hash = "sha256:9df3a4c617dae27bad1ac8607f1a084312c8498d831ebe1c6a2c8d5cb309daea", size = 7811 }, +] + +[[package]] +name = "adafruit-circuitpython-datetime" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "adafruit-blinka" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3f/1a/b04e3934004e7459a87cd64429c401b970e39d2d44b6eb0a9e14dc424ebc/adafruit_circuitpython_datetime-1.4.0.tar.gz", hash = "sha256:87caf1f2707515b245ed740694c1b1d5feb73a0e471756f2596ff8fd6e50aca6", size = 59502 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/32/dcfeb0832b2c8d591ea418fa5db816c21fe40c8017706c4943f71726301f/adafruit_circuitpython_datetime-1.4.0-py3-none-any.whl", hash = "sha256:0ccdebfaf8cee3c4ac568b5d6d94f903be83d6c1dfa0b95bd9c3ccb52ff83f31", size = 17584 }, +] + +[[package]] +name = "adafruit-circuitpython-requests" +version = "4.1.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "adafruit-blinka" }, + { name = "adafruit-circuitpython-connectionmanager" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6a/45/070129e6b77f801514cd974524c6b9fd502aa04dd5c2e45ab03e85c96cac/adafruit_circuitpython_requests-4.1.9.tar.gz", hash = "sha256:b9eeb252b43946f1a90c34ca8844e07bb1e01cd210c927f561d0e10b97c5ff9d", size = 66232 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/40/ff356fd61ef3ea044b7944687d62419c304217381ee20d9fa444aeb98339/adafruit_circuitpython_requests-4.1.9-py3-none-any.whl", hash = "sha256:d0f0a899c6ef143eab9a50a9625be43f5f8da7b9688c1496891999fa20107c93", size = 10721 }, +] + +[[package]] +name = "adafruit-circuitpython-typing" +version = "1.11.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "adafruit-blinka" }, + { name = "adafruit-circuitpython-busdevice" }, + { name = "adafruit-circuitpython-requests" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/24/80/8c280fa7d42a23dce40b2fe64f708d18fa32b384adbf6934955d2c2ebecf/adafruit_circuitpython_typing-1.11.2.tar.gz", hash = "sha256:c7ac8532a9ad7e4a65d5588764b7483c0b6967d305c37faebcc0c5356d677e33", size = 29277 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dd/d5/76a6bca9cf08907b48dfc8ccbccbd190155353f521876e02d6b7bb244003/adafruit_circuitpython_typing-1.11.2-py3-none-any.whl", hash = "sha256:e1401a09bbfdf67e43875cc6755b3af0eda8381b12c9c8f759bd7676b7425e1c", size = 11101 }, +] + +[[package]] +name = "adafruit-platformdetect" +version = "3.77.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/4e/2b2ca031227de47e2aab6cf092b78934c9c0033a685075ddab3e0c0b55fe/adafruit_platformdetect-3.77.0.tar.gz", hash = "sha256:adce6386059637e92b4cb5d3430d016119cd3eb19f9276920c54515f3d798949", size = 48024 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/18/b18e9ff2aee42f03082675c5d18d4eb02411477e07c86d74833d3396792e/Adafruit_PlatformDetect-3.77.0-py3-none-any.whl", hash = "sha256:93f599c21e7db2d92bc32ac69ba5063876404c4af87d11358863e62f407409be", size = 25542 }, +] + +[[package]] +name = "adafruit-pureio" +version = "1.1.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e5/b7/f1672435116822079bbdab42163f9e6424769b7db778873d95d18c085230/Adafruit_PureIO-1.1.11.tar.gz", hash = "sha256:c4cfbb365731942d1f1092a116f47dfdae0aef18c5b27f1072b5824ad5ea8c7c", size = 35511 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/19/9d/28e9d12f36e13c5f2acba3098187b0e931290ecd1d8df924391b5ad2db19/Adafruit_PureIO-1.1.11-py3-none-any.whl", hash = "sha256:281ab2099372cc0decc26326918996cbf21b8eed694ec4764d51eefa029d324e", size = 10678 }, +] + +[[package]] +name = "binho-host-adapter" +version = "0.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyserial" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/68/36/29b7b896e83e195fac6d64ccff95c0f24a18ee86e7437a22e60e0331d90a/binho-host-adapter-0.1.6.tar.gz", hash = "sha256:1e6da7a84e208c13b5f489066f05774bff1d593d0f5bf1ca149c2b8e83eae856", size = 10068 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/6b/0f13486003aea3eb349c2946b7ec9753e7558b78e35d22c938062a96959c/binho_host_adapter-0.1.6-py3-none-any.whl", hash = "sha256:f71ca176c1e2fc1a5dce128beb286da217555c6c7c805f2ed282a6f3507ec277", size = 10540 }, +] + [[package]] name = "cfgv" version = "3.4.0" @@ -147,9 +259,10 @@ wheels = [ [[package]] name = "proves-circuitpython-rv3028" -version = "1.0.0" +version = "1.0.1" source = { virtual = "." } dependencies = [ + { name = "adafruit-circuitpython-datetime" }, { name = "coverage" }, { name = "pre-commit" }, { name = "pytest" }, @@ -157,11 +270,33 @@ dependencies = [ [package.metadata] requires-dist = [ + { name = "adafruit-circuitpython-datetime", specifier = "==1.4.0" }, { name = "coverage", specifier = "==7.6.10" }, { name = "pre-commit", specifier = "==4.0.1" }, { name = "pytest", specifier = "==8.3.2" }, ] +[[package]] +name = "pyftdi" +version = "0.56.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyserial" }, + { name = "pyusb" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/96/a8de7b7e5556d4b00d1ca1969fc34c89a1b6d177876c7a31d42631b090fc/pyftdi-0.56.0-py3-none-any.whl", hash = "sha256:3ef0baadbf9031dde9d623ae66fac2d16ded36ce1b66c17765ca1944cb38b8b0", size = 145718 }, +] + +[[package]] +name = "pyserial" +version = "3.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1e/7d/ae3f0a63f41e4d2f6cb66a5b57197850f919f59e558159a4dd3a818f5082/pyserial-3.5.tar.gz", hash = "sha256:3c77e014170dfffbd816e6ffc205e9842efb10be9f58ec16d3e8675b4925cddb", size = 159125 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/bc/587a445451b253b285629263eb51c2d8e9bcea4fc97826266d186f96f558/pyserial-3.5-py2.py3-none-any.whl", hash = "sha256:c4451db6ba391ca6ca299fb3ec7bae67a5c55dde170964c7a14ceefec02f2cf0", size = 90585 }, +] + [[package]] name = "pytest" version = "8.3.2" @@ -177,6 +312,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0f/f9/cf155cf32ca7d6fa3601bc4c5dd19086af4b320b706919d48a4c79081cf9/pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5", size = 341802 }, ] +[[package]] +name = "pyusb" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/00/6b/ce3727395e52b7b76dfcf0c665e37d223b680b9becc60710d4bc08b7b7cb/pyusb-1.3.1.tar.gz", hash = "sha256:3af070b607467c1c164f49d5b0caabe8ac78dbed9298d703a8dbf9df4052d17e", size = 77281 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/28/b8/27e6312e86408a44fe16bd28ee12dd98608b39f7e7e57884a24e8f29b573/pyusb-1.3.1-py3-none-any.whl", hash = "sha256:bf9b754557af4717fe80c2b07cc2b923a9151f5c08d17bdb5345dac09d6a0430", size = 58465 }, +] + [[package]] name = "pyyaml" version = "6.0.2" @@ -203,6 +347,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, ] +[[package]] +name = "sysv-ipc" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0c/d7/5d2f861155e9749f981e6c58f2a482d3ab458bf8c35ae24d4b4d5899ebf9/sysv_ipc-1.1.0.tar.gz", hash = "sha256:0f063cbd36ec232032e425769ebc871f195a7d183b9af32f9901589ea7129ac3", size = 99448 } + +[[package]] +name = "typing-extensions" +version = "4.12.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, +] + [[package]] name = "virtualenv" version = "20.29.1"