Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix minute parsing #102

Merged
merged 7 commits into from
Mar 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"
tekktrik marked this conversation as resolved.
Show resolved Hide resolved
)

# 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.