Skip to content

Commit dfa20cc

Browse files
authored
Merge pull request #27 from makermelissa/master
Added DS3231 RTC Featherwing
2 parents 57b7e81 + b4de44a commit dfa20cc

File tree

8 files changed

+356
-4
lines changed

8 files changed

+356
-4
lines changed

README.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ These drivers depends on:
2727
* `HT16K33 <https://github.com/adafruit/Adafruit_CircuitPython_HT16K33>`_
2828
* `DotStar <https://github.com/adafruit/Adafruit_CircuitPython_DotStar>`_
2929
* `NeoPixel <https://github.com/adafruit/Adafruit_CircuitPython_NeoPixel>`_
30+
* `DS3231 <https://github.com/adafruit/Adafruit_CircuitPython_DS3231>`_
3031

3132
Please ensure all dependencies are available on the CircuitPython filesystem.
3233
This is easily achieved by downloading
Lines changed: 303 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,303 @@
1+
# The MIT License (MIT)
2+
#
3+
# Copyright (c) 2019 Melissa LeBlanc-Williams for Adafruit Industries LLC
4+
#
5+
# Permission is hereby granted, free of charge, to any person obtaining a copy
6+
# of this software and associated documentation files (the "Software"), to deal
7+
# in the Software without restriction, including without limitation the rights
8+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
# copies of the Software, and to permit persons to whom the Software is
10+
# furnished to do so, subject to the following conditions:
11+
#
12+
# The above copyright notice and this permission notice shall be included in
13+
# all copies or substantial portions of the Software.
14+
#
15+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
# THE SOFTWARE.
22+
"""
23+
`adafruit_featherwing.rtc_featherwing`
24+
====================================================
25+
26+
Helper for using the `DS3231 Precision RTC FeatherWing
27+
<https://www.adafruit.com/product/3028>`_.
28+
29+
* Author(s): Melissa LeBlanc-Williams
30+
"""
31+
32+
__version__ = "0.0.0-auto.0"
33+
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_FeatherWing.git"
34+
35+
import time
36+
from collections import namedtuple
37+
import adafruit_ds3231
38+
from adafruit_featherwing import shared
39+
40+
class RTCFeatherWing:
41+
"""Class representing an `DS3231 Precision RTC FeatherWing
42+
<https://www.adafruit.com/product/3028>`_.
43+
44+
Automatically uses the feather's I2C bus."""
45+
def __init__(self):
46+
self._rtc = adafruit_ds3231.DS3231(shared.I2C_BUS)
47+
48+
def __setitem__(self, index, value):
49+
"""
50+
Allow updates using setitem if that makes it easier
51+
"""
52+
self._set_time_value(index, value)
53+
54+
def __getitem__(self, index):
55+
"""
56+
Allow retrievals using getitem if that makes it easier
57+
"""
58+
return self._get_time_value(index)
59+
60+
def _set_time_value(self, unit, value):
61+
"""
62+
Set just the specific unit of time
63+
"""
64+
now = self._get_now()
65+
if unit in now:
66+
now[unit] = value
67+
else:
68+
raise ValueError('The specified unit of time is invalid')
69+
70+
self._rtc.datetime = self._encode(now)
71+
72+
def _get_time_value(self, unit):
73+
"""
74+
Get just the specific unit of time
75+
"""
76+
now = self._get_now()
77+
if unit in now:
78+
return now[unit]
79+
else:
80+
raise ValueError('The specified unit of time is invalid')
81+
82+
def _get_now(self):
83+
"""
84+
Return the current date and time in a nice updatable dictionary
85+
"""
86+
now = self._rtc.datetime
87+
return {'second': now.tm_sec, 'minute': now.tm_min, 'hour': now.tm_hour, 'day': now.tm_mday,
88+
'month': now.tm_mon, 'year': now.tm_year, 'weekday': now.tm_wday}
89+
90+
def _encode(self, date):
91+
"""
92+
Encode the updatable dictionary back into a time struct
93+
"""
94+
now = self._rtc.datetime
95+
return time.struct_time((date['year'], date['month'], date['day'], date['hour'],
96+
date['minute'], date['second'], date['weekday'], now.tm_yday,
97+
now.tm_isdst))
98+
99+
def is_leap_year(self, year=None):
100+
"""
101+
Check if the year is a leap year
102+
103+
:param int year: (Optional) The year to check. If none is provided, current year is used.
104+
"""
105+
if year is None:
106+
year = self._get_time_value('year')
107+
return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
108+
109+
def get_month_days(self, month=None, year=None):
110+
"""
111+
Return the number of days for the month of the given year
112+
113+
:param int month: (Optional) The month to use. If none is provided, current month is used.
114+
:param int year: (Optional) The year to check. If none is provided, current year is used.
115+
"""
116+
if month is None:
117+
month = self._get_time_value('month')
118+
leap_year = self.is_leap_year(year)
119+
max_days = (31, 29 if leap_year else 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
120+
return max_days[month - 1]
121+
122+
def set_time(self, hour, minute, second=0):
123+
"""
124+
Set the time only
125+
126+
:param int hour: The hour we want to set the time to
127+
:param int minute: The minute we want to set the time to
128+
:param int second: (Optional) The second we want to set the time to (default=0)
129+
"""
130+
if not isinstance(second, int) or not 0 <= second < 60:
131+
raise ValueError('The second must be an integer in the range of 0-59')
132+
133+
if not isinstance(minute, int) or not 0 <= minute < 60:
134+
raise ValueError('The minute must be an integer in the range of 0-59')
135+
136+
if not isinstance(hour, int) or not 0 <= hour < 24:
137+
raise ValueError('The hour must be an integer in the range of 0-23')
138+
139+
now = self._get_now()
140+
now['hour'] = hour
141+
now['minute'] = minute
142+
now['second'] = second
143+
self._rtc.datetime = self._encode(now)
144+
145+
def set_date(self, day, month, year):
146+
"""
147+
Set the date only
148+
149+
:param int day: The day we want to set the date to
150+
:param int month: The month we want to set the date to
151+
:param int year: The year we want to set the date to
152+
"""
153+
if not isinstance(year, int):
154+
raise ValueError('The year must be an integer')
155+
156+
if not isinstance(month, int) or not 1 <= month <= 12:
157+
raise ValueError('The month must be an integer in the range of 1-12')
158+
159+
month_days = self.get_month_days(month, year)
160+
if not isinstance(day, int) or not 1 <= day <= month_days:
161+
raise ValueError('The day must be an integer in the range of 1-{}'.format(month_days))
162+
163+
now = self._get_now()
164+
now['day'] = day
165+
now['month'] = month
166+
now['year'] = year
167+
self._rtc.datetime = self._encode(now)
168+
169+
@property
170+
def datetime(self):
171+
"""
172+
Passthru property to the ds3231 library for compatibility
173+
"""
174+
return self._rtc.datetime
175+
176+
@datetime.setter
177+
def datetime(self, datetime):
178+
self._rtc.datetime = datetime
179+
180+
@property
181+
def year(self):
182+
"""
183+
The Current Year
184+
"""
185+
return self._get_time_value('year')
186+
187+
@year.setter
188+
def year(self, year):
189+
if isinstance(year, int):
190+
self._set_time_value('year', year)
191+
else:
192+
raise ValueError('The year must be an integer')
193+
194+
@property
195+
def month(self):
196+
"""
197+
The Current Month
198+
"""
199+
return self._get_time_value('month')
200+
201+
@month.setter
202+
def month(self, month):
203+
if isinstance(month, int) and 1 <= month <= 12:
204+
self._set_time_value('month', month)
205+
else:
206+
raise ValueError('The month must be an integer in the range of 1-12')
207+
208+
@property
209+
def day(self):
210+
"""
211+
The Current Day
212+
"""
213+
return self._get_time_value('day')
214+
215+
@day.setter
216+
def day(self, day):
217+
month_days = self.get_month_days()
218+
if isinstance(day, int) and 1 <= day <= month_days:
219+
self._set_time_value('day', day)
220+
else:
221+
raise ValueError('The day must be an integer in the range of 1-{}'.format(month_days))
222+
223+
@property
224+
def hour(self):
225+
"""
226+
The Current Hour
227+
"""
228+
return self._get_time_value('hour')
229+
230+
@hour.setter
231+
def hour(self, hour):
232+
if isinstance(hour, int) and 0 <= hour < 24:
233+
self._set_time_value('hour', hour)
234+
else:
235+
raise ValueError('The hour must be an integer in the range of 0-23')
236+
237+
@property
238+
def minute(self):
239+
"""
240+
The Current Minute
241+
"""
242+
return self._get_time_value('minute')
243+
244+
@minute.setter
245+
def minute(self, minute):
246+
if isinstance(minute, int) and 0 <= minute < 60:
247+
self._set_time_value('minute', minute)
248+
else:
249+
raise ValueError('The minute must be an integer in the range of 0-59')
250+
251+
@property
252+
def second(self):
253+
"""
254+
The Current Second
255+
"""
256+
return self._get_time_value('second')
257+
258+
@second.setter
259+
def second(self, second):
260+
if isinstance(second, int) and 0 <= second < 60:
261+
self._set_time_value('second', second)
262+
else:
263+
raise ValueError('The second must be an integer in the range of 0-59')
264+
265+
@property
266+
def weekday(self):
267+
"""
268+
The Current Day of the Week Value (0-6) where Sunday is 0
269+
"""
270+
return self._get_time_value('weekday')
271+
272+
@weekday.setter
273+
def weekday(self, weekday):
274+
if isinstance(weekday, int) and 0 <= weekday < 7:
275+
self._set_time_value('weekday', weekday)
276+
else:
277+
raise ValueError('The weekday must be an integer in the range of 0-6')
278+
279+
@property
280+
def now(self):
281+
"""
282+
The Current Date and Time in Named Tuple Style (Read Only)
283+
"""
284+
date_time = namedtuple("DateTime", "second minute hour day month year weekday")
285+
return date_time(**self._get_now())
286+
287+
@property
288+
def unixtime(self):
289+
"""
290+
The Current Date and Time in Unix Time
291+
"""
292+
try:
293+
return time.mktime(self._rtc.datetime)
294+
except (AttributeError, RuntimeError) as error:
295+
print("Error attempting to run time.mktime() on this board\n", error)
296+
297+
@unixtime.setter
298+
def unixtime(self, unixtime):
299+
if isinstance(unixtime, int):
300+
try:
301+
self._rtc.datetime = time.localtime(unixtime)
302+
except (AttributeError, RuntimeError) as error:
303+
print("Error attempting to run time.localtime() on this board\n", error)

docs/api.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,6 @@
1515

1616
.. automodule:: adafruit_featherwing.neopixel_featherwing
1717
:members:
18+
19+
.. automodule:: adafruit_featherwing.rtc_featherwing
20+
:members:

docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
master_doc = 'index'
3636

3737
# General information about the project.
38-
project = u'Adafruit featherwing Library'
38+
project = u'Adafruit FeatherWing Library'
3939
copyright = u'2017 Scott Shawcroft'
4040
author = u'Scott Shawcroft'
4141

docs/examples.rst

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,11 @@ Ensure your device works with this simple test.
2727
:caption: examples/featherwing_sevensegment_simpletest.py
2828
:linenos:
2929

30-
Other tests
30+
.. literalinclude:: ../examples/featherwing_rtc_simpletest.py
31+
:caption: examples/featherwing_rtc_simpletest.py
32+
:linenos:
33+
34+
Other Tests
3135
------------
3236

3337
.. literalinclude:: ../examples/featherwing_dotstar_palette_example.py
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
"""
2+
This example will allow you to set the date and time
3+
and then loop through and display the current time
4+
"""
5+
import time
6+
from adafruit_featherwing import rtc_featherwing
7+
8+
days = ("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday")
9+
10+
# Create the RTC instance:
11+
rtc = rtc_featherwing.RTCFeatherWing()
12+
13+
#pylint: disable-msg=using-constant-test
14+
if True: # Change this to True to set the date and time
15+
rtc.set_time(13, 34) # Set the time (seconds are optional)
16+
print(rtc.now)
17+
rtc.set_date(16, 1, 2016) # Set the date
18+
print(rtc.now)
19+
rtc.year = 2019 # Set just the Year
20+
print(rtc.now)
21+
rtc.month = 2 # Set Just the Month
22+
print(rtc.now)
23+
rtc.hour = 16 # Set just the hour
24+
print(rtc.now)
25+
rtc.weekday = 6 # Set just the day of the week (Sunday = 0)
26+
print(rtc.now)
27+
rtc.unixtime = 1550335257 # Or set the date and time with a unix timestamp
28+
29+
# Main loop:
30+
while True:
31+
now = rtc.now
32+
print("The date is {} {}/{}/{}".format(days[now.weekday], now.day, now.month, now.year))
33+
print("The time is {}:{:02}:{:02}".format(now.hour, now.minute, now.second))
34+
print("The UNIX timestamp is {}".format(rtc.unixtime))
35+
print("The number of days in the current month is {}".format(rtc.get_month_days()))
36+
if rtc.is_leap_year():
37+
print("This year is a leap year")
38+
else:
39+
print("This year is not a leap year")
40+
time.sleep(1) # wait a second

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@ adafruit-circuitpython-seesaw
66
adafruit-circuitpython-ht16k33
77
adafruit-circuitpython-dotstar
88
adafruit-circuitpython-neopixel
9-
9+
adafruit-circuitpython-ds3231

setup.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@
3838
install_requires=['Adafruit-Blinka', 'adafruit-circuitpython-busdevice',
3939
'adafruit-circuitpython-register', 'adafruit-circuitpython-ina219',
4040
'adafruit-circuitpython-seesaw', 'adafruit-circuitpython-ht16k33',
41-
'adafruit-circuitpython-dotstar', 'adafruit-circuitpython-neopixel'],
41+
'adafruit-circuitpython-dotstar', 'adafruit-circuitpython-neopixel',
42+
'adafruit-circuitpython-ds3231'],
4243

4344
# Choose your license
4445
license='MIT',

0 commit comments

Comments
 (0)