diff --git a/README.rst b/README.rst index d4f919c..74f7bac 100644 --- a/README.rst +++ b/README.rst @@ -27,6 +27,7 @@ These drivers depends on: * `HT16K33 `_ * `DotStar `_ * `NeoPixel `_ +* `DS3231 `_ Please ensure all dependencies are available on the CircuitPython filesystem. This is easily achieved by downloading diff --git a/adafruit_featherwing/rtc_featherwing.py b/adafruit_featherwing/rtc_featherwing.py new file mode 100755 index 0000000..246263a --- /dev/null +++ b/adafruit_featherwing/rtc_featherwing.py @@ -0,0 +1,303 @@ +# The MIT License (MIT) +# +# Copyright (c) 2019 Melissa LeBlanc-Williams for Adafruit Industries LLC +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +""" +`adafruit_featherwing.rtc_featherwing` +==================================================== + +Helper for using the `DS3231 Precision RTC FeatherWing +`_. + +* Author(s): Melissa LeBlanc-Williams +""" + +__version__ = "0.0.0-auto.0" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_FeatherWing.git" + +import time +from collections import namedtuple +import adafruit_ds3231 +from adafruit_featherwing import shared + +class RTCFeatherWing: + """Class representing an `DS3231 Precision RTC FeatherWing + `_. + + Automatically uses the feather's I2C bus.""" + def __init__(self): + self._rtc = adafruit_ds3231.DS3231(shared.I2C_BUS) + + def __setitem__(self, index, value): + """ + Allow updates using setitem if that makes it easier + """ + self._set_time_value(index, value) + + def __getitem__(self, index): + """ + Allow retrievals using getitem if that makes it easier + """ + return self._get_time_value(index) + + def _set_time_value(self, unit, value): + """ + Set just the specific unit of time + """ + now = self._get_now() + if unit in now: + now[unit] = value + else: + raise ValueError('The specified unit of time is invalid') + + self._rtc.datetime = self._encode(now) + + def _get_time_value(self, unit): + """ + Get just the specific unit of time + """ + now = self._get_now() + if unit in now: + return now[unit] + else: + raise ValueError('The specified unit of time is invalid') + + def _get_now(self): + """ + Return the current date and time in a nice updatable dictionary + """ + now = self._rtc.datetime + return {'second': now.tm_sec, 'minute': now.tm_min, 'hour': now.tm_hour, 'day': now.tm_mday, + 'month': now.tm_mon, 'year': now.tm_year, 'weekday': now.tm_wday} + + def _encode(self, date): + """ + Encode the updatable dictionary back into a time struct + """ + now = self._rtc.datetime + return time.struct_time((date['year'], date['month'], date['day'], date['hour'], + date['minute'], date['second'], date['weekday'], now.tm_yday, + now.tm_isdst)) + + def is_leap_year(self, year=None): + """ + Check if the year is a leap year + + :param int year: (Optional) The year to check. If none is provided, current year is used. + """ + if year is None: + year = self._get_time_value('year') + return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0) + + def get_month_days(self, month=None, year=None): + """ + Return the number of days for the month of the given year + + :param int month: (Optional) The month to use. If none is provided, current month is used. + :param int year: (Optional) The year to check. If none is provided, current year is used. + """ + if month is None: + month = self._get_time_value('month') + leap_year = self.is_leap_year(year) + max_days = (31, 29 if leap_year else 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) + return max_days[month - 1] + + def set_time(self, hour, minute, second=0): + """ + Set the time only + + :param int hour: The hour we want to set the time to + :param int minute: The minute we want to set the time to + :param int second: (Optional) The second we want to set the time to (default=0) + """ + if not isinstance(second, int) or not 0 <= second < 60: + raise ValueError('The second must be an integer in the range of 0-59') + + if not isinstance(minute, int) or not 0 <= minute < 60: + raise ValueError('The minute must be an integer in the range of 0-59') + + if not isinstance(hour, int) or not 0 <= hour < 24: + raise ValueError('The hour must be an integer in the range of 0-23') + + now = self._get_now() + now['hour'] = hour + now['minute'] = minute + now['second'] = second + self._rtc.datetime = self._encode(now) + + def set_date(self, day, month, year): + """ + Set the date only + + :param int day: The day we want to set the date to + :param int month: The month we want to set the date to + :param int year: The year we want to set the date to + """ + if not isinstance(year, int): + raise ValueError('The year must be an integer') + + if not isinstance(month, int) or not 1 <= month <= 12: + raise ValueError('The month must be an integer in the range of 1-12') + + month_days = self.get_month_days(month, year) + if not isinstance(day, int) or not 1 <= day <= month_days: + raise ValueError('The day must be an integer in the range of 1-{}'.format(month_days)) + + now = self._get_now() + now['day'] = day + now['month'] = month + now['year'] = year + self._rtc.datetime = self._encode(now) + + @property + def datetime(self): + """ + Passthru property to the ds3231 library for compatibility + """ + return self._rtc.datetime + + @datetime.setter + def datetime(self, datetime): + self._rtc.datetime = datetime + + @property + def year(self): + """ + The Current Year + """ + return self._get_time_value('year') + + @year.setter + def year(self, year): + if isinstance(year, int): + self._set_time_value('year', year) + else: + raise ValueError('The year must be an integer') + + @property + def month(self): + """ + The Current Month + """ + return self._get_time_value('month') + + @month.setter + def month(self, month): + if isinstance(month, int) and 1 <= month <= 12: + self._set_time_value('month', month) + else: + raise ValueError('The month must be an integer in the range of 1-12') + + @property + def day(self): + """ + The Current Day + """ + return self._get_time_value('day') + + @day.setter + def day(self, day): + month_days = self.get_month_days() + if isinstance(day, int) and 1 <= day <= month_days: + self._set_time_value('day', day) + else: + raise ValueError('The day must be an integer in the range of 1-{}'.format(month_days)) + + @property + def hour(self): + """ + The Current Hour + """ + return self._get_time_value('hour') + + @hour.setter + def hour(self, hour): + if isinstance(hour, int) and 0 <= hour < 24: + self._set_time_value('hour', hour) + else: + raise ValueError('The hour must be an integer in the range of 0-23') + + @property + def minute(self): + """ + The Current Minute + """ + return self._get_time_value('minute') + + @minute.setter + def minute(self, minute): + if isinstance(minute, int) and 0 <= minute < 60: + self._set_time_value('minute', minute) + else: + raise ValueError('The minute must be an integer in the range of 0-59') + + @property + def second(self): + """ + The Current Second + """ + return self._get_time_value('second') + + @second.setter + def second(self, second): + if isinstance(second, int) and 0 <= second < 60: + self._set_time_value('second', second) + else: + raise ValueError('The second must be an integer in the range of 0-59') + + @property + def weekday(self): + """ + The Current Day of the Week Value (0-6) where Sunday is 0 + """ + return self._get_time_value('weekday') + + @weekday.setter + def weekday(self, weekday): + if isinstance(weekday, int) and 0 <= weekday < 7: + self._set_time_value('weekday', weekday) + else: + raise ValueError('The weekday must be an integer in the range of 0-6') + + @property + def now(self): + """ + The Current Date and Time in Named Tuple Style (Read Only) + """ + date_time = namedtuple("DateTime", "second minute hour day month year weekday") + return date_time(**self._get_now()) + + @property + def unixtime(self): + """ + The Current Date and Time in Unix Time + """ + try: + return time.mktime(self._rtc.datetime) + except (AttributeError, RuntimeError) as error: + print("Error attempting to run time.mktime() on this board\n", error) + + @unixtime.setter + def unixtime(self, unixtime): + if isinstance(unixtime, int): + try: + self._rtc.datetime = time.localtime(unixtime) + except (AttributeError, RuntimeError) as error: + print("Error attempting to run time.localtime() on this board\n", error) diff --git a/docs/api.rst b/docs/api.rst index 1c1e449..6e46a33 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -15,3 +15,6 @@ .. automodule:: adafruit_featherwing.neopixel_featherwing :members: + +.. automodule:: adafruit_featherwing.rtc_featherwing + :members: diff --git a/docs/conf.py b/docs/conf.py index 77a90fa..20b5b08 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -35,7 +35,7 @@ master_doc = 'index' # General information about the project. -project = u'Adafruit featherwing Library' +project = u'Adafruit FeatherWing Library' copyright = u'2017 Scott Shawcroft' author = u'Scott Shawcroft' diff --git a/docs/examples.rst b/docs/examples.rst index 65a2bc9..3237c13 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -27,7 +27,11 @@ Ensure your device works with this simple test. :caption: examples/featherwing_sevensegment_simpletest.py :linenos: -Other tests +.. literalinclude:: ../examples/featherwing_rtc_simpletest.py + :caption: examples/featherwing_rtc_simpletest.py + :linenos: + +Other Tests ------------ .. literalinclude:: ../examples/featherwing_dotstar_palette_example.py diff --git a/examples/featherwing_rtc_simpletest.py b/examples/featherwing_rtc_simpletest.py new file mode 100755 index 0000000..19044eb --- /dev/null +++ b/examples/featherwing_rtc_simpletest.py @@ -0,0 +1,40 @@ +""" +This example will allow you to set the date and time +and then loop through and display the current time +""" +import time +from adafruit_featherwing import rtc_featherwing + +days = ("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday") + +# Create the RTC instance: +rtc = rtc_featherwing.RTCFeatherWing() + +#pylint: disable-msg=using-constant-test +if True: # Change this to True to set the date and time + rtc.set_time(13, 34) # Set the time (seconds are optional) + print(rtc.now) + rtc.set_date(16, 1, 2016) # Set the date + print(rtc.now) + rtc.year = 2019 # Set just the Year + print(rtc.now) + rtc.month = 2 # Set Just the Month + print(rtc.now) + rtc.hour = 16 # Set just the hour + print(rtc.now) + rtc.weekday = 6 # Set just the day of the week (Sunday = 0) + print(rtc.now) + rtc.unixtime = 1550335257 # Or set the date and time with a unix timestamp + +# Main loop: +while True: + now = rtc.now + print("The date is {} {}/{}/{}".format(days[now.weekday], now.day, now.month, now.year)) + print("The time is {}:{:02}:{:02}".format(now.hour, now.minute, now.second)) + print("The UNIX timestamp is {}".format(rtc.unixtime)) + print("The number of days in the current month is {}".format(rtc.get_month_days())) + if rtc.is_leap_year(): + print("This year is a leap year") + else: + print("This year is not a leap year") + time.sleep(1) # wait a second diff --git a/requirements.txt b/requirements.txt index 32c8ec6..2ec7b8b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,4 +6,4 @@ adafruit-circuitpython-seesaw adafruit-circuitpython-ht16k33 adafruit-circuitpython-dotstar adafruit-circuitpython-neopixel - +adafruit-circuitpython-ds3231 diff --git a/setup.py b/setup.py index 90b67ee..7a8fd3c 100644 --- a/setup.py +++ b/setup.py @@ -38,7 +38,8 @@ install_requires=['Adafruit-Blinka', 'adafruit-circuitpython-busdevice', 'adafruit-circuitpython-register', 'adafruit-circuitpython-ina219', 'adafruit-circuitpython-seesaw', 'adafruit-circuitpython-ht16k33', - 'adafruit-circuitpython-dotstar', 'adafruit-circuitpython-neopixel'], + 'adafruit-circuitpython-dotstar', 'adafruit-circuitpython-neopixel', + 'adafruit-circuitpython-ds3231'], # Choose your license license='MIT',