Pytrack GPS API
-
@andrethemac i followed the original lib which use gc to clean memory. But haven't tried any other options.
Same problem here with the starting time. Sometimes i just unplug the cable from my computer and plug it to a powerbank and have to wait 5 minutes for it to get fix. For my project this will not be a problem but for a battery operated device it's not a good thing...
-
@neuromystix I used to search the nmea messages with _read looking for xxGGA until a fix was found. Only then I use the l76.coordinates to find the exact position.
a bit of a work around.
The new library you posted works as a charm, no more memory errors.
The Quectel is a bit "slow" but with a timeout of 180 (3 minutes), I manage to get a fix (almost) every time the wipy/lopy comes awake (after deep sleep). Not so good for the battery, 3 minutes on +- 70 mA vs 390 uA in deep sleep with the pytrack. The 390 uA are probably from the accelerator, I use the accelerator to wake up the unit when it gets more than 2g for over a second.
-
@andrethemac Yeah, ran into that issue aswell.
why we are re-doing the gps library that comes from pycom :)
and selectively adding more and more features ... so we won't run out of memory
-
@crumble I added a function where i can get time and date. If you want, try it and check if you have any problem.
Code:
## Test code for read date/time from gps and update RTC import machine import math import network import os import time import utime from machine import RTC from machine import SD from machine import Timer from L76GNSS import L76GNSS from pytrack import Pytrack import struct # setup as a station import gc time.sleep(2) gc.enable() #Start GPS py = Pytrack() l76 = L76GNSS(py, timeout=600) #start rtc rtc = machine.RTC() print('Aquiring GPS signal....') #try to get gps date to config rtc while (True): gps_datetime = l76.get_datetime() #case valid readings if gps_datetime[3]: day = int(gps_datetime[4][0] + gps_datetime[4][1] ) month = int(gps_datetime[4][2] + gps_datetime[4][3] ) year = int('20' + gps_datetime[4][4] + gps_datetime[4][5] ) hour = int(gps_datetime[2][0] + gps_datetime[2][1] ) minute = int(gps_datetime[2][2] + gps_datetime[2][3] ) second = int(gps_datetime[2][4] + gps_datetime[2][5] ) print("Current location: {} {} ; Date: {}/{}/{} ; Time: {}:{}:{}".format(gps_datetime[0],gps_datetime[1], day, month, year, hour, minute, second)) rtc.init( (year, month, day, hour, minute, second, 0, 0)) break print('RTC Set from GPS to UTC:', rtc.now()) chrono = Timer.Chrono() chrono.start() #sd = SD() #os.mount(sd, '/sd') #f = open('/sd/gps-record.txt', 'w') while (True): print("RTC time : {}".format(rtc.now())) coord = l76.coordinates() #f.write("{} - {}\n".format(coord, rtc.now())) print("$GPGLL>> {} - Free Mem: {}".format(coord, gc.mem_free())) coord1 = l76.coordinates1() print("$GPGGA>> {} - Free Mem: {}".format(coord1, gc.mem_free())) coord2 = l76.get_datetime() print("$G_RMC>> {} - Free Mem: {}".format(coord2, gc.mem_free()))
The L76GNSS.py code:
from machine import Timer import time import gc import binascii class L76GNSS: GPS_I2CADDR = const(0x10) def __init__(self, pytrack=None, sda='P22', scl='P21', timeout=None): if pytrack is not None: self.i2c = pytrack.i2c else: from machine import I2C self.i2c = I2C(0, mode=I2C.MASTER, pins=(sda, scl)) self.chrono = Timer.Chrono() self.timeout = timeout self.timeout_status = True self.reg = bytearray(1) self.i2c.writeto(GPS_I2CADDR, self.reg) def _read(self): self.reg = self.i2c.readfrom(GPS_I2CADDR, 128) #Changed from 64 to 128 - I2C L76 says it can read till 255 bytes return self.reg def _convert_coords(self, gngll_s): lat = gngll_s[1] lat_d = (float(lat) // 100) + ((float(lat) % 100) / 60) lon = gngll_s[3] lon_d = (float(lon) // 100) + ((float(lon) % 100) / 60) if gngll_s[2] == 'S': lat_d *= -1 if gngll_s[4] == 'W': lon_d *= -1 return(lat_d, lon_d) #diff indexes from original - Using GGA sentence def _convert_coords1(self, gngga_s): lat = gngga_s[2] lat_d = (float(lat) // 100) + ((float(lat) % 100) / 60) lon = gngga_s[4] lon_d = (float(lon) // 100) + ((float(lon) % 100) / 60) if gngga_s[3] == 'S': lat_d *= -1 if gngga_s[5] == 'W': lon_d *= -1 return(lat_d, lon_d) def _get_time(self, gngga_s): gps_time = gngga_s[1] return(gps_time) def _get_altitude(self, gngga_s): gps_altitude = gngga_s[9] return(gps_altitude) def _get_satellites(self, gngga_s): num_satellites = gngga_s[7] return(num_satellites) def _fix_quality(self, gngga_s): valid = gngga_s[6] if valid == '1': return True else: return False #Using RMC sentence def _get_time_rmc(self, gnrmc_s): gps_time = gnrmc_s[1] return(gps_time) def _data_valid_rmc(self, gnrmc_s): valid = gnrmc_s[2] if valid == 'A': return True else: return False def _get_date_rmc(self, gnrmc_s): gps_date = gnrmc_s[9] return(gps_date) def coordinates(self, debug=False): lat_d, lon_d, debug_timeout = None, None, False if self.timeout is not None: self.chrono.reset() self.chrono.start() nmea = b'' while True: if self.timeout is not None and self.chrono.read() >= self.timeout: self.chrono.stop() chrono_timeout = self.chrono.read() self.chrono.reset() self.timeout_status = False debug_timeout = True if not self.timeout_status: gc.collect() break nmea += self._read().lstrip(b'\n\n').rstrip(b'\n\n') gngll_idx = nmea.find(b'GNGLL') if gngll_idx >= 0: gngll = nmea[gngll_idx:] e_idx = gngll.find(b'\r\n') if e_idx >= 0: try: gngll = gngll[:e_idx].decode('ascii') gngll_s = gngll.split(',') lat_d, lon_d = self._convert_coords(gngll_s) except Exception: pass finally: nmea = nmea[(gngll_idx + e_idx):] gc.collect() break else: gc.collect() if len(nmea) > 410: # i suppose it can be safely changed to 82, which is longest NMEA frame nmea = nmea[-5:] # $GNGL without last L time.sleep(0.1) self.timeout_status = True if debug and debug_timeout: print('GPS timed out after %f seconds' % (chrono_timeout)) return(None, None) else: return(lat_d, lon_d) #TEST functions #Parser for GPGGA def coordinates1(self, debug=False): lat_d, lon_d, gps_time, valid, gps_altitude, num_satellites, debug_timeout = None, None, None, None, None, False, False if self.timeout is not None: self.chrono.reset() self.chrono.start() nmea = b'' while True: if self.timeout is not None and self.chrono.read() >= self.timeout: self.chrono.stop() chrono_timeout = self.chrono.read() self.chrono.reset() self.timeout_status = False debug_timeout = True if not self.timeout_status: gc.collect() break nmea += self._read().lstrip(b'\n\n').rstrip(b'\n\n') gpgga_idx = nmea.find(b'GPGGA') if gpgga_idx >= 0: gpgga = nmea[gpgga_idx:] gpgga_e_idx = gpgga.find(b'\r\n') if gpgga_e_idx >= 0: try: gpgga = gpgga[:gpgga_e_idx].decode('ascii') gpgga_s = gpgga.split(',') lat_d, lon_d = self._convert_coords1(gpgga_s) gps_time = self._get_time(gpgga_s) valid = self._fix_quality(gpgga_s) gps_altitude = self._get_altitude(gpgga_s) num_satellites = self._get_satellites(gpgga_s) except Exception: pass finally: nmea = nmea[(gpgga_idx + gpgga_e_idx):] gc.collect() break else: gc.collect() if len(nmea) > 410: # i suppose it can be safely changed to 82, which is longest NMEA frame nmea = nmea[-5:] # $GNGL without last L time.sleep(0.1) self.timeout_status = True if debug and debug_timeout: print('GPS timed out after %f seconds' % (chrono_timeout)) return(None, None, None, None, False, None) else: return(lat_d, lon_d, gps_time, gps_altitude, valid, num_satellites) #parser for UTC time and date >> Reads GPRMC def get_datetime(self, debug=False): lat_d, lon_d, gps_time, valid, gps_date, rmc_idx, debug_timeout = None, None, None, None, None, -1, False if self.timeout is not None: self.chrono.reset() self.chrono.start() nmea = b'' while True: if self.timeout is not None and self.chrono.read() >= self.timeout: self.chrono.stop() chrono_timeout = self.chrono.read() self.chrono.reset() self.timeout_status = False debug_timeout = True if not self.timeout_status: gc.collect() break nmea += self._read().lstrip(b'\n\n').rstrip(b'\n\n') #Since or spg or glonass could give date see which one is present -SEE page 10 GNSS protocol #GPS only - GPRMC GPGGA #Glonass only - GNRMC GPGGA #GPS+GLON - GNRMC GPGGA #No station - GPRMC GPGGA gprmc_idx = nmea.find(b'GPRMC') gnrmc_idx = nmea.find(b'GNRMC') if gprmc_idx >= 0: rmc_idx = gprmc_idx if gnrmc_idx >= 0: rmc_idx = gnrmc_idx if rmc_idx >= 0: rmc = nmea[rmc_idx:] rmc_e_idx = rmc.find(b'\r\n') if rmc_e_idx >= 0: try: rmc = rmc[:rmc_e_idx].decode('ascii') rmc_s = rmc.split(',') lat_d, lon_d = self._convert_coords1(rmc_s[1:]) gps_time = self._get_time_rmc(rmc_s) valid = self._data_valid_rmc(rmc_s) gps_date = self._get_date_rmc(rmc_s) except Exception: pass finally: nmea = nmea[(rmc_idx + rmc_e_idx):] gc.collect() break else: gc.collect() if len(nmea) > 512: # i suppose it can be safely changed to 82, which is longest NMEA frame --CHANGED to 512 nmea = nmea[-5:] # $GNGL without last L time.sleep(0.1) self.timeout_status = True if debug and debug_timeout: print('GPS timed out after %f seconds' % (chrono_timeout)) return(None, None, None, False, None) else: return(lat_d, lon_d, gps_time, valid, gps_date)
-
@crumble i followed the original lib from Pycom, just parse a different NMEA message. I've reading Quectel L76 GNSS protocol specification and i find they have only the following message types:
- xxRMC, where we can get UTC Time, validation, lat, long, speed, COG, date and other non important things;
- xxVTG, mostly for track course and speed data;
- xxGGA, where GPS fix data are. UTC time, lat, long, fix status, number of satellites, HDOP, altitude, height of geoid, DGPS info(if any) are all in this sentence;
- xxGSA, infos about satellites;
- xxGSV, more info about satellites;
- xxGLL, a basic sentence with geographic info like lat, long, UTC time, data validation and position mode.
So, depending on what things you need, you just have to parse the exact sentence. In my case, i changed the original library which parsed $GNGLL (few information) to a new function which parse $GPGGA (have more data!). The ideia was the same, reading sentences from i2c till a get a complete GNGLL sentence (searching for beggining and ending of that sentence >> nmea.find(b'GPGGA') and e_idx = gpgga.find(b'\r\n') >> It's a NMEA standard so it always start and end this way), then parse all the values i need. Today it has been working for nearly two hours, reading each second, and i don't get any errors.
For use it just call l76.coordinates1() which will return (lat, long, time(not formatted), altitude (M), valid fix (true ou false), number of satellites).
L76GNSS.py
-
@neuromystix said in Pytrack GPS API:
If anyone need i can show my basic test code.
Yes, please. I had sadly no time to work on my implementation. You run your code on a 4MB version, if you never had any memory problems, right? I can share my code as well, if it is ready. I used preallocated buffers to get around the memory problem, but my parsing of numbers is quite awefull.
For me it is hard to get one of the 4MB *Pys. The resellers in Germany seems to have only the old versions or the newer ones are sold out really fast.
-
Hello all,
I received today one Pytrack board for testing, and found it very easy to get more data from NMEA sentences. Just need to parse the correct NMEA sentence for what you need. Original library parse $GNGLL, however for what i need (lat/long, gps time, altitude, fix validation) i just changed the lib to parse $GPGGA and get that values. If anyone need i can show my basic test code.
Some time ago i tried the micropyGPS lib with an external ublox gps module, but also had that memory errors. I think they could be solved, however since i didn't need gps by that time, i never tried to fix anything.
Best regards
-
@combaindeft I ran the library on a pytrack / lopy combination, but gave up, to many out of memory errors.
-
Sounds really interesting, though we'd have to re-write some of the code to run on a PyTrack & LoPy setup :D
challange-accepted
ALSO, has anyone else found some documentation of how other GPS vendors do the "Accuracy" calculation?
-
Take a look at this library, it does a lot of nmea parsing, but its quite memory hungry.
[https://github.com/livius2/micropyGPS/blob/master/micropyGPS.py]
best regards
André
-
Have you looked at the library?
it's there, though you may have to hack your own code to get out everything you need :)