Welcome to the Pycom forum Forum
    • \[\[global:header.categories\]\]
    • \[\[global:header.recent\]\]
    • \[\[global:header.tags\]\]
    • \[\[global:header.popular\]\]
    • Login

    Explore Pybytes | Official Documentation | Report a Firmware Bug/Issue | GitHub

    Pytrack GPS API

    Scheduled Pinned Locked Moved Enhancements
    pytrack
    51 Posts 13 Posters 16.1k Views 20 Watching
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • N Offline
      Neuromystix @crumble
      last edited by Neuromystix

      @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

      Researcher @ UALG, Portugal

      N A M S 4 Replies Last reply Reply Quote 3
      • N Offline
        Neuromystix @Neuromystix
        last edited by Neuromystix

        @crumble I added a function where i can get time and date. If you want, try it and check if you have any problem.

        main.py
        L76GNSS

        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)
        

        Researcher @ UALG, Portugal

        G S 2 Replies Last reply Reply Quote 4
        • C Offline
          combaindeft @andrethemac
          last edited by

          @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

          1 Reply Last reply Reply Quote 0
          • A Offline
            andrethemac @Neuromystix
            last edited by

            @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.

            N 1 Reply Last reply Reply Quote 0
            • N Offline
              Neuromystix @andrethemac
              last edited by

              @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...

              Researcher @ UALG, Portugal

              A 1 Reply Last reply Reply Quote 0
              • G Offline
                gregcope @Neuromystix
                last edited by

                @neuromystix Works like a champ - it would be good if this was added to the main lib. I added HDOP (GPGGA) as that is a good indication of accuracy.

                1 Reply Last reply Reply Quote 0
                • M Offline
                  mwtidd @Neuromystix
                  last edited by

                  @neuromystix your work has been awesome. Thank you so much for your efforts. In running some test logs earlier today, I found that I got a series of fixes logged that were "invalid". Upon reviewing you code the protocol documents, I have one update to propose

                  def _fix_quality(self, gngga_s):
                      valid = gngga_s[6]
                      if valid == '0':
                          return False
                      else:
                          return True
                  

                  Your initial fix quality function only returned true if gngga_s[6] was 1, however it seems that values such as 2 or 3 may be valid as well.

                  Per v2.2 of the protocol spec:

                  Value Description
                  0 Fix not available or invalid
                  1 GPS SPS Mode, fix valid
                  2 Differential GPS, SPS Mode, fix valid
                  3 GPS PPS Mode, fix valid

                  Once again, thanks for your efforts. Just wanted to share my findings.

                  N 1 Reply Last reply Reply Quote 0
                  • N Offline
                    Neuromystix @mwtidd
                    last edited by

                    @mwtidd When i wrote it i was reading the "Quectel l76 Series GNSS protocol Specification V3.3" which says (@page 12):
                    Fix Status
                    "0"=Invalid
                    "1"=GNSS fix
                    "2"=DGPS fix
                    "6"=Estimated (dead reckoning) mode

                    That's why i only used "1". But even with that, we can use the value "2" for the DGPS fix. I haven´t checked if i had some fixes "2" ou another values. If it worked for you, nice ;)

                    Best regards
                    Nelson Pinto

                    Researcher @ UALG, Portugal

                    1 Reply Last reply Reply Quote 0
                    • S Offline
                      stefan85e @bmarkus
                      last edited by

                      @bmarkus said in Pytrack GPS API:

                      umber of satellites visible /used for fi

                      • number of satellites visible /used for fix - Is this possible yet?!
                      N 1 Reply Last reply Reply Quote 0
                      • N Offline
                        Neuromystix @stefan85e
                        last edited by

                        @stefan85e If you need the number of satellites used to fix, it's already parsed in l76.coordinates1(). The "Satellites in view" i haven't done it, but it's easy to implement, just parse any $__GSV sentence, the third field is "Satellites in view".

                        Researcher @ UALG, Portugal

                        S 1 Reply Last reply Reply Quote 0
                        • S Offline
                          stefan85e @Neuromystix
                          last edited by

                          @neuromystix said in Pytrack GPS API:

                          l76.coordinates1()

                          Sorry did not udnerstand that :)

                          1 Reply Last reply Reply Quote 0
                          • A Offline
                            andrethemac
                            last edited by

                            Hi all. I've taken the original librarya and @Neuromystix addtions and made my own library. It parses aml the sentences from the L76 gps and returns them as a dict. you can get the coordinates parsed, the time, ...
                            https://github.com/andrethemac/L76GLNSV4

                            I've noticed one (strange?) thing. the RMC (required minimum data to get a fix) message only pass once (just after starting the gps) and does not reappear. Any idea's ?
                            best regards

                            S 1 Reply Last reply Reply Quote 0
                            • S Offline
                              stefan85e @Neuromystix
                              last edited by

                              @neuromystix said in Pytrack GPS API:

                              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
                              So what did you do to get the nimber of satellites? We are trying this and we cant get it to work.

                              N 1 Reply Last reply Reply Quote 0
                              • N Offline
                                Neuromystix @stefan85e
                                last edited by

                                @stefan85e Hello,

                                You have to do something like this inside your code, and you'll get the number of satellites used for fix:

                                ...
                                gps_data = l76.coordinates1()
                                n_sat = gps_data[5]
                                ...
                                

                                Or you can use the @andrethemac library which will get you all the data ;)

                                Researcher @ UALG, Portugal

                                1 Reply Last reply Reply Quote 0
                                • S Offline
                                  stefan85e @andrethemac
                                  last edited by

                                  @andrethemac
                                  (2018, 4, 17, 11, 29, 12, 564887, None)(None(53.33632, 12.53262)
                                  (None, None) Got when I add this:

                                  gps_data = l76.coordinates1()
                                  n_sat = gps_data[5]

                                  Did also try to use this lib:
                                  https://github.com/andrethemac/L76GLNSV4

                                  A 1 Reply Last reply Reply Quote 0
                                  • S Offline
                                    stefan85e @Neuromystix
                                    last edited by

                                    @neuromystix said in Pytrack GPS API:

                                    num_satellites

                                    Does this work:

                                    return(lat_d, lon_d, gps_time, gps_altitude, valid, num_satellites)

                                    I want num_satellites...

                                    N 1 Reply Last reply Reply Quote 0
                                    • N Offline
                                      Neuromystix @stefan85e
                                      last edited by

                                      @stefan85e If you are using my code (and just my code, not the one from @andrethemac), when you call gps_data = l76.coordinates1() it will return a list gps_data with latitude at first position (gps_data[0]), longitude at second (gps_data[1]), and so on till the sixth position where you get the number of satellites (gps_data[5]). So, number_of_satellites = gps_data[5].

                                      Researcher @ UALG, Portugal

                                      1 Reply Last reply Reply Quote 0
                                      • A Offline
                                        andrethemac @stefan85e
                                        last edited by

                                        @stefan85e An example on how to get the number of satelites with my library

                                        from pytrack import Pytrack
                                        from L76GNSV4 import L76GNSS
                                        
                                        py = Pytrack()
                                        L76 = L76GNSS(pytrack=py)
                                        
                                        print("test L76")
                                        # returns the info about sattelites in view at this moment
                                        # even without the gps being fixed
                                        print(L76.gps_message('GSV'))
                                        
                                        # returns the number of sattelites in view at this moment
                                        # even without the gps being fixed
                                        print(L76.gps_message('GGA')['NumberOfSV'])
                                        
                                        # returns the coordinates
                                        # with debug true you see the messages parsed by the 
                                        # library until you get a the gps is fixed
                                        print(L76.coordinates(debug=True))
                                        #L76.getUTCDateTime(debug=True)
                                        #L76._read_message(debug=True)
                                        
                                        
                                        S 1 Reply Last reply Reply Quote 1
                                        • S Offline
                                          stefan85e @andrethemac
                                          last edited by stefan85e

                                          @andrethemac

                                          0_1524042540467_f2c4b355-5786-4440-8e0e-6e4ab4d095d0-image.png

                                          Got this

                                          I see that you have a 180 timeout in the code:

                                          -> L76GNSV4 - So how do I know how many sattelites i have then :) just so I look at right nr :)

                                          ´´´´
                                          def init(self, pytrack=None, sda='P22', scl='P21', timeout=180,debug=False):
                                          ´´´´

                                          A 1 Reply Last reply Reply Quote 0
                                          • A Offline
                                            andrethemac @stefan85e
                                            last edited by

                                            @stefan85e The timeout is to prevent the library to wait for ever. 180 seconds (3 minutes) is (for me) most of the time enough to get a fixed location. (that is after a cold start)
                                            the GGA sentences contains the number of messages seen by the GPS, In the dict that GGA returns, this is the "NumberOfSV' fied.
                                            with this you get this number -> L76.gps_message('GGA')['NumberOfSV']
                                            In your screenshot the line that prints the number of satellites has scrolled out of view
                                            but if you make this line the last you get the number:
                                            print(L76.gps_message('GGA')['NumberOfSV'])
                                            or remove the 'debug=True) from the coordinates line (the debug spews out a lot of information)
                                            I'm still working on the library to make it faster
                                            best regards

                                            A S 2 Replies Last reply Reply Quote 0

                                            Hello! It looks like you're interested in this conversation, but you don't have an account yet.

                                            Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.

                                            With your input, this post could be even better 💗

                                            Register Login
                                            • 1
                                            • 2
                                            • 3
                                            • 1 / 3
                                            • First post
                                              Last post

                                            Pycom on Twitter

                                            Pycom