# 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
<https://www.adafruit.com/product/3028>`_.
* 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
[docs]class RTCFeatherWing:
"""Class representing an `DS3231 Precision RTC FeatherWing
<https://www.adafruit.com/product/3028>`_.
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))
[docs] 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)
[docs] 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]
[docs] 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)
[docs] 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)