From ae0019d7145c045a46d0bb1ce228a651c5ff4ca4 Mon Sep 17 00:00:00 2001 From: Alec Delaney <89490472+tekktrik@users.noreply.github.com> Date: Mon, 22 May 2023 23:17:38 -0400 Subject: [PATCH 1/7] Fix format specifier --- examples/gps_simpletest.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/examples/gps_simpletest.py b/examples/gps_simpletest.py index 0e028e4..8c5714d 100644 --- a/examples/gps_simpletest.py +++ b/examples/gps_simpletest.py @@ -7,6 +7,7 @@ import time import board import busio +import math import adafruit_gps @@ -84,13 +85,13 @@ print("Latitude: {0:.6f} degrees".format(gps.latitude)) print("Longitude: {0:.6f} degrees".format(gps.longitude)) print( - "Precise Latitude: {:2.}{:2.4f} degrees".format( - gps.latitude_degrees, gps.latitude_minutes + "Precise Latitude: {:3.0f} degs, {:2.4f} mins".format( + math.floor(gps.latitude_degrees), gps.latitude_minutes ) ) print( - "Precise Longitude: {:2.}{:2.4f} degrees".format( - gps.longitude_degrees, gps.longitude_minutes + "Precise Longitude: {:3.0f} degs, {:2.4f} mins".format( + math.floor(gps.longitude_degrees), gps.longitude_minutes ) ) print("Fix quality: {}".format(gps.fix_quality)) From 9fd82a7b77c36f314553769c6d063bff5e777481 Mon Sep 17 00:00:00 2001 From: Alec Delaney <89490472+tekktrik@users.noreply.github.com> Date: Mon, 22 May 2023 23:23:57 -0400 Subject: [PATCH 2/7] Reorganize imports --- examples/gps_simpletest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/gps_simpletest.py b/examples/gps_simpletest.py index 8c5714d..6434f27 100644 --- a/examples/gps_simpletest.py +++ b/examples/gps_simpletest.py @@ -5,9 +5,9 @@ # Will wait for a fix and print a message every second with the current location # and other details. import time +import math import board import busio -import math import adafruit_gps From 42dd7550473f902fa7c5e9b747fc2ba6703acda2 Mon Sep 17 00:00:00 2001 From: Jonas Kittner Date: Tue, 30 May 2023 17:32:00 +0200 Subject: [PATCH 3/7] fix minute parsing --- adafruit_gps.py | 100 ++++++++++++++++++++++--------------- tests/adafruit_gps_test.py | 35 ++++++++++++- tox.ini | 2 +- 3 files changed, 96 insertions(+), 41 deletions(-) diff --git a/adafruit_gps.py b/adafruit_gps.py index e97ca46..4d3ec99 100644 --- a/adafruit_gps.py +++ b/adafruit_gps.py @@ -95,7 +95,7 @@ def _parse_degrees(nmea_data: str) -> int: degrees = int(raw[0]) // 100 * 1000000 # the ddd minutes = int(raw[0]) % 100 # the mm. minutes += int(f"{raw[1][:4]:0<4}") / 10000 - minutes = int(minutes / 60 * 1000000) + minutes = int((minutes * 1000000) / 60) return degrees + minutes @@ -125,12 +125,22 @@ def _read_degrees(data: List[float], index: int, neg: str) -> float: return x -def _read_int_degrees(data: List[float], index: int, neg: str) -> Tuple[int, float]: - deg = data[index] // 1000000 - minutes = data[index] % 1000000 / 10000 +def _read_deg_mins(data: List[str], index: int, neg: str) -> Tuple[int, float]: + # the degrees come in different formats and vary between latitudes and + # longitudes, which makes parsing tricky: + # for latitudes: ddmm,mmmm (0 - 7 decimal places, not zero padded) + # for longitudes: dddmm,mmmm (0 - 7 decimal places, not zero padded) + int_part, _, minutes_decimal = data[index].partition(".") + # we need to parse from right to left, minutes can only have 2 digits + minutes_int = int_part[-2:] + # the rest mus be degrees which are either 2 or 3 digits + deg = int(int_part[:-2]) + # combine the parts of the minutes, this also works when there are no + # decimal places specified in the sentence + minutes = float(f"{minutes_int}.{minutes_decimal}") if data[index + 1].lower() == neg: deg *= -1 - return (deg, minutes) + return deg, minutes def _parse_talker(data_type: bytes) -> Tuple[bytes, bytes]: @@ -490,26 +500,30 @@ def _parse_gll(self, data: List[str]) -> bool: if data is None or len(data) != 7: return False # Unexpected number of params. - data = _parse_data(_GLL, data) + parsed_data = _parse_data(_GLL, data) if data is None: return False # Params didn't parse # Latitude - self.latitude = _read_degrees(data, 0, "s") - self.latitude_degrees, self.latitude_minutes = _read_int_degrees(data, 0, "s") + self.latitude = _read_degrees(parsed_data, 0, "s") + self.latitude_degrees, self.latitude_minutes = _read_deg_mins( + data=data, index=0, neg="s" + ) # Longitude - self.longitude = _read_degrees(data, 2, "w") - self.longitude_degrees, self.longitude_minutes = _read_int_degrees(data, 2, "w") + self.longitude = _read_degrees(parsed_data, 2, "w") + self.longitude_degrees, self.longitude_minutes = _read_deg_mins( + data=data, index=2, neg="w" + ) # UTC time of position - self._update_timestamp_utc(data[4]) + self._update_timestamp_utc(parsed_data[4]) # Status Valid(A) or Invalid(V) - self.isactivedata = data[5] + self.isactivedata = parsed_data[5] # Parse FAA mode indicator - self._mode_indicator = data[6] + self._mode_indicator = parsed_data[6] return True @@ -518,44 +532,48 @@ def _parse_rmc(self, data: List[str]) -> bool: if data is None or len(data) not in (12, 13): return False # Unexpected number of params. - data = _parse_data({12: _RMC, 13: _RMC_4_1}[len(data)], data) - if data is None: + parsed_data = _parse_data({12: _RMC, 13: _RMC_4_1}[len(data)], data) + if parsed_data is None: self.fix_quality = 0 return False # Params didn't parse # UTC time of position and date - self._update_timestamp_utc(data[0], data[8]) + self._update_timestamp_utc(parsed_data[0], parsed_data[8]) # Status Valid(A) or Invalid(V) - self.isactivedata = data[1] - if data[1].lower() == "a": + self.isactivedata = parsed_data[1] + if parsed_data[1].lower() == "a": if self.fix_quality == 0: self.fix_quality = 1 else: self.fix_quality = 0 # Latitude - self.latitude = _read_degrees(data, 2, "s") - self.latitude_degrees, self.latitude_minutes = _read_int_degrees(data, 2, "s") + self.latitude = _read_degrees(parsed_data, 2, "s") + self.latitude_degrees, self.latitude_minutes = _read_deg_mins( + data=data, index=2, neg="s" + ) # Longitude - self.longitude = _read_degrees(data, 4, "w") - self.longitude_degrees, self.longitude_minutes = _read_int_degrees(data, 4, "w") + self.longitude = _read_degrees(parsed_data, 4, "w") + self.longitude_degrees, self.longitude_minutes = _read_deg_mins( + data=data, index=4, neg="w" + ) # Speed over ground, knots - self.speed_knots = data[6] + self.speed_knots = parsed_data[6] # Track made good, degrees true - self.track_angle_deg = data[7] + self.track_angle_deg = parsed_data[7] # Magnetic variation - if data[9] is None or data[10] is None: + if parsed_data[9] is None or parsed_data[10] is None: self._magnetic_variation = None else: - self._magnetic_variation = _read_degrees(data, 9, "w") + self._magnetic_variation = _read_degrees(parsed_data, 9, "w") # Parse FAA mode indicator - self._mode_indicator = data[11] + self._mode_indicator = parsed_data[11] return True @@ -564,37 +582,41 @@ def _parse_gga(self, data: List[str]) -> bool: if data is None or len(data) != 14: return False # Unexpected number of params. - data = _parse_data(_GGA, data) - if data is None: + parsed_data = _parse_data(_GGA, data) + if parsed_data is None: self.fix_quality = 0 return False # Params didn't parse # UTC time of position - self._update_timestamp_utc(data[0]) + self._update_timestamp_utc(parsed_data[0]) # Latitude - self.latitude = _read_degrees(data, 1, "s") - self.latitude_degrees, self.latitude_minutes = _read_int_degrees(data, 1, "s") + self.latitude = _read_degrees(parsed_data, 1, "s") + self.longitude_degrees, self.longitude_minutes = _read_deg_mins( + data=data, index=3, neg="w" + ) # Longitude - self.longitude = _read_degrees(data, 3, "w") - self.longitude_degrees, self.longitude_minutes = _read_int_degrees(data, 3, "w") + self.longitude = _read_degrees(parsed_data, 3, "w") + self.latitude_degrees, self.latitude_minutes = _read_deg_mins( + data=data, index=1, neg="s" + ) # GPS quality indicator - self.fix_quality = data[5] + self.fix_quality = parsed_data[5] # Number of satellites in use, 0 - 12 - self.satellites = data[6] + self.satellites = parsed_data[6] # Horizontal dilution of precision - self.horizontal_dilution = data[7] + self.horizontal_dilution = parsed_data[7] # Antenna altitude relative to mean sea level - self.altitude_m = _parse_float(data[8]) + self.altitude_m = _parse_float(parsed_data[8]) # data[9] - antenna altitude unit, always 'M' ??? # Geoidal separation relative to WGS 84 - self.height_geoid = _parse_float(data[10]) + self.height_geoid = _parse_float(parsed_data[10]) # data[11] - geoidal separation unit, always 'M' ??? # data[12] - Age of differential GPS data, can be null diff --git a/tests/adafruit_gps_test.py b/tests/adafruit_gps_test.py index eb8de9b..e2c09f6 100644 --- a/tests/adafruit_gps_test.py +++ b/tests/adafruit_gps_test.py @@ -15,6 +15,7 @@ from adafruit_gps import _read_degrees from adafruit_gps import _parse_talker from adafruit_gps import _parse_data +from adafruit_gps import _read_deg_mins from adafruit_gps import GPS @@ -177,6 +178,10 @@ def test_GPS_update_rmc_no_magnetic_variation(): assert gps.timestamp_utc == exp_time assert gps.latitude == pytest.approx(12.57613) assert gps.longitude == pytest.approx(1.385391) + assert gps.latitude_degrees == 12 + assert gps.longitude_degrees == 1 + assert gps.latitude_minutes == 34.5678 + assert gps.longitude_minutes == 23.12345 assert gps.fix_quality == 1 assert gps.fix_quality_3d == 0 assert gps.speed_knots == 0.45 @@ -324,6 +329,10 @@ def test_GPS_update_from_GLL(): assert gps.timestamp_utc == exp_time assert gps.latitude == pytest.approx(49.27417) assert gps.longitude == pytest.approx(-123.1853) + assert gps.latitude_degrees == 49 + assert gps.longitude_degrees == -123 + assert gps.latitude_minutes == 16.45 + assert gps.longitude_minutes == 11.12 assert gps.isactivedata == "A" assert gps._mode_indicator == "A" assert gps.fix_quality == 0 @@ -335,7 +344,7 @@ def test_GPS_update_from_GLL(): def test_GPS_update_from_RMC(): - r = b"$GNRMC,001031.00,A,4404.13993,N,12118.86023,W,0.146,084.4,100117,,,A*5d\r\n" + r = b"$GNRMC,001031.00,A,4404.1399,N,12118.8602,W,0.146,084.4,100117,,,A*5d\r\n" # TODO: length 13 and 14 version with mock.patch.object(GPS, "readline", return_value=r): gps = GPS(uart=UartMock()) @@ -349,6 +358,10 @@ def test_GPS_update_from_RMC(): assert gps.has_3d_fix is False assert gps.latitude == pytest.approx(44.069) assert gps.longitude == pytest.approx(-121.3143) + assert gps.latitude_degrees == 44 + assert gps.longitude_degrees == -121 + assert gps.latitude_minutes == 4.1399 + assert gps.longitude_minutes == 18.8602 assert gps.speed_knots == 0.146 assert gps.track_angle_deg == 84.4 assert gps._magnetic_variation is None @@ -364,6 +377,10 @@ def test_GPS_update_from_GGA(): assert gps.timestamp_utc == exp_time assert gps.latitude == pytest.approx(48.1173) assert gps.longitude == pytest.approx(11.51667) + assert gps.latitude_degrees == 48 + assert gps.longitude_degrees == 11 + assert gps.latitude_minutes == 7.038 + assert gps.longitude_minutes == 31.000 assert gps.fix_quality == 1 assert gps.fix_quality_3d == 0 assert gps.satellites == 8 @@ -467,3 +484,19 @@ def test_GPS_update_from_GSV_both_parts_sats_are_removed(): assert gps.update() assert gps.satellites == 2 assert set(gps.sats.keys()) == {"GP12", "GP14", "GP13", "GP15"} + + +@pytest.mark.parametrize( + ("input_str", "exp", "neg"), + ( + (["3723.2475", "N"], (37, 23.2475), "S"), + (["3723.2475", "S"], (-37, 23.2475), "s"), + (["00123.1234", "E"], (1, 23.1234), "W"), + (["00123", "E"], (1, 23), "W"), + (["1234.5678", "E"], (12, 34.5678), "W"), + (["3723.2475123", "N"], (37, 23.2475123), "S"), + (["3723", "N"], (37, 23), "S"), + ), +) +def test_read_min_secs(input_str, exp, neg): + assert _read_deg_mins(data=input_str, index=0, neg=neg) == exp diff --git a/tox.ini b/tox.ini index 6fe1a7a..9383bb5 100644 --- a/tox.ini +++ b/tox.ini @@ -5,7 +5,7 @@ envlist = py37 [testenv] -deps = -rrequirements-dev.txt +deps = -roptional_requirements.txt commands = coverage erase From 01bcf65ece2a83e8e7d15db6d1f9bc5606866890 Mon Sep 17 00:00:00 2001 From: Jonas Kittner Date: Sun, 29 Oct 2023 17:46:02 +0100 Subject: [PATCH 4/7] fix float formatters in example with longitude_degrees being always ints --- examples/gps_simpletest.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/examples/gps_simpletest.py b/examples/gps_simpletest.py index 6434f27..ecf1ca7 100644 --- a/examples/gps_simpletest.py +++ b/examples/gps_simpletest.py @@ -5,7 +5,6 @@ # Will wait for a fix and print a message every second with the current location # and other details. import time -import math import board import busio @@ -85,13 +84,13 @@ print("Latitude: {0:.6f} degrees".format(gps.latitude)) print("Longitude: {0:.6f} degrees".format(gps.longitude)) print( - "Precise Latitude: {:3.0f} degs, {:2.4f} mins".format( - math.floor(gps.latitude_degrees), gps.latitude_minutes + "Precise Latitude: {} degs, {:2.4f} mins".format( + gps.latitude_degrees, gps.latitude_minutes ) ) print( - "Precise Longitude: {:3.0f} degs, {:2.4f} mins".format( - math.floor(gps.longitude_degrees), gps.longitude_minutes + "Precise Longitude: {} degs, {:2.4f} mins".format( + gps.longitude_degrees, gps.longitude_minutes ) ) print("Fix quality: {}".format(gps.fix_quality)) From 500559b0d8dc14d56a1515c82d7e8bc94565c6d2 Mon Sep 17 00:00:00 2001 From: Jonas Kittner Date: Thu, 18 Jan 2024 17:00:50 +0100 Subject: [PATCH 5/7] fix typo --- adafruit_gps.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_gps.py b/adafruit_gps.py index 4d3ec99..ae5ab5c 100644 --- a/adafruit_gps.py +++ b/adafruit_gps.py @@ -133,7 +133,7 @@ def _read_deg_mins(data: List[str], index: int, neg: str) -> Tuple[int, float]: int_part, _, minutes_decimal = data[index].partition(".") # we need to parse from right to left, minutes can only have 2 digits minutes_int = int_part[-2:] - # the rest mus be degrees which are either 2 or 3 digits + # the rest must be degrees which are either 2 or 3 digits deg = int(int_part[:-2]) # combine the parts of the minutes, this also works when there are no # decimal places specified in the sentence From f89329a981b758b61df27bb9c5930a16e0ee0108 Mon Sep 17 00:00:00 2001 From: Jonas Kittner Date: Thu, 18 Jan 2024 17:02:39 +0100 Subject: [PATCH 6/7] remove tox --- tox.ini | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 tox.ini diff --git a/tox.ini b/tox.ini deleted file mode 100644 index 9383bb5..0000000 --- a/tox.ini +++ /dev/null @@ -1,13 +0,0 @@ -# SPDX-FileCopyrightText: 2021 Jonas Kittner -# -# SPDX-License-Identifier: Unlicense -[tox] -envlist = py37 - -[testenv] -deps = -roptional_requirements.txt - -commands = - coverage erase - coverage run -m pytest tests/ - coverage report From 35f4904de310e1168fc5160065244ec5fa2fd85c Mon Sep 17 00:00:00 2001 From: Jonas Kittner Date: Fri, 19 Jan 2024 17:25:24 +0100 Subject: [PATCH 7/7] replace partition with split partition is not available on all boards --- adafruit_gps.py | 6 +++++- tests/adafruit_gps_test.py | 14 +++++++------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/adafruit_gps.py b/adafruit_gps.py index ae5ab5c..163ade2 100644 --- a/adafruit_gps.py +++ b/adafruit_gps.py @@ -130,7 +130,11 @@ def _read_deg_mins(data: List[str], index: int, neg: str) -> Tuple[int, float]: # longitudes, which makes parsing tricky: # for latitudes: ddmm,mmmm (0 - 7 decimal places, not zero padded) # for longitudes: dddmm,mmmm (0 - 7 decimal places, not zero padded) - int_part, _, minutes_decimal = data[index].partition(".") + if "." in data[index]: + int_part, minutes_decimal = data[index].split(".") + else: + int_part, minutes_decimal = data[index], 0 + # we need to parse from right to left, minutes can only have 2 digits minutes_int = int_part[-2:] # the rest must be degrees which are either 2 or 3 digits diff --git a/tests/adafruit_gps_test.py b/tests/adafruit_gps_test.py index e2c09f6..291fd09 100644 --- a/tests/adafruit_gps_test.py +++ b/tests/adafruit_gps_test.py @@ -489,13 +489,13 @@ def test_GPS_update_from_GSV_both_parts_sats_are_removed(): @pytest.mark.parametrize( ("input_str", "exp", "neg"), ( - (["3723.2475", "N"], (37, 23.2475), "S"), - (["3723.2475", "S"], (-37, 23.2475), "s"), - (["00123.1234", "E"], (1, 23.1234), "W"), - (["00123", "E"], (1, 23), "W"), - (["1234.5678", "E"], (12, 34.5678), "W"), - (["3723.2475123", "N"], (37, 23.2475123), "S"), - (["3723", "N"], (37, 23), "S"), + (["3723.2475", "n"], (37, 23.2475), "s"), + (["3723.2475", "s"], (-37, 23.2475), "s"), + (["00123.1234", "e"], (1, 23.1234), "w"), + (["00123", "e"], (1, 23), "w"), + (["1234.5678", "e"], (12, 34.5678), "w"), + (["3723.2475123", "n"], (37, 23.2475123), "s"), + (["3723", "n"], (37, 23), "s"), ), ) def test_read_min_secs(input_str, exp, neg):