wwvb 4.0.0__py3-none-any.whl → 4.1.0a0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- leapseconddata/__init__.py +342 -0
- leapseconddata/__main__.py +169 -0
- {wwvb → leapseconddata}/__version__.py +2 -2
- wwvb-4.1.0a0.dist-info/METADATA +60 -0
- wwvb-4.1.0a0.dist-info/RECORD +9 -0
- wwvb-4.1.0a0.dist-info/entry_points.txt +2 -0
- wwvb-4.1.0a0.dist-info/top_level.txt +1 -0
- uwwvb.py +0 -193
- wwvb/__init__.py +0 -935
- wwvb/decode.py +0 -90
- wwvb/dut1table.py +0 -32
- wwvb/gen.py +0 -128
- wwvb/iersdata.py +0 -28
- wwvb/iersdata_dist.py +0 -38
- wwvb/testcli.py +0 -291
- wwvb/testdaylight.py +0 -60
- wwvb/testls.py +0 -63
- wwvb/testpm.py +0 -33
- wwvb/testuwwvb.py +0 -221
- wwvb/testwwvb.py +0 -403
- wwvb/tz.py +0 -13
- wwvb/updateiers.py +0 -199
- wwvb/wwvbtk.py +0 -144
- wwvb-4.0.0.dist-info/METADATA +0 -199
- wwvb-4.0.0.dist-info/RECORD +0 -23
- wwvb-4.0.0.dist-info/entry_points.txt +0 -8
- wwvb-4.0.0.dist-info/top_level.txt +0 -2
- {wwvb → leapseconddata}/py.typed +0 -0
- {wwvb-4.0.0.dist-info → wwvb-4.1.0a0.dist-info}/WHEEL +0 -0
uwwvb.py
DELETED
@@ -1,193 +0,0 @@
|
|
1
|
-
# SPDX-FileCopyrightText: 2021 Jeff Epler
|
2
|
-
#
|
3
|
-
# SPDX-License-Identifier: GPL-3.0-only
|
4
|
-
|
5
|
-
# ruff: noqa: C405 PYI024 PLR2004 FBT001 FBT002
|
6
|
-
|
7
|
-
"""Implementation of a WWVB state machine & decoder for resource-constrained systems"""
|
8
|
-
|
9
|
-
from __future__ import annotations
|
10
|
-
|
11
|
-
from collections import namedtuple
|
12
|
-
|
13
|
-
import adafruit_datetime as datetime
|
14
|
-
|
15
|
-
ZERO, ONE, MARK = range(3)
|
16
|
-
|
17
|
-
always_mark = set((0, 9, 19, 29, 39, 49, 59))
|
18
|
-
always_zero = set((4, 10, 11, 14, 20, 21, 34, 35, 44, 54))
|
19
|
-
bcd_weights = (1, 2, 4, 8, 10, 20, 40, 80, 100, 200, 400, 800)
|
20
|
-
|
21
|
-
WWVBMinute = namedtuple("WWVBMinute", ["year", "days", "hour", "minute", "dst", "ut1", "ls", "ly"])
|
22
|
-
|
23
|
-
|
24
|
-
class WWVBDecoder:
|
25
|
-
"""A state machine for receiving WWVB timecodes."""
|
26
|
-
|
27
|
-
def __init__(self) -> None:
|
28
|
-
"""Construct a WWVBDecoder"""
|
29
|
-
self.minute: list[int] = []
|
30
|
-
self.state = 1
|
31
|
-
|
32
|
-
def update(self, value: int) -> list[int] | None:
|
33
|
-
"""Update the _state machine when a new symbol is received.
|
34
|
-
|
35
|
-
If a possible complete _minute is received, return it; otherwise, return None
|
36
|
-
"""
|
37
|
-
result = None
|
38
|
-
if self.state == 1:
|
39
|
-
self.minute = []
|
40
|
-
if value == MARK:
|
41
|
-
self.state = 2
|
42
|
-
|
43
|
-
elif self.state == 2:
|
44
|
-
if value == MARK:
|
45
|
-
self.state = 3
|
46
|
-
else:
|
47
|
-
self.state = 1
|
48
|
-
|
49
|
-
elif self.state == 3:
|
50
|
-
if value != MARK:
|
51
|
-
self.minute = [MARK, value]
|
52
|
-
self.state = 4
|
53
|
-
|
54
|
-
else: # self.state == 4:
|
55
|
-
idx = len(self.minute)
|
56
|
-
self.minute.append(value)
|
57
|
-
if (idx in always_mark) != (value == MARK):
|
58
|
-
self.state = 3 if self.minute[-2] == MARK else 2
|
59
|
-
elif idx in always_zero and value != ZERO:
|
60
|
-
self.state = 1
|
61
|
-
|
62
|
-
elif idx == 59:
|
63
|
-
result = self.minute
|
64
|
-
self.minute = []
|
65
|
-
self.state = 2
|
66
|
-
|
67
|
-
return result
|
68
|
-
|
69
|
-
def __str__(self) -> str:
|
70
|
-
"""Return a string representation of self"""
|
71
|
-
return f"<WWVBDecoder {self.state} {self.minute}>"
|
72
|
-
|
73
|
-
|
74
|
-
def get_am_bcd(seq: list[int], *poslist: int) -> int | None:
|
75
|
-
"""Convert the bits seq[positions[0]], ... seq[positions[len(positions-1)]] [in MSB order] from BCD to decimal"""
|
76
|
-
pos = list(poslist)[::-1]
|
77
|
-
val = [int(seq[p]) for p in pos]
|
78
|
-
while len(val) % 4 != 0:
|
79
|
-
val.append(0)
|
80
|
-
result = 0
|
81
|
-
base = 1
|
82
|
-
for i in range(0, len(val), 4):
|
83
|
-
digit = 0
|
84
|
-
for j in range(4):
|
85
|
-
digit += 1 << j if val[i + j] else 0
|
86
|
-
if digit > 9:
|
87
|
-
return None
|
88
|
-
result += digit * base
|
89
|
-
base *= 10
|
90
|
-
return result
|
91
|
-
|
92
|
-
|
93
|
-
def decode_wwvb(
|
94
|
-
t: list[int] | None,
|
95
|
-
) -> WWVBMinute | None:
|
96
|
-
"""Convert a received minute of wwvb symbols to a WWVBMinute. Returns None if any error is detected."""
|
97
|
-
if not t:
|
98
|
-
return None
|
99
|
-
if not all(t[i] == MARK for i in always_mark):
|
100
|
-
return None
|
101
|
-
if not all(t[i] == ZERO for i in always_zero):
|
102
|
-
return None
|
103
|
-
# Checking redundant DUT1 sign bits
|
104
|
-
if t[36] == t[37]:
|
105
|
-
return None
|
106
|
-
if t[36] != t[38]:
|
107
|
-
return None
|
108
|
-
minute = get_am_bcd(t, 1, 2, 3, 5, 6, 7, 8)
|
109
|
-
if minute is None:
|
110
|
-
return None
|
111
|
-
|
112
|
-
hour = get_am_bcd(t, 12, 13, 15, 16, 17, 18)
|
113
|
-
if hour is None:
|
114
|
-
return None
|
115
|
-
|
116
|
-
days = get_am_bcd(t, 22, 23, 25, 26, 27, 28, 30, 31, 32, 33)
|
117
|
-
if days is None:
|
118
|
-
return None
|
119
|
-
|
120
|
-
abs_ut1 = get_am_bcd(t, 40, 41, 42, 43)
|
121
|
-
if abs_ut1 is None:
|
122
|
-
return None
|
123
|
-
|
124
|
-
abs_ut1 *= 100
|
125
|
-
ut1_sign = t[38]
|
126
|
-
ut1 = abs_ut1 if ut1_sign else -abs_ut1
|
127
|
-
year = get_am_bcd(t, 45, 46, 47, 48, 50, 51, 52, 53)
|
128
|
-
if year is None:
|
129
|
-
return None
|
130
|
-
|
131
|
-
is_ly = t[55]
|
132
|
-
if days > 366 or (not is_ly and days > 365):
|
133
|
-
return None
|
134
|
-
ls = t[56]
|
135
|
-
dst = get_am_bcd(t, 57, 58)
|
136
|
-
assert dst is not None # No possibility of BCD decode error in 2 bits
|
137
|
-
|
138
|
-
return WWVBMinute(year, days, hour, minute, dst, ut1, ls, is_ly)
|
139
|
-
|
140
|
-
|
141
|
-
def as_datetime_utc(decoded_timestamp: WWVBMinute) -> datetime.datetime:
|
142
|
-
"""Convert a WWVBMinute to a UTC datetime"""
|
143
|
-
d = datetime.datetime(decoded_timestamp.year + 2000, 1, 1)
|
144
|
-
d += datetime.timedelta(
|
145
|
-
decoded_timestamp.days - 1,
|
146
|
-
decoded_timestamp.hour * 3600 + decoded_timestamp.minute * 60,
|
147
|
-
)
|
148
|
-
return d
|
149
|
-
|
150
|
-
|
151
|
-
def is_dst(
|
152
|
-
dt: datetime.datetime,
|
153
|
-
dst_bits: int,
|
154
|
-
standard_time_offset: int = 7 * 3600,
|
155
|
-
dst_observed: bool = True,
|
156
|
-
) -> bool:
|
157
|
-
"""Return True iff DST is observed at the given moment"""
|
158
|
-
d = dt - datetime.timedelta(seconds=standard_time_offset)
|
159
|
-
if not dst_observed:
|
160
|
-
return False
|
161
|
-
if dst_bits == 0b10:
|
162
|
-
transition_time = dt.replace(hour=2)
|
163
|
-
return d >= transition_time
|
164
|
-
if dst_bits == 0b11:
|
165
|
-
return True
|
166
|
-
if dst_bits == 0b01:
|
167
|
-
# DST ends at 2AM *DST* which is 1AM *standard*
|
168
|
-
transition_time = dt.replace(hour=1)
|
169
|
-
return d < transition_time
|
170
|
-
return False
|
171
|
-
|
172
|
-
|
173
|
-
def apply_dst(
|
174
|
-
dt: datetime.datetime,
|
175
|
-
dst_bits: int,
|
176
|
-
standard_time_offset: int = 7 * 3600,
|
177
|
-
dst_observed: bool = True,
|
178
|
-
) -> datetime.datetime:
|
179
|
-
"""Apply time zone and DST (if applicable) to the given moment"""
|
180
|
-
d = dt - datetime.timedelta(seconds=standard_time_offset)
|
181
|
-
if is_dst(dt, dst_bits, standard_time_offset, dst_observed):
|
182
|
-
d += datetime.timedelta(seconds=3600)
|
183
|
-
return d
|
184
|
-
|
185
|
-
|
186
|
-
def as_datetime_local(
|
187
|
-
decoded_timestamp: WWVBMinute,
|
188
|
-
standard_time_offset: int = 7 * 3600,
|
189
|
-
dst_observed: bool = True,
|
190
|
-
) -> datetime.datetime:
|
191
|
-
"""Convert a WWVBMinute to a local datetime with tzinfo=None"""
|
192
|
-
dt = as_datetime_utc(decoded_timestamp)
|
193
|
-
return apply_dst(dt, decoded_timestamp.dst, standard_time_offset, dst_observed)
|