diff --git a/micropyGPS.py b/micropyGPS.py index 61b19ed..c7e3843 100644 --- a/micropyGPS.py +++ b/micropyGPS.py @@ -11,6 +11,24 @@ # Dynamically limit sentences types to parse from math import floor, modf +from typing import Final, Callable + +# +# Proposal of improvements 2023-02 +# - formatted by black +# - add new format in date_string ('2023-01-01'): 'l_ymd' +# - add new format in speed_string ('3.2f') +# - add new 'timestamp_string' helper function +# - adjust code for being compliant with typing (pylance & mypy) +# - add parser for the antenna status: PGTOP: $PGTOP,11,x +# - implement suggestions from https://github.com/inmcm/micropyGPS/issues/14 +# - use of fstring instead of % formating +# - use of lists instead of tuples +# - adjust code in 'speed_string' when knot speed is within +-1 range (English grammar to be confirmed) +# - add pytest tests for checking the above +# - adjust minor clerical errors +# Works with python 3.11.2 +# Works with MicroPython 1.19.1 after stripping annotations (refer to https://github.com/orgs/micropython/discussions/10529#discussioncomment-4764412) # Import utime or time for fix time handling try: @@ -21,27 +39,118 @@ # Should still support millisecond resolution. import time +L_YMD: Final[str] = "l_ymd" +S_MDY: Final[str] = "s_mdy" +S_DMY: Final[str] = "s_dmy" +LONG: Final[str] = "long" +_DATE_FORMAT: tuple = ( + L_YMD, + S_MDY, + S_DMY, + LONG, +) # Not in use; could be checked against, as guard + +S_HMS: Final[str] = "s_hms" +_TIME_FORMAT: tuple = (S_HMS,) # Not in use; could be checked against, as guard + +_NORTH: Final[str] = "N" +_SOUTH: Final[str] = "S" +_EAST: Final[str] = "E" +_WEST: Final[str] = "W" + +MPH: Final[str] = "mph" +KPH: Final[str] = "km/h" +KNOT: Final[str] = "knot" +_SPEED_UNIT: tuple = (MPH, KPH, KNOT) + +FULL_FLOAT: Final[str] = "full_float" +F2_2: Final[str] = "f2_2" +_SPEED_FORMAT: tuple = ( + FULL_FLOAT, + F2_2, +) # Not in use; could be checked against, as guard + +DD: Final[str] = "dd" +DMS: Final[str] = "dms" +DDM: Final[str] = "ddm" +_LONG_LAT_FORMAT: tuple = ( + DD, + DMS, + DDM, +) # Not in use; could be checked against, as guard + +# Month ending formats +_FIRST: tuple = (1, 21, 31) +_SECOND: tuple = (2, 22) +_THIRD: tuple = (3, 23) + +# Specific types +t_code_message = dict[str, str] +t_antenna = tuple[dict[str, str], ...] +t_satellite = tuple[int | None, int | None, int | None] +t_satellite_dict = dict[int, t_satellite] + + +ANTENNA_PA1616S: Final[t_antenna] = ( + {"code": "1", "message": "Internal"}, + {"code": "2", "message": "Active"}, + {"code": "3", "message": "Error. Antenna shorted"}, +) +ANTENNA_PA6H: Final[t_antenna] = ( + {"code": "2", "message": "Internal"}, + {"code": "3", "message": "Active"}, + {"code": "1", "message": "Error. Antenna shorted"}, +) + 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(). """ + 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') + SENTENCE_LIMIT: Final[int] = 90 - def __init__(self, local_offset=0, location_formatting='ddm'): + __HEMISPHERES: Final[tuple[str, str, str, str]] = (_NORTH, _SOUTH, _EAST, _WEST) + __NO_FIX = 1 + # __FIX_2D = 2 # Not used + # __FIX_3D = 3 # Not used + __DIRECTIONS: Final[tuple[str, ...]] = ( + "N", + "NNE", + "NE", + "ENE", + "E", + "ESE", + "SE", + "SSE", + "S", + "SSW", + "SW", + "WSW", + "W", + "WNW", + "NW", + "NNW", + ) + __MONTHS: Final[tuple[str, ...]] = ( + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", + ) + + def __init__(self, local_offset: int = 0, location_formatting: str = DDM): """ Setup GPS Object Status Flags, Internal Data Registers, etc - local_offset (int): Timzone Difference to UTC + local_offset (int): Timezone Difference to UTC location_formatting (str): Style For Presenting Longitude/Latitude: Decimal Degree Minute (ddm) - 40° 26.767′ N Degrees Minutes Seconds (dms) - 40° 26′ 46″ N @@ -50,12 +159,12 @@ def __init__(self, local_offset=0, location_formatting='ddm'): ##################### # 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.sentence_active: bool = False + self.active_segment: int = 0 + self.process_crc: bool = False + self.gps_segments: list[str] = [] + self.crc_xor: int = 0 + self.char_count: int = 0 self.fix_time = 0 ##################### @@ -67,20 +176,20 @@ def __init__(self, local_offset=0, location_formatting='ddm'): ##################### # Logging Related self.log_handle = None - self.log_en = False + self.log_en: bool = False ##################### # Data From Sentences # Time - self.timestamp = [0, 0, 0.0] - self.date = [0, 0, 0] + self.timestamp: list[int | float] = [0, 0, 0.0] + self.date: list[int | float] = [0, 0, 0] self.local_offset = local_offset # Position/Motion - self._latitude = [0, 0.0, 'N'] - self._longitude = [0, 0.0, 'W'] + self._latitude = [0, 0.0, _NORTH] + self._longitude = [0, 0.0, _WEST] self.coord_format = location_formatting - self.speed = [0.0, 0.0, 0.0] + self.speed: list[float] = [0.0, 0.0, 0.0] self.course = 0.0 self.altitude = 0.0 self.geoid_height = 0.0 @@ -88,27 +197,33 @@ def __init__(self, local_offset=0, location_formatting='ddm'): # GPS Info self.satellites_in_view = 0 self.satellites_in_use = 0 - self.satellites_used = [] + self.satellites_used: list[int] = [] self.last_sv_sentence = 0 self.total_sv_sentences = 0 - self.satellite_data = dict() + self.satellite_data: t_satellite_dict = dict() self.hdop = 0.0 self.pdop = 0.0 self.vdop = 0.0 - self.valid = False + self.valid: bool = False self.fix_stat = 0 self.fix_type = 1 + # Antenna status + self.antenna_status = "Unknown antenna status" + + # Speed format representation + self._speed_formatter: Callable = self.full_format + ######################################## # 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) + if self.coord_format == DD: + decimal_degrees: float = self._latitude[0] + (self._latitude[1] / 60) return [decimal_degrees, self._latitude[2]] - elif self.coord_format == 'dms': + 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]] @@ -118,25 +233,30 @@ def latitude(self): @property def longitude(self): """Format Longitude Data Correctly""" - if self.coord_format == 'dd': - decimal_degrees = self._longitude[0] + (self._longitude[1] / 60) + if self.coord_format == DD: + decimal_degrees = self._longitude[0] + (float(self._longitude[1]) / 60) return [decimal_degrees, self._longitude[2]] - elif self.coord_format == 'dms': - minute_parts = modf(self._longitude[1]) + elif self.coord_format == DMS: + minute_parts: tuple[float, float] = modf(float(self._longitude[1])) seconds = round(minute_parts[0] * 60) - return [self._longitude[0], int(minute_parts[1]), seconds, self._longitude[2]] + return [ + self._longitude[0], + int(minute_parts[1]), + seconds, + self._longitude[2], + ] else: return self._longitude ######################################## # Logging Related Functions ######################################## - def start_logging(self, target_file, mode="append"): + def start_logging(self, target_file, mode="append") -> bool: """ Create GPS data log object """ # Set Write Mode Overwrite or Append - mode_code = 'w' if mode == 'new' else 'a' + mode_code = "w" if mode == "new" else "a" try: self.log_handle = open(target_file, mode_code) @@ -147,32 +267,40 @@ def start_logging(self, target_file, mode="append"): self.log_en = True return True - def stop_logging(self): + def stop_logging(self) -> bool: """ Closes the log file handler and disables further logging """ - try: - self.log_handle.close() - except AttributeError: + # try: + # self.log_handle.close() + # except AttributeError: + # print("Invalid Handle") + # return False + if self.log_handle is None: print("Invalid Handle") return False + self.log_handle.close() self.log_en = False return True - def write_log(self, log_string): - """Attempts to write the last valid NMEA sentence character to the active file handler - """ - try: - self.log_handle.write(log_string) - except TypeError: + def write_log(self, log_string) -> bool: + """Attempts to write the last valid NMEA sentence character to the active file handler""" + # try: + # self.log_handle.write(log_string) + # except TypeError: + # return False + # return True + if self.log_handle is None: + print("Invalid Handle") return False + self.log_handle.write(log_string) return True ######################################## # Sentence Parsers ######################################## - def gprmc(self): + def gprmc(self) -> bool: """Parse Recommended Minimum Specific GPS/Transit data (RMC)Sentence. Updates UTC timestamp, latitude, longitude, Course, Speed, Date, and fix status """ @@ -202,16 +330,15 @@ def gprmc(self): day = int(date_string[0:2]) month = int(date_string[2:4]) year = int(date_string[4:6]) - self.date = (day, month, year) + self.date = [day, month, year] else: # No Date stamp yet - self.date = (0, 0, 0) + 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 - + if self.gps_segments[2] == "A": # Data from Receiver is Valid/Has Fix # Longitude / Latitude try: # Latitude @@ -263,15 +390,15 @@ def gprmc(self): 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._latitude = [0, 0.0, _NORTH] + self._longitude = [0, 0.0, _WEST] self.speed = [0.0, 0.0, 0.0] self.course = 0.0 self.valid = False return True - def gpgll(self): + def gpgll(self) -> bool: """Parse Geographic Latitude and Longitude (GLL)Sentence. Updates UTC timestamp, latitude, longitude, and fix status""" @@ -291,8 +418,7 @@ def gpgll(self): return False # Check Receiver Data Valid Flag - if self.gps_segments[6] == 'A': # Data from Receiver is Valid/Has Fix - + if self.gps_segments[6] == "A": # Data from Receiver is Valid/Has Fix # Longitude / Latitude try: # Latitude @@ -324,13 +450,13 @@ def gpgll(self): 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._latitude = [0, 0.0, _NORTH] + self._longitude = [0, 0.0, _WEST] self.valid = False return True - def gpvtg(self): + def gpvtg(self) -> bool: """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 @@ -339,13 +465,15 @@ def gpvtg(self): return False # Include mph and km/h - self.speed = (spd_knt, spd_knt * 1.151, spd_knt * 1.852) + # self.speed = (spd_knt, spd_knt * 1.151, spd_knt * 1.852) + self.speed = [spd_knt, spd_knt * 1.151, spd_knt * 1.852] self.course = course return True - def gpgga(self): + def gpgga(self) -> bool: """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""" + fix status, satellites in use, Horizontal Dilution of Precision (HDOP), altitude, geoid height and fix status + """ try: # UTC Timestamp @@ -378,7 +506,6 @@ def gpgga(self): # Process Location and Speed Data if Fix is GOOD if fix_stat: - # Longitude / Latitude try: # Latitude @@ -427,7 +554,7 @@ def gpgga(self): return True - def gpgsa(self): + def gpgsa(self) -> bool: """Parse GNSS DOP and Active Satellites (GSA) sentence. Updates GPS fix type, list of satellites used in fix calculation, Position Dilution of Precision (PDOP), Horizontal Dilution of Precision (HDOP), Vertical Dilution of Precision, and fix status""" @@ -439,7 +566,7 @@ def gpgsa(self): return False # Read All (up to 12) Available PRN Satellite Numbers - sats_used = [] + sats_used: list[int] = [] for sats in range(12): sat_number_str = self.gps_segments[3 + sats] if sat_number_str: @@ -473,7 +600,7 @@ def gpgsa(self): return True - def gpgsv(self): + def gpgsv(self) -> bool: """Parse Satellites in View (GSV) sentence. Updates number of SV Sentences,the number of the last SV sentence parsed, and data on each satellite present in the sentence""" try: @@ -485,8 +612,7 @@ def gpgsv(self): # Create a blank dict to store all the satellite data from this sentence in: # satellite PRN is key, tuple containing telemetry is value - satellite_dict = dict() - + satellite_dict: t_satellite_dict = dict() # Calculate Number of Satelites to pull data for and thus how many segment positions to read if num_sv_sentences == current_sv_sentence: # Last sentence may have 1-4 satellites; 5 - 20 positions @@ -496,27 +622,26 @@ def gpgsv(self): # Try to recover data for up to 4 satellites in sentence for sats in range(4, sat_segment_limit, 4): - # If a PRN is present, grab satellite data if self.gps_segments[sats]: try: sat_id = int(self.gps_segments[sats]) - except (ValueError,IndexError): + except (ValueError, IndexError): return False try: # elevation can be null (no value) when not tracking - elevation = int(self.gps_segments[sats+1]) - except (ValueError,IndexError): + elevation = int(self.gps_segments[sats + 1]) + except (ValueError, IndexError): elevation = None try: # azimuth can be null (no value) when not tracking - azimuth = int(self.gps_segments[sats+2]) - except (ValueError,IndexError): + azimuth = int(self.gps_segments[sats + 2]) + except (ValueError, IndexError): azimuth = None try: # SNR can be null (no value) when not tracking - snr = int(self.gps_segments[sats+3]) - except (ValueError,IndexError): + snr = int(self.gps_segments[sats + 3]) + except (ValueError, IndexError): snr = None # If no PRN is found, then the sentence has no more satellites to read else: @@ -539,23 +664,63 @@ def gpgsv(self): return True + def pgtop(self) -> bool: + """ + Receive antenna status + + # $PGTOP,11,x + When issuing one of the following commands: + - PGCMD_PERIODIC_ANTENNA_STATUS or PGCMD_NO_PERIODIC_ANTENNA_STATUS to disable + periodic output + - INQUIRE_ANTENNA_STATUS: Inquiry antenna status - one shot + + The MTK responses with: $PGTOP,11,value*checksum + """ + try: + segment1: str = self.gps_segments[1] + except ValueError: + return False + if segment1 == "11": + self.antenna_status = self.gps_segments[2] + else: + self.antenna_status = "-1" # unknown status + return True + + def get_antenna_status(self, GPS_module_type: t_antenna) -> str: + """ + Get Antenna status, after MTK has responded with: $PGTOP,11,value*checksum. + + :param GPS_module_type holds the data for the relevant GPS module + + :return: status of the antenna connected to the GPS module. + """ + for antenna in GPS_module_type: + if self.antenna_status == antenna["code"]: + return antenna["message"] + else: + return "Error in antenna status" + ########################################## # Data Stream Handler Functions ########################################## - def new_sentence(self): + def new_sentence(self) -> None: """Adjust Object Flags in Preparation for a New Sentence""" - self.gps_segments = [''] + 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 ('$', ',', '*') + def update(self, new_char: str) -> None | str: + """ + 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""" + sentence function. + :return: sentence type on successful parse, None otherwise + """ valid_sentence = False @@ -570,24 +735,23 @@ def update(self, new_char): self.write_log(new_char) # Check if a new string is starting ($) - if new_char == '$': + if new_char == "$": self.new_sentence() return None elif self.sentence_active: - # Check if sentence is ending (*) - if new_char == '*': + if new_char == "*": self.process_crc = False self.active_segment += 1 - self.gps_segments.append('') + self.gps_segments.append("") return None # Check if a section is ended (,), Create a new substring to feed # characters to - elif new_char == ',': + elif new_char == ",": self.active_segment += 1 - self.gps_segments.append('') + self.gps_segments.append("") # Store All Other printable character and check CRC when ready else: @@ -595,10 +759,11 @@ def update(self, 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) + final_crc = int( + self.gps_segments[self.active_segment], 16 + ) if self.crc_xor == final_crc: valid_sentence = True else: @@ -616,10 +781,8 @@ def update(self, new_char): 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] @@ -631,9 +794,12 @@ def update(self, new_char): # 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""" + def new_fix_time(self) -> None: + """ + Updates a high resolution (soft) 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: @@ -644,36 +810,40 @@ def new_fix_time(self): # These functions make working with the GPS object data easier ######################################### - def satellite_data_updated(self): + def satellite_data_updated(self) -> bool: """ 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: + 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): + def unset_satellite_data_updated(self) -> None: """ 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): + def satellites_visible(self) -> list[int]: """ Returns a list of of the satellite PRNs currently visible to the receiver - :return: list + + :return: list of satellite ids """ 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 + def time_since_fix(self) -> float: + """Returns number of millisecond since the last sentence with a valid fix was parsed. Returns -1.0 if no fix has been found""" # Test if a Fix has been found if self.fix_time == 0: - return -1 + return -1.0 # Try calculating fix time using utime; if not running MicroPython # time.time() returns a floating point value in secs @@ -702,129 +872,221 @@ def compass_direction(self): return final_dir - def latitude_string(self): + def latitude_string(self) -> str: """ Create a readable string of the current latitude data - :return: string + + :return: formatted string according to 'self.coord_format' """ - if self.coord_format == 'dd': + if self.coord_format == DD: formatted_latitude = self.latitude - lat_string = str(formatted_latitude[0]) + '° ' + str(self._latitude[2]) - elif self.coord_format == 'dms': + lat_string = f"{formatted_latitude[0]}° {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]) + lat_string = ( + f"{formatted_latitude[0]}° {formatted_latitude[1]}'" + f' {formatted_latitude[2]}" {formatted_latitude[3]}' + ) else: - lat_string = str(self._latitude[0]) + '° ' + str(self._latitude[1]) + "' " + str(self._latitude[2]) + lat_string = ( + f"{self._latitude[0]}° {self._latitude[1]}' {self._latitude[2]}" + ) return lat_string - def longitude_string(self): + def longitude_string(self) -> str: """ Create a readable string of the current longitude data - :return: string + + :return: formatted string according to """ - if self.coord_format == 'dd': + if self.coord_format == DD: formatted_longitude = self.longitude - lon_string = str(formatted_longitude[0]) + '° ' + str(self._longitude[2]) - elif self.coord_format == 'dms': + lon_string = f"{formatted_longitude[0]}° {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]) + lon_string = ( + f"{formatted_longitude[0]}° {formatted_longitude[1]}'" + f' {formatted_longitude[2]}" {formatted_longitude[3]}' + ) else: - lon_string = str(self._longitude[0]) + '° ' + str(self._longitude[1]) + "' " + str(self._longitude[2]) + lon_string = ( + f"{self._longitude[0]}° {self._longitude[1]}' {self._longitude[2]}" + ) return lon_string - def speed_string(self, unit='kph'): + def set_speed_formatter(self, formatter: Callable) -> None: + """Initialise the format function for speed representation""" + self._speed_formatter = formatter + + def full_format(self, speed: float) -> str: """ - Creates a readable string of the current speed data in one of three units - :param unit: string of 'kph','mph, or 'knot' - :return: + Function to format the speed. + + Here: full float format (any length) + + :return: string formatted speed value """ - if unit == 'mph': - speed_string = str(self.speed[1]) + ' mph' + return f"{speed}" - elif unit == 'knot': - if self.speed[0] == 1: - unit_str = ' knot' - else: - unit_str = ' knots' - speed_string = str(self.speed[0]) + unit_str + def f2_2_format(self, speed: float) -> str: + """ + Function to format the speed. + + Here: '06.78' + :return: string formatted speed value + """ + return f"{speed:0>5.2f}" + + def speed_string(self, unit=KPH) -> str: + """ + Creates a readable string of the current speed data in one of three units + + The 'self._speed_formatter()' object contains the format function representation to apply to the speed + + :param unit: string of SPEED_UNIT + :return: speed formatted string according to the SPEED_UNIT + """ + if unit not in _SPEED_UNIT: + print(f"Unknown unit for speed: {unit}") + + if unit == MPH: + speed_string = f"{self._speed_formatter(self.speed[1])} {MPH}" + elif unit == KNOT: + if 1 >= self.speed[0] >= -1: + speed_string = f"{self._speed_formatter(self.speed[0])} {KNOT}" + else: + speed_string = f"{self._speed_formatter(self.speed[0])} {KNOT}s" else: - speed_string = str(self.speed[2]) + ' km/h' + speed_string = f"{self._speed_formatter(self.speed[2])} {KPH}" return speed_string - def date_string(self, formatting='s_mdy', century='20'): + def date_string(self, formatting=S_MDY, century="20") -> str: """ 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' + + Select between: + - long format: Januray 1st, 2014 + - or two short numeric formats: + - 11/01/2014 (MM/DD/YYYY) + - 01/11/2014 (DD/MM/YYYY) + - or this long numeric one: + - 2023-01-01 (YYY-MM-DD) + + :param formatting: string of DATE_FORMAT :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': + # Long Format January 1st, 2014 + if formatting == LONG: # Retrieve Month string from private set - month = self.__MONTHS[self.date[1] - 1] + month = self.__MONTHS[int(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' + if self.date[0] in _FIRST: + day = f"{self.date[0]}st" + elif self.date[0] in _SECOND: + day = f"{self.date[0]}nd" + elif self.date[0] in _THIRD: + day = f"{self.date[0]}rd" else: - suffix = 'th' - - day = str(self.date[0]) + suffix # Create Day String + day = f"{self.date[0]}th" - year = century + str(self.date[2]) # Create Year String - - date_string = month + ' ' + day + ', ' + year # Put it all together + year = f"{century}{self.date[2]}" # Create Year String + date_string = f"{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]) + day = f"0{self.date[0]}" else: - day = str(self.date[0]) + day = f"{self.date[0]}" # Add leading zeros to month string if necessary if self.date[1] < 10: - month = '0' + str(self.date[1]) + month = f"0{self.date[1]}" else: - month = str(self.date[1]) + month = f"{self.date[1]}" # Add leading zeros to year string if necessary if self.date[2] < 10: - year = '0' + str(self.date[2]) + year = f"0{self.date[2]}" else: - year = str(self.date[2]) + year = f"{self.date[2]}" # Build final string based on desired formatting - if formatting == 's_dmy': - date_string = day + '/' + month + '/' + year + if formatting == S_DMY: + date_string = f"{day}/{month}/{year}" + + elif formatting == L_YMD: + date_string = f"{century}{year}-{month}-{day}" - else: # Default date format - date_string = month + '/' + day + '/' + year + else: # Default date format (S_MDY) + date_string = f"{month}/{day}/{year}" return date_string + def timestamp_string(self, formatting=S_HMS) -> str: + """ + Creates a readable string of the current timestamp. + + One format hh:mm:ss.us 12:00:23.789 + + :param formatting: string 's_hms' + :return: timestamp_string string format timestamp + """ + + if formatting == S_HMS: + # Add leading zeros to hour string if necessary + if self.timestamp[0] < 10: + hour = f"0{self.timestamp[0]}" + else: + hour = f"{self.timestamp[0]}" + + # Add leading zeros to minute string if necessary + if self.timestamp[1] < 10: + minute = f"0{self.timestamp[1]}" + else: + minute = f"{self.timestamp[1]}" + + # Add leading zeros to second string if necessary + if self.timestamp[2] < 10: + second = f"0{self.timestamp[2]}" + else: + second = f"{self.timestamp[2]}" + + # Build final string + timestamp_string = f"{hour}:{minute}:{second}" + + else: + raise ValueError("Unkown timestamp format") + + return timestamp_string + # All the currently supported NMEA sentences - supported_sentences = {'GPRMC': gprmc, 'GLRMC': gprmc, - 'GPGGA': gpgga, 'GLGGA': gpgga, - 'GPVTG': gpvtg, 'GLVTG': gpvtg, - 'GPGSA': gpgsa, 'GLGSA': gpgsa, - 'GPGSV': gpgsv, 'GLGSV': gpgsv, - 'GPGLL': gpgll, 'GLGLL': gpgll, - 'GNGGA': gpgga, 'GNRMC': gprmc, - 'GNVTG': gpvtg, 'GNGLL': gpgll, - 'GNGSA': gpgsa, - } + supported_sentences = { + "GPRMC": gprmc, + "GLRMC": gprmc, + "GPGGA": gpgga, + "GLGGA": gpgga, + "GPVTG": gpvtg, + "GLVTG": gpvtg, + "GPGSA": gpgsa, + "GLGSA": gpgsa, + "GPGSV": gpgsv, + "GLGSV": gpgsv, + "GPGLL": gpgll, + "GLGLL": gpgll, + "GNGGA": gpgga, + "GNRMC": gprmc, + "GNVTG": gpvtg, + "GNGLL": gpgll, + "GNGSA": gpgsa, + "PGTOP": pgtop, + } + if __name__ == "__main__": pass diff --git a/test_micropyGPS.py b/test_micropyGPS.py index a97eb21..8f07365 100644 --- a/test_micropyGPS.py +++ b/test_micropyGPS.py @@ -9,80 +9,320 @@ """ import hashlib from micropyGPS import MicropyGPS +from micropyGPS import LONG, L_YMD, S_DMY, S_MDY +from micropyGPS import S_HMS +from micropyGPS import MPH, KPH, KNOT +from micropyGPS import DD, DMS -test_RMC = ['$GPRMC,081836,A,3751.65,S,14507.36,E,000.0,360.0,130998,011.3,E*62\n', - '$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A\n', - '$GPRMC,225446,A,4916.45,N,12311.12,W,000.5,054.7,191194,020.3,E*68\n', - '$GPRMC,180041.896,A,3749.1851,N,08338.7891,W,001.9,154.9,240911,,,A*7A\n', - '$GPRMC,180049.896,A,3749.1808,N,08338.7869,W,001.8,156.3,240911,,,A*70\n', - '$GPRMC,092751.000,A,5321.6802,N,00630.3371,W,0.06,31.66,280511,,,A*45\n', - '$GPRMC,193448.00,A,3746.2622056,N,12224.1897266,W,0.01,,041218,,,D*58\n', - '$GPRMC,193449.00,A,3746.2622284,N,12224.1897308,W,0.01,,041218,,,D*5D\n'] -rmc_parsed_strings = [['GPRMC', '081836', 'A', '3751.65', 'S', '14507.36', 'E', '000.0', '360.0', '130998', '011.3', 'E', '62'], - ['GPRMC', '123519', 'A', '4807.038', 'N', '01131.000', 'E', '022.4', '084.4', '230394', '003.1', 'W', '6A'], - ['GPRMC', '225446', 'A', '4916.45', 'N', '12311.12', 'W', '000.5', '054.7', '191194', '020.3', 'E', '68'], - ['GPRMC', '180041.896', 'A', '3749.1851', 'N', '08338.7891', 'W', '001.9', '154.9', '240911', '', '', 'A', '7A'], - ['GPRMC', '180049.896', 'A', '3749.1808', 'N', '08338.7869', 'W', '001.8', '156.3', '240911', '', '', 'A', '70'], - ['GPRMC', '092751.000', 'A', '5321.6802', 'N', '00630.3371', 'W', '0.06', '31.66', '280511', '', '', 'A', '45'], - ['GPRMC', '193448.00', 'A', '3746.2622056', 'N', '12224.1897266', 'W', '0.01', '', '041218', '', '', 'D', '58'], - ['GPRMC', '193449.00', 'A', '3746.2622284', 'N', '12224.1897308', 'W', '0.01', '', '041218', '', '', 'D', '5D']] -rmc_crc_values = [0x62, 0x6a, 0x68, 0x7a, 0x70, 0x45, 0x58, 0x5D] -rmc_longitude = [[145, 7.36, 'E'], - [11, 31.0, 'E'], - [123, 11.12, 'W'], - [83, 38.7891, 'W'], - [83, 38.7869, 'W'], - [6, 30.3371, 'W'], - [122, 24.1897266, 'W'], - [122, 24.1897308, 'W']] -rmc_latitude = [[37, 51.65, 'S'], - [48, 7.038, 'N'], - [49, 16.45, 'N'], - [37, 49.1851, 'N'], - [37, 49.1808, 'N'], - [53, 21.6802, 'N'], - [37, 46.2622056, 'N'], - [37, 46.2622284, 'N']] -rmc_utc = [[8, 18, 36.0], - [12, 35, 19.0], - [22, 54, 46.0], - [18, 0, 41.896], - [18, 0, 49.896], - [9, 27, 51.0], - [19, 34, 48.0], - [19, 34, 49.0]] -rmc_speed = [[0.0, 0.0, 0.0], - [22.4, 25.7824, 41.4848], - [0.5, 0.5755, 0.926], - [1.9, 2.1869, 3.5188], - [1.8, 2.0718, 3.3336], - [0.06, 0.06906, 0.11112], - [0.01, 0.011510000000000001, 0.018520000000000002], - [0.01, 0.011510000000000001, 0.018520000000000002]] -rmc_date = [(13, 9, 98), - (23, 3, 94), - (19, 11, 94), - (24, 9, 11), - (24, 9, 11), - (28, 5, 11), - (4, 12, 18), - (4, 12, 18)] -rmc_course = [360.0, 84.4, 54.7, 154.9, 156.3, 31.66, 0.0, 0.0] -rmc_compass = ['N', 'E', 'NE', 'SSE', 'SSE', 'NNE', 'N', 'N'] +test_RMC = [ + "$GPRMC,081836,A,3751.65,S,14507.36,E,000.0,360.0,130998,011.3,E*62\n", + "$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A\n", + "$GPRMC,225446,A,4916.45,N,12311.12,W,000.5,054.7,191194,020.3,E*68\n", + "$GPRMC,180041.896,A,3749.1851,N,08338.7891,W,001.9,154.9,240911,,,A*7A\n", + "$GPRMC,180049.896,A,3749.1808,N,08338.7869,W,001.8,156.3,240911,,,A*70\n", + "$GPRMC,092751.000,A,5321.6802,N,00630.3371,W,0.06,31.66,280511,,,A*45\n", + "$GPRMC,193448.00,A,3746.2622056,N,12224.1897266,W,0.01,,041218,,,D*58\n", + "$GPRMC,193449.00,A,3746.2622284,N,12224.1897308,W,0.01,,041218,,,D*5D\n", + "$GPRMC,193449.00,A,3746.2622284,N,12224.1897308,W,1.00,,041218,,,D*5D\n", +] +rmc_parsed_strings = [ + [ + "GPRMC", + "081836", + "A", + "3751.65", + "S", + "14507.36", + "E", + "000.0", + "360.0", + "130998", + "011.3", + "E", + "62", + ], + [ + "GPRMC", + "123519", + "A", + "4807.038", + "N", + "01131.000", + "E", + "022.4", + "084.4", + "230394", + "003.1", + "W", + "6A", + ], + [ + "GPRMC", + "225446", + "A", + "4916.45", + "N", + "12311.12", + "W", + "000.5", + "054.7", + "191194", + "020.3", + "E", + "68", + ], + [ + "GPRMC", + "180041.896", + "A", + "3749.1851", + "N", + "08338.7891", + "W", + "001.9", + "154.9", + "240911", + "", + "", + "A", + "7A", + ], + [ + "GPRMC", + "180049.896", + "A", + "3749.1808", + "N", + "08338.7869", + "W", + "001.8", + "156.3", + "240911", + "", + "", + "A", + "70", + ], + [ + "GPRMC", + "092751.000", + "A", + "5321.6802", + "N", + "00630.3371", + "W", + "0.06", + "31.66", + "280511", + "", + "", + "A", + "45", + ], + [ + "GPRMC", + "193448.00", + "A", + "3746.2622056", + "N", + "12224.1897266", + "W", + "0.01", + "", + "041218", + "", + "", + "D", + "58", + ], + [ + "GPRMC", + "193449.00", + "A", + "3746.2622284", + "N", + "12224.1897308", + "W", + "0.01", + "", + "041218", + "", + "", + "D", + "5D", + ], + [ + "GPRMC", + "193449.00", + "A", + "3746.2622284", + "N", + "12224.1897308", + "W", + "1.00", + "", + "041218", + "", + "", + "D", + "5D", + ], +] +rmc_crc_values = [0x62, 0x6A, 0x68, 0x7A, 0x70, 0x45, 0x58, 0x5D, 0x5D] +rmc_longitude = [ + [145, 7.36, "E"], + [11, 31.0, "E"], + [123, 11.12, "W"], + [83, 38.7891, "W"], + [83, 38.7869, "W"], + [6, 30.3371, "W"], + [122, 24.1897266, "W"], + [122, 24.1897308, "W"], + [122, 24.1897308, "W"], +] +rmc_latitude = [ + [37, 51.65, "S"], + [48, 7.038, "N"], + [49, 16.45, "N"], + [37, 49.1851, "N"], + [37, 49.1808, "N"], + [53, 21.6802, "N"], + [37, 46.2622056, "N"], + [37, 46.2622284, "N"], + [37, 46.2622284, "N"], +] -test_VTG = ['$GPVTG,232.9,T,,M,002.3,N,004.3,K,A*01\n'] +rmc_utc = [ + [8, 18, 36.0], + [12, 35, 19.0], + [22, 54, 46.0], + [18, 0, 41.896], + [18, 0, 49.896], + [9, 27, 51.0], + [19, 34, 48.0], + [19, 34, 49.0], + [19, 34, 49.0], +] +rmc_speed = [ + [0.0, 0.0, 0.0], + [22.4, 25.7824, 41.4848], + [0.5, 0.5755, 0.926], + [1.9, 2.1869, 3.5188], + [1.8, 2.0718, 3.3336], + [0.06, 0.06906, 0.11112], + [0.01, 0.011510000000000001, 0.018520000000000002], + [0.01, 0.011510000000000001, 0.018520000000000002], + [1.00, 1.1510000000000001, 1.8520000000000002], +] +rmc_date = [ + [13, 9, 98], + [23, 3, 94], + [19, 11, 94], + [24, 9, 11], + [24, 9, 11], + [28, 5, 11], + [4, 12, 18], + [4, 12, 18], + [4, 12, 18], +] +rmc_course = [360.0, 84.4, 54.7, 154.9, 156.3, 31.66, 0.0, 0.0, 0.0] +rmc_compass = ["N", "E", "NE", "SSE", "SSE", "NNE", "N", "N", "N"] -test_GGA = ['$GPGGA,180126.905,4254.931,N,07702.496,W,0,00,,,M,,M,,*54\n', - '$GPGGA,181433.343,4054.931,N,07502.498,W,0,00,,,M,,M,,*52\n', - '$GPGGA,180050.896,3749.1802,N,08338.7865,W,1,07,1.1,397.4,M,-32.5,M,,0000*6C\n', - '$GPGGA,172814.0,3723.46587704,N,12202.26957864,W,2,6,1.2,18.893,M,-25.669,M,2.0,0031*4F\n'] -gga_parsed_strings = [['GPGGA', '180126.905', '4254.931', 'N', '07702.496', 'W', '0', '00', '', '', 'M', '', 'M', '', '', '54'], - ['GPGGA', '181433.343', '4054.931', 'N', '07502.498', 'W', '0', '00', '', '', 'M', '', 'M', '', '', '52'], - ['GPGGA', '180050.896', '3749.1802', 'N', '08338.7865', 'W', '1', '07', '1.1', '397.4', 'M', '-32.5', 'M', '', '0000', '6C'], - ['GPGGA', '172814.0', '3723.46587704', 'N', '12202.26957864', 'W', '2', '6', '1.2', '18.893', 'M', '-25.669', 'M', '2.0', '0031', '4F']] -gga_latitudes = [[0, 0.0, 'N'], [0, 0.0, 'N'], [37, 49.1802, 'N'], [37, 23.46587704, 'N']] -gga_longitudes = [[0, 0.0, 'W'], [0, 0.0, 'W'], [83, 38.7865, 'W'], [122, 2.26957864, 'W']] +test_VTG = ["$GPVTG,232.9,T,,M,002.3,N,004.3,K,A*01\n"] + +test_GGA = [ + "$GPGGA,180126.905,4254.931,N,07702.496,W,0,00,,,M,,M,,*54\n", + "$GPGGA,181433.343,4054.931,N,07502.498,W,0,00,,,M,,M,,*52\n", + "$GPGGA,180050.896,3749.1802,N,08338.7865,W,1,07,1.1,397.4,M,-32.5,M,,0000*6C\n", + "$GPGGA,172814.0,3723.46587704,N,12202.26957864,W,2,6,1.2,18.893,M,-25.669,M,2.0,0031*4F\n", +] +gga_parsed_strings = [ + [ + "GPGGA", + "180126.905", + "4254.931", + "N", + "07702.496", + "W", + "0", + "00", + "", + "", + "M", + "", + "M", + "", + "", + "54", + ], + [ + "GPGGA", + "181433.343", + "4054.931", + "N", + "07502.498", + "W", + "0", + "00", + "", + "", + "M", + "", + "M", + "", + "", + "52", + ], + [ + "GPGGA", + "180050.896", + "3749.1802", + "N", + "08338.7865", + "W", + "1", + "07", + "1.1", + "397.4", + "M", + "-32.5", + "M", + "", + "0000", + "6C", + ], + [ + "GPGGA", + "172814.0", + "3723.46587704", + "N", + "12202.26957864", + "W", + "2", + "6", + "1.2", + "18.893", + "M", + "-25.669", + "M", + "2.0", + "0031", + "4F", + ], +] +gga_latitudes = [ + [0, 0.0, "N"], + [0, 0.0, "N"], + [37, 49.1802, "N"], + [37, 23.46587704, "N"], +] +gga_longitudes = [ + [0, 0.0, "W"], + [0, 0.0, "W"], + [83, 38.7865, "W"], + [122, 2.26957864, "W"], +] gga_fixes = [0, 0, 1, 2] gga_timestamps = [[18, 1, 26.905], [18, 14, 33.343], [18, 0, 50.896], [17, 28, 14.0]] gga_hdops = [0.0, 0.0, 1.1, 1.2] @@ -91,128 +331,585 @@ gga_geoid_heights = [0.0, 0.0, -32.5, -25.669] gga_crc_xors = [84, 82, 108, 79] -test_GSA = ['$GPGSA,A,3,07,11,28,24,26,08,17,,,,,,2.0,1.1,1.7*37\n', - '$GPGSA,A,3,07,02,26,27,09,04,15,,,,,,1.8,1.0,1.5*33\n'] -gsa_parsed_strings = [['GPGSA', 'A', '3', '07', '11', '28', '24', '26', '08', '17', '', '', '', '', '', '2.0', '1.1', '1.7', '37'], - ['GPGSA', 'A', '3', '07', '02', '26', '27', '09', '04', '15', '', '', '', '', '', '1.8', '1.0', '1.5', '33']] +test_GSA = [ + "$GPGSA,A,3,07,11,28,24,26,08,17,,,,,,2.0,1.1,1.7*37\n", + "$GPGSA,A,3,07,02,26,27,09,04,15,,,,,,1.8,1.0,1.5*33\n", +] +gsa_parsed_strings = [ + [ + "GPGSA", + "A", + "3", + "07", + "11", + "28", + "24", + "26", + "08", + "17", + "", + "", + "", + "", + "", + "2.0", + "1.1", + "1.7", + "37", + ], + [ + "GPGSA", + "A", + "3", + "07", + "02", + "26", + "27", + "09", + "04", + "15", + "", + "", + "", + "", + "", + "1.8", + "1.0", + "1.5", + "33", + ], +] gsa_crc_values = [0x37, 0x33] -gsa_sats_used = [[7, 11, 28, 24, 26, 8, 17], - [7, 2, 26, 27, 9, 4, 15]] +gsa_sats_used = [[7, 11, 28, 24, 26, 8, 17], [7, 2, 26, 27, 9, 4, 15]] gsa_hdop = [1.1, 1.0] gsa_vdop = [1.7, 1.5] gsa_pdop = [2.0, 1.8] -test_GSV = ['$GPGSV,3,1,12,28,72,355,39,01,52,063,33,17,51,272,44,08,46,184,38*74\n', - '$GPGSV,3,2,12,24,42,058,33,11,34,053,33,07,20,171,40,20,15,116,*71\n', - '$GPGSV,3,3,12,04,12,204,34,27,11,324,35,32,11,089,,26,10,264,40*7B\n', - '$GPGSV,3,1,11,03,03,111,00,04,15,270,00,06,01,010,00,13,06,292,00*74\n', - '$GPGSV,3,2,11,14,25,170,00,16,57,208,39,18,67,296,40,19,40,246,00*74\n', - '$GPGSV,3,3,11,22,42,067,42,24,14,311,43,27,05,244,00,,,,*4D\n', - '$GPGSV,4,1,14,22,81,349,25,14,64,296,22,18,54,114,21,51,40,212,*7D\n', - '$GPGSV,4,2,14,24,30,047,22,04,22,312,26,31,22,204,,12,19,088,23*72\n', - '$GPGSV,4,3,14,25,17,127,18,21,16,175,,11,09,315,16,19,05,273,*72\n', - '$GPGSV,4,4,14,32,05,303,,15,02,073,*7A\n', - '$GPGSV,3,1,12,13,65,002,50,02,61,098,47,39,60,352,,05,56,183,49*70\n', - '$GPGSV,3,2,12,15,35,325,50,29,32,229,49,06,25,070,44,30,16,096,38*70\n', - '$GPGSV,3,3,12,19,08,022,35,07,07,122,,12,06,316,49,25,03,278,36*7D\n'] -gsv_parsed_string = [['GPGSV', '3', '1', '12', '28', '72', '355', '39', '01', '52', '063', '33', '17', '51', '272', '44', '08', '46', '184', '38', '74'], - ['GPGSV', '3', '2', '12', '24', '42', '058', '33', '11', '34', '053', '33', '07', '20', '171', '40', '20', '15', '116', '', '71'], - ['GPGSV', '3', '3', '12', '04', '12', '204', '34', '27', '11', '324', '35', '32', '11', '089', '', '26', '10', '264', '40', '7B'], - ['GPGSV', '3', '1', '11', '03', '03', '111', '00', '04', '15', '270', '00', '06', '01', '010', '00', '13', '06', '292', '00', '74'], - ['GPGSV', '3', '2', '11', '14', '25', '170', '00', '16', '57', '208', '39', '18', '67', '296', '40', '19', '40', '246', '00', '74'], - ['GPGSV', '3', '3', '11', '22', '42', '067', '42', '24', '14', '311', '43', '27', '05', '244', '00', '', '', '', '', '4D'], - ['GPGSV', '4', '1', '14', '22', '81', '349', '25', '14', '64', '296', '22', '18', '54', '114', '21', '51', '40', '212', '', '7D'], - ['GPGSV', '4', '2', '14', '24', '30', '047', '22', '04', '22', '312', '26', '31', '22', '204', '', '12', '19', '088', '23', '72'], - ['GPGSV', '4', '3', '14', '25', '17', '127', '18', '21', '16', '175', '', '11', '09', '315', '16', '19', '05', '273', '', '72'], - ['GPGSV', '4', '4', '14', '32', '05', '303', '', '15', '02', '073', '', '7A'], - ['GPGSV', '3', '1', '12', '13', '65', '002', '50', '02', '61', '098', '47', '39', '60', '352', '', '05', '56', '183', '49', '70'], - ['GPGSV', '3', '2', '12', '15', '35', '325', '50', '29', '32', '229', '49', '06', '25', '070', '44', '30', '16', '096', '38', '70'], - ['GPGSV', '3', '3', '12', '19', '08', '022', '35', '07', '07', '122', '', '12', '06', '316', '49', '25', '03', '278', '36', '7D']] -gsv_crc_values = [0x74, 0x71, 0x7b, 0x74, 0x74, 0x4d, 0x7d, 0x72, 0x72, 0x7a, 0x70, 0x70, 0x7d] +test_GSV = [ + "$GPGSV,3,1,12,28,72,355,39,01,52,063,33,17,51,272,44,08,46,184,38*74\n", + "$GPGSV,3,2,12,24,42,058,33,11,34,053,33,07,20,171,40,20,15,116,*71\n", + "$GPGSV,3,3,12,04,12,204,34,27,11,324,35,32,11,089,,26,10,264,40*7B\n", + "$GPGSV,3,1,11,03,03,111,00,04,15,270,00,06,01,010,00,13,06,292,00*74\n", + "$GPGSV,3,2,11,14,25,170,00,16,57,208,39,18,67,296,40,19,40,246,00*74\n", + "$GPGSV,3,3,11,22,42,067,42,24,14,311,43,27,05,244,00,,,,*4D\n", + "$GPGSV,4,1,14,22,81,349,25,14,64,296,22,18,54,114,21,51,40,212,*7D\n", + "$GPGSV,4,2,14,24,30,047,22,04,22,312,26,31,22,204,,12,19,088,23*72\n", + "$GPGSV,4,3,14,25,17,127,18,21,16,175,,11,09,315,16,19,05,273,*72\n", + "$GPGSV,4,4,14,32,05,303,,15,02,073,*7A\n", + "$GPGSV,3,1,12,13,65,002,50,02,61,098,47,39,60,352,,05,56,183,49*70\n", + "$GPGSV,3,2,12,15,35,325,50,29,32,229,49,06,25,070,44,30,16,096,38*70\n", + "$GPGSV,3,3,12,19,08,022,35,07,07,122,,12,06,316,49,25,03,278,36*7D\n", +] +gsv_parsed_string = [ + [ + "GPGSV", + "3", + "1", + "12", + "28", + "72", + "355", + "39", + "01", + "52", + "063", + "33", + "17", + "51", + "272", + "44", + "08", + "46", + "184", + "38", + "74", + ], + [ + "GPGSV", + "3", + "2", + "12", + "24", + "42", + "058", + "33", + "11", + "34", + "053", + "33", + "07", + "20", + "171", + "40", + "20", + "15", + "116", + "", + "71", + ], + [ + "GPGSV", + "3", + "3", + "12", + "04", + "12", + "204", + "34", + "27", + "11", + "324", + "35", + "32", + "11", + "089", + "", + "26", + "10", + "264", + "40", + "7B", + ], + [ + "GPGSV", + "3", + "1", + "11", + "03", + "03", + "111", + "00", + "04", + "15", + "270", + "00", + "06", + "01", + "010", + "00", + "13", + "06", + "292", + "00", + "74", + ], + [ + "GPGSV", + "3", + "2", + "11", + "14", + "25", + "170", + "00", + "16", + "57", + "208", + "39", + "18", + "67", + "296", + "40", + "19", + "40", + "246", + "00", + "74", + ], + [ + "GPGSV", + "3", + "3", + "11", + "22", + "42", + "067", + "42", + "24", + "14", + "311", + "43", + "27", + "05", + "244", + "00", + "", + "", + "", + "", + "4D", + ], + [ + "GPGSV", + "4", + "1", + "14", + "22", + "81", + "349", + "25", + "14", + "64", + "296", + "22", + "18", + "54", + "114", + "21", + "51", + "40", + "212", + "", + "7D", + ], + [ + "GPGSV", + "4", + "2", + "14", + "24", + "30", + "047", + "22", + "04", + "22", + "312", + "26", + "31", + "22", + "204", + "", + "12", + "19", + "088", + "23", + "72", + ], + [ + "GPGSV", + "4", + "3", + "14", + "25", + "17", + "127", + "18", + "21", + "16", + "175", + "", + "11", + "09", + "315", + "16", + "19", + "05", + "273", + "", + "72", + ], + ["GPGSV", "4", "4", "14", "32", "05", "303", "", "15", "02", "073", "", "7A"], + [ + "GPGSV", + "3", + "1", + "12", + "13", + "65", + "002", + "50", + "02", + "61", + "098", + "47", + "39", + "60", + "352", + "", + "05", + "56", + "183", + "49", + "70", + ], + [ + "GPGSV", + "3", + "2", + "12", + "15", + "35", + "325", + "50", + "29", + "32", + "229", + "49", + "06", + "25", + "070", + "44", + "30", + "16", + "096", + "38", + "70", + ], + [ + "GPGSV", + "3", + "3", + "12", + "19", + "08", + "022", + "35", + "07", + "07", + "122", + "", + "12", + "06", + "316", + "49", + "25", + "03", + "278", + "36", + "7D", + ], +] +gsv_crc_values = [ + 0x74, + 0x71, + 0x7B, + 0x74, + 0x74, + 0x4D, + 0x7D, + 0x72, + 0x72, + 0x7A, + 0x70, + 0x70, + 0x7D, +] gsv_sv_setence = [1, 2, 3, 1, 2, 3, 1, 2, 3, 4, 1, 2, 3] gsv_total_sentence = [3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 3, 3, 3] gsv_num_sats_in_view = [12, 12, 12, 11, 11, 11, 14, 14, 14, 14, 12, 12, 12] -gsv_data_valid = [False, False, True, False, False, True, False, False, False, True, False, False, True] -gsv_sat_data = [{28: (72, 355, 39), 1: (52, 63, 33), 17: (51, 272, 44), 8: (46, 184, 38)}, - {28: (72, 355, 39), 1: (52, 63, 33), 17: (51, 272, 44), 8: (46, 184, 38), 24: (42, 58, 33), 11: (34, 53, 33), 7: (20, 171, 40), 20: (15, 116, None)}, - {28: (72, 355, 39), 1: (52, 63, 33), 17: (51, 272, 44), 8: (46, 184, 38), 24: (42, 58, 33), 11: (34, 53, 33), 7: (20, 171, 40), 20: (15, 116, None), 4: (12, 204, 34), 27: (11, 324, 35), 32: (11, 89, None), 26: (10, 264, 40)}, - {3: (3, 111, 0), 4: (15, 270, 0), 6: (1, 10, 0), 13: (6, 292, 0)}, - {3: (3, 111, 0), 4: (15, 270, 0), 6: (1, 10, 0), 13: (6, 292, 0), 14: (25, 170, 0), 16: (57, 208, 39), 18: (67, 296, 40), 19: (40, 246, 0)}, - {3: (3, 111, 0), 4: (15, 270, 0), 6: (1, 10, 0), 13: (6, 292, 0), 14: (25, 170, 0), 16: (57, 208, 39), 18: (67, 296, 40), 19: (40, 246, 0), 22: (42, 67, 42), 24: (14, 311, 43), 27: (5, 244, 0)}, - {22: (81, 349, 25), 14: (64, 296, 22), 18: (54, 114, 21), 51: (40, 212, None)}, - {22: (81, 349, 25), 14: (64, 296, 22), 18: (54, 114, 21), 51: (40, 212, None), 24: (30, 47, 22), 4: (22, 312, 26), 31: (22, 204, None), 12: (19, 88, 23)}, - {22: (81, 349, 25), 14: (64, 296, 22), 18: (54, 114, 21), 51: (40, 212, None), 24: (30, 47, 22), 4: (22, 312, 26), 31: (22, 204, None), 12: (19, 88, 23), 25: (17, 127, 18), 21: (16, 175, None), 11: (9, 315, 16), 19: (5, 273, None)}, - {22: (81, 349, 25), 14: (64, 296, 22), 18: (54, 114, 21), 51: (40, 212, None), 24: (30, 47, 22), 4: (22, 312, 26), 31: (22, 204, None), 12: (19, 88, 23), 25: (17, 127, 18), 21: (16, 175, None), 11: (9, 315, 16), 19: (5, 273, None), 32: (5, 303, None), 15: (2, 73, None)}, - {13: (65, 2, 50), 2: (61, 98, 47), 39: (60, 352, None), 5: (56, 183, 49)}, - {13: (65, 2, 50), 2: (61, 98, 47), 39: (60, 352, None), 5: (56, 183, 49), 15: (35, 325, 50), 29: (32, 229, 49), 6: (25, 70, 44), 30: (16, 96, 38)}, - {13: (65, 2, 50), 2: (61, 98, 47), 39: (60, 352, None), 5: (56, 183, 49), 15: (35, 325, 50), 29: (32, 229, 49), 6: (25, 70, 44), 30: (16, 96, 38), 19: (8, 22, 35), 7: (7, 122, None), 12: (6, 316, 49), 25: (3, 278, 36)}] -gsv_sats_in_view = [[28, 1, 17, 8], - [28, 1, 17, 8, 24, 11, 7, 20], - [28, 1, 17, 8, 24, 11, 7, 20, 4, 27, 32, 26], - [3, 4, 6, 13], - [3, 4, 6, 13, 14, 16, 18, 19], - [3, 4, 6, 13, 14, 16, 18, 19, 22, 24, 27], - [22, 14, 18, 51], - [22, 14, 18, 51, 24, 4, 31, 12], - [22, 14, 18, 51, 24, 4, 31, 12, 25, 21, 11, 19], - [22, 14, 18, 51, 24, 4, 31, 12, 25, 21, 11, 19, 32, 15], - [13, 2, 39, 5], - [13, 2, 39, 5, 15, 29, 6, 30], - [13, 2, 39, 5, 15, 29, 6, 30, 19, 7, 12, 25]] -test_GLL = ['$GPGLL,3711.0942,N,08671.4472,W,000812.000,A,A*46\n', - '$GPGLL,4916.45,N,12311.12,W,225444,A,*1D\n', - '$GPGLL,4250.5589,S,14718.5084,E,092204.999,A*2D\n', - '$GPGLL,0000.0000,N,00000.0000,E,235947.000,V*2D\n'] - -gll_parsed_string = [['GPGLL', '3711.0942', 'N', '08671.4472', 'W', '000812.000', 'A', 'A', '46'], - ['GPGLL', '4916.45', 'N', '12311.12', 'W', '225444', 'A', '', '1D'], - ['GPGLL', '4250.5589', 'S', '14718.5084', 'E', '092204.999', 'A', '2D'], - ['GPGLL', '0000.0000', 'N', '00000.0000', 'E', '235947.000', 'V', '2D']] -gll_crc_values = [0x46, 0x1d, 0x2d, 0x2d] -gll_longitude = [[86, 71.4472, 'W'], - [123, 11.12, 'W'], - [147, 18.5084, 'E'], - [0, 0.0, 'W']] -gll_latitude = [[37, 11.0942, 'N'], - [49, 16.45, 'N'], - [42, 50.5589, 'S'], - [0, 0.0, 'N']] -gll_timestamp = [[0, 8, 12.0], - [22, 54, 44.0], - [9, 22, 4.999], - [23, 59, 47.0]] +gsv_data_valid = [ + False, + False, + True, + False, + False, + True, + False, + False, + False, + True, + False, + False, + True, +] +gsv_sat_data = [ + {28: (72, 355, 39), 1: (52, 63, 33), 17: (51, 272, 44), 8: (46, 184, 38)}, + { + 28: (72, 355, 39), + 1: (52, 63, 33), + 17: (51, 272, 44), + 8: (46, 184, 38), + 24: (42, 58, 33), + 11: (34, 53, 33), + 7: (20, 171, 40), + 20: (15, 116, None), + }, + { + 28: (72, 355, 39), + 1: (52, 63, 33), + 17: (51, 272, 44), + 8: (46, 184, 38), + 24: (42, 58, 33), + 11: (34, 53, 33), + 7: (20, 171, 40), + 20: (15, 116, None), + 4: (12, 204, 34), + 27: (11, 324, 35), + 32: (11, 89, None), + 26: (10, 264, 40), + }, + {3: (3, 111, 0), 4: (15, 270, 0), 6: (1, 10, 0), 13: (6, 292, 0)}, + { + 3: (3, 111, 0), + 4: (15, 270, 0), + 6: (1, 10, 0), + 13: (6, 292, 0), + 14: (25, 170, 0), + 16: (57, 208, 39), + 18: (67, 296, 40), + 19: (40, 246, 0), + }, + { + 3: (3, 111, 0), + 4: (15, 270, 0), + 6: (1, 10, 0), + 13: (6, 292, 0), + 14: (25, 170, 0), + 16: (57, 208, 39), + 18: (67, 296, 40), + 19: (40, 246, 0), + 22: (42, 67, 42), + 24: (14, 311, 43), + 27: (5, 244, 0), + }, + {22: (81, 349, 25), 14: (64, 296, 22), 18: (54, 114, 21), 51: (40, 212, None)}, + { + 22: (81, 349, 25), + 14: (64, 296, 22), + 18: (54, 114, 21), + 51: (40, 212, None), + 24: (30, 47, 22), + 4: (22, 312, 26), + 31: (22, 204, None), + 12: (19, 88, 23), + }, + { + 22: (81, 349, 25), + 14: (64, 296, 22), + 18: (54, 114, 21), + 51: (40, 212, None), + 24: (30, 47, 22), + 4: (22, 312, 26), + 31: (22, 204, None), + 12: (19, 88, 23), + 25: (17, 127, 18), + 21: (16, 175, None), + 11: (9, 315, 16), + 19: (5, 273, None), + }, + { + 22: (81, 349, 25), + 14: (64, 296, 22), + 18: (54, 114, 21), + 51: (40, 212, None), + 24: (30, 47, 22), + 4: (22, 312, 26), + 31: (22, 204, None), + 12: (19, 88, 23), + 25: (17, 127, 18), + 21: (16, 175, None), + 11: (9, 315, 16), + 19: (5, 273, None), + 32: (5, 303, None), + 15: (2, 73, None), + }, + {13: (65, 2, 50), 2: (61, 98, 47), 39: (60, 352, None), 5: (56, 183, 49)}, + { + 13: (65, 2, 50), + 2: (61, 98, 47), + 39: (60, 352, None), + 5: (56, 183, 49), + 15: (35, 325, 50), + 29: (32, 229, 49), + 6: (25, 70, 44), + 30: (16, 96, 38), + }, + { + 13: (65, 2, 50), + 2: (61, 98, 47), + 39: (60, 352, None), + 5: (56, 183, 49), + 15: (35, 325, 50), + 29: (32, 229, 49), + 6: (25, 70, 44), + 30: (16, 96, 38), + 19: (8, 22, 35), + 7: (7, 122, None), + 12: (6, 316, 49), + 25: (3, 278, 36), + }, +] +gsv_sats_in_view = [ + [28, 1, 17, 8], + [28, 1, 17, 8, 24, 11, 7, 20], + [28, 1, 17, 8, 24, 11, 7, 20, 4, 27, 32, 26], + [3, 4, 6, 13], + [3, 4, 6, 13, 14, 16, 18, 19], + [3, 4, 6, 13, 14, 16, 18, 19, 22, 24, 27], + [22, 14, 18, 51], + [22, 14, 18, 51, 24, 4, 31, 12], + [22, 14, 18, 51, 24, 4, 31, 12, 25, 21, 11, 19], + [22, 14, 18, 51, 24, 4, 31, 12, 25, 21, 11, 19, 32, 15], + [13, 2, 39, 5], + [13, 2, 39, 5, 15, 29, 6, 30], + [13, 2, 39, 5, 15, 29, 6, 30, 19, 7, 12, 25], +] +test_GLL = [ + "$GPGLL,3711.0942,N,08671.4472,W,000812.000,A,A*46\n", + "$GPGLL,4916.45,N,12311.12,W,225444,A,*1D\n", + "$GPGLL,4250.5589,S,14718.5084,E,092204.999,A*2D\n", + "$GPGLL,0000.0000,N,00000.0000,E,235947.000,V*2D\n", +] +gll_parsed_string = [ + ["GPGLL", "3711.0942", "N", "08671.4472", "W", "000812.000", "A", "A", "46"], + ["GPGLL", "4916.45", "N", "12311.12", "W", "225444", "A", "", "1D"], + ["GPGLL", "4250.5589", "S", "14718.5084", "E", "092204.999", "A", "2D"], + ["GPGLL", "0000.0000", "N", "00000.0000", "E", "235947.000", "V", "2D"], +] +gll_crc_values = [0x46, 0x1D, 0x2D, 0x2D] +gll_longitude = [ + [86, 71.4472, "W"], + [123, 11.12, "W"], + [147, 18.5084, "E"], + [0, 0.0, "W"], +] +gll_latitude = [[37, 11.0942, "N"], [49, 16.45, "N"], [42, 50.5589, "S"], [0, 0.0, "N"]] +gll_timestamp = [[0, 8, 12.0], [22, 54, 44.0], [9, 22, 4.999], [23, 59, 47.0]] gll_valid = [True, True, True, False] +test_PGTOP = [ # PGTOP,11,3 *6F\r\n + "$PGTOP,11,1*6D\n", + "$PGTOP,11,2*6E\n", + "$PGTOP,11,3*6F\n", +] +pgtop_parsed_string = [ + ["PGTOP", "11", "1", "6D"], + ["PGTOP", "11", "2", "6E"], + ["PGTOP", "11", "3", "6F"], +] +pgtop_crc_values = [0x6D, 0x6E, 0x6F] +pgtop_valid = [True, True, True] + def test_rmc_sentences(): my_gps = MicropyGPS() - sentence = '' - print('') + sentence = "" + print("") for sentence_count, RMC_sentence in enumerate(test_RMC): for y in RMC_sentence: sentence = my_gps.update(y) if sentence: assert sentence == "GPRMC" - print('Parsed a', sentence, 'Sentence') + print("Parsed a", sentence, "Sentence") assert my_gps.gps_segments == rmc_parsed_strings[sentence_count] - print('Parsed Strings:', my_gps.gps_segments) + print("Parsed Strings:", my_gps.gps_segments) assert my_gps.crc_xor == rmc_crc_values[sentence_count] - print('Sentence CRC Value:', hex(my_gps.crc_xor)) + print("Sentence CRC Value:", hex(my_gps.crc_xor)) assert my_gps.longitude == rmc_longitude[sentence_count] - print('Longitude:', my_gps.longitude) + print("Longitude:", my_gps.longitude) assert my_gps.latitude == rmc_latitude[sentence_count] - print('Latitude', my_gps.latitude) + print("Latitude", my_gps.latitude) assert my_gps.timestamp == rmc_utc[sentence_count] - print('UTC Timestamp:', my_gps.timestamp) + print("UTC Timestamp:", my_gps.timestamp) assert my_gps.speed == rmc_speed[sentence_count] - print('Speed:', my_gps.speed) + print("Speed:", my_gps.speed) assert my_gps.date == rmc_date[sentence_count] - print('Date Stamp:', my_gps.date) + print("Date Stamp:", my_gps.date) assert my_gps.course == rmc_course[sentence_count] - print('Course', my_gps.course) + print("Course", my_gps.course) assert my_gps.valid - print('Data is Valid:', my_gps.valid) + print("Data is Valid:", my_gps.valid) assert my_gps.compass_direction() == rmc_compass[sentence_count] - print('Compass Direction:', my_gps.compass_direction()) + print("Compass Direction:", my_gps.compass_direction()) assert my_gps.clean_sentences == len(test_RMC) assert my_gps.parsed_sentences == len(test_RMC) assert my_gps.crc_fails == 0 @@ -220,24 +917,36 @@ def test_rmc_sentences(): def test_vtg_sentences(): my_gps = MicropyGPS() - sentence = '' - print('') + sentence = "" + print("") for VTG_sentence in test_VTG: for y in VTG_sentence: sentence = my_gps.update(y) if sentence: assert sentence == "GPVTG" - print('Parsed a', sentence, 'Sentence') - assert my_gps.gps_segments == ['GPVTG', '232.9', 'T', '', 'M', '002.3', 'N', '004.3', 'K', 'A', '01'] - print('Parsed Strings', my_gps.gps_segments) + print("Parsed a", sentence, "Sentence") + assert my_gps.gps_segments == [ + "GPVTG", + "232.9", + "T", + "", + "M", + "002.3", + "N", + "004.3", + "K", + "A", + "01", + ] + print("Parsed Strings", my_gps.gps_segments) assert my_gps.crc_xor == 0x1 - print('Sentence CRC Value:', hex(my_gps.crc_xor)) - assert my_gps.speed == (2.3, 2.6473, 4.2596) - print('Speed:', my_gps.speed) + print("Sentence CRC Value:", hex(my_gps.crc_xor)) + assert my_gps.speed == [2.3, 2.6473, 4.2596] + print("Speed:", my_gps.speed) assert my_gps.course == 232.9 - print('Course', my_gps.course) - assert my_gps.compass_direction() == 'SW' - print('Compass Direction:', my_gps.compass_direction()) + print("Course", my_gps.course) + assert my_gps.compass_direction() == "SW" + print("Compass Direction:", my_gps.compass_direction()) assert my_gps.clean_sentences == len(test_VTG) assert my_gps.parsed_sentences == len(test_VTG) assert my_gps.crc_fails == 0 @@ -245,34 +954,36 @@ def test_vtg_sentences(): def test_gga_sentences(): my_gps = MicropyGPS() - sentence = '' - print('') + sentence = "" + print("") for sentence_count, GGA_sentence in enumerate(test_GGA): for y in GGA_sentence: sentence = my_gps.update(y) if sentence: assert sentence == "GPGGA" - print('Parsed a', sentence, 'Sentence') + print("Parsed a", sentence, "Sentence") assert my_gps.gps_segments == gga_parsed_strings[sentence_count] - print('Parsed Strings', my_gps.gps_segments) + print("Parsed Strings", my_gps.gps_segments) assert my_gps.crc_xor == gga_crc_xors[sentence_count] - print('Sentence CRC Value:', hex(my_gps.crc_xor)) + print("Sentence CRC Value:", hex(my_gps.crc_xor)) assert my_gps.longitude == gga_longitudes[sentence_count] - print('Longitude', my_gps.longitude) + print("Longitude", my_gps.longitude) assert my_gps.latitude == gga_latitudes[sentence_count] - print('Latitude', my_gps.latitude) + print("Latitude", my_gps.latitude) assert my_gps.timestamp == gga_timestamps[sentence_count] - print('UTC Timestamp:', my_gps.timestamp) + print("UTC Timestamp:", my_gps.timestamp) assert my_gps.fix_stat == gga_fixes[sentence_count] - print('Fix Status:', my_gps.fix_stat) + print("Fix Status:", my_gps.fix_stat) assert my_gps.altitude == gga_altitudes[sentence_count] - print('Altitude:', my_gps.altitude) + print("Altitude:", my_gps.altitude) assert my_gps.geoid_height == gga_geoid_heights[sentence_count] - print('Height Above Geoid:', my_gps.geoid_height) + print("Height Above Geoid:", my_gps.geoid_height) assert my_gps.hdop == gga_hdops[sentence_count] - print('Horizontal Dilution of Precision:', my_gps.hdop) - assert my_gps.satellites_in_use == gga_satellites_in_uses[sentence_count] - print('Satellites in Use by Receiver:', my_gps.satellites_in_use) + print("Horizontal Dilution of Precision:", my_gps.hdop) + assert ( + my_gps.satellites_in_use == gga_satellites_in_uses[sentence_count] + ) + print("Satellites in Use by Receiver:", my_gps.satellites_in_use) assert my_gps.clean_sentences == len(test_GGA) assert my_gps.parsed_sentences == len(test_GGA) assert my_gps.crc_fails == 0 @@ -280,28 +991,28 @@ def test_gga_sentences(): def test_gsa_sentences(): my_gps = MicropyGPS() - sentence = '' - print('') + sentence = "" + print("") for sentence_count, GSA_sentence in enumerate(test_GSA): for y in GSA_sentence: sentence = my_gps.update(y) if sentence: assert sentence == "GPGSA" - print('Parsed a', sentence, 'Sentence') + print("Parsed a", sentence, "Sentence") assert my_gps.gps_segments == gsa_parsed_strings[sentence_count] - print('Parsed Strings', my_gps.gps_segments) + print("Parsed Strings", my_gps.gps_segments) assert my_gps.crc_xor == gsa_crc_values[sentence_count] - print('Sentence CRC Value:', hex(my_gps.crc_xor)) + print("Sentence CRC Value:", hex(my_gps.crc_xor)) assert my_gps.satellites_used == gsa_sats_used[sentence_count] - print('Satellites Used', my_gps.satellites_used) + print("Satellites Used", my_gps.satellites_used) assert my_gps.fix_type == 3 - print('Fix Type Code:', my_gps.fix_type) + print("Fix Type Code:", my_gps.fix_type) assert my_gps.hdop == gsa_hdop[sentence_count] - print('Horizontal Dilution of Precision:', my_gps.hdop) + print("Horizontal Dilution of Precision:", my_gps.hdop) assert my_gps.vdop == gsa_vdop[sentence_count] - print('Vertical Dilution of Precision:', my_gps.vdop) + print("Vertical Dilution of Precision:", my_gps.vdop) assert my_gps.pdop == gsa_pdop[sentence_count] - print('Position Dilution of Precision:', my_gps.pdop) + print("Position Dilution of Precision:", my_gps.pdop) assert my_gps.clean_sentences == len(test_GSA) assert my_gps.parsed_sentences == len(test_GSA) assert my_gps.crc_fails == 0 @@ -309,33 +1020,33 @@ def test_gsa_sentences(): def test_gsv_sentences(): my_gps = MicropyGPS() - sentence = '' - print('') + sentence = "" + print("") for sentence_count, GSV_sentence in enumerate(test_GSV): for y in GSV_sentence: sentence = my_gps.update(y) if sentence: assert sentence == "GPGSV" - print('Parsed a', sentence, 'Sentence') + print("Parsed a", sentence, "Sentence") assert my_gps.gps_segments == gsv_parsed_string[sentence_count] - print('Parsed Strings', my_gps.gps_segments) + print("Parsed Strings", my_gps.gps_segments) assert my_gps.crc_xor == gsv_crc_values[sentence_count] - print('Sentence CRC Value:', hex(my_gps.crc_xor)) + print("Sentence CRC Value:", hex(my_gps.crc_xor)) assert my_gps.last_sv_sentence == gsv_sv_setence[sentence_count] - print('SV Sentences Parsed', my_gps.last_sv_sentence) + print("SV Sentences Parsed", my_gps.last_sv_sentence) assert my_gps.total_sv_sentences == gsv_total_sentence[sentence_count] - print('SV Sentences in Total', my_gps.total_sv_sentences) + print("SV Sentences in Total", my_gps.total_sv_sentences) assert my_gps.satellites_in_view == gsv_num_sats_in_view[sentence_count] - print('# of Satellites in View:', my_gps.satellites_in_view) + print("# of Satellites in View:", my_gps.satellites_in_view) assert my_gps.satellite_data_updated() == gsv_data_valid[sentence_count] data_valid = my_gps.satellite_data_updated() - print('Is Satellite Data Valid?:', data_valid) + print("Is Satellite Data Valid?:", data_valid) if data_valid: - print('Complete Satellite Data:', my_gps.satellite_data) - print('Complete Satellites Visible:', my_gps.satellites_visible()) + print("Complete Satellite Data:", my_gps.satellite_data) + print("Complete Satellites Visible:", my_gps.satellites_visible()) else: - print('Current Satellite Data:', my_gps.satellite_data) - print('Current Satellites Visible:', my_gps.satellites_visible()) + print("Current Satellite Data:", my_gps.satellite_data) + print("Current Satellites Visible:", my_gps.satellites_visible()) assert my_gps.satellite_data == gsv_sat_data[sentence_count] assert my_gps.satellites_visible() == gsv_sats_in_view[sentence_count] assert my_gps.clean_sentences == len(test_GSV) @@ -345,52 +1056,71 @@ def test_gsv_sentences(): def test_gll_sentences(): my_gps = MicropyGPS() - sentence = '' - print('') + sentence = "" + print("") for sentence_count, GLL_sentence in enumerate(test_GLL): for y in GLL_sentence: sentence = my_gps.update(y) if sentence: assert sentence == "GPGLL" - print('Parsed a', sentence, 'Sentence') + print("Parsed a", sentence, "Sentence") assert my_gps.gps_segments == gll_parsed_string[sentence_count] - print('Parsed Strings', my_gps.gps_segments) + print("Parsed Strings", my_gps.gps_segments) assert my_gps.crc_xor == gll_crc_values[sentence_count] - print('Sentence CRC Value:', hex(my_gps.crc_xor)) + print("Sentence CRC Value:", hex(my_gps.crc_xor)) assert my_gps.longitude == gll_longitude[sentence_count] - print('Longitude:', my_gps.longitude) + print("Longitude:", my_gps.longitude) assert my_gps.latitude == gll_latitude[sentence_count] - print('Latitude', my_gps.latitude) + print("Latitude", my_gps.latitude) assert my_gps.timestamp == gll_timestamp[sentence_count] - print('UTC Timestamp:', my_gps.timestamp) + print("UTC Timestamp:", my_gps.timestamp) assert my_gps.valid == gll_valid[sentence_count] - print('Data is Valid:', my_gps.valid) + print("Data is Valid:", my_gps.valid) assert my_gps.clean_sentences == len(test_GLL) assert my_gps.parsed_sentences == len(test_GLL) assert my_gps.crc_fails == 0 +def test_pgtop_sentences(): + my_gps = MicropyGPS() + sentence = "" + print("") + for sentence_count, PGTOP_sentence in enumerate(test_PGTOP): + for y in PGTOP_sentence: + sentence = my_gps.update(y) + if sentence: + assert sentence == "PGTOP" + print("Parsed a", sentence, "Sentence") + assert my_gps.gps_segments == pgtop_parsed_string[sentence_count] + print("Parsed Strings", my_gps.gps_segments) + assert my_gps.crc_xor == pgtop_crc_values[sentence_count] + print("Sentence CRC Value:", hex(my_gps.crc_xor)) + assert my_gps.clean_sentences == len(test_PGTOP) + assert my_gps.parsed_sentences == len(test_PGTOP) + assert my_gps.crc_fails == 0 + + def test_logging(): my_gps = MicropyGPS() - assert my_gps.start_logging('test.txt', mode="new") - assert my_gps.write_log('micropyGPS test log\n') + assert my_gps.start_logging("test.txt", mode="new") + assert my_gps.write_log("micropyGPS test log\n") for RMC_sentence in test_RMC: for y in RMC_sentence: my_gps.update(y) assert my_gps.stop_logging() - with open('test.txt', 'rb') as log_file: + with open("test.txt", "rb") as log_file: log_hash = hashlib.md5() log_hash.update(log_file.read()) - assert log_hash.digest() == b'\x33\xa7\x5e\xae\xeb\x8d\xf8\xe8\xad\x5e\x54\xa2\xfd\x6a\x11\xa3' - assert my_gps.start_logging('test.txt', mode="append") + assert log_hash.digest() == b"'\xd8\xaeN\xa0\nS\x04e\x1cgb'\x95\xb7\xe3" + assert my_gps.start_logging("test.txt", mode="append") for GSV_sentence in test_GSV: for y in GSV_sentence: my_gps.update(y) assert my_gps.stop_logging() - with open('test.txt', 'rb') as log_file: + with open("test.txt", "rb") as log_file: log_hash = hashlib.md5() log_hash.update(log_file.read()) - assert log_hash.digest() == b'\xa4\x16\x79\xe1\xf9\x30\x0e\xd9\x73\xc8\x43\xc4\xa4\x0f\xe4\x3b' + assert log_hash.digest() == b"\xceY\xdeMlwH\x85N\xf2\x1b=\xf9\x8e\x81\x16" def test_pretty_print(): @@ -404,35 +1134,63 @@ def test_pretty_print(): for VTG_sentence in test_VTG: for y in VTG_sentence: my_gps.update(y) - print('') + print("") assert my_gps.latitude_string() == "37° 49.1802' N" - print('Latitude:', my_gps.latitude_string()) + print("Latitude:", my_gps.latitude_string()) assert my_gps.longitude_string() == "83° 38.7865' W" - print('Longitude:', my_gps.longitude_string()) - assert my_gps.speed_string('kph') == '4.2596 km/h' - print('Speed:', my_gps.speed_string('kph'), 'or', my_gps.speed_string('mph'), 'or', my_gps.speed_string('knot')) - assert my_gps.speed_string('mph') == '2.6473 mph' - assert my_gps.speed_string('knot') == '2.3 knots' - assert my_gps.date_string('long') == 'May 28th, 2011' - print('Date (Long Format):', my_gps.date_string('long')) - assert my_gps.date_string('s_dmy') == '28/05/11' - print('Date (Short D/M/Y Format):', my_gps.date_string('s_dmy')) - assert my_gps.date_string('s_mdy') == '05/28/11' - print('Date (Short M/D/Y Format):', my_gps.date_string('s_mdy')) + print("Longitude:", my_gps.longitude_string()) + # Legacy speed format + assert my_gps.speed_string(KPH) == "4.2596 km/h" + print( + "Speed:", + my_gps.speed_string(KPH), + "or", + my_gps.speed_string(MPH), + "or", + my_gps.speed_string(KNOT), + ) + assert my_gps.speed_string(MPH) == "2.6473 mph" + assert my_gps.speed_string(KNOT) == "2.3 knots" + + # xx.yy speed format + my_gps.set_speed_formatter(my_gps.f2_2_format) + assert my_gps.speed_string(KPH) == "04.26 km/h" + print( + "Speed:", + my_gps.speed_string(KPH), + "or", + my_gps.speed_string(MPH), + "or", + my_gps.speed_string(KNOT), + ) + assert my_gps.speed_string(MPH) == "02.65 mph" + assert my_gps.speed_string(KNOT) == "02.30 knots" + + assert my_gps.date_string(LONG) == "May 28th, 2011" + print("Date (Long Format):", my_gps.date_string(LONG)) + assert my_gps.date_string(S_DMY) == "28/05/11" + print("Date (Short D/M/Y Format):", my_gps.date_string(S_DMY)) + assert my_gps.date_string(S_MDY) == "05/28/11" + print("Date (Short M/D/Y Format):", my_gps.date_string(S_MDY)) + assert my_gps.date_string(L_YMD) == "2011-05-28" + print("Date (Long Y/M/D Format):", my_gps.date_string(L_YMD)) + + assert my_gps.timestamp_string(S_HMS) == "18:00:50.896" + print("Timestamp (hh:mm:ss.us format):", my_gps.timestamp_string(S_HMS)) def test_coordinate_representations(): - my_gps = MicropyGPS(location_formatting='dd') + my_gps = MicropyGPS(location_formatting=DD) for RMC_sentence in test_RMC[5]: for y in RMC_sentence: my_gps.update(y) - print('') - assert my_gps.latitude_string() == '53.361336666666666° N' - print('Decimal Degrees Latitude:', my_gps.latitude_string()) - assert my_gps.longitude_string() == '6.5056183333333335° W' - print('Decimal Degrees Longitude:', my_gps.longitude_string()) - my_gps.coord_format = 'dms' - print('Degrees Minutes Seconds Latitude:', my_gps.latitude_string()) + print("") + assert my_gps.latitude_string() == "53.361336666666666° N" + print("Decimal Degrees Latitude:", my_gps.latitude_string()) + assert my_gps.longitude_string() == "6.5056183333333335° W" + print("Decimal Degrees Longitude:", my_gps.longitude_string()) + my_gps.coord_format = DMS + print("Degrees Minutes Seconds Latitude:", my_gps.latitude_string()) assert my_gps.latitude_string() == """53° 21' 41" N""" assert my_gps.longitude_string() == """6° 30' 20" W""" - print('Degrees Minutes Seconds Longitude:', my_gps.longitude_string()) + print("Degrees Minutes Seconds Longitude:", my_gps.longitude_string())