Skip to content

Commit ab6b33a

Browse files
authored
Merge pull request #102 from jkittner/minutes_fix
fix minute parsing
2 parents 84a835f + 35f4904 commit ab6b33a

File tree

4 files changed

+101
-55
lines changed

4 files changed

+101
-55
lines changed

adafruit_gps.py

Lines changed: 65 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ def _parse_degrees(nmea_data: str) -> int:
9595
degrees = int(raw[0]) // 100 * 1000000 # the ddd
9696
minutes = int(raw[0]) % 100 # the mm.
9797
minutes += int(f"{raw[1][:4]:0<4}") / 10000
98-
minutes = int(minutes / 60 * 1000000)
98+
minutes = int((minutes * 1000000) / 60)
9999
return degrees + minutes
100100

101101

@@ -125,12 +125,26 @@ def _read_degrees(data: List[float], index: int, neg: str) -> float:
125125
return x
126126

127127

128-
def _read_int_degrees(data: List[float], index: int, neg: str) -> Tuple[int, float]:
129-
deg = data[index] // 1000000
130-
minutes = data[index] % 1000000 / 10000
128+
def _read_deg_mins(data: List[str], index: int, neg: str) -> Tuple[int, float]:
129+
# the degrees come in different formats and vary between latitudes and
130+
# longitudes, which makes parsing tricky:
131+
# for latitudes: ddmm,mmmm (0 - 7 decimal places, not zero padded)
132+
# for longitudes: dddmm,mmmm (0 - 7 decimal places, not zero padded)
133+
if "." in data[index]:
134+
int_part, minutes_decimal = data[index].split(".")
135+
else:
136+
int_part, minutes_decimal = data[index], 0
137+
138+
# we need to parse from right to left, minutes can only have 2 digits
139+
minutes_int = int_part[-2:]
140+
# the rest must be degrees which are either 2 or 3 digits
141+
deg = int(int_part[:-2])
142+
# combine the parts of the minutes, this also works when there are no
143+
# decimal places specified in the sentence
144+
minutes = float(f"{minutes_int}.{minutes_decimal}")
131145
if data[index + 1].lower() == neg:
132146
deg *= -1
133-
return (deg, minutes)
147+
return deg, minutes
134148

135149

136150
def _parse_talker(data_type: bytes) -> Tuple[bytes, bytes]:
@@ -490,26 +504,30 @@ def _parse_gll(self, data: List[str]) -> bool:
490504

491505
if data is None or len(data) != 7:
492506
return False # Unexpected number of params.
493-
data = _parse_data(_GLL, data)
507+
parsed_data = _parse_data(_GLL, data)
494508
if data is None:
495509
return False # Params didn't parse
496510

497511
# Latitude
498-
self.latitude = _read_degrees(data, 0, "s")
499-
self.latitude_degrees, self.latitude_minutes = _read_int_degrees(data, 0, "s")
512+
self.latitude = _read_degrees(parsed_data, 0, "s")
513+
self.latitude_degrees, self.latitude_minutes = _read_deg_mins(
514+
data=data, index=0, neg="s"
515+
)
500516

501517
# Longitude
502-
self.longitude = _read_degrees(data, 2, "w")
503-
self.longitude_degrees, self.longitude_minutes = _read_int_degrees(data, 2, "w")
518+
self.longitude = _read_degrees(parsed_data, 2, "w")
519+
self.longitude_degrees, self.longitude_minutes = _read_deg_mins(
520+
data=data, index=2, neg="w"
521+
)
504522

505523
# UTC time of position
506-
self._update_timestamp_utc(data[4])
524+
self._update_timestamp_utc(parsed_data[4])
507525

508526
# Status Valid(A) or Invalid(V)
509-
self.isactivedata = data[5]
527+
self.isactivedata = parsed_data[5]
510528

511529
# Parse FAA mode indicator
512-
self._mode_indicator = data[6]
530+
self._mode_indicator = parsed_data[6]
513531

514532
return True
515533

@@ -518,44 +536,48 @@ def _parse_rmc(self, data: List[str]) -> bool:
518536

519537
if data is None or len(data) not in (12, 13):
520538
return False # Unexpected number of params.
521-
data = _parse_data({12: _RMC, 13: _RMC_4_1}[len(data)], data)
522-
if data is None:
539+
parsed_data = _parse_data({12: _RMC, 13: _RMC_4_1}[len(data)], data)
540+
if parsed_data is None:
523541
self.fix_quality = 0
524542
return False # Params didn't parse
525543

526544
# UTC time of position and date
527-
self._update_timestamp_utc(data[0], data[8])
545+
self._update_timestamp_utc(parsed_data[0], parsed_data[8])
528546

529547
# Status Valid(A) or Invalid(V)
530-
self.isactivedata = data[1]
531-
if data[1].lower() == "a":
548+
self.isactivedata = parsed_data[1]
549+
if parsed_data[1].lower() == "a":
532550
if self.fix_quality == 0:
533551
self.fix_quality = 1
534552
else:
535553
self.fix_quality = 0
536554

537555
# Latitude
538-
self.latitude = _read_degrees(data, 2, "s")
539-
self.latitude_degrees, self.latitude_minutes = _read_int_degrees(data, 2, "s")
556+
self.latitude = _read_degrees(parsed_data, 2, "s")
557+
self.latitude_degrees, self.latitude_minutes = _read_deg_mins(
558+
data=data, index=2, neg="s"
559+
)
540560

541561
# Longitude
542-
self.longitude = _read_degrees(data, 4, "w")
543-
self.longitude_degrees, self.longitude_minutes = _read_int_degrees(data, 4, "w")
562+
self.longitude = _read_degrees(parsed_data, 4, "w")
563+
self.longitude_degrees, self.longitude_minutes = _read_deg_mins(
564+
data=data, index=4, neg="w"
565+
)
544566

545567
# Speed over ground, knots
546-
self.speed_knots = data[6]
568+
self.speed_knots = parsed_data[6]
547569

548570
# Track made good, degrees true
549-
self.track_angle_deg = data[7]
571+
self.track_angle_deg = parsed_data[7]
550572

551573
# Magnetic variation
552-
if data[9] is None or data[10] is None:
574+
if parsed_data[9] is None or parsed_data[10] is None:
553575
self._magnetic_variation = None
554576
else:
555-
self._magnetic_variation = _read_degrees(data, 9, "w")
577+
self._magnetic_variation = _read_degrees(parsed_data, 9, "w")
556578

557579
# Parse FAA mode indicator
558-
self._mode_indicator = data[11]
580+
self._mode_indicator = parsed_data[11]
559581

560582
return True
561583

@@ -564,37 +586,41 @@ def _parse_gga(self, data: List[str]) -> bool:
564586

565587
if data is None or len(data) != 14:
566588
return False # Unexpected number of params.
567-
data = _parse_data(_GGA, data)
568-
if data is None:
589+
parsed_data = _parse_data(_GGA, data)
590+
if parsed_data is None:
569591
self.fix_quality = 0
570592
return False # Params didn't parse
571593

572594
# UTC time of position
573-
self._update_timestamp_utc(data[0])
595+
self._update_timestamp_utc(parsed_data[0])
574596

575597
# Latitude
576-
self.latitude = _read_degrees(data, 1, "s")
577-
self.latitude_degrees, self.latitude_minutes = _read_int_degrees(data, 1, "s")
598+
self.latitude = _read_degrees(parsed_data, 1, "s")
599+
self.longitude_degrees, self.longitude_minutes = _read_deg_mins(
600+
data=data, index=3, neg="w"
601+
)
578602

579603
# Longitude
580-
self.longitude = _read_degrees(data, 3, "w")
581-
self.longitude_degrees, self.longitude_minutes = _read_int_degrees(data, 3, "w")
604+
self.longitude = _read_degrees(parsed_data, 3, "w")
605+
self.latitude_degrees, self.latitude_minutes = _read_deg_mins(
606+
data=data, index=1, neg="s"
607+
)
582608

583609
# GPS quality indicator
584-
self.fix_quality = data[5]
610+
self.fix_quality = parsed_data[5]
585611

586612
# Number of satellites in use, 0 - 12
587-
self.satellites = data[6]
613+
self.satellites = parsed_data[6]
588614

589615
# Horizontal dilution of precision
590-
self.horizontal_dilution = data[7]
616+
self.horizontal_dilution = parsed_data[7]
591617

592618
# Antenna altitude relative to mean sea level
593-
self.altitude_m = _parse_float(data[8])
619+
self.altitude_m = _parse_float(parsed_data[8])
594620
# data[9] - antenna altitude unit, always 'M' ???
595621

596622
# Geoidal separation relative to WGS 84
597-
self.height_geoid = _parse_float(data[10])
623+
self.height_geoid = _parse_float(parsed_data[10])
598624
# data[11] - geoidal separation unit, always 'M' ???
599625

600626
# data[12] - Age of differential GPS data, can be null

examples/gps_simpletest.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,12 +84,12 @@
8484
print("Latitude: {0:.6f} degrees".format(gps.latitude))
8585
print("Longitude: {0:.6f} degrees".format(gps.longitude))
8686
print(
87-
"Precise Latitude: {:2.}{:2.4f} degrees".format(
87+
"Precise Latitude: {} degs, {:2.4f} mins".format(
8888
gps.latitude_degrees, gps.latitude_minutes
8989
)
9090
)
9191
print(
92-
"Precise Longitude: {:2.}{:2.4f} degrees".format(
92+
"Precise Longitude: {} degs, {:2.4f} mins".format(
9393
gps.longitude_degrees, gps.longitude_minutes
9494
)
9595
)

tests/adafruit_gps_test.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from adafruit_gps import _read_degrees
1616
from adafruit_gps import _parse_talker
1717
from adafruit_gps import _parse_data
18+
from adafruit_gps import _read_deg_mins
1819
from adafruit_gps import GPS
1920

2021

@@ -177,6 +178,10 @@ def test_GPS_update_rmc_no_magnetic_variation():
177178
assert gps.timestamp_utc == exp_time
178179
assert gps.latitude == pytest.approx(12.57613)
179180
assert gps.longitude == pytest.approx(1.385391)
181+
assert gps.latitude_degrees == 12
182+
assert gps.longitude_degrees == 1
183+
assert gps.latitude_minutes == 34.5678
184+
assert gps.longitude_minutes == 23.12345
180185
assert gps.fix_quality == 1
181186
assert gps.fix_quality_3d == 0
182187
assert gps.speed_knots == 0.45
@@ -324,6 +329,10 @@ def test_GPS_update_from_GLL():
324329
assert gps.timestamp_utc == exp_time
325330
assert gps.latitude == pytest.approx(49.27417)
326331
assert gps.longitude == pytest.approx(-123.1853)
332+
assert gps.latitude_degrees == 49
333+
assert gps.longitude_degrees == -123
334+
assert gps.latitude_minutes == 16.45
335+
assert gps.longitude_minutes == 11.12
327336
assert gps.isactivedata == "A"
328337
assert gps._mode_indicator == "A"
329338
assert gps.fix_quality == 0
@@ -335,7 +344,7 @@ def test_GPS_update_from_GLL():
335344

336345

337346
def test_GPS_update_from_RMC():
338-
r = b"$GNRMC,001031.00,A,4404.13993,N,12118.86023,W,0.146,084.4,100117,,,A*5d\r\n"
347+
r = b"$GNRMC,001031.00,A,4404.1399,N,12118.8602,W,0.146,084.4,100117,,,A*5d\r\n"
339348
# TODO: length 13 and 14 version
340349
with mock.patch.object(GPS, "readline", return_value=r):
341350
gps = GPS(uart=UartMock())
@@ -349,6 +358,10 @@ def test_GPS_update_from_RMC():
349358
assert gps.has_3d_fix is False
350359
assert gps.latitude == pytest.approx(44.069)
351360
assert gps.longitude == pytest.approx(-121.3143)
361+
assert gps.latitude_degrees == 44
362+
assert gps.longitude_degrees == -121
363+
assert gps.latitude_minutes == 4.1399
364+
assert gps.longitude_minutes == 18.8602
352365
assert gps.speed_knots == 0.146
353366
assert gps.track_angle_deg == 84.4
354367
assert gps._magnetic_variation is None
@@ -364,6 +377,10 @@ def test_GPS_update_from_GGA():
364377
assert gps.timestamp_utc == exp_time
365378
assert gps.latitude == pytest.approx(48.1173)
366379
assert gps.longitude == pytest.approx(11.51667)
380+
assert gps.latitude_degrees == 48
381+
assert gps.longitude_degrees == 11
382+
assert gps.latitude_minutes == 7.038
383+
assert gps.longitude_minutes == 31.000
367384
assert gps.fix_quality == 1
368385
assert gps.fix_quality_3d == 0
369386
assert gps.satellites == 8
@@ -467,3 +484,19 @@ def test_GPS_update_from_GSV_both_parts_sats_are_removed():
467484
assert gps.update()
468485
assert gps.satellites == 2
469486
assert set(gps.sats.keys()) == {"GP12", "GP14", "GP13", "GP15"}
487+
488+
489+
@pytest.mark.parametrize(
490+
("input_str", "exp", "neg"),
491+
(
492+
(["3723.2475", "n"], (37, 23.2475), "s"),
493+
(["3723.2475", "s"], (-37, 23.2475), "s"),
494+
(["00123.1234", "e"], (1, 23.1234), "w"),
495+
(["00123", "e"], (1, 23), "w"),
496+
(["1234.5678", "e"], (12, 34.5678), "w"),
497+
(["3723.2475123", "n"], (37, 23.2475123), "s"),
498+
(["3723", "n"], (37, 23), "s"),
499+
),
500+
)
501+
def test_read_min_secs(input_str, exp, neg):
502+
assert _read_deg_mins(data=input_str, index=0, neg=neg) == exp

tox.ini

Lines changed: 0 additions & 13 deletions
This file was deleted.

0 commit comments

Comments
 (0)