adafruit / Adafruit_CircuitPython_GPS

GPS parsing module for CircuitPython. Meant to parse NMEA data from serial GPS modules.
MIT License
75 stars 57 forks source link

Fix Timestamp not working on GPS Featherwing #84

Open DJDevon3 opened 2 years ago

DJDevon3 commented 2 years ago

https://github.com/adafruit/Adafruit_CircuitPython_GPS/blob/583fbd01ef7a85859f8bde51a8e83a0c86fe52c0/examples/gps_simpletest.py#L74

Fix Timestamp isn't working for me. Always remains the same timestamp. Using this example with Ultimate GPS Featherwing on nrf52840 board on UART. Didn't change anything in the example, just ran it, everything else seems to work fine.

rtc.set_time_source(gps) doesn't seem to work correctly on the featherwing or at least it doesn't set the correct date. the current code seems to be a mashup of Fix + RTC to produce a UTC timestamp and still lacks timezone and DST offset capability. not as user friendly as it could be to hit the ground running with the gps featherwing.

The Fix timestamp time loads once and never updates. RTC timestamp is only initializing the date, not updated with GPS, fixed at 01/02/2000 (set at line 428 in adafruit_gps.py)

 ===============================
Fix timestamp: 07/05/2022 02:39:02
RTC timestamp: 01/02/2000 07:30:08
Local time: 07/05/2022 02:39:02
===============================
Fix timestamp: 07/05/2022 02:39:02
RTC timestamp: 01/02/2000 07:31:09
Local time: 07/05/2022 02:39:02
===============================
Fix timestamp: 07/05/2022 02:39:02
RTC timestamp: 01/02/2000 07:32:10
Local time: 07/05/2022 02:39:02
===============================
Fix timestamp: 07/05/2022 02:39:02
RTC timestamp: 01/02/2000 07:33:12
Local time: 07/05/2022 02:39:02
===============================
Fix timestamp: 07/05/2022 02:39:02
RTC timestamp: 01/02/2000 07:34:13
Local time: 07/05/2022 02:39:02

The code is cobbled together oddly and doesn't account for DST or timezone. Here's some code I worked on that does work in properly setting the GPS Featherwing RTC date & time.

gps = adafruit_gps.GPS(uart, debug=False)
gps.send_command(b"PMTK314,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0")
gps.send_command(b"PMTK220,1000")
rtc.set_time_source(gps)
the_rtc = rtc.RTC()
# Sets RTC timestamp YYYY, MM, DD, HH, MM, SS, DOW, DOY, 
# Last variable is it DST? -1=Unknown, 0=No, 1=Yes
the_rtc.datetime = time.struct_time((2022, 07, 05, 02, 25, 00, 1, 365, 0))

Because struct_time is hooked into monotonic the RTC set begins when you save the file, not when code is executed in the script. It's a much better way to ensure the RTC time you want set is actually set when you hit ctrl+s. It then gets updated when it connects to GPS so the time keeping from file save to GPS handoff is seamless.

struct_time should be updated to keep the RTC as accurate as possible. Drift will occur but can be minimized from a lost power event by having GPS constantly updating and resetting struct_time.

===============================
Fix timestamp: 07/05/2022 06:27:29
RTC timestamp: 07/05/2022 02:28:02
Local time: 07/05/2022 06:27:29
===============================
Fix timestamp: 07/05/2022 06:27:29
RTC timestamp: 07/05/2022 02:29:03
Local time: 07/05/2022 06:27:29

RTC timestamp is now properly initialized and keeping time. That should be enough to start on whipping up an example that includes a DST and timezone offset.

_update_timestamp_utc on line 414 in adafruit_gps.py needs work, i don't think it's working like it should.

DJDevon3 commented 2 years ago

Neradoc came up with a fix that includes a timezone variable. Sets the RTC and can offset easily with a timezone variable. This is much easier to hit the ground running at least with a GPS featherwing. Can confirm this code works great. To change DST all you have to do is change the timezone variable either manually (or append a DST script).

# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries
# SPDX-License-Identifier: MIT

# Simple script using GPS timestamps as RTC time source
# The GPS timestamps are available without a fix and keep the track of
# time while there is powersource (ie coin cell battery)

import time
import board
import busio
import rtc
import adafruit_gps

TIMEZONE = -4

uart = busio.UART(board.TX, board.RX, baudrate=9600, timeout=10)
# i2c = busio.I2C(board.SCL, board.SDA)

gps = adafruit_gps.GPS(uart, debug=False)
# gps = adafruit_gps.GPS_GtopI2C(i2c, debug=False)  # Use I2C interface

gps.send_command(b"PMTK314,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0")
gps.send_command(b"PMTK220,1000")

the_rtc = rtc.RTC()

def _format_datetime(datetime):
    return "{:02}/{:02}/{} {:02}:{:02}:{:02}".format(
        datetime.tm_mon,
        datetime.tm_mday,
        datetime.tm_year,
        datetime.tm_hour,
        datetime.tm_min,
        datetime.tm_sec,
    )

had_a_fix = False
last_print = time.monotonic()
while True:
    gps.update()

    if gps.timestamp_utc and not had_a_fix:
        print("Setting the time")
        the_rtc.datetime = gps.datetime
        the_rtc.datetime = time.localtime(time.time() + TIMEZONE * 3600)
        had_a_fix = True

    # Every second print out current time from GPS, RTC and time.localtime()
    current = time.monotonic()
    if current - last_print >= 1.0:
        last_print = current

        if not gps.timestamp_utc:
            print("Connecting to GPS...")
            continue
        print("==========================")
        # Time & date from GPS informations
        print("GPS timestamp: {}".format(_format_datetime(gps.timestamp_utc)))
        # Time & date from internal RTC
        print("RTC timestamp: {}".format(_format_datetime(the_rtc.datetime)))
        # Time & date from time.localtime() function
        print("Local time: {}".format(_format_datetime(time.localtime())))
    time.sleep(10)
Neradoc commented 2 years ago

I think the basic issue might be with using the GPS as a time source for the RTC. I don't know all the inner workings, but it looks like doing that causes the time functions to return time based on gps.datetime. But that value is apparently only updated when the GPS gets a timestamp from the satellites (when receiving a GLL type "sentence").

import time
import board
import busio
import rtc
import adafruit_gps

uart = busio.UART(board.TX, board.RX, baudrate=9600, timeout=10)
gps = adafruit_gps.GPS(uart, debug=False)
gps.send_command(b"PMTK314,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0")
gps.send_command(b"PMTK220,1000")

rtc.set_time_source(gps)

while not gps.timestamp_utc: gps.update()

print(time.time())
time.sleep(5)
print(time.time())

Result, which is quite unexpected when using the time module.

code.py output:
2271171509
2271171509

So, a better use of it is to not use set_time_source but instead use the local RTC and update the time reference when we get one (maybe not every time either, like we do with NTP to sync only every now and then). Even then there is no telling when the timestamp was received if the code spends a long time between reads of the uart buffer though. Maybe clarify what the "time source" example is supposed to do ?