diff --git a/README.md b/README.md index b38cec9..5f1a5f7 100644 --- a/README.md +++ b/README.md @@ -1,249 +1,6 @@ -# micropyGPS +# mini_micropyGPS ## Overview -micropyGPS is a full featured GPS NMEA sentence parser for use with [MicroPython] and the PyBoard embedded -platform. It's also fully compatible with Python 3.x - -Features: - - - Parses and verifies most of the important [NMEA-0183] output messages into easily handled data structures - - Provides helper methods to interpret, present, log, and manipulate the GPS data - - Written in pure Python 3.x using only the standard libraries available in Micropython - - Implemented as a single class within a single file for easy integration into an embedded project - - Parser written with a serial UART data source in mind; works on a single character at a time with - robust error handling for noisy embedded environments - - Modeled after the great [TinyGPS] Arduino library - -## Install / uninstall - -Install by cloning from git and running install via setuptools. - -```sh -git clone https://github.com/inmcm/micropyGPS.git -python setup.py install -``` - -Or install directly from github using pip. - -```sh -pip install git+https://github.com/inmcm/micropyGPS.git -``` - -To uninstall use the following pip command. - -```sh -pip uninstall micropyGPS -``` - - -## Basic Usage - -micropyGPS is easy to use: copy micropyGPS.py into your project directory and import the MicropyGPS class into your script. From -there, just create a new GPS object and start feeding it data using the ```update()``` method. After you've feed it an entire valid sentence, it will return the sentence type and the internal GPS attributes will be updated. The example below shows the parsing of an RMC sentence and the object returns a tuple with the current latitude data - -```sh ->>> from micropyGPS import MicropyGPS ->>> my_gps = MicropyGPS() ->>> my_sentence = '$GPRMC,081836,A,3751.65,S,14507.36,E,000.0,360.0,130998,011.3,E*62' ->>> for x in my_sentence: -... my_gps.update(x) -... -'GPRMC' ->>> my_gps.latitude -(37, 51.65, 'S') -``` -The object will continue to accept new characters and parse sentences for as long as it exists. Each type of sentence parsed can update different internal attributes in your GPS object. - -If you have `pytest` installed, running it with the ```test_micropyGPS.py``` script will parse a number of example sentences of various types and test the various parsing, logging, and printing mechanics. - -```sh -$ pytest -svvv test_micropyGPS.py -``` - -### Currently Supported Sentences - -* GPRMC -* GLRMC -* GNRMC -* GPGLL -* GLGLL -* GNGLL -* GPGGA -* GLGGA -* GNGGA -* GPVTG -* GLVTG -* GNVTG -* GPGSA -* GLGSA -* GNGSA -* GPGSV -* GLGSV - - -### Position Data -Data successfully parsed from valid sentences is stored in easily accessible object variables. Data with multiple components (like latitude and longitude) is stored in tuples. -```sh -## Latitude is 37° 51.65' S ->>> my_gps.latitude -(37, 51.65, 'S') -# Longitude is 145° 7.36' E ->>> my_gps.longitude -(145, 7.36, 'E') -# Course is 54.7° ->>> my_gps.course -54.7 -# Altitude is 280.2 meters ->>> my_gps.altitude -280.2 -# Distance from ideal geoid is -34 meters ->>> my_gps.geoid_height --34.0 -``` -Current speed is stored in a tuple of values representing knots, miles per hours and kilometers per hour -```sh ->>> my_gps.speed -(5.5, 6.3305, 10.186) -``` - -### Time and Date -The current UTC time is stored (hours,minutes,seconds) -```sh ->>> my_gps.timestamp -(8, 18, 36.0) ->>> my_gps.date -(22, 9, 05) -``` -The timezone can be automatically adjusted for using the by setting the ```local_offset``` when you create the object or anytime after. Setting it to ```-5``` means you are on Eastern Standard time in the United States. -```sh ->>> my_gps = MicropyGPS(-5) ->>> my_gps.local_offset --5 -# Update With Timestamp Sentence Data... ->>> my_gps.timestamp -(3, 18, 36.0) -``` - -The current UTC date is stored (day,month,year). **NOTE:** The date is not currently adjusted to match the timezone set in ```local_offset```. -```sh ->>> my_gps.date -(22, 9, 05) -``` - -### Satellite Data -Signal quality and individual satellite information is collected from GSV, GSA, and GGA sentences and made available in the following variables. -```sh ->>> my_gps.satellites_in_use -7 ->>> my_gps.satellites_used -[7, 2, 26, 27, 9, 4, 15] -# Fix types can be: 1 = no fix, 2 = 2D fix, 3 = 3D fix ->>> my_gps.fix_type -3 -# Dilution of Precision (DOP) values close to 1.0 indicate excellent quality position data ->>> my_gps.hdop -1.0 ->>> my_gps.vdop -1.5 ->>> my_gps.pdop -1.8 -``` - The ```satellite_data_updated()``` method should be check to be ```True``` before trying to read out individual satellite data. This ensures all related GSV sentences are parsed and satellite info is complete -```sh ->>> my_gps.satellite_data_updated() -True -# Satellite data is a dict where the key is the satellite number and the value pair is a tuple containing (Elevation, Azimuth, SNR (if available)) ->>> my_gps.satellite_data -{19: (5, 273, None), 32: (5, 303, None), 4: (22, 312, 26), 11: (9, 315, 16), 12: (19, 88, 23), 14: (64, 296, 22), 15: (2, 73, None), 18: (54, 114, 21), 51: (40, 212, None), 21: (16, 175, None), 22: (81, 349, 25), 24: (30, 47, 22), 25: (17, 127, 18), 31: (22, 204, None)} -# Returns just the satellite PRNs visible ->>> my_gps.satellites_visible() -[19, 32, 4, 11, 12, 14, 15, 18, 51, 21, 22, 24, 25, 31] -``` - -### GPS Statistics -While parsing sentences, the MicropyGPS object tracks the number of number of parsed sentences as well as the number of CRC failures. ```parsed_sentences``` are those sentences that passed the base sentence catcher with clean CRCs. ```clean_sentences``` refers to the number of sentences parsed by their specific function successfully. -```sh ->>> my_gps.parsed_sentences -14 ->>> my_gps.clean_sentences -14 ->>> my_gps.crc_fails -0 -``` -The amount of real time passed since the last sentence with valid fix data was parse is also made available. **NOTE:** On the pyBoard, this value is returned in milliseconds while on Unix/Windows it is returned in seconds. -```sh -# Assume running on pyBoard ->>> my_gps.time_since_fix() -3456 -``` - -### Logging -micropyGPS currently can do very basic automatic logging of raw NMEA sentence data to a file. Any valid ASCII character passed into the parser, while the logging is enabled, is logged to a target file. This is useful if processing GPS sentences, but want to save the collected data for archive or further analysis. Due to the relative size of the log files, it's highly recommended to use an SD card as your storage medium as opposed to the emulated memory on the STM32 micro. All logging methods return a boolean if the operation succeeded or not. -```sh -# Logging can be started at any time with the start_logging() ->>> my_gps.start_logging('log.txt') -True -# Arbitrary strings can be written into the log file with write_log() method ->>> my_gps.write_log('Some note for the log file') -True -# Stop logging and close the log file with stop_logging() ->>> my_gps.stop_logging() -True -``` - -### Prettier Printing -Several functions are included that allow for GPS data to be expressed in nicer formats than tuples and ints. -```sh ->>> my_gps.latitude_string() -"41° 24.8963' N" ->>> my_gps.longitude_string() -"81° 51.6838' W" ->>> my_gps.speed_string('kph') -'10.186 km/h' ->>> my_gps.speed_string('mph') -'6.3305 mph' -my_gps.speed_string('knot') -'5.5 knots' -# Nearest compass point based on current course -my_gps.compass_direction() -'NE' ->>> my_gps.date_string('long') -'September 13th, 2098' -# Note the correct century should be provided for GPS data taken in the 1900s ->>> my_gps.date_string('long','19') -'September 13th, 1998' ->>> my_gps.date_string('s_mdy') -'09/13/98' ->>> my_gps.date_string('s_dmy') -'13/09/98' -``` -## Pyboard Usage - -Test scripts are included to help get started with using micropyGPS on the [pyboard] platform. These scripts can be copied over to the pyboards internal memory or placed on the SD Card. Make sure, when running them on the pyboard, to rename script you're using to **main.py** or update **boot.py** with the name of script you wish to run. - - - **uart_test.py** is a simple UART echo program to test if both your GPS is hooked up and UART is configured correctly. Some of the standard NMEA sentences should print out once a second (or faster depending on your GPS update rate) if everything is OK - - **sentence_test.py** will try and parse all incoming characters from the UART. This script requires micropyGPS.py be present in the same area of storage (SD Card or internal). Whenever a set of characters comprising a valid sentence is received and parsed, the script will print the type of sentence. - - **GPIO_interrupt_updater.py** is an example of how to use external interrupt to trigger an update of GPS data. In this case, a periodic signal (1Hz GPS output) is attached to pin X8 causing a mass parsing event every second. - -Adjusting the baud rate and update rate of the receiver can be easily accomplished with my companion [MTK_command] script - -An example of how to hookup the pyboard to the Adafruit [Ultimate GPS Breakout] (minus the PPS signal needed in the external interrupt example) is shown below. - -![hookup](http://i.imgur.com/yd4Mjka.jpg?1) - -## ESP32 -You can follow the setup instructions for the pyboard. The only difference is, that you shoud use micropyGPS as a [frozen module]. Otherwise there will be exceptions, because there is not enough heap space available. - -## Other Platforms -As mentioned above, micropyGPS also runs on Python3.x (that's where most of the development was done!). This is useful for testing code or just parsing existing log files. - -Beyond the pyBoard and ESP32, micropyGPS should run on other embedded platforms that have an Python3 interpreter such as the Raspberry Pi and BeagleBone boards. These other devices are currently untested. - -[Micropython]:https://micropython.org/ -[frozen module]:https://learn.adafruit.com/micropython-basics-loading-modules/frozen-modules -[NMEA-0183]:http://aprs.gids.nl/nmea/ -[TinyGPS]:http://arduiniana.org/libraries/tinygps/ -[pyboard]:http://docs.micropython.org/en/latest/pyboard/pyboard/quickref.html -[MTK_command]:https://github.com/inmcm/MTK_commands -[Ultimate GPS Breakout]:http://www.adafruit.com/product/746 +This repository is a fork of micropyGPS, with the addition of the directory of esp32, which has files for +a fully operational version of micropyGPS for the ESP32 without the need of using frozen modules. diff --git a/esp32/esp32_gps_receive.py b/esp32/esp32_gps_receive.py new file mode 100644 index 0000000..7d0cb6c --- /dev/null +++ b/esp32/esp32_gps_receive.py @@ -0,0 +1,31 @@ + +#Receives GPS data over the ESP32 UART +#and prints main GPS information: date, time, latitute, longitude + +from machine import UART + +from mini_micropyGPS import MicropyGPS + +uart = UART(2, tx=17, rx=16) +uart.init(38400, bits=8, parity=None, stop=1) + +# Instatntiate the micropyGPS object +my_gps = MicropyGPS() + +# Continuous Tests for characters available in the UART buffer, any characters are feed into the GPS +# object. When enough char are feed to represent a whole, valid sentence, stat is set as the name of the +# sentence and printed +while True: + if uart.any(): + c=int.from_bytes(uart.read(1), "big") + stat = my_gps.update(chr(c)) # Note the conversion to to chr, UART outputs ints normally + if stat: + print(stat) + stat = None + print('UTC Timestamp:', my_gps.timestamp) + print('Date:', my_gps.date_string('long')) + print('Latitude:', my_gps.latitude_string()) + print('Longitude:', my_gps.longitude_string()) + print('Horizontal Dilution of Precision:', my_gps.hdop) + print() + new_data = False # Clear the flag diff --git a/esp32/mini_micropyGPS.py b/esp32/mini_micropyGPS.py new file mode 100644 index 0000000..363b46a --- /dev/null +++ b/esp32/mini_micropyGPS.py @@ -0,0 +1,623 @@ +""" +# MicropyGPS - a GPS NMEA sentence parser for Micropython/Python 3.X +# Copyright (c) 2017 Michael Calvin McCoy (calvin.mccoy@protonmail.com) +# The MIT License (MIT) - see LICENSE file +# +# mini_micropyGPS.py is a is a reduced version of micropyGPS.py +# with the support for the main NMEA messages. +# Tested and working with ESP32 without the need of including it as a frozen module. +# +""" + +# TODO: +# Time Since First Fix +# Distance/Time to Target +# More Helper Functions +# Dynamically limit sentences types to parse + +from math import floor, modf + +# Import utime or time for fix time handling +try: + # Assume running on MicroPython + import utime +except ImportError: + # Otherwise default to time module for non-embedded implementations + # Should still support millisecond resolution. + import time + + +class MicropyGPS(object): + """GPS NMEA Sentence Parser. Creates object that stores all relevant GPS data and statistics. + Parses sentences one character at a time using update(). """ + + # Max Number of Characters a valid sentence can be (based on GGA sentence) + SENTENCE_LIMIT = 90 + __HEMISPHERES = ('N', 'S', 'E', 'W') + __NO_FIX = 1 + __FIX_2D = 2 + __FIX_3D = 3 + __DIRECTIONS = ('N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', + 'WNW', 'NW', 'NNW') + __MONTHS = ('January', 'February', 'March', 'April', 'May', + 'June', 'July', 'August', 'September', 'October', + 'November', 'December') + + def __init__(self, local_offset=0, location_formatting='ddm'): + """ + Setup GPS Object Status Flags, Internal Data Registers, etc + local_offset (int): Timzone Difference to UTC + location_formatting (str): Style For Presenting Longitude/Latitude: + Decimal Degree Minute (ddm) - 40 26.7672 N + Degrees Minutes Seconds (dms) - 40 262 463 N + Decimal Degrees (dd) - 40.446 N + """ + + ##################### + # Object Status Flags + self.sentence_active = False + self.active_segment = 0 + self.process_crc = False + self.gps_segments = [] + self.crc_xor = 0 + self.char_count = 0 + self.fix_time = 0 + + ##################### + # Sentence Statistics + self.crc_fails = 0 + self.clean_sentences = 0 + self.parsed_sentences = 0 + + ##################### + # Logging Related + self.log_handle = None + self.log_en = False + + ##################### + # Data From Sentences + # Time + self.timestamp = [0, 0, 0.0] + self.date = [0, 0, 0] + self.local_offset = local_offset + + # Position/Motion + self._latitude = [0, 0.0, 'N'] + self._longitude = [0, 0.0, 'W'] + self.coord_format = location_formatting + self.speed = [0.0, 0.0, 0.0] + self.course = 0.0 + self.altitude = 0.0 + self.geoid_height = 0.0 + + # GPS Info + self.satellites_in_view = 0 + self.satellites_in_use = 0 + self.satellites_used = [] + self.last_sv_sentence = 0 + self.total_sv_sentences = 0 + self.satellite_data = dict() + self.hdop = 0.0 + self.pdop = 0.0 + self.vdop = 0.0 + self.valid = False + self.fix_stat = 0 + self.fix_type = 1 + + ######################################## + # Coordinates Translation Functions + ######################################## + @property + def latitude(self): + """Format Latitude Data Correctly""" + if self.coord_format == 'dd': + decimal_degrees = self._latitude[0] + (self._latitude[1] / 60) + return [decimal_degrees, self._latitude[2]] + elif self.coord_format == 'dms': + minute_parts = modf(self._latitude[1]) + seconds = round(minute_parts[0] * 60) + return [self._latitude[0], int(minute_parts[1]), seconds, self._latitude[2]] + else: + return self._latitude + + @property + def longitude(self): + """Format Longitude Data Correctly""" + if self.coord_format == 'dd': + decimal_degrees = self._longitude[0] + (self._longitude[1] / 60) + return [decimal_degrees, self._longitude[2]] + elif self.coord_format == 'dms': + minute_parts = modf(self._longitude[1]) + seconds = round(minute_parts[0] * 60) + return [self._longitude[0], int(minute_parts[1]), seconds, self._longitude[2]] + else: + return self._longitude + + + ######################################## + # Sentence Parsers + ######################################## + def gprmc(self): + """Parse Recommended Minimum Specific GPS/Transit data (RMC)Sentence. + Updates UTC timestamp, latitude, longitude, Course, Speed, Date, and fix status + """ + + # UTC Timestamp + try: + utc_string = self.gps_segments[1] + + if utc_string: # Possible timestamp found + hours = (int(utc_string[0:2]) + self.local_offset) % 24 + minutes = int(utc_string[2:4]) + seconds = float(utc_string[4:]) + self.timestamp = [hours, minutes, seconds] + else: # No Time stamp yet + self.timestamp = [0, 0, 0.0] + + except ValueError: # Bad Timestamp value present + return False + + # Date stamp + try: + date_string = self.gps_segments[9] + + # Date string printer function assumes to be year >=2000, + # date_string() must be supplied with the correct century argument to display correctly + if date_string: # Possible date stamp found + day = int(date_string[0:2]) + month = int(date_string[2:4]) + year = int(date_string[4:6]) + self.date = (day, month, year) + else: # No Date stamp yet + self.date = (0, 0, 0) + + except ValueError: # Bad Date stamp value present + return False + + # Check Receiver Data Valid Flag + if self.gps_segments[2] == 'A': # Data from Receiver is Valid/Has Fix + + # Longitude / Latitude + try: + # Latitude + l_string = self.gps_segments[3] + lat_degs = int(l_string[0:2]) + lat_mins = float(l_string[2:]) + lat_hemi = self.gps_segments[4] + + # Longitude + l_string = self.gps_segments[5] + lon_degs = int(l_string[0:3]) + lon_mins = float(l_string[3:]) + lon_hemi = self.gps_segments[6] + except ValueError: + return False + + if lat_hemi not in self.__HEMISPHERES: + return False + + if lon_hemi not in self.__HEMISPHERES: + return False + + # Speed + try: + spd_knt = float(self.gps_segments[7]) + except ValueError: + return False + + # Course + try: + if self.gps_segments[8]: + course = float(self.gps_segments[8]) + else: + course = 0.0 + except ValueError: + return False + + # TODO - Add Magnetic Variation + + # Update Object Data + self._latitude = [lat_degs, lat_mins, lat_hemi] + self._longitude = [lon_degs, lon_mins, lon_hemi] + # Include mph and hm/h + self.speed = [spd_knt, spd_knt * 1.151, spd_knt * 1.852] + self.course = course + self.valid = True + + # Update Last Fix Time + self.new_fix_time() + + else: # Clear Position Data if Sentence is 'Invalid' + self._latitude = [0, 0.0, 'N'] + self._longitude = [0, 0.0, 'W'] + self.speed = [0.0, 0.0, 0.0] + self.course = 0.0 + self.valid = False + + return True + + + def gpvtg(self): + """Parse Track Made Good and Ground Speed (VTG) Sentence. Updates speed and course""" + try: + course = float(self.gps_segments[1]) if self.gps_segments[1] else 0.0 + spd_knt = float(self.gps_segments[5]) if self.gps_segments[5] else 0.0 + except ValueError: + return False + + # Include mph and km/h + self.speed = (spd_knt, spd_knt * 1.151, spd_knt * 1.852) + self.course = course + return True + + def gpgga(self): + """Parse Global Positioning System Fix Data (GGA) Sentence. Updates UTC timestamp, latitude, longitude, + fix status, satellites in use, Horizontal Dilution of Precision (HDOP), altitude, geoid height and fix status""" + + try: + # UTC Timestamp + utc_string = self.gps_segments[1] + + # Skip timestamp if receiver doesn't have on yet + if utc_string: + hours = (int(utc_string[0:2]) + self.local_offset) % 24 + minutes = int(utc_string[2:4]) + seconds = float(utc_string[4:]) + else: + hours = 0 + minutes = 0 + seconds = 0.0 + + # Number of Satellites in Use + satellites_in_use = int(self.gps_segments[7]) + + # Get Fix Status + fix_stat = int(self.gps_segments[6]) + + except (ValueError, IndexError): + return False + + try: + # Horizontal Dilution of Precision + hdop = float(self.gps_segments[8]) + except (ValueError, IndexError): + hdop = 0.0 + + # Process Location and Speed Data if Fix is GOOD + if fix_stat: + + # Longitude / Latitude + try: + # Latitude + l_string = self.gps_segments[2] + lat_degs = int(l_string[0:2]) + lat_mins = float(l_string[2:]) + lat_hemi = self.gps_segments[3] + + # Longitude + l_string = self.gps_segments[4] + lon_degs = int(l_string[0:3]) + lon_mins = float(l_string[3:]) + lon_hemi = self.gps_segments[5] + except ValueError: + return False + + if lat_hemi not in self.__HEMISPHERES: + return False + + if lon_hemi not in self.__HEMISPHERES: + return False + + # Altitude / Height Above Geoid + try: + altitude = float(self.gps_segments[9]) + geoid_height = float(self.gps_segments[11]) + except ValueError: + altitude = 0 + geoid_height = 0 + + # Update Object Data + self._latitude = [lat_degs, lat_mins, lat_hemi] + self._longitude = [lon_degs, lon_mins, lon_hemi] + self.altitude = altitude + self.geoid_height = geoid_height + + # Update Object Data + self.timestamp = [hours, minutes, seconds] + self.satellites_in_use = satellites_in_use + self.hdop = hdop + self.fix_stat = fix_stat + + # If Fix is GOOD, update fix timestamp + if fix_stat: + self.new_fix_time() + + return True + + + + ########################################## + # Data Stream Handler Functions + ########################################## + + def new_sentence(self): + """Adjust Object Flags in Preparation for a New Sentence""" + self.gps_segments = [''] + self.active_segment = 0 + self.crc_xor = 0 + self.sentence_active = True + self.process_crc = True + self.char_count = 0 + + def update(self, new_char): + """Process a new input char and updates GPS object if necessary based on special characters ('$', ',', '*') + Function builds a list of received string that are validate by CRC prior to parsing by the appropriate + sentence function. Returns sentence type on successful parse, None otherwise""" + + valid_sentence = False + + # Validate new_char is a printable char + ascii_char = ord(new_char) + + if 10 <= ascii_char <= 126: + self.char_count += 1 + + # Write Character to log file if enabled + if self.log_en: + self.write_log(new_char) + + # Check if a new string is starting ($) + if new_char == '$': + self.new_sentence() + return None + + elif self.sentence_active: + + # Check if sentence is ending (*) + if new_char == '*': + self.process_crc = False + self.active_segment += 1 + self.gps_segments.append('') + return None + + # Check if a section is ended (,), Create a new substring to feed + # characters to + elif new_char == ',': + self.active_segment += 1 + self.gps_segments.append('') + + # Store All Other printable character and check CRC when ready + else: + self.gps_segments[self.active_segment] += new_char + + # When CRC input is disabled, sentence is nearly complete + if not self.process_crc: + + if len(self.gps_segments[self.active_segment]) == 2: + try: + final_crc = int(self.gps_segments[self.active_segment], 16) + if self.crc_xor == final_crc: + valid_sentence = True + else: + self.crc_fails += 1 + except ValueError: + pass # CRC Value was deformed and could not have been correct + + # Update CRC + if self.process_crc: + self.crc_xor ^= ascii_char + + # If a Valid Sentence Was received and it's a supported sentence, then parse it!! + if valid_sentence: + self.clean_sentences += 1 # Increment clean sentences received + self.sentence_active = False # Clear Active Processing Flag + + if self.gps_segments[0] in self.supported_sentences: + + # parse the Sentence Based on the message type, return True if parse is clean + if self.supported_sentences[self.gps_segments[0]](self): + + # Let host know that the GPS object was updated by returning parsed sentence type + self.parsed_sentences += 1 + return self.gps_segments[0] + + # Check that the sentence buffer isn't filling up with Garage waiting for the sentence to complete + if self.char_count > self.SENTENCE_LIMIT: + self.sentence_active = False + + # Tell Host no new sentence was parsed + return None + + def new_fix_time(self): + """Updates a high resolution counter with current time when fix is updated. Currently only triggered from + GGA, GSA and RMC sentences""" + try: + self.fix_time = utime.ticks_ms() + except NameError: + self.fix_time = time.time() + + ######################################### + # User Helper Functions + # These functions make working with the GPS object data easier + ######################################### + + def satellite_data_updated(self): + """ + Checks if the all the GSV sentences in a group have been read, making satellite data complete + :return: boolean + """ + if self.total_sv_sentences > 0 and self.total_sv_sentences == self.last_sv_sentence: + return True + else: + return False + + def unset_satellite_data_updated(self): + """ + Mark GSV sentences as read indicating the data has been used and future updates are fresh + """ + self.last_sv_sentence = 0 + + def satellites_visible(self): + """ + Returns a list of of the satellite PRNs currently visible to the receiver + :return: list + """ + return list(self.satellite_data.keys()) + + def time_since_fix(self): + """Returns number of millisecond since the last sentence with a valid fix was parsed. Returns 0 if + no fix has been found""" + + # Test if a Fix has been found + if self.fix_time == 0: + return -1 + + # Try calculating fix time using utime; if not running MicroPython + # time.time() returns a floating point value in secs + try: + current = utime.ticks_diff(utime.ticks_ms(), self.fix_time) + except NameError: + current = (time.time() - self.fix_time) * 1000 # ms + + return current + + def compass_direction(self): + """ + Determine a cardinal or inter-cardinal direction based on current course. + :return: string + """ + # Calculate the offset for a rotated compass + if self.course >= 348.75: + offset_course = 360 - self.course + else: + offset_course = self.course + 11.25 + + # Each compass point is separated by 22.5 degrees, divide to find lookup value + dir_index = floor(offset_course / 22.5) + + final_dir = self.__DIRECTIONS[dir_index] + + return final_dir + + def latitude_string(self): + """ + Create a readable string of the current latitude data + :return: string + """ + if self.coord_format == 'dd': + formatted_latitude = self.latitude + lat_string = str(formatted_latitude[0]) + ' ' + str(self._latitude[2]) + elif self.coord_format == 'dms': + formatted_latitude = self.latitude + lat_string = str(formatted_latitude[0]) + ' ' + str(formatted_latitude[1]) + "' " + str(formatted_latitude[2]) + '" ' + str(formatted_latitude[3]) + else: + lat_string = str(self._latitude[0]) + ' ' + str(self._latitude[1]) + "' " + str(self._latitude[2]) + return lat_string + + def longitude_string(self): + """ + Create a readable string of the current longitude data + :return: string + """ + if self.coord_format == 'dd': + formatted_longitude = self.longitude + lon_string = str(formatted_longitude[0]) + ' ' + str(self._longitude[2]) + elif self.coord_format == 'dms': + formatted_longitude = self.longitude + lon_string = str(formatted_longitude[0]) + ' ' + str(formatted_longitude[1]) + "' " + str(formatted_longitude[2]) + '" ' + str(formatted_longitude[3]) + else: + lon_string = str(self._longitude[0]) + ' ' + str(self._longitude[1]) + "' " + str(self._longitude[2]) + return lon_string + + def speed_string(self, unit='kph'): + """ + Creates a readable string of the current speed data in one of three units + :param unit: string of 'kph','mph, or 'knot' + :return: + """ + if unit == 'mph': + speed_string = str(self.speed[1]) + ' mph' + + elif unit == 'knot': + if self.speed[0] == 1: + unit_str = ' knot' + else: + unit_str = ' knots' + speed_string = str(self.speed[0]) + unit_str + + else: + speed_string = str(self.speed[2]) + ' km/h' + + return speed_string + + def date_string(self, formatting='s_mdy', century='20'): + """ + Creates a readable string of the current date. + Can select between long format: Januray 1st, 2014 + or two short formats: + 11/01/2014 (MM/DD/YYYY) + 01/11/2014 (DD/MM/YYYY) + :param formatting: string 's_mdy', 's_dmy', or 'long' + :param century: int delineating the century the GPS data is from (19 for 19XX, 20 for 20XX) + :return: date_string string with long or short format date + """ + + # Long Format Januray 1st, 2014 + if formatting == 'long': + # Retrieve Month string from private set + month = self.__MONTHS[self.date[1] - 1] + + # Determine Date Suffix + if self.date[0] in (1, 21, 31): + suffix = 'st' + elif self.date[0] in (2, 22): + suffix = 'nd' + elif self.date[0] == (3, 23): + suffix = 'rd' + else: + suffix = 'th' + + day = str(self.date[0]) + suffix # Create Day String + + year = century + str(self.date[2]) # Create Year String + + date_string = month + ' ' + day + ', ' + year # Put it all together + + else: + # Add leading zeros to day string if necessary + if self.date[0] < 10: + day = '0' + str(self.date[0]) + else: + day = str(self.date[0]) + + # Add leading zeros to month string if necessary + if self.date[1] < 10: + month = '0' + str(self.date[1]) + else: + month = str(self.date[1]) + + # Add leading zeros to year string if necessary + if self.date[2] < 10: + year = '0' + str(self.date[2]) + else: + year = str(self.date[2]) + + # Build final string based on desired formatting + if formatting == 's_dmy': + date_string = day + '/' + month + '/' + year + + else: # Default date format + date_string = month + '/' + day + '/' + year + + return date_string + + # All the currently supported NMEA sentences + supported_sentences = {'GPRMC': gprmc, 'GLRMC': gprmc, + 'GPGGA': gpgga, 'GLGGA': gpgga, + 'GPVTG': gpvtg, 'GLVTG': gpvtg, + 'GNGGA': gpgga, 'GNRMC': gprmc, + + } + +if __name__ == "__main__": + pass diff --git a/esp32/uart_test_esp32.py b/esp32/uart_test_esp32.py new file mode 100644 index 0000000..4957b79 --- /dev/null +++ b/esp32/uart_test_esp32.py @@ -0,0 +1,16 @@ +#This program shows raw GPS messages received over the serial port, just to check if the data reception is ok. +#You may have to adjust the speed, according with your GPS: for example: 4800, 9600, 19200... + +from machine import UART + +uart = UART(2, tx=17, rx=16) +uart.init(38400, bits=8, parity=None, stop=1) + +while True: + data = uart.read(1) + + if data is not None: + # Show the byte as 2 hex digits then in the default way + #print("%02x " % (data[0]), end='') + # Or, show the string as received + print("%c" % (data[0]), end='')