Skip to content

Commit 62bc0f0

Browse files
committed
Prior to subclass branch.
1 parent e28603b commit 62bc0f0

File tree

6 files changed

+157
-151
lines changed

6 files changed

+157
-151
lines changed

gps/README.md

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
** WARNING: Under development and subject to change **
2+
13
# 1. as_GPS
24

35
This is an asynchronous device driver for GPS devices which communicate with
@@ -216,7 +218,8 @@ gps = as_GPS.AS_GPS(sreader, fix_cb=callback, cb_mask= as_GPS.RMC | as_GPS.VTG)
216218
`as_GPS.DMY` returns 'DD/MM/YY'.
217219
`as_GPS.LONG` returns a string of form 'January 1st, 2014'.
218220

219-
* `time` No args. Returns the current time in form 'hh:mm:ss'.
221+
* `time_string` Arg `local` default `True`. Returns the current time in form
222+
'hh:mm:ss.sss'. If `local` is `False` returns UTC time.
220223

221224
## 2.3 Public coroutines
222225

@@ -262,7 +265,7 @@ Note that if the GPS module does not support producing GSV sentences this
262265
coroutine will pause forever. It can also pause for arbitrary periods if
263266
satellite reception is blocked, such as in a building.
264267

265-
## 2.4 Public bound variables
268+
## 2.4 Public bound variables/properties
266269

267270
These are updated whenever a sentence of the relevant type has been correctly
268271
received from the GPS unit. For crucial navigation data the `time_since_fix`
@@ -291,18 +294,18 @@ The following are counts since instantiation.
291294

292295
### 2.4.3 Date and time
293296

294-
* `utc` [hrs: int, mins: int, secs: float] UTC time e.g. [23, 3, 58.0]. Note
297+
* `utc` [hrs: int, mins: int, secs: int] UTC time e.g. [23, 3, 58]. Note
295298
that some GPS hardware may only provide integer seconds. The MTK3339 chip
296-
provides a float.
297-
* `local_time` [hrs: int, mins: int, secs: float] Local time.
299+
provides a float whose value is always an integer.
300+
* `local_time` [hrs: int, mins: int, secs: int] Local time.
298301
* `date` [day: int, month: int, year: int] e.g. [23, 3, 18]
299302
* `local_offset` Local time offset in hrs as specified to constructor.
300303

301304
The `utc` bound variable updates on receipt of RMC, GLL or GGA messages.
302305

303306
The `date` and `local_time` variables are updated when an RMC message is
304-
received. A local time offset will result in date changes where the time
305-
offset causes the local time to pass midnight.
307+
received. A local time offset will affect the `date` value where the offset
308+
causes the local time to pass midnight.
306309

307310
### 2.4.4 Satellite data
308311

@@ -504,13 +507,15 @@ This takes the following arguments:
504507

505508
## 4.3 Public methods
506509

507-
These return immediately. Times are derived from the GPS PPS signal. These
508-
functions should not be called until a valid time/date message and PPS signal
509-
have occurred: await the `ready` coroutine prior to first use.
510+
These return an accurate GPS time of day. As such they return as fast as
511+
possible without error checking: these functions should not be called until a
512+
valid time/date message and PPS signal have occurred. Await the `ready`
513+
coroutine prior to first use. Subsequent calls may occur without restriction.
510514

511-
* `get_secs` No args. Returns a float: the period past midnight in seconds.
515+
* `get_ms` No args. Returns an integer: the period past midnight in ms. This
516+
method does not allocate and may be called in an interrupt context.
512517
* `get_t_split` No args. Returns time of day tuple of form
513-
(hrs: int, mins: int, secs: float).
518+
(hrs: int, mins: int, secs: int, μs: int).
514519

515520
## 4.4 Public coroutines
516521

gps/as_GPS.py

Lines changed: 49 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,7 @@ class AS_GPS(object):
6262
# https://stackoverflow.com/questions/9847213/how-do-i-get-the-day-of-week-given-a-date-in-python?noredirect=1&lq=1
6363
# Adapted for Python 3 and Pyboard RTC format.
6464
@staticmethod
65-
def _week_day(year, month, day):
66-
offset = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]
65+
def _week_day(year, month, day, offset = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]):
6766
aux = year - 1700 - (1 if month <= 2 else 0)
6867
# day_of_week for 1700/1/1 = 5, Friday
6968
day_of_week = 5
@@ -137,12 +136,13 @@ def __init__(self, sreader, local_offset=0, fix_cb=lambda *_ : None, cb_mask=RMC
137136

138137
#####################
139138
# Data From Sentences
140-
# Time. Ignore http://www.gpsinformation.org/dale/nmea.htm, hardware
141-
# returns a float.
142-
self.utc = [0, 0, 0.0] # [h: int, m: int, s: float]
143-
self.local_time = [0, 0, 0.0] # [h: int, m: int, s: float]
144-
self.date = [0, 0, 0] # [dd: int, mm: int, yy: int]
139+
# Time. http://www.gpsinformation.org/dale/nmea.htm indicates seconds
140+
# is an integer. However hardware returns a float, but the fractional
141+
# part is always zero. So treat seconds value as an integer. For
142+
# precise timing use PPS signal and as_tGPS library.
145143
self.local_offset = local_offset # hrs
144+
self._rtcbuf = [0]*8 # Buffer for RTC setting
145+
self.epoch_time = 0 # Integer secs since epoch (Y2K under MicroPython)
146146

147147
# Position/Motion
148148
self._latitude = [0, 0.0, 'N'] # (°, mins, N/S)
@@ -202,7 +202,6 @@ def _update(self, line):
202202
if not self._crc_check(line, segs[-1]):
203203
self.crc_fails += 1 # Update statistics
204204
return None
205-
206205
self.clean_sentences += 1 # Sentence is good but unparsed.
207206
segs[0] = segs[0][1:] # discard $
208207
segs = segs[:-1] # and checksum
@@ -258,47 +257,33 @@ def _fix(self, gps_segments, idx_lat, idx_long):
258257
self._fix_time = self._get_time()
259258
return True
260259

261-
# Set timestamp. If time/date not present retain last reading (if any).
262-
def _set_timestamp(self, utc_string):
263-
if not utc_string:
264-
return False
265-
# Possible timestamp found
266-
try:
267-
self.utc[0] = int(utc_string[0:2]) # h
268-
self.utc[1] = int(utc_string[2:4]) # mins
269-
self.utc[2] = float(utc_string[4:]) # secs from chip is a float
270-
return True
271-
except ValueError:
272-
return False
273-
for idx in range(3):
274-
self.local_time[idx] = self.utc[idx]
275-
return True
276-
277260
# A local offset may exist so check for date rollover. Local offsets can
278261
# include fractions of an hour but not seconds (AFAIK).
279-
def _set_date(self, date_string):
280-
if not date_string:
262+
def _set_date_time(self, utc_string, date_string):
263+
if not date_string or not utc_string:
281264
return False
282265
try:
266+
hrs = int(utc_string[0:2]) # h
267+
mins = int(utc_string[2:4]) # mins
268+
secs = int(utc_string[4:6]) # secs from chip is a float but FP is always 0
283269
d = int(date_string[0:2]) # day
284270
m = int(date_string[2:4]) # month
285271
y = int(date_string[4:6]) + 2000 # year
286-
except ValueError: # Bad Date stamp value present
272+
except ValueError: # Bad date or time strings
287273
return False
288-
hrs = self.utc[0]
289-
mins = self.utc[1]
290-
secs = self.utc[2]
291-
wday = self._week_day(y, m, d) - 1
292-
t = self._mktime((y, m, d, hrs, mins, int(secs), wday, 0, 0))
274+
wday = self._week_day(y, m, d)
275+
t = self._mktime((y, m, d, hrs, mins, int(secs), wday - 1, 0, 0))
276+
self.epoch_time = t # This is the fundamental datetime reference.
277+
# Need local time for setting Pyboard RTC in interrupt context
293278
t += int(3600 * self.local_offset)
294-
y, m, d, hrs, mins, *_ = self._localtime(t) # Preserve float seconds
295-
y -= 2000
296-
self.local_time[0] = hrs
297-
self.local_time[1] = mins
298-
self.local_time[2] = secs
299-
self.date[0] = d
300-
self.date[1] = m
301-
self.date[2] = y
279+
y, m, d, hrs, mins, secs, *_ = self._localtime(t)
280+
self._rtcbuf[0] = y
281+
self._rtcbuf[1] = m
282+
self._rtcbuf[2] = d
283+
self._rtcbuf[3] = wday
284+
self._rtcbuf[4] = hrs
285+
self._rtcbuf[5] = mins
286+
self._rtcbuf[6] = secs
302287
return True
303288

304289
########################################
@@ -313,10 +298,8 @@ def _set_date(self, date_string):
313298

314299
def _gprmc(self, gps_segments): # Parse RMC sentence
315300
self._valid &= ~RMC
316-
# UTC Timestamp.
317-
if not self._set_timestamp(gps_segments[1]):
318-
return False # Bad Timestamp value present
319-
if not self._set_date(gps_segments[9]):
301+
# UTC Timestamp and date.
302+
if not self._set_date_time(gps_segments[1], gps_segments[9]):
320303
return False
321304

322305
# Check Receiver Data Valid Flag
@@ -356,12 +339,6 @@ def _gprmc(self, gps_segments): # Parse RMC sentence
356339

357340
def _gpgll(self, gps_segments): # Parse GLL sentence
358341
self._valid &= ~GLL
359-
# UTC Timestamp.
360-
try:
361-
self._set_timestamp(gps_segments[5])
362-
except ValueError: # Bad Timestamp value present
363-
return False
364-
365342
# Check Receiver Data Valid Flag
366343
if gps_segments[6] != 'A': # Invalid. Don't update data
367344
return True # Correctly parsed
@@ -391,18 +368,12 @@ def _gpvtg(self, gps_segments): # Parse VTG sentence
391368
def _gpgga(self, gps_segments): # Parse GGA sentence
392369
self._valid &= ~GGA
393370
try:
394-
# UTC Timestamp
395-
self._set_timestamp(gps_segments[1])
396-
397371
# Number of Satellites in Use
398372
satellites_in_use = int(gps_segments[7])
399-
400373
# Horizontal Dilution of Precision
401374
hdop = float(gps_segments[8])
402-
403375
# Get Fix Status
404376
fix_stat = int(gps_segments[6])
405-
406377
except ValueError:
407378
return False
408379

@@ -647,9 +618,28 @@ def speed_string(self, unit=KPH):
647618
return sform.format(speed, 'knots')
648619
return sform.format(speed, 'km/h')
649620

650-
def time(self, local=True):
651-
t = self.local_time if local else self.utc
652-
return '{:02d}:{:02d}:{:06.3f}'.format(*t)
621+
# Return local time (hrs: int, mins: int, secs:float)
622+
@property
623+
def local_time(self):
624+
t = self.epoch_time + int(3600 * self.local_offset)
625+
_, _, _, hrs, mins, secs, *_ = self._localtime(t)
626+
return hrs, mins, secs
627+
628+
@property
629+
def date(self):
630+
t = self.epoch_time + int(3600 * self.local_offset)
631+
y, m, d, *_ = self._localtime(t)
632+
return d, m, y - 2000
633+
634+
@property
635+
def utc(self):
636+
t = self.epoch_time + int(3600 * self.local_offset)
637+
_, _, _, hrs, mins, secs, *_ = self._localtime(t)
638+
return hrs, mins, secs
639+
640+
def time_string(self, local=True):
641+
hrs, mins, secs = self.local_time if local else self.utc
642+
return '{:02d}:{:02d}:{:02d}'.format(hrs, mins, secs)
653643

654644
def date_string(self, formatting=MDY):
655645
day, month, year = self.date

gps/as_GPS_time.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
print('Available tests:')
1414
print('calibrate(minutes=5) Set and calibrate the RTC.')
1515
print('drift(minutes=5) Repeatedly print the difference between RTC and GPS time.')
16+
print('time(minutes=1) Print get_ms() and get_t_split values.')
1617

1718
# Setup for tests. Red LED toggles on fix, blue on PPS interrupt.
1819
async def setup():
@@ -35,7 +36,7 @@ async def drift_test(gps_tim):
3536
dstart = await gps_tim.delta()
3637
while running:
3738
dt = await gps_tim.delta()
38-
print('{} Delta {}μs'.format(gps_tim.gps.time(), dt))
39+
print('{} Delta {}μs'.format(gps_tim.gps.time_string(), dt))
3940
await asyncio.sleep(10)
4041
return dt - dstart
4142

@@ -58,11 +59,33 @@ async def do_drift(minutes):
5859
await gps_tim.set_rtc()
5960
print('Measuring drift.')
6061
change = await drift_test(gps_tim)
61-
print('Rate of change {}μs/hr'.format(int(60 * change/minutes)))
62+
ush = int(60 * change/minutes)
63+
spa = int(ush * 365 * 24 / 1000000)
64+
print('Rate of change {}μs/hr {}secs/year'.format(ush, spa))
6265

6366
def drift(minutes=5):
6467
global running
6568
running = True
6669
loop = asyncio.get_event_loop()
6770
loop.create_task(killer(minutes))
6871
loop.run_until_complete(do_drift(minutes))
72+
73+
# Every 10s print the difference between GPS time and RTC time
74+
async def do_time(minutes):
75+
print('Setting up GPS.')
76+
gps_tim = await setup()
77+
print('Waiting for time data.')
78+
await gps_tim.ready()
79+
print('Setting RTC.')
80+
await gps_tim.set_rtc()
81+
while running:
82+
await asyncio.sleep(1)
83+
hrs, mins, secs, us = gps_tim.get_t_split()
84+
print('{}ms Time: {:02d}:{:02d}:{:02d}:{:06d}'.format(gps_tim.get_ms(), hrs, mins, secs, us))
85+
86+
def time(minutes=1):
87+
global running
88+
running = True
89+
loop = asyncio.get_event_loop()
90+
loop.create_task(killer(minutes))
91+
loop.run_until_complete(do_time(minutes))

0 commit comments

Comments
 (0)