Skip to content

Commit

Permalink
Merge pull request #102 from jkittner/minutes_fix
Browse files Browse the repository at this point in the history
fix minute parsing
  • Loading branch information
tekktrik authored Mar 21, 2024
2 parents 84a835f + 35f4904 commit ab6b33a
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 55 deletions.
104 changes: 65 additions & 39 deletions adafruit_gps.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -125,12 +125,26 @@ 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)
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
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]:
Expand Down Expand Up @@ -490,26 +504,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

Expand All @@ -518,44 +536,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

Expand All @@ -564,37 +586,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
Expand Down
4 changes: 2 additions & 2 deletions examples/gps_simpletest.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,12 @@
print("Latitude: {0:.6f} degrees".format(gps.latitude))
print("Longitude: {0:.6f} degrees".format(gps.longitude))
print(
"Precise Latitude: {:2.}{:2.4f} degrees".format(
"Precise Latitude: {} degs, {:2.4f} mins".format(
gps.latitude_degrees, gps.latitude_minutes
)
)
print(
"Precise Longitude: {:2.}{:2.4f} degrees".format(
"Precise Longitude: {} degs, {:2.4f} mins".format(
gps.longitude_degrees, gps.longitude_minutes
)
)
Expand Down
35 changes: 34 additions & 1 deletion tests/adafruit_gps_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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())
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
13 changes: 0 additions & 13 deletions tox.ini

This file was deleted.

0 comments on commit ab6b33a

Please sign in to comment.