-
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathexternal_rtc.py
124 lines (96 loc) · 3.96 KB
/
external_rtc.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
"""
Abstraction of an external hardware real-time clock (RTC).
"""
from time import struct_time
import adafruit_datetime
import adafruit_requests
import os
import adafruit_pcf8523.pcf8523
from adafruit_datetime import datetime
from busio import I2C
from util import Util, I2CDeviceAutoSelector
# noinspection PyBroadException
try:
from typing import Optional
except:
pass
class ExternalRTC:
"""
Abstraction around a real time clock (RTC) peripheral separate from the system's default one. Right now only
supports a PFC8523 connected via I2C.
"""
def __init__(self, i2c: I2C):
"""
Be sure to set the offline_state attribute to an instance of OfflineState after construction.
:param i2c: I2C bus that has the PCF8523 device on it
"""
self.device = adafruit_pcf8523.pcf8523.PCF8523(i2c)
from offline_state import OfflineState
self.offline_state: Optional[OfflineState] = None
def sync(self, requests: adafruit_requests.Session) -> None:
"""
Updates the RTC and stores the last updated time in the offline state.
:param requests: Session to use for sending the HTTP request to adafruit.io
"""
if not self.offline_state:
raise RuntimeError("Must set offline_state before syncing")
print("Updating RTC...", end = "")
username = os.getenv("ADAFRUIT_AIO_USERNAME")
api_key = os.getenv("ADAFRUIT_AIO_KEY")
if not username or not api_key:
raise ValueError("adafruit.io username or key not defined in settings.toml")
response = requests.get(f"https://io.adafruit.com/api/v2/{username}/integrations/time/clock?x-aio-key={api_key}")
now = Util.to_datetime(response.text)
self.offline_state.rtc_utc_offset = (now.utcoffset().seconds / 60 / 60) - 24
while self.offline_state.rtc_utc_offset >= 24:
self.offline_state.rtc_utc_offset -= 24
while self.offline_state.rtc_utc_offset <= -24:
self.offline_state.rtc_utc_offset += 24
self.device.datetime = struct_time((
now.year,
now.month,
now.day,
now.hour,
now.minute,
now.second,
now.weekday(),
-1,
-1
))
self.offline_state.last_rtc_set = now
self.offline_state.to_sdcard()
print(f"set to {self.device.datetime}, UTC offset {self.offline_state.rtc_utc_offset}")
def now(self) -> Optional[datetime]:
"""
Gets the current date/time from the RTC, or None if the RTC is set to an implausible value.
:return: Current date/time or None if not available
"""
if not self.offline_state:
raise RuntimeError("Must set offline_state before getting time")
now = self.device.datetime
if now.tm_year < 2024 or now.tm_year > 2050:
print(f"RTC date/time is implausible because year is {now.tm_year}")
return None
if self.offline_state.rtc_utc_offset is None:
print("UTC offset not stored in offline set; RTC must be set")
return None
# noinspection PyUnresolvedReferences
tz = adafruit_datetime.timezone.utc
tz._offset = adafruit_datetime.timedelta(seconds = int(self.offline_state.rtc_utc_offset * 60 * 60))
return datetime(
year = now.tm_year,
month = now.tm_mon,
day = now.tm_mday,
hour = now.tm_hour,
minute = now.tm_min,
second = now.tm_sec
).replace(tzinfo = tz)
@staticmethod
def exists(i2c: I2C) -> bool:
"""
Checks if an RTC exists on the I2C bus. More formally right now: checks if the I2C bus has a device on address
0x68 because that's what a PCF8523 uses.
:param i2c: I2C bus to check
:return: True if a compatible RTC was found, False if not
"""
return I2CDeviceAutoSelector(i2c).address_exists(0x68)