adafruit-circuitpython-gps 3.11.7__tar.gz → 3.11.9__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. {adafruit_circuitpython_gps-3.11.7 → adafruit_circuitpython_gps-3.11.9}/.pre-commit-config.yaml +1 -1
  2. {adafruit_circuitpython_gps-3.11.7 → adafruit_circuitpython_gps-3.11.9}/PKG-INFO +1 -1
  3. {adafruit_circuitpython_gps-3.11.7 → adafruit_circuitpython_gps-3.11.9}/adafruit_circuitpython_gps.egg-info/PKG-INFO +1 -1
  4. {adafruit_circuitpython_gps-3.11.7 → adafruit_circuitpython_gps-3.11.9}/adafruit_gps.py +67 -19
  5. {adafruit_circuitpython_gps-3.11.7 → adafruit_circuitpython_gps-3.11.9}/pyproject.toml +1 -1
  6. {adafruit_circuitpython_gps-3.11.7 → adafruit_circuitpython_gps-3.11.9}/tests/adafruit_gps_test.py +2 -2
  7. {adafruit_circuitpython_gps-3.11.7 → adafruit_circuitpython_gps-3.11.9}/.coveragerc +0 -0
  8. {adafruit_circuitpython_gps-3.11.7 → adafruit_circuitpython_gps-3.11.9}/.gitattributes +0 -0
  9. {adafruit_circuitpython_gps-3.11.7 → adafruit_circuitpython_gps-3.11.9}/.github/PULL_REQUEST_TEMPLATE/adafruit_circuitpython_pr.md +0 -0
  10. {adafruit_circuitpython_gps-3.11.7 → adafruit_circuitpython_gps-3.11.9}/.github/workflows/build.yml +0 -0
  11. {adafruit_circuitpython_gps-3.11.7 → adafruit_circuitpython_gps-3.11.9}/.github/workflows/failure-help-text.yml +0 -0
  12. {adafruit_circuitpython_gps-3.11.7 → adafruit_circuitpython_gps-3.11.9}/.github/workflows/release_gh.yml +0 -0
  13. {adafruit_circuitpython_gps-3.11.7 → adafruit_circuitpython_gps-3.11.9}/.github/workflows/release_pypi.yml +0 -0
  14. {adafruit_circuitpython_gps-3.11.7 → adafruit_circuitpython_gps-3.11.9}/.gitignore +0 -0
  15. {adafruit_circuitpython_gps-3.11.7 → adafruit_circuitpython_gps-3.11.9}/.readthedocs.yaml +0 -0
  16. {adafruit_circuitpython_gps-3.11.7 → adafruit_circuitpython_gps-3.11.9}/CODE_OF_CONDUCT.md +0 -0
  17. {adafruit_circuitpython_gps-3.11.7 → adafruit_circuitpython_gps-3.11.9}/LICENSE +0 -0
  18. {adafruit_circuitpython_gps-3.11.7 → adafruit_circuitpython_gps-3.11.9}/LICENSES/CC-BY-4.0.txt +0 -0
  19. {adafruit_circuitpython_gps-3.11.7 → adafruit_circuitpython_gps-3.11.9}/LICENSES/MIT.txt +0 -0
  20. {adafruit_circuitpython_gps-3.11.7 → adafruit_circuitpython_gps-3.11.9}/LICENSES/Unlicense.txt +0 -0
  21. {adafruit_circuitpython_gps-3.11.7 → adafruit_circuitpython_gps-3.11.9}/README.rst +0 -0
  22. {adafruit_circuitpython_gps-3.11.7 → adafruit_circuitpython_gps-3.11.9}/README.rst.license +0 -0
  23. {adafruit_circuitpython_gps-3.11.7 → adafruit_circuitpython_gps-3.11.9}/adafruit_circuitpython_gps.egg-info/SOURCES.txt +0 -0
  24. {adafruit_circuitpython_gps-3.11.7 → adafruit_circuitpython_gps-3.11.9}/adafruit_circuitpython_gps.egg-info/dependency_links.txt +0 -0
  25. {adafruit_circuitpython_gps-3.11.7 → adafruit_circuitpython_gps-3.11.9}/adafruit_circuitpython_gps.egg-info/requires.txt +0 -0
  26. {adafruit_circuitpython_gps-3.11.7 → adafruit_circuitpython_gps-3.11.9}/adafruit_circuitpython_gps.egg-info/top_level.txt +0 -0
  27. {adafruit_circuitpython_gps-3.11.7 → adafruit_circuitpython_gps-3.11.9}/docs/_static/custom.css +0 -0
  28. {adafruit_circuitpython_gps-3.11.7 → adafruit_circuitpython_gps-3.11.9}/docs/_static/favicon.ico +0 -0
  29. {adafruit_circuitpython_gps-3.11.7 → adafruit_circuitpython_gps-3.11.9}/docs/_static/favicon.ico.license +0 -0
  30. {adafruit_circuitpython_gps-3.11.7 → adafruit_circuitpython_gps-3.11.9}/docs/api.rst +0 -0
  31. {adafruit_circuitpython_gps-3.11.7 → adafruit_circuitpython_gps-3.11.9}/docs/api.rst.license +0 -0
  32. {adafruit_circuitpython_gps-3.11.7 → adafruit_circuitpython_gps-3.11.9}/docs/conf.py +0 -0
  33. {adafruit_circuitpython_gps-3.11.7 → adafruit_circuitpython_gps-3.11.9}/docs/details.rst +0 -0
  34. {adafruit_circuitpython_gps-3.11.7 → adafruit_circuitpython_gps-3.11.9}/docs/details.rst.license +0 -0
  35. {adafruit_circuitpython_gps-3.11.7 → adafruit_circuitpython_gps-3.11.9}/docs/examples.rst +0 -0
  36. {adafruit_circuitpython_gps-3.11.7 → adafruit_circuitpython_gps-3.11.9}/docs/examples.rst.license +0 -0
  37. {adafruit_circuitpython_gps-3.11.7 → adafruit_circuitpython_gps-3.11.9}/docs/index.rst +0 -0
  38. {adafruit_circuitpython_gps-3.11.7 → adafruit_circuitpython_gps-3.11.9}/docs/index.rst.license +0 -0
  39. {adafruit_circuitpython_gps-3.11.7 → adafruit_circuitpython_gps-3.11.9}/docs/requirements.txt +0 -0
  40. {adafruit_circuitpython_gps-3.11.7 → adafruit_circuitpython_gps-3.11.9}/examples/gps_datalogging.py +0 -0
  41. {adafruit_circuitpython_gps-3.11.7 → adafruit_circuitpython_gps-3.11.9}/examples/gps_displayio_simpletest.py +0 -0
  42. {adafruit_circuitpython_gps-3.11.7 → adafruit_circuitpython_gps-3.11.9}/examples/gps_echotest.py +0 -0
  43. {adafruit_circuitpython_gps-3.11.7 → adafruit_circuitpython_gps-3.11.9}/examples/gps_satellitefix.py +0 -0
  44. {adafruit_circuitpython_gps-3.11.7 → adafruit_circuitpython_gps-3.11.9}/examples/gps_simpletest.py +0 -0
  45. {adafruit_circuitpython_gps-3.11.7 → adafruit_circuitpython_gps-3.11.9}/examples/gps_time_source.py +0 -0
  46. {adafruit_circuitpython_gps-3.11.7 → adafruit_circuitpython_gps-3.11.9}/optional_requirements.txt +0 -0
  47. {adafruit_circuitpython_gps-3.11.7 → adafruit_circuitpython_gps-3.11.9}/requirements.txt +0 -0
  48. {adafruit_circuitpython_gps-3.11.7 → adafruit_circuitpython_gps-3.11.9}/ruff.toml +0 -0
  49. {adafruit_circuitpython_gps-3.11.7 → adafruit_circuitpython_gps-3.11.9}/setup.cfg +0 -0
  50. {adafruit_circuitpython_gps-3.11.7 → adafruit_circuitpython_gps-3.11.9}/tests/__init__.py +0 -0
@@ -10,7 +10,7 @@ repos:
10
10
  - id: end-of-file-fixer
11
11
  - id: trailing-whitespace
12
12
  - repo: https://github.com/astral-sh/ruff-pre-commit
13
- rev: v0.3.4
13
+ rev: v0.15.8
14
14
  hooks:
15
15
  - id: ruff-format
16
16
  - id: ruff
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: adafruit-circuitpython-gps
3
- Version: 3.11.7
3
+ Version: 3.11.9
4
4
  Summary: CircuitPython library for GPS modules.
5
5
  Author-email: Adafruit Industries <circuitpython@adafruit.com>
6
6
  License-Expression: MIT
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: adafruit-circuitpython-gps
3
- Version: 3.11.7
3
+ Version: 3.11.9
4
4
  Summary: CircuitPython library for GPS modules.
5
5
  Author-email: Adafruit Industries <circuitpython@adafruit.com>
6
6
  License-Expression: MIT
@@ -40,7 +40,7 @@ try:
40
40
  except ImportError:
41
41
  pass
42
42
 
43
- __version__ = "3.11.7"
43
+ __version__ = "3.11.9"
44
44
  __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_GPS.git"
45
45
 
46
46
 
@@ -89,6 +89,22 @@ _SENTENCE_PARAMS = (
89
89
  # Internal helper parsing functions.
90
90
  # These handle input that might be none or null and return none instead of
91
91
  # throwing errors.
92
+ def _split_decimal(nmea_data: str) -> Tuple[Optional[str], str]:
93
+ # Split a numeric NMEA field into its integer and fractional digits.
94
+ # Returns (None, "") if either part is missing or non-numeric. The
95
+ # fractional part is optional; some receivers omit it entirely.
96
+ dot = nmea_data.find(".")
97
+ if dot == -1:
98
+ int_part, minutes_decimal = nmea_data, "0"
99
+ else:
100
+ int_part, minutes_decimal = nmea_data[:dot], nmea_data[dot + 1 :]
101
+ if not minutes_decimal:
102
+ minutes_decimal = "0"
103
+ if not int_part.isdigit() or not minutes_decimal.isdigit():
104
+ return None, ""
105
+ return int_part, minutes_decimal
106
+
107
+
92
108
  def _parse_degrees(nmea_data: str) -> int:
93
109
  # Parse a NMEA lat/long data pair 'dddmm.mmmm' into a pure degrees value.
94
110
  # Where ddd is the degrees, mm.mmmm is the minutes.
@@ -97,10 +113,12 @@ def _parse_degrees(nmea_data: str) -> int:
97
113
  # To avoid losing precision handle degrees and minutes separately
98
114
  # Return the final value as an integer. Further functions can parse
99
115
  # this into a float or separate parts to retain the precision
100
- raw = nmea_data.split(".")
101
- degrees = int(raw[0]) // 100 * 1000000 # the ddd
102
- minutes = int(raw[0]) % 100 # the mm.
103
- minutes += int(f"{raw[1][:4]:0<4}") / 10000
116
+ int_part, minutes_decimal = _split_decimal(nmea_data)
117
+ if int_part is None:
118
+ return None
119
+ degrees = int(int_part) // 100 * 1000000 # the ddd
120
+ minutes = int(int_part) % 100 # the mm.
121
+ minutes += int(f"{minutes_decimal[:4]:0<4}") / 10000
104
122
  minutes = int((minutes * 1000000) / 60)
105
123
  return degrees + minutes
106
124
 
@@ -125,6 +143,8 @@ def _parse_str(nmea_data: str) -> str:
125
143
 
126
144
  def _read_degrees(data: List[float], index: int, neg: str) -> float:
127
145
  # This function loses precision with float32
146
+ if data[index] is None or data[index + 1] is None:
147
+ return None
128
148
  x = data[index] / 1000000
129
149
  if data[index + 1].lower() == neg:
130
150
  x *= -1.0
@@ -136,12 +156,14 @@ def _read_deg_mins(data: List[str], index: int, neg: str) -> Tuple[int, float]:
136
156
  # longitudes, which makes parsing tricky:
137
157
  # for latitudes: ddmm,mmmm (0 - 7 decimal places, not zero padded)
138
158
  # for longitudes: dddmm,mmmm (0 - 7 decimal places, not zero padded)
139
- if "." in data[index]:
140
- int_part, minutes_decimal = data[index].split(".")
141
- else:
142
- int_part, minutes_decimal = data[index], 0
159
+ if data[index] is None or len(data[index]) < 3:
160
+ return None, None
143
161
 
144
162
  # we need to parse from right to left, minutes can only have 2 digits
163
+ int_part, minutes_decimal = _split_decimal(data[index])
164
+ if int_part is None or len(int_part) < 3:
165
+ return None, None
166
+
145
167
  minutes_int = int_part[-2:]
146
168
  # the rest must be degrees which are either 2 or 3 digits
147
169
  deg = int(int_part[:-2])
@@ -161,7 +183,8 @@ def _parse_talker(data_type: bytes) -> Tuple[bytes, bytes]:
161
183
  return (data_type[:2], data_type[2:])
162
184
 
163
185
 
164
- def _parse_data(sentence_type: int, data: List[str]) -> Optional[List]:
186
+ def _parse_data(sentence_type: int, data: List[str]) -> Optional[List]: # noqa: PLR0911
187
+ # PLR0911: each early return is a distinct, self-documenting rejection reason ...
165
188
  """Parse sentence data for the specified sentence type and
166
189
  return a list of parameters in the correct format, or return None.
167
190
  """
@@ -196,8 +219,14 @@ def _parse_data(sentence_type: int, data: List[str]) -> Optional[List]:
196
219
  else:
197
220
  params.append(dti)
198
221
  elif pti == "d":
199
- # A number parseable as degrees
200
- params.append(_parse_degrees(dti))
222
+ # A number parseable as degrees.
223
+ # Required field so unparseable or empty should reject whole sentence.
224
+ if nothing:
225
+ return None
226
+ parsed_degrees = _parse_degrees(dti)
227
+ if parsed_degrees is None:
228
+ return None
229
+ params.append(parsed_degrees)
201
230
  elif pti == "D":
202
231
  # A number parseable as degrees or Nothing
203
232
  if nothing:
@@ -490,11 +519,32 @@ class GPS:
490
519
  data_type = sentence[1:delimiter]
491
520
  return (data_type, sentence[delimiter + 1 :])
492
521
 
522
+ @staticmethod
523
+ def _parse_hhmmss(time_utc: Optional[str]) -> Optional[Tuple[int, int, int]]:
524
+ # Parse an NMEA hhmmss(.sss) field into (hours, mins, secs).
525
+ # Returns None if the field is absent, short, or non-numeric.
526
+ if time_utc is None or len(time_utc) < 6 or not time_utc[0:6].isdigit():
527
+ return None
528
+ return (int(time_utc[0:2]), int(time_utc[2:4]), int(time_utc[4:6]))
529
+
530
+ @staticmethod
531
+ def _parse_ddmmyy(date: Optional[str]) -> Optional[Tuple[int, int, int]]:
532
+ # Parse an NMEA ddmmyy field into (day, month, year).
533
+ # Returns None if the field is absent, short, or non-numeric.
534
+ if date is None or len(date) < 6 or not date[0:6].isdigit():
535
+ return None
536
+ return (int(date[0:2]), int(date[2:4]), 2000 + int(date[4:6]))
537
+
493
538
  def _update_timestamp_utc(self, time_utc: str, date: Optional[str] = None) -> None:
494
- hours = int(time_utc[0:2])
495
- mins = int(time_utc[2:4])
496
- secs = int(time_utc[4:6])
497
- if date is None:
539
+ parsed_time = self._parse_hhmmss(time_utc)
540
+ if parsed_time is None:
541
+ # Malformed time field; leave the previous timestamp untouched.
542
+ return
543
+ hours, mins, secs = parsed_time
544
+
545
+ parsed_date = self._parse_ddmmyy(date)
546
+ if parsed_date is None:
547
+ # No usable date field; carry forward the date we already have.
498
548
  if self.timestamp_utc is None:
499
549
  day, month, year = 0, 0, 0
500
550
  else:
@@ -502,9 +552,7 @@ class GPS:
502
552
  month = self.timestamp_utc.tm_mon
503
553
  year = self.timestamp_utc.tm_year
504
554
  else:
505
- day = int(date[0:2])
506
- month = int(date[2:4])
507
- year = 2000 + int(date[4:6])
555
+ day, month, year = parsed_date
508
556
 
509
557
  self.timestamp_utc = time.struct_time((year, month, day, hours, mins, secs, 0, 0, -1))
510
558
 
@@ -12,7 +12,7 @@ requires = [
12
12
  [project]
13
13
  name = "adafruit-circuitpython-gps"
14
14
  description = "CircuitPython library for GPS modules."
15
- version = "3.11.7"
15
+ version = "3.11.9"
16
16
  readme = "README.rst"
17
17
  authors = [
18
18
  {name = "Adafruit Industries", email = "circuitpython@adafruit.com"}
@@ -301,7 +301,7 @@ def test_GPS_send_command_with_checksum(capsys):
301
301
  gps.send_command(command=b"$PMTK001,314,3\r\n", add_checksum=True)
302
302
  out, err = capsys.readouterr()
303
303
  assert not err
304
- assert out == ("b'$'" "b'$PMTK001,314,3\\r\\n'" "b'*'" "b'15'" "b'\\r\\n'")
304
+ assert out == ("b'$'b'$PMTK001,314,3\\r\\n'b'*'b'15'b'\\r\\n'")
305
305
 
306
306
 
307
307
  def test_GPS_send_command_without_checksum(capsys):
@@ -309,7 +309,7 @@ def test_GPS_send_command_without_checksum(capsys):
309
309
  gps.send_command(command=b"$PMTK001,314,3\r\n", add_checksum=False)
310
310
  out, err = capsys.readouterr()
311
311
  assert not err
312
- assert out == ("b'$'" "b'$PMTK001,314,3\\r\\n'" "b'\\r\\n'")
312
+ assert out == ("b'$'b'$PMTK001,314,3\\r\\n'b'\\r\\n'")
313
313
 
314
314
 
315
315
  def test_GPS_update_from_GLL():