pygnssutils 1.2.7__tar.gz → 1.2.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. {pygnssutils-1.2.7/src/pygnssutils.egg-info → pygnssutils-1.2.9}/PKG-INFO +17 -11
  2. {pygnssutils-1.2.7 → pygnssutils-1.2.9}/README.md +5 -3
  3. {pygnssutils-1.2.7 → pygnssutils-1.2.9}/pyproject.toml +12 -7
  4. {pygnssutils-1.2.7 → pygnssutils-1.2.9}/src/pygnssutils/_version.py +1 -1
  5. {pygnssutils-1.2.7 → pygnssutils-1.2.9}/src/pygnssutils/gnssntripclient.py +89 -85
  6. {pygnssutils-1.2.7 → pygnssutils-1.2.9}/src/pygnssutils/gnssreader.py +25 -33
  7. {pygnssutils-1.2.7 → pygnssutils-1.2.9}/src/pygnssutils/helpers.py +3 -3
  8. {pygnssutils-1.2.7 → pygnssutils-1.2.9}/src/pygnssutils/rinex_conv.py +0 -1
  9. {pygnssutils-1.2.7 → pygnssutils-1.2.9}/src/pygnssutils/rinex_conv_met.py +4 -4
  10. {pygnssutils-1.2.7 → pygnssutils-1.2.9/src/pygnssutils.egg-info}/PKG-INFO +17 -11
  11. {pygnssutils-1.2.7 → pygnssutils-1.2.9}/src/pygnssutils.egg-info/requires.txt +6 -5
  12. {pygnssutils-1.2.7 → pygnssutils-1.2.9}/tests/test_static.py +2 -2
  13. {pygnssutils-1.2.7 → pygnssutils-1.2.9}/LICENSE +0 -0
  14. {pygnssutils-1.2.7 → pygnssutils-1.2.9}/setup.cfg +0 -0
  15. {pygnssutils-1.2.7 → pygnssutils-1.2.9}/src/pygnssutils/__init__.py +0 -0
  16. {pygnssutils-1.2.7 → pygnssutils-1.2.9}/src/pygnssutils/exceptions.py +0 -0
  17. {pygnssutils-1.2.7 → pygnssutils-1.2.9}/src/pygnssutils/globals.py +0 -0
  18. {pygnssutils-1.2.7 → pygnssutils-1.2.9}/src/pygnssutils/gnssntripclient_cli.py +0 -0
  19. {pygnssutils-1.2.7 → pygnssutils-1.2.9}/src/pygnssutils/gnssserver.py +0 -0
  20. {pygnssutils-1.2.7 → pygnssutils-1.2.9}/src/pygnssutils/gnssserver_cli.py +0 -0
  21. {pygnssutils-1.2.7 → pygnssutils-1.2.9}/src/pygnssutils/gnssstreamer.py +0 -0
  22. {pygnssutils-1.2.7 → pygnssutils-1.2.9}/src/pygnssutils/gnssstreamer_cli.py +0 -0
  23. {pygnssutils-1.2.7 → pygnssutils-1.2.9}/src/pygnssutils/rawnav.py +0 -0
  24. {pygnssutils-1.2.7 → pygnssutils-1.2.9}/src/pygnssutils/rawnav_reader.py +0 -0
  25. {pygnssutils-1.2.7 → pygnssutils-1.2.9}/src/pygnssutils/rawnav_subframes_bds.py +0 -0
  26. {pygnssutils-1.2.7 → pygnssutils-1.2.9}/src/pygnssutils/rawnav_subframes_gal.py +0 -0
  27. {pygnssutils-1.2.7 → pygnssutils-1.2.9}/src/pygnssutils/rawnav_subframes_glo.py +0 -0
  28. {pygnssutils-1.2.7 → pygnssutils-1.2.9}/src/pygnssutils/rawnav_subframes_gps.py +0 -0
  29. {pygnssutils-1.2.7 → pygnssutils-1.2.9}/src/pygnssutils/rawnav_subframes_irn.py +0 -0
  30. {pygnssutils-1.2.7 → pygnssutils-1.2.9}/src/pygnssutils/rawnav_subframes_qzs.py +0 -0
  31. {pygnssutils-1.2.7 → pygnssutils-1.2.9}/src/pygnssutils/rawnav_subframes_sba.py +0 -0
  32. {pygnssutils-1.2.7 → pygnssutils-1.2.9}/src/pygnssutils/rinex_conv_cli.py +0 -0
  33. {pygnssutils-1.2.7 → pygnssutils-1.2.9}/src/pygnssutils/rinex_conv_nav.py +0 -0
  34. {pygnssutils-1.2.7 → pygnssutils-1.2.9}/src/pygnssutils/rinex_conv_obs.py +0 -0
  35. {pygnssutils-1.2.7 → pygnssutils-1.2.9}/src/pygnssutils/rinex_globals.py +0 -0
  36. {pygnssutils-1.2.7 → pygnssutils-1.2.9}/src/pygnssutils/rinex_helpers.py +0 -0
  37. {pygnssutils-1.2.7 → pygnssutils-1.2.9}/src/pygnssutils/socket_server.py +0 -0
  38. {pygnssutils-1.2.7 → pygnssutils-1.2.9}/src/pygnssutils.egg-info/SOURCES.txt +0 -0
  39. {pygnssutils-1.2.7 → pygnssutils-1.2.9}/src/pygnssutils.egg-info/dependency_links.txt +0 -0
  40. {pygnssutils-1.2.7 → pygnssutils-1.2.9}/src/pygnssutils.egg-info/entry_points.txt +0 -0
  41. {pygnssutils-1.2.7 → pygnssutils-1.2.9}/src/pygnssutils.egg-info/top_level.txt +0 -0
  42. {pygnssutils-1.2.7 → pygnssutils-1.2.9}/tests/test_cli.py +0 -0
  43. {pygnssutils-1.2.7 → pygnssutils-1.2.9}/tests/test_gnssstreamer.py +0 -0
  44. {pygnssutils-1.2.7 → pygnssutils-1.2.9}/tests/test_rawnav.py +0 -0
  45. {pygnssutils-1.2.7 → pygnssutils-1.2.9}/tests/test_rinex.py +0 -0
  46. {pygnssutils-1.2.7 → pygnssutils-1.2.9}/tests/test_rinex_defs.py +0 -0
  47. {pygnssutils-1.2.7 → pygnssutils-1.2.9}/tests/test_rinex_parse.py +0 -0
  48. {pygnssutils-1.2.7 → pygnssutils-1.2.9}/tests/test_socketwrapper.py +0 -0
  49. {pygnssutils-1.2.7 → pygnssutils-1.2.9}/tests/test_sourcetable.py +0 -0
  50. {pygnssutils-1.2.7 → pygnssutils-1.2.9}/tests/test_stream.py +0 -0
@@ -1,9 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pygnssutils
3
- Version: 1.2.7
3
+ Version: 1.2.9
4
4
  Summary: GNSS Command Line Utilities
5
- Author-email: Steve Smith <semuadmin@semuconsulting.com>
6
- Maintainer-email: Steve Smith <semuadmin@semuconsulting.com>
5
+ Author: Steve Smith
6
+ Author-email: semuadmin@semuconsulting.com
7
+ Maintainer: Steve Smith
8
+ Maintainer-email: semuadmin@semuconsulting.com
7
9
  License-Expression: BSD-3-Clause
8
10
  Project-URL: homepage, https://github.com/semuconsulting/pygnssutils
9
11
  Project-URL: documentation, https://www.semuconsulting.com/pygnssutils/
@@ -24,20 +26,22 @@ Classifier: Programming Language :: Python :: 3.11
24
26
  Classifier: Programming Language :: Python :: 3.12
25
27
  Classifier: Programming Language :: Python :: 3.13
26
28
  Classifier: Programming Language :: Python :: 3.14
29
+ Classifier: Programming Language :: Python :: 3.15
27
30
  Classifier: Topic :: Utilities
28
31
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
29
32
  Classifier: Topic :: Scientific/Engineering :: GIS
30
33
  Requires-Python: >=3.10
31
34
  Description-Content-Type: text/markdown
32
35
  License-File: LICENSE
33
- Requires-Dist: certifi>=2025.0.0
34
- Requires-Dist: pyserial>=3.5
35
- Requires-Dist: pyspartn>=1.0.8
36
- Requires-Dist: pyubx2>=1.3.6
36
+ Requires-Dist: certifi>=2026.0.0
37
37
  Requires-Dist: pynmeagps>=1.1.7
38
+ Requires-Dist: pyqgc>=1.0.0
39
+ Requires-Dist: pyrtcm>=1.2.0
38
40
  Requires-Dist: pysbf2>=1.0.4
41
+ Requires-Dist: pyserial>=3.5
42
+ Requires-Dist: pyspartn>=1.0.8
43
+ Requires-Dist: pyubx2>=1.3.7
39
44
  Requires-Dist: pyubxutils>=1.0.6
40
- Requires-Dist: pyqgc>=1.0.0
41
45
  Requires-Dist: pyunigps>=1.0.0
42
46
  Dynamic: license-file
43
47
 
@@ -92,18 +96,18 @@ The pygnssutils homepage is located at [https://github.com/semuconsulting/pygnss
92
96
 
93
97
  Sphinx API Documentation in HTML format is available at [https://www.semuconsulting.com/pygnssutils](https://www.semuconsulting.com/pygnssutils).
94
98
 
95
- Contributions **_from human beings_** welcome - please refer to [CONTRIBUTING.MD](https://github.com/semuconsulting/pygnssutils/blob/main/CONTRIBUTING.md).
99
+ Contributions _**from human beings**_ welcome - please refer to [CONTRIBUTING.MD](https://github.com/semuconsulting/pygnssutils/blob/main/CONTRIBUTING.md).
96
100
 
97
101
  [Bug reports](https://github.com/semuconsulting/pygnssutils/blob/main/.github/ISSUE_TEMPLATE/bug_report.md) and [Feature requests](https://github.com/semuconsulting/pygnssutils/blob/main/.github/ISSUE_TEMPLATE/feature_request.md) - please use the templates provided. For general queries and advice, post a message to one of the [pygnssutils Discussions](https://github.com/semuconsulting/pygnssutils/discussions) channels.
98
102
 
99
- ![No Copilot](https://github.com/semuconsulting/PyGPSClient/blob/master/images/nocopilot100.png?raw=true)
103
+ ![No Copilot](https://github.com/semuconsulting/PyGPSClient/blob/main/images/nocopilot100.png?raw=true)
100
104
 
101
105
  ---
102
106
  ## <a name="installation">Installation</a>
103
107
 
104
108
  ![Python version](https://img.shields.io/pypi/pyversions/pygnssutils.svg?style=flat)
105
109
  [![PyPI version](https://img.shields.io/pypi/v/pygnssutils.svg?style=flat)](https://pypi.org/project/pygnssutils/)
106
- [![PyPI downloads](https://github.com/semuconsulting/pygpsclient/blob/master/images/clickpy_top10.svg?raw=true)](https://clickpy.clickhouse.com/dashboard/pygnssutils)
110
+ [![PyPI downloads](https://github.com/semuconsulting/pygpsclient/blob/main/images/clickpy_top10.svg?raw=true)](https://clickpy.clickhouse.com/dashboard/pygnssutils)
107
111
 
108
112
  `pygnssutils` is compatible with Python>=3.10.
109
113
 
@@ -630,6 +634,8 @@ semuadmin@semuconsulting.com
630
634
 
631
635
  ![License](https://img.shields.io/github/license/semuconsulting/pygnssutils.svg)
632
636
 
637
+ [![ORCID iD icon](https://orcid.org/sites/default/files/images/orcid_16x16.png) https://orcid.org/0009-0002-5751-8719](https://orcid.org/0009-0002-5751-8719)
638
+
633
639
  `pygnssutils` is maintained entirely by unpaid volunteers. It receives no funding from advertising or corporate sponsorship. If you find the utility useful, please consider sponsoring the project with the price of a coffee...
634
640
 
635
641
  [![Sponsor](https://github.com/semuconsulting/pyubx2/blob/master/images/sponsor.png?raw=true)](https://buymeacoffee.com/semuconsulting)
@@ -49,18 +49,18 @@ The pygnssutils homepage is located at [https://github.com/semuconsulting/pygnss
49
49
 
50
50
  Sphinx API Documentation in HTML format is available at [https://www.semuconsulting.com/pygnssutils](https://www.semuconsulting.com/pygnssutils).
51
51
 
52
- Contributions **_from human beings_** welcome - please refer to [CONTRIBUTING.MD](https://github.com/semuconsulting/pygnssutils/blob/main/CONTRIBUTING.md).
52
+ Contributions _**from human beings**_ welcome - please refer to [CONTRIBUTING.MD](https://github.com/semuconsulting/pygnssutils/blob/main/CONTRIBUTING.md).
53
53
 
54
54
  [Bug reports](https://github.com/semuconsulting/pygnssutils/blob/main/.github/ISSUE_TEMPLATE/bug_report.md) and [Feature requests](https://github.com/semuconsulting/pygnssutils/blob/main/.github/ISSUE_TEMPLATE/feature_request.md) - please use the templates provided. For general queries and advice, post a message to one of the [pygnssutils Discussions](https://github.com/semuconsulting/pygnssutils/discussions) channels.
55
55
 
56
- ![No Copilot](https://github.com/semuconsulting/PyGPSClient/blob/master/images/nocopilot100.png?raw=true)
56
+ ![No Copilot](https://github.com/semuconsulting/PyGPSClient/blob/main/images/nocopilot100.png?raw=true)
57
57
 
58
58
  ---
59
59
  ## <a name="installation">Installation</a>
60
60
 
61
61
  ![Python version](https://img.shields.io/pypi/pyversions/pygnssutils.svg?style=flat)
62
62
  [![PyPI version](https://img.shields.io/pypi/v/pygnssutils.svg?style=flat)](https://pypi.org/project/pygnssutils/)
63
- [![PyPI downloads](https://github.com/semuconsulting/pygpsclient/blob/master/images/clickpy_top10.svg?raw=true)](https://clickpy.clickhouse.com/dashboard/pygnssutils)
63
+ [![PyPI downloads](https://github.com/semuconsulting/pygpsclient/blob/main/images/clickpy_top10.svg?raw=true)](https://clickpy.clickhouse.com/dashboard/pygnssutils)
64
64
 
65
65
  `pygnssutils` is compatible with Python>=3.10.
66
66
 
@@ -587,6 +587,8 @@ semuadmin@semuconsulting.com
587
587
 
588
588
  ![License](https://img.shields.io/github/license/semuconsulting/pygnssutils.svg)
589
589
 
590
+ [![ORCID iD icon](https://orcid.org/sites/default/files/images/orcid_16x16.png) https://orcid.org/0009-0002-5751-8719](https://orcid.org/0009-0002-5751-8719)
591
+
590
592
  `pygnssutils` is maintained entirely by unpaid volunteers. It receives no funding from advertising or corporate sponsorship. If you find the utility useful, please consider sponsoring the project with the price of a coffee...
591
593
 
592
594
  [![Sponsor](https://github.com/semuconsulting/pyubx2/blob/master/images/sponsor.png?raw=true)](https://buymeacoffee.com/semuconsulting)
@@ -5,8 +5,11 @@ build-backend = "setuptools.build_meta:__legacy__"
5
5
  [project]
6
6
  name = "pygnssutils"
7
7
  dynamic = ["version"]
8
- authors = [{ name = "Steve Smith", email = "semuadmin@semuconsulting.com" }]
9
- maintainers = [{ name = "Steve Smith", email = "semuadmin@semuconsulting.com" }]
8
+ authors = [{ name = "Steve Smith" }, { email = "semuadmin@semuconsulting.com" }]
9
+ maintainers = [
10
+ { name = "Steve Smith" },
11
+ { email = "semuadmin@semuconsulting.com" },
12
+ ]
10
13
  description = "GNSS Command Line Utilities"
11
14
  license = "BSD-3-Clause"
12
15
  license-files = ["LICENSE"]
@@ -28,20 +31,22 @@ classifiers = [
28
31
  "Programming Language :: Python :: 3.12",
29
32
  "Programming Language :: Python :: 3.13",
30
33
  "Programming Language :: Python :: 3.14",
34
+ "Programming Language :: Python :: 3.15",
31
35
  "Topic :: Utilities",
32
36
  "Topic :: Software Development :: Libraries :: Python Modules",
33
37
  "Topic :: Scientific/Engineering :: GIS",
34
38
  ]
35
39
 
36
40
  dependencies = [
37
- "certifi>=2025.0.0",
38
- "pyserial>=3.5",
39
- "pyspartn>=1.0.8",
40
- "pyubx2>=1.3.6",
41
+ "certifi>=2026.0.0",
41
42
  "pynmeagps>=1.1.7",
43
+ "pyqgc>=1.0.0",
44
+ "pyrtcm>=1.2.0",
42
45
  "pysbf2>=1.0.4",
46
+ "pyserial>=3.5",
47
+ "pyspartn>=1.0.8",
48
+ "pyubx2>=1.3.7",
43
49
  "pyubxutils>=1.0.6",
44
- "pyqgc>=1.0.0",
45
50
  "pyunigps>=1.0.0",
46
51
  ]
47
52
 
@@ -8,4 +8,4 @@ Created on 2 Oct 2020
8
8
  :license: BSD 3-Clause
9
9
  """
10
10
 
11
- __version__ = "1.2.7"
11
+ __version__ = "1.2.9"
@@ -27,21 +27,28 @@ Created on 03 Jun 2022
27
27
  :license: BSD 3-Clause
28
28
  """
29
29
 
30
- import socket
31
- import ssl
32
30
  from base64 import b64encode
33
31
  from datetime import datetime, timedelta, timezone
34
32
  from io import BufferedWriter, TextIOWrapper
35
33
  from logging import getLogger
36
34
  from os import getenv
37
35
  from queue import Queue
36
+ from socket import (
37
+ SHUT_RDWR,
38
+ create_connection,
39
+ gaierror,
40
+ gethostbyname,
41
+ socket,
42
+ timeout,
43
+ )
44
+ from ssl import PROTOCOL_TLS_CLIENT, SSLCertVerificationError, SSLContext, SSLError
38
45
  from threading import Event, Thread
46
+ from types import NoneType
47
+ from typing import Any
39
48
 
40
49
  from certifi import where as findcacerts
41
50
  from pynmeagps import GET, NMEAMessage, SocketWrapper
42
- from pyrtcm import RTCMMessageError, RTCMParseError, RTCMTypeError
43
- from pyspartn import SPARTNMessageError, SPARTNParseError, SPARTNReader, SPARTNTypeError
44
- from pyubx2 import ERR_LOG, RTCM3_PROTOCOL, UBXReader
51
+ from pyspartn import SPARTNReader
45
52
  from serial import Serial
46
53
 
47
54
  from pygnssutils._version import __version__ as VERSION
@@ -70,6 +77,7 @@ from pygnssutils.globals import (
70
77
  PYGNSSUTILS_CRTPATH,
71
78
  VERBOSITY_MEDIUM,
72
79
  )
80
+ from pygnssutils.gnssreader import ERR_LOG, RTCM3_PROTOCOL, GNSSReader
73
81
  from pygnssutils.helpers import find_mp_distance, ipprot2int, set_logging
74
82
 
75
83
  TIMEOUT = 3
@@ -91,13 +99,13 @@ class GNSSNTRIPClient:
91
99
 
92
100
  def __init__(
93
101
  self,
94
- app=None,
102
+ app: Any | NoneType = None,
95
103
  **kwargs,
96
104
  ):
97
105
  """
98
106
  Constructor.
99
107
 
100
- :param object app: application from which this class is invoked (None)
108
+ :param Any | NoneType app: application from which this class is invoked (None)
101
109
  :param int retries: (kwarg) maximum failed connection retries (5)
102
110
  :param int retryinterval: (kwarg) retry interval in seconds (10)
103
111
  :param int timeout: (kwarg) inactivity timeout in seconds (10)
@@ -185,7 +193,7 @@ class GNSSNTRIPClient:
185
193
  :param bool spartndecode: (kwarg) decode SPARTN messages (0)
186
194
  :param str | NoneType spartnkey: (kwarg) SPARTN decryption key (None)
187
195
  :param str | datetime datetime: (kwarg) SPARTN decryption basedate (now(utc))
188
- :param object output: (kwarg) writeable output medium (serial, file, socket, queue) (None)
196
+ :param Any output: (kwarg) writeable output medium (serial, file, socket, queue) (None)
189
197
  :param Event stopevent: (kwarg) stopevent to terminate `run()` (internal `Event()`)
190
198
  :returns: boolean flag 0 = stream terminated, 1 = streaming data
191
199
  :rtype: bool
@@ -221,8 +229,8 @@ class GNSSNTRIPClient:
221
229
  daemon=True,
222
230
  ).start()
223
231
  if self.settings["mountpoint"] != "":
224
- return 1
225
- return 0
232
+ return True
233
+ return False
226
234
 
227
235
  def stop(self):
228
236
  """
@@ -237,7 +245,7 @@ class GNSSNTRIPClient:
237
245
  self,
238
246
  settings: dict,
239
247
  stopevent: Event,
240
- output: object,
248
+ output: Any,
241
249
  ):
242
250
  """
243
251
  Main read thread.
@@ -247,7 +255,7 @@ class GNSSNTRIPClient:
247
255
 
248
256
  :param dict settings: settings as dictionary
249
257
  :param Event stopevent: stop event
250
- :param object output: output stream for raw data
258
+ :param Any output: output stream for raw data
251
259
  """
252
260
 
253
261
  self._retrycount = 0
@@ -271,8 +279,8 @@ class GNSSNTRIPClient:
271
279
  ConnectionRefusedError,
272
280
  ConnectionResetError,
273
281
  OverflowError,
274
- socket.gaierror,
275
- socket.timeout, # subclass of OSError in Python<=3.9
282
+ gaierror,
283
+ timeout, # subclass of OSError in Python<=3.9
276
284
  TimeoutError,
277
285
  ) as err:
278
286
  errm = str(repr(err))
@@ -286,14 +294,14 @@ class GNSSNTRIPClient:
286
294
  )
287
295
  self._app_update_status(True, (errm, ERRCOL))
288
296
  # critical errors...
289
- except (ssl.SSLError, ssl.SSLCertVerificationError) as err:
290
- errc = err.strerror
291
- if "certificate is not valid for 'www." in err.strerror:
297
+ except (SSLError, SSLCertVerificationError) as err:
298
+ errc = str(err.strerror)
299
+ if "certificate is not valid for 'www." in str(err.strerror):
292
300
  errc += (
293
301
  f" - try using '{hostname[4:]}' rather than "
294
302
  f"'{hostname}' for the NTRIP caster URL"
295
303
  )
296
- elif "unable to get local issuer certificate" in err.strerror:
304
+ elif "unable to get local issuer certificate" in str(err.strerror):
297
305
  errc += f" - try adding the NTRIP caster URL SSL certificate to {findcacerts()}"
298
306
  except OSError: # socket already closed, ignore
299
307
  errc = "socket closed"
@@ -322,12 +330,12 @@ class GNSSNTRIPClient:
322
330
  """
323
331
 
324
332
  hostname = settings["server"]
325
- sock = socket.create_connection(
326
- (socket.gethostbyname(hostname), int(settings["port"])),
333
+ sock = create_connection(
334
+ (gethostbyname(hostname), int(settings["port"])),
327
335
  timeout=self._timeout,
328
336
  )
329
337
  if int(settings["https"]):
330
- context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
338
+ context = SSLContext(PROTOCOL_TLS_CLIENT)
331
339
  context.load_verify_locations(findcacerts())
332
340
  if int(settings.get("selfsign", 0)):
333
341
  # context.verify_mode = ssl.CERT_NONE
@@ -336,16 +344,17 @@ class GNSSNTRIPClient:
336
344
  sock = context.wrap_socket(sock, server_hostname=hostname)
337
345
  return sock
338
346
 
339
- def _close_connection(self, sock: socket):
347
+ def _close_connection(self, sock: socket | NoneType):
340
348
  """
341
349
  Close socket connection.
342
350
 
343
- :param socket sock: open socket
351
+ :param socket | NoneType sock: open socket
344
352
  """
345
353
 
346
354
  try:
347
- sock.shutdown(socket.SHUT_RDWR)
348
- sock.close()
355
+ if sock is not None:
356
+ sock.shutdown(SHUT_RDWR)
357
+ sock.close()
349
358
  except (AttributeError, OSError): # already closed, ignore
350
359
  pass
351
360
 
@@ -354,14 +363,14 @@ class GNSSNTRIPClient:
354
363
  sock: socket,
355
364
  settings: dict,
356
365
  stopevent: Event,
357
- output: object,
366
+ output: Any,
358
367
  ) -> int:
359
368
  """
360
369
  Send HTTP request to NTRIP server and process incoming data.
361
370
 
362
371
  :param dict settings: settings as dictionary
363
372
  :param Event stopevent: stop event
364
- :param object output: output stream for raw data
373
+ :param Any output: output stream for raw data
365
374
  :returns rc: return code (0 - stop, 1 - ok)
366
375
  :rtype: int
367
376
  :raises: Various socket error types if connection fails
@@ -497,12 +506,21 @@ class GNSSNTRIPClient:
497
506
  )
498
507
  return bdy
499
508
 
509
+ def _error_handler(self, err: Exception):
510
+ """
511
+ Error handler.
512
+
513
+ :param Exception err: exception
514
+ """
515
+
516
+ self.logger.debug(err)
517
+
500
518
  def _parse_ntrip_data(
501
519
  self,
502
520
  sock: socket,
503
521
  settings: dict,
504
522
  stopevent: Event,
505
- output: object,
523
+ output: Any,
506
524
  ):
507
525
  """
508
526
  Read and parse incoming NTRIP RTCM3/SPARTN data stream.
@@ -510,6 +528,7 @@ class GNSSNTRIPClient:
510
528
  :param socket sock: raw socket
511
529
  :param dict settings: settings as dictionary
512
530
  :param Event stopevent: stop event
531
+ :param Any output: output medium
513
532
  :raises: TimeoutError if inactivity timeout exceeded
514
533
  """
515
534
 
@@ -528,44 +547,29 @@ class GNSSNTRIPClient:
528
547
  decode=settings.get("spartndecode", False),
529
548
  key=settings.get("spartnkey", "ABCD1234"),
530
549
  basedate=settings.get("spartnbasedate", 0),
550
+ errorhandler=self._error_handler,
531
551
  )
532
552
  else:
533
- parser = UBXReader(
553
+ parser = GNSSReader(
534
554
  stream,
535
555
  protfilter=RTCM3_PROTOCOL,
536
556
  quitonerror=ERR_LOG,
537
557
  bufsize=DEFAULT_BUFSIZE,
538
- labelmsm=True,
558
+ labelmsm=1,
559
+ errorhandler=self._error_handler,
539
560
  )
540
561
 
541
562
  while not stopevent.is_set():
542
- try:
543
- raw_data, parsed_data = parser.read()
544
- if raw_data is None:
545
- if datetime.now() - last_activity > timedelta(
546
- seconds=self._timeout
547
- ):
548
- raise TimeoutError(
549
- f"Inactivity timeout error after {self._timeout} seconds"
550
- )
551
- else:
552
- if hasattr(parsed_data, "identity"):
553
- self.logger.debug(f"Message received: {parsed_data.identity}")
554
- self._do_output(output, raw_data, parsed_data)
555
- last_activity = datetime.now()
556
- self._send_gga(sock, settings["ggainterval"], output)
557
-
558
- except (
559
- RTCMMessageError,
560
- RTCMParseError,
561
- RTCMTypeError,
562
- SPARTNMessageError,
563
- SPARTNParseError,
564
- SPARTNTypeError,
565
- ) as err:
566
- parsed_data = f"Error parsing data stream {err}"
563
+ raw_data, parsed_data = parser.read()
564
+ if raw_data is None:
565
+ if datetime.now() - last_activity > timedelta(seconds=self._timeout):
566
+ raise TimeoutError(
567
+ f"Inactivity timeout error after {self._timeout} seconds"
568
+ )
569
+ else:
567
570
  self._do_output(output, raw_data, parsed_data)
568
- continue
571
+ last_activity = datetime.now()
572
+ self._send_gga(sock, settings["ggainterval"], output)
569
573
 
570
574
  def _parse_sourcetable(self, response: str) -> list:
571
575
  """
@@ -576,10 +580,10 @@ class GNSSNTRIPClient:
576
580
  :rtype: list
577
581
  """
578
582
 
579
- self.logger.info(f"Sourcetable:\n{response=}")
583
+ self.logger.debug(f"Sourcetable:\n{response=}")
580
584
  sourcetable = []
581
- response = response.split("\r\n")
582
- for line in response:
585
+ respons = response.split("\r\n")
586
+ for line in respons:
583
587
  if line.find("STR;") >= 0: # mountpoint entry
584
588
  strbits = line.split(";")
585
589
  if strbits[0] == "STR":
@@ -601,9 +605,9 @@ class GNSSNTRIPClient:
601
605
  for i, col in enumerate(row):
602
606
  dlm = "," if i < len(row) - 1 else "\r\n"
603
607
  srt += f"{col}{dlm}"
604
- return bytearray(srt, "utf-8")
608
+ return bytes(srt, "utf-8")
605
609
 
606
- def _format_gga(self) -> tuple:
610
+ def _format_gga(self) -> tuple[bytes | NoneType, NMEAMessage | NoneType]:
607
611
  """
608
612
  Format NMEA GGA sentence using pynmeagps. The raw string
609
613
  output is suitable for sending to an NTRIP socket.
@@ -611,8 +615,7 @@ class GNSSNTRIPClient:
611
615
  derived from fix string.
612
616
 
613
617
  :returns: tuple of (raw NMEA message as bytes, NMEAMessage)
614
- :rtype: tuple
615
- :rtype: tuple
618
+ :rtype: tuple[bytes | NoneType, NMEAMessage | NoneType]
616
619
  """
617
620
 
618
621
  try:
@@ -639,36 +642,35 @@ class GNSSNTRIPClient:
639
642
  diffStation=diffstation,
640
643
  )
641
644
 
642
- raw_data = parsed_data.serialize()
643
- return raw_data, parsed_data
645
+ return parsed_data.serialize(), parsed_data
644
646
  except ValueError:
645
647
  return None, None
646
648
 
647
- def _send_gga(self, sock: socket, ggainterval: int, output: object):
649
+ def _send_gga(self, sock: socket, ggainterval: int, output: Any):
648
650
  """
649
651
  Send NMEA GGA sentence to NTRIP server at prescribed interval.
650
652
 
651
653
  :param socket sock: open socket
652
654
  :param int ggainterval: GGA send interval in seconds (-1 = don't send)
653
- :param object output: writeable output medium e.g. serial port
655
+ :param Any output: writeable output medium e.g. serial port
654
656
  """
655
657
 
656
658
  if ggainterval != NOGGA:
657
659
  if datetime.now() > self._last_gga + timedelta(seconds=ggainterval):
658
660
  raw_data, parsed_data = self._format_gga()
659
- if parsed_data is not None:
661
+ if raw_data is not None and parsed_data is not None:
660
662
  sock.sendall(raw_data)
661
663
  self._do_output(output, raw_data, parsed_data)
662
664
  self._last_gga = datetime.now()
663
665
 
664
- def _get_closest_mountpoint(self) -> tuple:
666
+ def _get_closest_mountpoint(self) -> tuple[str | NoneType, float | NoneType]:
665
667
  """
666
668
  THREADED
667
669
  Find closest mountpoint in sourcetable
668
670
  if valid reference lat/lon are available.
669
671
 
670
672
  :returns: tuple of (mountpoint, distance)
671
- :rtype: tuple
673
+ :rtype: tuple[str | NoneType, float | NoneType]
672
674
  """
673
675
 
674
676
  try:
@@ -678,7 +680,7 @@ class GNSSNTRIPClient:
678
680
  )
679
681
  if self._settings["mountpoint"] == "":
680
682
  self._settings["mountpoint"] = closest_mp
681
- self.logger.info(
683
+ self.logger.debug(
682
684
  "Closest mountpoint to reference location "
683
685
  f"({lat}, {lon}) = {closest_mp}, {dist} km."
684
686
  )
@@ -687,28 +689,28 @@ class GNSSNTRIPClient:
687
689
  return None, None
688
690
  return closest_mp, dist
689
691
 
690
- def _do_output(self, output: object, raw: bytes, parsed: object):
692
+ def _do_output(self, output: Any, raw: bytes | NoneType, parsed: Any | NoneType):
691
693
  """
692
694
  Send sourcetable/closest mountpoint or RTCM3/SPARTN data to designated output medium.
693
695
 
694
696
  If output is Queue, will send both raw and parsed data.
695
697
 
696
- :param object output: writeable output medium for raw data
697
- :param bytes raw: raw data
698
- :param object parsed: parsed message
698
+ :param Any output: writeable output medium for raw data
699
+ :param bytes | NoneType raw: raw data
700
+ :param Any | NoneType parsed: parsed message
699
701
  """
700
702
 
701
703
  if output is not None:
702
704
  # serialize sourcetable if outputting to stream
703
705
  if isinstance(raw, list) and not isinstance(output, Queue):
704
706
  raw = self._serialize_sourcetable(raw)
705
- if isinstance(output, (Serial, BufferedWriter)):
707
+ if isinstance(output, (Serial, BufferedWriter)) and raw is not None:
706
708
  output.write(raw)
707
- elif isinstance(output, TextIOWrapper):
709
+ elif isinstance(output, TextIOWrapper) and parsed is not None:
708
710
  output.write(str(parsed))
709
- elif isinstance(output, Queue):
711
+ elif isinstance(output, Queue) and raw is not None:
710
712
  output.put(raw if self.__app == CLIAPP else (raw, parsed))
711
- elif isinstance(output, socket.socket):
713
+ elif isinstance(output, socket) and raw is not None:
712
714
  output.sendall(raw)
713
715
 
714
716
  # notify any calling app that data is available
@@ -716,18 +718,18 @@ class GNSSNTRIPClient:
716
718
  if hasattr(self.__app, "set_event"):
717
719
  self.__app.set_event(NTRIP_EVENT)
718
720
 
719
- def _app_update_status(self, status: bool, msgt: tuple = None):
721
+ def _app_update_status(self, status: bool, msgt: tuple[str, str]):
720
722
  """
721
723
  Update NTRIP connection status in calling application.
722
724
 
723
725
  :param bool status: NTRIP server connection status
724
- :param tuple msgt: (message, color)
726
+ :param tuple[str,str] msgt: (message, color)
725
727
  """
726
728
 
727
729
  if msgt[1] == ERRCOL:
728
730
  self.logger.error(msgt[0])
729
731
  else:
730
- self.logger.info(msgt[0])
732
+ self.logger.debug(msgt[0])
731
733
  if self.__app is not None:
732
734
  if hasattr(self.__app, "dialog"):
733
735
  dlg = self.__app.dialog(DLGTNTRIP)
@@ -866,15 +868,17 @@ class GNSSNTRIPClient:
866
868
  return self._response_headers.get("content-type", "")
867
869
 
868
870
  @property
869
- def response_body(self) -> object:
871
+ def response_body(self) -> bytes | str | NoneType:
870
872
  """
871
873
  Get response body if available.
872
874
 
873
875
  :returns: response body as bytes or string, depending on encoding
874
- :rtype: object
876
+ :rtype: bytes | str | NoneType
875
877
  """
876
878
 
877
- if "text/" in self.content_type or self.is_sourcetable:
879
+ if isinstance(self._response_body, bytes) and (
880
+ "text/" in self.content_type or self.is_sourcetable
881
+ ):
878
882
  return self._response_body.decode()
879
883
  return self._response_body
880
884
 
@@ -13,7 +13,7 @@ Returns both the raw binary data (as bytes) and the parsed data.
13
13
 
14
14
  - 'protfilter' governs which protocols (NMEA, UBX, SBF, QGC, UNI or RTCM3) are processed.
15
15
  - 'msgfilter' governs which individual message types are processed.
16
- - `parsing` governs whether payloads are fully or partially parsed.
16
+ - `parsing` governs whether individual payloads are parsed.
17
17
  - 'quitonerror' governs how errors are handled.
18
18
  - 'msgmode' indicates the type of UBX datastream (output GET, input SET, query POLL).
19
19
  If msgmode is set to SETPOLL, input/query mode will be automatically detected by parser.
@@ -29,7 +29,7 @@ Created on 6 Oct 2025
29
29
 
30
30
  from logging import getLogger
31
31
  from socket import socket
32
- from types import FunctionType, NoneType
32
+ from types import FunctionType, MethodType, NoneType
33
33
  from typing import Any, Literal
34
34
 
35
35
  from pynmeagps import (
@@ -236,7 +236,7 @@ class GNSSReader:
236
236
  bufsize: int = DEFAULT_BUFSIZE,
237
237
  parsing: Literal[0, 1, 2] = PARSE_FULL,
238
238
  msgfilter: tuple | int | str = "",
239
- errorhandler: FunctionType | NoneType = None,
239
+ errorhandler: FunctionType | MethodType | NoneType = None,
240
240
  ):
241
241
  """Constructor.
242
242
 
@@ -318,8 +318,7 @@ class GNSSReader:
318
318
 
319
319
  raw_data = None
320
320
  parsed_data = None
321
- parsing = True
322
- while parsing: # loop until end of valid message or EOF
321
+ while True: # loop until end of valid message or EOF
323
322
  try:
324
323
 
325
324
  raw_data = None
@@ -336,38 +335,34 @@ class GNSSReader:
336
335
  # if protocol filter passes UBX, return message,
337
336
  # otherwise discard and continue
338
337
  if self._protfilter & UBX_PROTOCOL:
339
- parsing = False
340
- else:
341
- continue
338
+ break
339
+ continue
342
340
  # if it's an NMEA message (b'\x24\x..)
343
- elif bytehdr in NMEA_HDR:
341
+ if bytehdr in NMEA_HDR:
344
342
  raw_data, parsed_data = self._parse_nmea(bytehdr)
345
343
  # if protocol filter passes NMEA, return message,
346
344
  # otherwise discard and continue
347
345
  if self._protfilter & NMEA_PROTOCOL:
348
- parsing = False
349
- else:
350
- continue
346
+ break
347
+ continue
351
348
  # if it's an SBF message (b'\x24\x40')
352
- elif bytehdr in SBF_HDR:
349
+ if bytehdr in SBF_HDR:
353
350
  raw_data, parsed_data = self._parse_sbf(bytehdr)
354
351
  # if protocol filter passes SBF, return message,
355
352
  # otherwise discard and continue
356
353
  if self._protfilter & SBF_PROTOCOL:
357
- parsing = False
358
- else:
359
- continue
354
+ break
355
+ continue
360
356
  # if it's an QGCmessage (b'\x51\x47')
361
- elif bytehdr in QGC_HDR:
357
+ if bytehdr in QGC_HDR:
362
358
  raw_data, parsed_data = self._parse_qgc(bytehdr)
363
359
  # if protocol filter passes QGC, return message,
364
360
  # otherwise discard and continue
365
361
  if self._protfilter & QGC_PROTOCOL:
366
- parsing = False
367
- else:
368
- continue
362
+ break
363
+ continue
369
364
  # if it's an UNImessage (b'\xaa\x44\xb5')
370
- elif bytehdr in UNI_HDR[:2]:
365
+ if bytehdr in UNI_HDR[:2]:
371
366
  byte3 = self._read_bytes(1)
372
367
  bytehdr += byte3
373
368
  if bytehdr != UNI_HDR:
@@ -376,21 +371,18 @@ class GNSSReader:
376
371
  # if protocol filter passes UNI, return message,
377
372
  # otherwise discard and continue
378
373
  if self._protfilter & UNI_PROTOCOL:
379
- parsing = False
380
- else:
381
- continue
374
+ break
375
+ continue
382
376
  # if it's a RTCM3 message
383
377
  # (byte1 = 0xd3; byte2 = 0b000000**)
384
- elif byte1 == b"\xd3" and (byte2[0] & ~0x03) == 0:
378
+ if byte1 == b"\xd3" and (byte2[0] & ~0x03) == 0:
385
379
  raw_data, parsed_data = self._parse_rtcm3(bytehdr)
386
380
  # if protocol filter passes RTCM, return message,
387
381
  # otherwise discard and continue
388
382
  if self._protfilter & RTCM3_PROTOCOL:
389
- parsing = False
390
- else:
391
- continue
392
- else:
393
- raise GNSSStreamError(f"Unknown protocol header {bytehdr}.")
383
+ break
384
+ continue
385
+ raise GNSSStreamError(f"Unknown protocol header {escapeall(bytehdr)}.")
394
386
 
395
387
  except EOFError:
396
388
  return (None, None)
@@ -682,17 +674,17 @@ class GNSSReader:
682
674
  # pass to error handler if there is one
683
675
  # else just log
684
676
  if self._errorhandler is None:
685
- self._logger.error(err)
677
+ self._logger.exception(err)
686
678
  else:
687
679
  self._errorhandler(err)
688
680
 
689
681
  @property
690
- def datastream(self) -> object:
682
+ def datastream(self) -> Any:
691
683
  """
692
684
  Getter for stream.
693
685
 
694
686
  :return: data stream
695
- :rtype: object
687
+ :rtype: Any
696
688
  """
697
689
 
698
690
  return self._stream
@@ -253,7 +253,7 @@ def get_mp_distance(lat: float, lon: float, mp: list) -> float:
253
253
 
254
254
  def find_mp_distance(
255
255
  lat: float, lon: float, sourcetable: list, name: str = ""
256
- ) -> tuple:
256
+ ) -> tuple[str, float]:
257
257
  """
258
258
  Find distance to named mountpoint. If mountpoint name
259
259
  is not provided, find closest mountpoint in sourcetable.
@@ -269,11 +269,11 @@ def find_mp_distance(
269
269
  :param list sourcetable: sourcetable as list
270
270
  :param str name: (optional) mountpoint name (None)
271
271
  :returns: tuple of (name of closest mountpoint, distance in km)
272
- :rtype: tuple
272
+ :rtype: tuple[str, float]
273
273
  """
274
274
 
275
275
  mindist = 9999999
276
- mpname = None
276
+ mpname = ""
277
277
  for mp in sourcetable:
278
278
  dist = get_mp_distance(lat, lon, mp)
279
279
  if dist is not None:
@@ -42,7 +42,6 @@ from pygnssutils.rinex_globals import (
42
42
  RINEX_OK,
43
43
  RINEXDATASOURCE,
44
44
  RINEXVER_DEFAULT,
45
- RTCM3,
46
45
  UBLOX,
47
46
  )
48
47
  from pygnssutils.rinex_helpers import (
@@ -56,7 +56,7 @@ class RinexConverterMeteorology:
56
56
  verbosity: Literal[-1, 0, 1, 2, 3] = VERBOSITY_MEDIUM,
57
57
  logtofile: str = "",
58
58
  **kwargs,
59
- ): # pylint: disable=too-many-arguments, too-many-positional-arguments
59
+ ): # pylint: disable=too-many-arguments, too-many-positional-arguments, unused-argument
60
60
  """
61
61
  Constructor.
62
62
 
@@ -111,12 +111,12 @@ class RinexConverterMeteorology:
111
111
  ret = 0
112
112
  if isinstance(parsed, NMEAMessage):
113
113
  self._datasource = NMEA
114
- if parsed.identity[2:] in ("RMC"):
114
+ if parsed.identity[2:] == "RMC":
115
115
  self.get_nmea_epoch(parsed)
116
- if parsed.identity[2:] in ("MWD"): # NMEA Wind Speed and Direction
116
+ if parsed.identity[2:] == "MWD": # NMEA Wind Speed and Direction
117
117
  self.convert_nmea_mwd(parsed)
118
118
  ret += 1
119
- if parsed.identity[2:] in ("XDR"): # NMEA Transducer (Temp & Pressure)
119
+ if parsed.identity[2:] == "XDR": # NMEA Transducer (Temp & Pressure)
120
120
  self.convert_nmea_xdr(parsed)
121
121
  ret += 1
122
122
  # ADD ADDITIONAL PARSED DATA SOURCES HERE...
@@ -1,9 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pygnssutils
3
- Version: 1.2.7
3
+ Version: 1.2.9
4
4
  Summary: GNSS Command Line Utilities
5
- Author-email: Steve Smith <semuadmin@semuconsulting.com>
6
- Maintainer-email: Steve Smith <semuadmin@semuconsulting.com>
5
+ Author: Steve Smith
6
+ Author-email: semuadmin@semuconsulting.com
7
+ Maintainer: Steve Smith
8
+ Maintainer-email: semuadmin@semuconsulting.com
7
9
  License-Expression: BSD-3-Clause
8
10
  Project-URL: homepage, https://github.com/semuconsulting/pygnssutils
9
11
  Project-URL: documentation, https://www.semuconsulting.com/pygnssutils/
@@ -24,20 +26,22 @@ Classifier: Programming Language :: Python :: 3.11
24
26
  Classifier: Programming Language :: Python :: 3.12
25
27
  Classifier: Programming Language :: Python :: 3.13
26
28
  Classifier: Programming Language :: Python :: 3.14
29
+ Classifier: Programming Language :: Python :: 3.15
27
30
  Classifier: Topic :: Utilities
28
31
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
29
32
  Classifier: Topic :: Scientific/Engineering :: GIS
30
33
  Requires-Python: >=3.10
31
34
  Description-Content-Type: text/markdown
32
35
  License-File: LICENSE
33
- Requires-Dist: certifi>=2025.0.0
34
- Requires-Dist: pyserial>=3.5
35
- Requires-Dist: pyspartn>=1.0.8
36
- Requires-Dist: pyubx2>=1.3.6
36
+ Requires-Dist: certifi>=2026.0.0
37
37
  Requires-Dist: pynmeagps>=1.1.7
38
+ Requires-Dist: pyqgc>=1.0.0
39
+ Requires-Dist: pyrtcm>=1.2.0
38
40
  Requires-Dist: pysbf2>=1.0.4
41
+ Requires-Dist: pyserial>=3.5
42
+ Requires-Dist: pyspartn>=1.0.8
43
+ Requires-Dist: pyubx2>=1.3.7
39
44
  Requires-Dist: pyubxutils>=1.0.6
40
- Requires-Dist: pyqgc>=1.0.0
41
45
  Requires-Dist: pyunigps>=1.0.0
42
46
  Dynamic: license-file
43
47
 
@@ -92,18 +96,18 @@ The pygnssutils homepage is located at [https://github.com/semuconsulting/pygnss
92
96
 
93
97
  Sphinx API Documentation in HTML format is available at [https://www.semuconsulting.com/pygnssutils](https://www.semuconsulting.com/pygnssutils).
94
98
 
95
- Contributions **_from human beings_** welcome - please refer to [CONTRIBUTING.MD](https://github.com/semuconsulting/pygnssutils/blob/main/CONTRIBUTING.md).
99
+ Contributions _**from human beings**_ welcome - please refer to [CONTRIBUTING.MD](https://github.com/semuconsulting/pygnssutils/blob/main/CONTRIBUTING.md).
96
100
 
97
101
  [Bug reports](https://github.com/semuconsulting/pygnssutils/blob/main/.github/ISSUE_TEMPLATE/bug_report.md) and [Feature requests](https://github.com/semuconsulting/pygnssutils/blob/main/.github/ISSUE_TEMPLATE/feature_request.md) - please use the templates provided. For general queries and advice, post a message to one of the [pygnssutils Discussions](https://github.com/semuconsulting/pygnssutils/discussions) channels.
98
102
 
99
- ![No Copilot](https://github.com/semuconsulting/PyGPSClient/blob/master/images/nocopilot100.png?raw=true)
103
+ ![No Copilot](https://github.com/semuconsulting/PyGPSClient/blob/main/images/nocopilot100.png?raw=true)
100
104
 
101
105
  ---
102
106
  ## <a name="installation">Installation</a>
103
107
 
104
108
  ![Python version](https://img.shields.io/pypi/pyversions/pygnssutils.svg?style=flat)
105
109
  [![PyPI version](https://img.shields.io/pypi/v/pygnssutils.svg?style=flat)](https://pypi.org/project/pygnssutils/)
106
- [![PyPI downloads](https://github.com/semuconsulting/pygpsclient/blob/master/images/clickpy_top10.svg?raw=true)](https://clickpy.clickhouse.com/dashboard/pygnssutils)
110
+ [![PyPI downloads](https://github.com/semuconsulting/pygpsclient/blob/main/images/clickpy_top10.svg?raw=true)](https://clickpy.clickhouse.com/dashboard/pygnssutils)
107
111
 
108
112
  `pygnssutils` is compatible with Python>=3.10.
109
113
 
@@ -630,6 +634,8 @@ semuadmin@semuconsulting.com
630
634
 
631
635
  ![License](https://img.shields.io/github/license/semuconsulting/pygnssutils.svg)
632
636
 
637
+ [![ORCID iD icon](https://orcid.org/sites/default/files/images/orcid_16x16.png) https://orcid.org/0009-0002-5751-8719](https://orcid.org/0009-0002-5751-8719)
638
+
633
639
  `pygnssutils` is maintained entirely by unpaid volunteers. It receives no funding from advertising or corporate sponsorship. If you find the utility useful, please consider sponsoring the project with the price of a coffee...
634
640
 
635
641
  [![Sponsor](https://github.com/semuconsulting/pyubx2/blob/master/images/sponsor.png?raw=true)](https://buymeacoffee.com/semuconsulting)
@@ -1,9 +1,10 @@
1
- certifi>=2025.0.0
2
- pyserial>=3.5
3
- pyspartn>=1.0.8
4
- pyubx2>=1.3.6
1
+ certifi>=2026.0.0
5
2
  pynmeagps>=1.1.7
3
+ pyqgc>=1.0.0
4
+ pyrtcm>=1.2.0
6
5
  pysbf2>=1.0.4
6
+ pyserial>=3.5
7
+ pyspartn>=1.0.8
8
+ pyubx2>=1.3.7
7
9
  pyubxutils>=1.0.6
8
- pyqgc>=1.0.0
9
10
  pyunigps>=1.0.0
@@ -115,14 +115,14 @@ class StaticTest(unittest.TestCase):
115
115
  lon = -2.24
116
116
  name = "AUADL"
117
117
  res = find_mp_distance(lat, lon, TESTSRT, name)
118
- self.assertEqual(res, (None, 9999999))
118
+ self.assertEqual(res, ("", 9999999))
119
119
 
120
120
  def testfindmpdist4(self): # invalid coords
121
121
  name = "apinhal"
122
122
  lat = ""
123
123
  lon = ""
124
124
  res = find_mp_distance(lat, lon, TESTSRT, name)
125
- self.assertEqual(res, (None, 9999999))
125
+ self.assertEqual(res, ("", 9999999))
126
126
 
127
127
  def testgetmpdist1(self): # valid
128
128
  mp = TESTSRT[12]
File without changes
File without changes