047 - MicroPython TechNotes: E108 GPS

Introduction

In this article, I will discuss how you can use a GPS module with ESP32 using MicroPython.

GPS is an acronym which stands for Global Positioning System. With the help of GPS, we can determine our location called coordinates which is mainly composed of the latitude, the longitude, and the altitude.

The latitude is the angle formed between the equator and a certain location of the Earth. A positive latitude value when going North while a negative value when going South. North pole is (+)90 degrees, South Pole is -90 degrees, and of course Equator is 0 degrees. Latitude is the first value for each given coordinates.

The longitude is the angle formed between the Greenwich timeline (Greenwich, England) and a certain location of the Earth. A positive longitude value when going to the East while a negative value when going to the West. In the middle of Pacific ocean is either 180 degrees East or -180 degrees West. Longitude is the second value for each given coordinates.

The altitude is the elevation above the horizon. It is a distance measurement above sea level usually measured in meter unit. Altitude is the third value for each given coordinates.

What I have is a GPS module from Gorillacell which is included in the ESP32 development kit. It contains the E108 Integrated Circuit of Chengdu EByte Electronic Technology. The GPS kit also comes with an external antenna for better GPS signal reception.

The E108 GPS module can be connected directly to a computer by connecting it through a type-C USB cable where it appears as a Serial Port device and by using any serial terminal application, you should see a GPS data dump to your app. But it is more beneficial for us to interface it to a microcontroller such as the ESP32 and custom parse the receive data through serial interface.

Pinout

The E108 GPS module has 4 pins namely:

  1. GND – for the ground pin.
  2. VCC – for the supply voltage.
  3. Tx – for the UART serial transmit pin, connect the Rx pin of the microcontroller.
  4. Rx – for the UART serial receive pin, connect the Tx pin of the microcontroller.

Bill Of Materials

  1. ESP32 development board.
  2. Gorillacell ESP32 shield, optional.
  3. Gorillacell GPS module set with antenna.
  4. 0.96 OLED for the display or just use the REPL to observe the GPS data.
  5. 4-pin dupont wires.

Hardware Instruction

  1. Connect the GPS module GND pin to the ESP32 GND pin.
  2. Connect the GPS module VCC pin to the ESP32 5V pin.
  3. Connect the GPS module Tx pin to the ESP32 GPIO 25.
  4. Connect the GPS module Rx pin to the EPS32 GPIO 26.
  5. Connect the OLED module GND pin to the ESP32 GND pin.
  6. Connect the OLED module VCC pin to the ESP32 5V pin.
  7. Connect the OLED module SDA pin to the ESP32 GPIO 21.
  8. Connect the OLED module SCL pin to the ESP32 GPIO 22.

Software Instruction

  1. Save the micropyGPS.py to your ESP32 MicroPython device root directory. This is the driver library that will handle receiving and parsing GPS data dump to the serial.

  2. Save the ssd1306.py to your ESP32 MicroPython device root directory. This is the driver library that will handle for displaying text strings in the OLED display.

  3. Copy and paste E108GPS_Example#1.py to Thonny IDE. You may save it to MicroPython device root directory as main.py. This will make the program run after reset or after power ON. Or just save it to your local hard drive, save it the filename you like, and run it clicking the RUN button.

Video Demonstration

Call To Action

If you have any concern regarding this video, please do not hesitate to leave your message in the comment section.

You might also like to support my journey on Youtube by subscribing to my channel. Click this link to Subscribe to TechToTinker Youtube channel.

As always, thank you for visiting. Regards, – George Bantique | tech.to.tinker@gmail.com

Source Code

1. Example # 1:

 1# More details can be found in TechToTinker.blogspot.com 
 2# George Bantique | tech.to.tinker@gmail.com
 3
 4from machine import Pin
 5from machine import SoftI2C
 6from machine import UART
 7from micropyGPS import MicropyGPS
 8from ssd1306 import SSD1306_I2C 
 9
10def main():
11    uart = UART(1, rx=25, tx=26, baudrate=9600, bits=8, parity=None, stop=1, timeout=5000, rxbuf=1024)
12    gps = MicropyGPS()
13    i2c = SoftI2C(scl=Pin(22), sda=Pin(21), freq=400000) 
14    oled = SSD1306_I2C(128, 64, i2c, addr=0x3C)
15    led = Pin(2, Pin.OUT)
16    
17    while True:
18      buf = uart.readline()
19
20      for char in buf:
21        gps.update(chr(char))  # Note the conversion to to chr, UART outputs ints normally
22
23      print('UTC Timestamp:', gps.timestamp)
24      print('Date:', gps.date_string('long'))
25      print('Satellites:', gps.satellites_in_use)
26      print('Altitude:', gps.altitude)
27      print('Latitude:', gps.latitude)
28      print('Longitude:', gps.longitude_string())
29      print('Horizontal Dilution of Precision:', gps.hdop)
30      print()
31      
32      oled.fill(0)
33      oled.text('Date:{}'.format(gps.date_string('s_mdy')),0,0)
34      oled.text('Sat:{}'.format(gps.satellites_in_use),0,10)
35      oled.text('Alt:{}'.format(gps.altitude),0,20)
36      oled.text('{}'.format(gps.latitude_string()),0,35)
37      oled.text('{}'.format(gps.longitude_string()),0,45)
38      oled.show()
39      
40      led.value(not led.value())
41        
42# def startGPSthread():
43#     _thread.start_new_thread(main, ())
44
45if __name__ == "__main__":
46  print('...running main, GPS testing')
47  main()

2. microGPS.py

  1"""
  2# MicropyGPS - a GPS NMEA sentence parser for Micropython/Python 3.X
  3# Copyright (c) 2017 Michael Calvin McCoy (calvin.mccoy@protonmail.com)
  4# The MIT License (MIT) - see LICENSE file
  5"""
  6
  7# TODO:
  8# Time Since First Fix
  9# Distance/Time to Target
 10# More Helper Functions
 11# Dynamically limit sentences types to parse
 12
 13from math import floor, modf
 14
 15# Import utime or time for fix time handling
 16try:
 17    # Assume running on MicroPython
 18    import utime
 19except ImportError:
 20    # Otherwise default to time module for non-embedded implementations
 21    # Should still support millisecond resolution.
 22    import time
 23
 24
 25class MicropyGPS(object):
 26    """GPS NMEA Sentence Parser. Creates object that stores all relevant GPS data and statistics.
 27    Parses sentences one character at a time using update(). """
 28
 29    # Max Number of Characters a valid sentence can be (based on GGA sentence)
 30    SENTENCE_LIMIT = 90
 31    __HEMISPHERES = ('N', 'S', 'E', 'W')
 32    __NO_FIX = 1
 33    __FIX_2D = 2
 34    __FIX_3D = 3
 35    __DIRECTIONS = ('N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W',
 36                    'WNW', 'NW', 'NNW')
 37    __MONTHS = ('January', 'February', 'March', 'April', 'May',
 38                'June', 'July', 'August', 'September', 'October',
 39                'November', 'December')
 40
 41    def __init__(self, local_offset=0, location_formatting='ddm'):
 42        """
 43        Setup GPS Object Status Flags, Internal Data Registers, etc
 44            local_offset (int): Timzone Difference to UTC
 45            location_formatting (str): Style For Presenting Longitude/Latitude:
 46                                       Decimal Degree Minute (ddm) - 40° 26.767′ N
 47                                       Degrees Minutes Seconds (dms) - 40° 26′ 46″ N
 48                                       Decimal Degrees (dd) - 40.446° N
 49        """
 50
 51        #####################
 52        # Object Status Flags
 53        self.sentence_active = False
 54        self.active_segment = 0
 55        self.process_crc = False
 56        self.gps_segments = []
 57        self.crc_xor = 0
 58        self.char_count = 0
 59        self.fix_time = 0
 60
 61        #####################
 62        # Sentence Statistics
 63        self.crc_fails = 0
 64        self.clean_sentences = 0
 65        self.parsed_sentences = 0
 66
 67        #####################
 68        # Logging Related
 69        self.log_handle = None
 70        self.log_en = False
 71
 72        #####################
 73        # Data From Sentences
 74        # Time
 75        self.timestamp = [0, 0, 0.0]
 76        self.date = [0, 0, 0]
 77        self.local_offset = local_offset
 78
 79        # Position/Motion
 80        self._latitude = [0, 0.0, 'N']
 81        self._longitude = [0, 0.0, 'W']
 82        self.coord_format = location_formatting
 83        self.speed = [0.0, 0.0, 0.0]
 84        self.course = 0.0
 85        self.altitude = 0.0
 86        self.geoid_height = 0.0
 87
 88        # GPS Info
 89        self.satellites_in_view = 0
 90        self.satellites_in_use = 0
 91        self.satellites_used = []
 92        self.last_sv_sentence = 0
 93        self.total_sv_sentences = 0
 94        self.satellite_data = dict()
 95        self.hdop = 0.0
 96        self.pdop = 0.0
 97        self.vdop = 0.0
 98        self.valid = False
 99        self.fix_stat = 0
100        self.fix_type = 1
101
102    ########################################
103    # Coordinates Translation Functions
104    ########################################
105    @property
106    def latitude(self):
107        """Format Latitude Data Correctly"""
108        if self.coord_format == 'dd':
109            decimal_degrees = self._latitude[0] + (self._latitude[1] / 60)
110            return [decimal_degrees, self._latitude[2]]
111        elif self.coord_format == 'dms':
112            minute_parts = modf(self._latitude[1])
113            seconds = round(minute_parts[0] * 60)
114            return [self._latitude[0], int(minute_parts[1]), seconds, self._latitude[2]]
115        else:
116            return self._latitude
117
118    @property
119    def longitude(self):
120        """Format Longitude Data Correctly"""
121        if self.coord_format == 'dd':
122            decimal_degrees = self._longitude[0] + (self._longitude[1] / 60)
123            return [decimal_degrees, self._longitude[2]]
124        elif self.coord_format == 'dms':
125            minute_parts = modf(self._longitude[1])
126            seconds = round(minute_parts[0] * 60)
127            return [self._longitude[0], int(minute_parts[1]), seconds, self._longitude[2]]
128        else:
129            return self._longitude
130
131    ########################################
132    # Logging Related Functions
133    ########################################
134    def start_logging(self, target_file, mode="append"):
135        """
136        Create GPS data log object
137        """
138        # Set Write Mode Overwrite or Append
139        mode_code = 'w' if mode == 'new' else 'a'
140
141        try:
142            self.log_handle = open(target_file, mode_code)
143        except AttributeError:
144            print("Invalid FileName")
145            return False
146
147        self.log_en = True
148        return True
149
150    def stop_logging(self):
151        """
152        Closes the log file handler and disables further logging
153        """
154        try:
155            self.log_handle.close()
156        except AttributeError:
157            print("Invalid Handle")
158            return False
159
160        self.log_en = False
161        return True
162
163    def write_log(self, log_string):
164        """Attempts to write the last valid NMEA sentence character to the active file handler
165        """
166        try:
167            self.log_handle.write(log_string)
168        except TypeError:
169            return False
170        return True
171
172    ########################################
173    # Sentence Parsers
174    ########################################
175    def gprmc(self):
176        """Parse Recommended Minimum Specific GPS/Transit data (RMC)Sentence.
177        Updates UTC timestamp, latitude, longitude, Course, Speed, Date, and fix status
178        """
179
180        # UTC Timestamp
181        try:
182            utc_string = self.gps_segments[1]
183
184            if utc_string:  # Possible timestamp found
185                hours = (int(utc_string[0:2]) + self.local_offset) % 24
186                minutes = int(utc_string[2:4])
187                seconds = float(utc_string[4:])
188                self.timestamp = [hours, minutes, seconds]
189            else:  # No Time stamp yet
190                self.timestamp = [0, 0, 0.0]
191
192        except ValueError:  # Bad Timestamp value present
193            return False
194
195        # Date stamp
196        try:
197            date_string = self.gps_segments[9]
198
199            # Date string printer function assumes to be year >=2000,
200            # date_string() must be supplied with the correct century argument to display correctly
201            if date_string:  # Possible date stamp found
202                day = int(date_string[0:2])
203                month = int(date_string[2:4])
204                year = int(date_string[4:6])
205                self.date = (day, month, year)
206            else:  # No Date stamp yet
207                self.date = (0, 0, 0)
208
209        except ValueError:  # Bad Date stamp value present
210            return False
211
212        # Check Receiver Data Valid Flag
213        if self.gps_segments[2] == 'A':  # Data from Receiver is Valid/Has Fix
214
215            # Longitude / Latitude
216            try:
217                # Latitude
218                l_string = self.gps_segments[3]
219                lat_degs = int(l_string[0:2])
220                lat_mins = float(l_string[2:])
221                lat_hemi = self.gps_segments[4]
222
223                # Longitude
224                l_string = self.gps_segments[5]
225                lon_degs = int(l_string[0:3])
226                lon_mins = float(l_string[3:])
227                lon_hemi = self.gps_segments[6]
228            except ValueError:
229                return False
230
231            if lat_hemi not in self.__HEMISPHERES:
232                return False
233
234            if lon_hemi not in self.__HEMISPHERES:
235                return False
236
237            # Speed
238            try:
239                spd_knt = float(self.gps_segments[7])
240            except ValueError:
241                return False
242
243            # Course
244            try:
245                if self.gps_segments[8]:
246                    course = float(self.gps_segments[8])
247                else:
248                    course = 0.0
249            except ValueError:
250                return False
251
252            # TODO - Add Magnetic Variation
253
254            # Update Object Data
255            self._latitude = [lat_degs, lat_mins, lat_hemi]
256            self._longitude = [lon_degs, lon_mins, lon_hemi]
257            # Include mph and hm/h
258            self.speed = [spd_knt, spd_knt * 1.151, spd_knt * 1.852]
259            self.course = course
260            self.valid = True
261
262            # Update Last Fix Time
263            self.new_fix_time()
264
265        else:  # Clear Position Data if Sentence is 'Invalid'
266            self._latitude = [0, 0.0, 'N']
267            self._longitude = [0, 0.0, 'W']
268            self.speed = [0.0, 0.0, 0.0]
269            self.course = 0.0
270            self.valid = False
271
272        return True
273
274    def gpgll(self):
275        """Parse Geographic Latitude and Longitude (GLL)Sentence. Updates UTC timestamp, latitude,
276        longitude, and fix status"""
277
278        # UTC Timestamp
279        try:
280            utc_string = self.gps_segments[5]
281
282            if utc_string:  # Possible timestamp found
283                hours = (int(utc_string[0:2]) + self.local_offset) % 24
284                minutes = int(utc_string[2:4])
285                seconds = float(utc_string[4:])
286                self.timestamp = [hours, minutes, seconds]
287            else:  # No Time stamp yet
288                self.timestamp = [0, 0, 0.0]
289
290        except ValueError:  # Bad Timestamp value present
291            return False
292
293        # Check Receiver Data Valid Flag
294        if self.gps_segments[6] == 'A':  # Data from Receiver is Valid/Has Fix
295
296            # Longitude / Latitude
297            try:
298                # Latitude
299                l_string = self.gps_segments[1]
300                lat_degs = int(l_string[0:2])
301                lat_mins = float(l_string[2:])
302                lat_hemi = self.gps_segments[2]
303
304                # Longitude
305                l_string = self.gps_segments[3]
306                lon_degs = int(l_string[0:3])
307                lon_mins = float(l_string[3:])
308                lon_hemi = self.gps_segments[4]
309            except ValueError:
310                return False
311
312            if lat_hemi not in self.__HEMISPHERES:
313                return False
314
315            if lon_hemi not in self.__HEMISPHERES:
316                return False
317
318            # Update Object Data
319            self._latitude = [lat_degs, lat_mins, lat_hemi]
320            self._longitude = [lon_degs, lon_mins, lon_hemi]
321            self.valid = True
322
323            # Update Last Fix Time
324            self.new_fix_time()
325
326        else:  # Clear Position Data if Sentence is 'Invalid'
327            self._latitude = [0, 0.0, 'N']
328            self._longitude = [0, 0.0, 'W']
329            self.valid = False
330
331        return True
332
333    def gpvtg(self):
334        """Parse Track Made Good and Ground Speed (VTG) Sentence. Updates speed and course"""
335        try:
336            course = float(self.gps_segments[1]) if self.gps_segments[1] else 0.0
337            spd_knt = float(self.gps_segments[5]) if self.gps_segments[5] else 0.0
338        except ValueError:
339            return False
340
341        # Include mph and km/h
342        self.speed = (spd_knt, spd_knt * 1.151, spd_knt * 1.852)
343        self.course = course
344        return True
345
346    def gpgga(self):
347        """Parse Global Positioning System Fix Data (GGA) Sentence. Updates UTC timestamp, latitude, longitude,
348        fix status, satellites in use, Horizontal Dilution of Precision (HDOP), altitude, geoid height and fix status"""
349
350        try:
351            # UTC Timestamp
352            utc_string = self.gps_segments[1]
353
354            # Skip timestamp if receiver doesn't have on yet
355            if utc_string:
356                hours = (int(utc_string[0:2]) + self.local_offset) % 24
357                minutes = int(utc_string[2:4])
358                seconds = float(utc_string[4:])
359            else:
360                hours = 0
361                minutes = 0
362                seconds = 0.0
363
364            # Number of Satellites in Use
365            satellites_in_use = int(self.gps_segments[7])
366
367            # Get Fix Status
368            fix_stat = int(self.gps_segments[6])
369
370        except (ValueError, IndexError):
371            return False
372
373        try:
374            # Horizontal Dilution of Precision
375            hdop = float(self.gps_segments[8])
376        except (ValueError, IndexError):
377            hdop = 0.0
378
379        # Process Location and Speed Data if Fix is GOOD
380        if fix_stat:
381
382            # Longitude / Latitude
383            try:
384                # Latitude
385                l_string = self.gps_segments[2]
386                lat_degs = int(l_string[0:2])
387                lat_mins = float(l_string[2:])
388                lat_hemi = self.gps_segments[3]
389
390                # Longitude
391                l_string = self.gps_segments[4]
392                lon_degs = int(l_string[0:3])
393                lon_mins = float(l_string[3:])
394                lon_hemi = self.gps_segments[5]
395            except ValueError:
396                return False
397
398            if lat_hemi not in self.__HEMISPHERES:
399                return False
400
401            if lon_hemi not in self.__HEMISPHERES:
402                return False
403
404            # Altitude / Height Above Geoid
405            try:
406                altitude = float(self.gps_segments[9])
407                geoid_height = float(self.gps_segments[11])
408            except ValueError:
409                altitude = 0
410                geoid_height = 0
411
412            # Update Object Data
413            self._latitude = [lat_degs, lat_mins, lat_hemi]
414            self._longitude = [lon_degs, lon_mins, lon_hemi]
415            self.altitude = altitude
416            self.geoid_height = geoid_height
417
418        # Update Object Data
419        self.timestamp = [hours, minutes, seconds]
420        self.satellites_in_use = satellites_in_use
421        self.hdop = hdop
422        self.fix_stat = fix_stat
423
424        # If Fix is GOOD, update fix timestamp
425        if fix_stat:
426            self.new_fix_time()
427
428        return True
429
430    def gpgsa(self):
431        """Parse GNSS DOP and Active Satellites (GSA) sentence. Updates GPS fix type, list of satellites used in
432        fix calculation, Position Dilution of Precision (PDOP), Horizontal Dilution of Precision (HDOP), Vertical
433        Dilution of Precision, and fix status"""
434
435        # Fix Type (None,2D or 3D)
436        try:
437            fix_type = int(self.gps_segments[2])
438        except ValueError:
439            return False
440
441        # Read All (up to 12) Available PRN Satellite Numbers
442        sats_used = []
443        for sats in range(12):
444            sat_number_str = self.gps_segments[3 + sats]
445            if sat_number_str:
446                try:
447                    sat_number = int(sat_number_str)
448                    sats_used.append(sat_number)
449                except ValueError:
450                    return False
451            else:
452                break
453
454        # PDOP,HDOP,VDOP
455        try:
456            pdop = float(self.gps_segments[15])
457            hdop = float(self.gps_segments[16])
458            vdop = float(self.gps_segments[17])
459        except ValueError:
460            return False
461
462        # Update Object Data
463        self.fix_type = fix_type
464
465        # If Fix is GOOD, update fix timestamp
466        if fix_type > self.__NO_FIX:
467            self.new_fix_time()
468
469        self.satellites_used = sats_used
470        self.hdop = hdop
471        self.vdop = vdop
472        self.pdop = pdop
473
474        return True
475
476    def gpgsv(self):
477        """Parse Satellites in View (GSV) sentence. Updates number of SV Sentences,the number of the last SV sentence
478        parsed, and data on each satellite present in the sentence"""
479        try:
480            num_sv_sentences = int(self.gps_segments[1])
481            current_sv_sentence = int(self.gps_segments[2])
482            sats_in_view = int(self.gps_segments[3])
483        except ValueError:
484            return False
485
486        # Create a blank dict to store all the satellite data from this sentence in:
487        # satellite PRN is key, tuple containing telemetry is value
488        satellite_dict = dict()
489
490        # Calculate  Number of Satelites to pull data for and thus how many segment positions to read
491        if num_sv_sentences == current_sv_sentence:
492            # Last sentence may have 1-4 satellites; 5 - 20 positions
493            sat_segment_limit = (sats_in_view - ((num_sv_sentences - 1) * 4)) * 5
494        else:
495            sat_segment_limit = 20  # Non-last sentences have 4 satellites and thus read up to position 20
496
497        # Try to recover data for up to 4 satellites in sentence
498        for sats in range(4, sat_segment_limit, 4):
499
500            # If a PRN is present, grab satellite data
501            if self.gps_segments[sats]:
502                try:
503                    sat_id = int(self.gps_segments[sats])
504                except (ValueError,IndexError):
505                    return False
506
507                try:  # elevation can be null (no value) when not tracking
508                    elevation = int(self.gps_segments[sats+1])
509                except (ValueError,IndexError):
510                    elevation = None
511
512                try:  # azimuth can be null (no value) when not tracking
513                    azimuth = int(self.gps_segments[sats+2])
514                except (ValueError,IndexError):
515                    azimuth = None
516
517                try:  # SNR can be null (no value) when not tracking
518                    snr = int(self.gps_segments[sats+3])
519                except (ValueError,IndexError):
520                    snr = None
521            # If no PRN is found, then the sentence has no more satellites to read
522            else:
523                break
524
525            # Add Satellite Data to Sentence Dict
526            satellite_dict[sat_id] = (elevation, azimuth, snr)
527
528        # Update Object Data
529        self.total_sv_sentences = num_sv_sentences
530        self.last_sv_sentence = current_sv_sentence
531        self.satellites_in_view = sats_in_view
532
533        # For a new set of sentences, we either clear out the existing sat data or
534        # update it as additional SV sentences are parsed
535        if current_sv_sentence == 1:
536            self.satellite_data = satellite_dict
537        else:
538            self.satellite_data.update(satellite_dict)
539
540        return True
541
542    ##########################################
543    # Data Stream Handler Functions
544    ##########################################
545
546    def new_sentence(self):
547        """Adjust Object Flags in Preparation for a New Sentence"""
548        self.gps_segments = ['']
549        self.active_segment = 0
550        self.crc_xor = 0
551        self.sentence_active = True
552        self.process_crc = True
553        self.char_count = 0
554
555    def update(self, new_char):
556        """Process a new input char and updates GPS object if necessary based on special characters ('$', ',', '*')
557        Function builds a list of received string that are validate by CRC prior to parsing by the  appropriate
558        sentence function. Returns sentence type on successful parse, None otherwise"""
559
560        valid_sentence = False
561
562        # Validate new_char is a printable char
563        ascii_char = ord(new_char)
564
565        if 10 <= ascii_char <= 126:
566            self.char_count += 1
567
568            # Write Character to log file if enabled
569            if self.log_en:
570                self.write_log(new_char)
571
572            # Check if a new string is starting ($)
573            if new_char == '$':
574                self.new_sentence()
575                return None
576
577            elif self.sentence_active:
578
579                # Check if sentence is ending (*)
580                if new_char == '*':
581                    self.process_crc = False
582                    self.active_segment += 1
583                    self.gps_segments.append('')
584                    return None
585
586                # Check if a section is ended (,), Create a new substring to feed
587                # characters to
588                elif new_char == ',':
589                    self.active_segment += 1
590                    self.gps_segments.append('')
591
592                # Store All Other printable character and check CRC when ready
593                else:
594                    self.gps_segments[self.active_segment] += new_char
595
596                    # When CRC input is disabled, sentence is nearly complete
597                    if not self.process_crc:
598
599                        if len(self.gps_segments[self.active_segment]) == 2:
600                            try:
601                                final_crc = int(self.gps_segments[self.active_segment], 16)
602                                if self.crc_xor == final_crc:
603                                    valid_sentence = True
604                                else:
605                                    self.crc_fails += 1
606                            except ValueError:
607                                pass  # CRC Value was deformed and could not have been correct
608
609                # Update CRC
610                if self.process_crc:
611                    self.crc_xor ^= ascii_char
612
613                # If a Valid Sentence Was received and it's a supported sentence, then parse it!!
614                if valid_sentence:
615                    self.clean_sentences += 1  # Increment clean sentences received
616                    self.sentence_active = False  # Clear Active Processing Flag
617
618                    if self.gps_segments[0] in self.supported_sentences:
619
620                        # parse the Sentence Based on the message type, return True if parse is clean
621                        if self.supported_sentences[self.gps_segments[0]](self):
622
623                            # Let host know that the GPS object was updated by returning parsed sentence type
624                            self.parsed_sentences += 1
625                            return self.gps_segments[0]
626
627                # Check that the sentence buffer isn't filling up with Garage waiting for the sentence to complete
628                if self.char_count > self.SENTENCE_LIMIT:
629                    self.sentence_active = False
630
631        # Tell Host no new sentence was parsed
632        return None
633
634    def new_fix_time(self):
635        """Updates a high resolution counter with current time when fix is updated. Currently only triggered from
636        GGA, GSA and RMC sentences"""
637        try:
638            self.fix_time = utime.ticks_ms()
639        except NameError:
640            self.fix_time = time.time()
641
642    #########################################
643    # User Helper Functions
644    # These functions make working with the GPS object data easier
645    #########################################
646
647    def satellite_data_updated(self):
648        """
649        Checks if the all the GSV sentences in a group have been read, making satellite data complete
650        :return: boolean
651        """
652        if self.total_sv_sentences > 0 and self.total_sv_sentences == self.last_sv_sentence:
653            return True
654        else:
655            return False
656
657    def unset_satellite_data_updated(self):
658        """
659        Mark GSV sentences as read indicating the data has been used and future updates are fresh
660        """
661        self.last_sv_sentence = 0
662
663    def satellites_visible(self):
664        """
665        Returns a list of of the satellite PRNs currently visible to the receiver
666        :return: list
667        """
668        return list(self.satellite_data.keys())
669
670    def time_since_fix(self):
671        """Returns number of millisecond since the last sentence with a valid fix was parsed. Returns 0 if
672        no fix has been found"""
673
674        # Test if a Fix has been found
675        if self.fix_time == 0:
676            return -1
677
678        # Try calculating fix time using utime; if not running MicroPython
679        # time.time() returns a floating point value in secs
680        try:
681            current = utime.ticks_diff(utime.ticks_ms(), self.fix_time)
682        except NameError:
683            current = (time.time() - self.fix_time) * 1000  # ms
684
685        return current
686
687    def compass_direction(self):
688        """
689        Determine a cardinal or inter-cardinal direction based on current course.
690        :return: string
691        """
692        # Calculate the offset for a rotated compass
693        if self.course >= 348.75:
694            offset_course = 360 - self.course
695        else:
696            offset_course = self.course + 11.25
697
698        # Each compass point is separated by 22.5 degrees, divide to find lookup value
699        dir_index = floor(offset_course / 22.5)
700
701        final_dir = self.__DIRECTIONS[dir_index]
702
703        return final_dir
704
705    def latitude_string(self):
706        """
707        Create a readable string of the current latitude data
708        :return: string
709        """
710        if self.coord_format == 'dd':
711            formatted_latitude = self.latitude
712            lat_string = str(formatted_latitude[0]) + '° ' + str(self._latitude[2])
713        elif self.coord_format == 'dms':
714            formatted_latitude = self.latitude
715            lat_string = str(formatted_latitude[0]) + '° ' + str(formatted_latitude[1]) + "' " + str(formatted_latitude[2]) + '" ' + str(formatted_latitude[3])
716        else:
717            lat_string = str(self._latitude[0]) + '° ' + str(self._latitude[1]) + "' " + str(self._latitude[2])
718        return lat_string
719
720    def longitude_string(self):
721        """
722        Create a readable string of the current longitude data
723        :return: string
724        """
725        if self.coord_format == 'dd':
726            formatted_longitude = self.longitude
727            lon_string = str(formatted_longitude[0]) + '° ' + str(self._longitude[2])
728        elif self.coord_format == 'dms':
729            formatted_longitude = self.longitude
730            lon_string = str(formatted_longitude[0]) + '° ' + str(formatted_longitude[1]) + "' " + str(formatted_longitude[2]) + '" ' + str(formatted_longitude[3])
731        else:
732            lon_string = str(self._longitude[0]) + '° ' + str(self._longitude[1]) + "' " + str(self._longitude[2])
733        return lon_string
734
735    def speed_string(self, unit='kph'):
736        """
737        Creates a readable string of the current speed data in one of three units
738        :param unit: string of 'kph','mph, or 'knot'
739        :return:
740        """
741        if unit == 'mph':
742            speed_string = str(self.speed[1]) + ' mph'
743
744        elif unit == 'knot':
745            if self.speed[0] == 1:
746                unit_str = ' knot'
747            else:
748                unit_str = ' knots'
749            speed_string = str(self.speed[0]) + unit_str
750
751        else:
752            speed_string = str(self.speed[2]) + ' km/h'
753
754        return speed_string
755
756    def date_string(self, formatting='s_mdy', century='20'):
757        """
758        Creates a readable string of the current date.
759        Can select between long format: Januray 1st, 2014
760        or two short formats:
761        11/01/2014 (MM/DD/YYYY)
762        01/11/2014 (DD/MM/YYYY)
763        :param formatting: string 's_mdy', 's_dmy', or 'long'
764        :param century: int delineating the century the GPS data is from (19 for 19XX, 20 for 20XX)
765        :return: date_string  string with long or short format date
766        """
767
768        # Long Format Januray 1st, 2014
769        if formatting == 'long':
770            # Retrieve Month string from private set
771            month = self.__MONTHS[self.date[1] - 1]
772
773            # Determine Date Suffix
774            if self.date[0] in (1, 21, 31):
775                suffix = 'st'
776            elif self.date[0] in (2, 22):
777                suffix = 'nd'
778            elif self.date[0] == (3, 23):
779                suffix = 'rd'
780            else:
781                suffix = 'th'
782
783            day = str(self.date[0]) + suffix  # Create Day String
784
785            year = century + str(self.date[2])  # Create Year String
786
787            date_string = month + ' ' + day + ', ' + year  # Put it all together
788
789        else:
790            # Add leading zeros to day string if necessary
791            if self.date[0] < 10:
792                day = '0' + str(self.date[0])
793            else:
794                day = str(self.date[0])
795
796            # Add leading zeros to month string if necessary
797            if self.date[1] < 10:
798                month = '0' + str(self.date[1])
799            else:
800                month = str(self.date[1])
801
802            # Add leading zeros to year string if necessary
803            if self.date[2] < 10:
804                year = '0' + str(self.date[2])
805            else:
806                year = str(self.date[2])
807
808            # Build final string based on desired formatting
809            if formatting == 's_dmy':
810                date_string = day + '/' + month + '/' + year
811
812            else:  # Default date format
813                date_string = month + '/' + day + '/' + year
814
815        return date_string
816
817    # All the currently supported NMEA sentences
818    supported_sentences = {'GPRMC': gprmc, 'GLRMC': gprmc,
819                           'GPGGA': gpgga, 'GLGGA': gpgga,
820                           'GPVTG': gpvtg, 'GLVTG': gpvtg,
821                           'GPGSA': gpgsa, 'GLGSA': gpgsa,
822                           'GPGSV': gpgsv, 'GLGSV': gpgsv,
823                           'GPGLL': gpgll, 'GLGLL': gpgll,
824                           'GNGGA': gpgga, 'GNRMC': gprmc,
825                           'GNVTG': gpvtg, 'GNGLL': gpgll,
826                           'GNGSA': gpgsa,
827                          }
828
829if __name__ == "__main__":
830    pass

3. SSD1306.py

Copy it from: https://techtotinker.com/010-micropython-technotes-0-96-oled-display/

References And Credits

  1. Purchase your Gorillacell ESP32 development kit at: https://gorillacell.kr/

Or email them at: alphaco@hanmail.net

  1. microGPS.py Github repository: https://github.com/inmcm/micropyGPS

  2. Convert degrees minutes seconds to decimal degrees by visiting: https://www.rapidtables.com/convert/number/degrees-minutes-seconds-to-degrees.html

Additional Information

In case you want to plot the GPS coordinates on the map, you may achieve it by:

  1. Parse the latitude and convert it as decimal degrees.
  2. Parse the longitude and convert it as decimal degrees.
  3. Parse the altitude in meters.
  4. Concatenate the coordinates to the following internet link:

https://www.google.com/maps/@&lt;latitude&gt;,&lt;longitude&gt;,&lt;altitude&gt;

For example: https://www.google.com/maps/@7.360303,125.7525,14z

  1. The links in the example should open a google maps in the location provided by the GPS module.


Posts in this series



No comments yet!

GitHub-flavored Markdown & a sane subset of HTML is supported.