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:
- GND – for the ground pin.
- VCC – for the supply voltage.
- Tx – for the UART serial transmit pin, connect the Rx pin of the microcontroller.
- Rx – for the UART serial receive pin, connect the Tx pin of the microcontroller.
Bill Of Materials
- ESP32 development board.
- Gorillacell ESP32 shield, optional.
- Gorillacell GPS module set with antenna.
- 0.96 OLED for the display or just use the REPL to observe the GPS data.
- 4-pin dupont wires.
Hardware Instruction
- Connect the GPS module GND pin to the ESP32 GND pin.
- Connect the GPS module VCC pin to the ESP32 5V pin.
- Connect the GPS module Tx pin to the ESP32 GPIO 25.
- Connect the GPS module Rx pin to the EPS32 GPIO 26.
- Connect the OLED module GND pin to the ESP32 GND pin.
- Connect the OLED module VCC pin to the ESP32 5V pin.
- Connect the OLED module SDA pin to the ESP32 GPIO 21.
- Connect the OLED module SCL pin to the ESP32 GPIO 22.
Software Instruction
-
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.
-
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.
-
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
- Purchase your Gorillacell ESP32 development kit at: https://gorillacell.kr/
Or email them at: alphaco@hanmail.net
-
microGPS.py Github repository: https://github.com/inmcm/micropyGPS
-
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:
- Parse the latitude and convert it as decimal degrees.
- Parse the longitude and convert it as decimal degrees.
- Parse the altitude in meters.
- Concatenate the coordinates to the following internet link:
https://www.google.com/maps/@<latitude>,<longitude>,<altitude>
For example:
https://www.google.com/maps/@7.360303,125.7525,14z
- The links in the example should open a google maps in the location provided by the GPS module.
Posts in this series
- 049 - MicroPython TechNotes: MP3 Player
- 048 - MicroPython TechNotes: Analog Touch Sensor
- 046 - MicroPython TechNotes: RF433 Transceivers
- 045 - MicroPython TechNotes: Infrared Transmitter
- 044 - MicroPython TechNotes: Infrared Receiver
- 043 - MicroPython TechNotes: ESP12E WiFi | External WiFi module
- 042 - MicroPython TechNotes: JDY-32 | Bluetooth Low Energy BLE
- 041 - MicroPython TechNotes: Bluetooth HC-06
- 040 - MicroPython TechNotes: Relay
- 039 - MicroPython TechNotes: Electromagnet
- 038 - MicroPython TechNotes: Buzzer
- 037 - MicroPython TechNotes: Servo Motor
- 036 - MicroPython TechNotes: Stepper Motor
- 035 - MicroPython TechNotes: Dual Motor Driver
- 034 - MicroPython TechNotes: DC Motors | Gear Motor and Fan Motor
- 033 - MicroPython TechNotes: TCS34725 RGB Color Sensor
- 032 - MicroPython TechNotes: BMP280 Sensor
- 031 - MicroPython TechNotes: TOF Distance Sensor
- 030 - MicroPython TechNotes: DS3231 RTC
- 029 - MicroPython TechNotes: HC-SR04 Ultrasonic Sensor
- 028 - MicroPython TechNotes: DHT11 Temperature and Humidity Sensor
- 027 - MicroPython TechNotes: Rotary Encoder
- 026 - MicroPython TechNotes: Light Dependent Resistor (LDR)
- 025 - MicroPython TechNotes: Joystick
- 024 - MicroPython TechNotes: Slider Switch
- 023 - MicroPython TechNotes: Continuous Rotation Potentiometer
- 022 - MicroPython TechNotes: Potentiometer | Reading an Analog Input
- 021 - MicroPython TechNotes: Color Touch Sensor
- 020 - MicroPython TechNotes: Touch Sensor
- 019 - MicroPython TechNotes: Switch Module
- 018 - MicroPython TechNotes: Button | Reading an Input
- 017 - MicroPython TechNotes: LASER Module
- 016 - MicroPython TechNotes: RGB LED Matrix
- 015 - MicroPython TechNotes: Neopixel 16
- 014 - MicroPython TechNotes: 8x8 Dot Matrix Display (I2C)
- 013 - MicroPython TechNotes: 8x16 Dot Matrix Display (SPI)
- 012 - MicroPython TechNotes: 8x8 Dot Matrix Display (SPI)
- 011 - MicroPython TechNotes: 1.3 OLED Display
- 010 - MicroPython TechNotes: 0.96 OLED Display
- 009 - MicroPython TechNotes: 7 Segment Display
- 008 - MicroPython TechNotes: 16x2 LCD
- 007 - MicroPython TechNotes: RGB LED
- 006 - MicroPython TechNotes: Traffic Light LED Module
- 005 - MicroPython TechNotes: Gorilla Cell LED | MicroPython Hello World
- 004 - MicroPython TechNotes: Gorilla Cell I/O Devices
- 003 - MicroPython TechNotes: Gorillacell ESP32 Shield
- 002 - MicroPython TechNotes: Introduction for Gorillacell ESP32 Dev Kit
- 001 - MicroPython TechNotes: Get Started with MicroPython
- 000 - MicroPython TechNotes: Unboxing Gorillacell ESP32 Development Kit
No comments yet!