From f55d86c9e6079c34af3234e894bab782a478d48a Mon Sep 17 00:00:00 2001
From: SEMU Admin <28569967+semuadmin@users.noreply.github.com>
Date: Sun, 15 Mar 2026 12:47:38 +0000
Subject: [PATCH] update nmea GGA handler Fixes #244
---
.github/workflows/checkpr.yml | 4 +-
.github/workflows/deploy.yml | 14 +++---
.github/workflows/main.yml | 4 +-
README.md | 59 ++++++++++----------------
RELEASE_NOTES.md | 11 +++++
src/pygpsclient/_version.py | 2 +-
src/pygpsclient/nmea_handler.py | 6 ++-
src/pygpsclient/ntrip_client_dialog.py | 10 +++--
src/pygpsclient/recorder_dialog.py | 1 +
9 files changed, 57 insertions(+), 54 deletions(-)
diff --git a/.github/workflows/checkpr.yml b/.github/workflows/checkpr.yml
index 250fdc24..f85678ac 100644
--- a/.github/workflows/checkpr.yml
+++ b/.github/workflows/checkpr.yml
@@ -11,9 +11,9 @@ jobs:
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v6
- name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v5
+ uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
- name: Install deploy dependencies
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index 441b0b06..b3bf21ed 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -11,10 +11,9 @@ jobs:
release-build:
runs-on: ubuntu-latest
steps:
-
- - uses: actions/checkout@v4
- - name: Set up Python
- uses: actions/setup-python@v5
+ - uses: actions/checkout@v6
+ - name: Set up Python
+ uses: actions/setup-python@v6
with:
python-version: 3.13
@@ -24,7 +23,7 @@ jobs:
python -m build
- name: Upload distributions # upload build artifacts
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v6
with:
name: release-dists
path: dist/
@@ -35,13 +34,12 @@ jobs:
name: Upload release to PyPI
runs-on: ubuntu-latest
environment:
- name: pypi
+ name: pypi
permissions:
id-token: write
steps:
-
- name: Download all the dists # download build artifacts saved in previous job
- uses: actions/download-artifact@v4
+ uses: actions/download-artifact@v8
with:
name: release-dists
path: dist/
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index c3516508..7802bf84 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -13,9 +13,9 @@ jobs:
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v6
- name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v5
+ uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
- name: Install deploy dependencies
diff --git a/README.md b/README.md
index e330bd6b..4b21d776 100644
--- a/README.md
+++ b/README.md
@@ -198,7 +198,7 @@ For more comprehensive installation instructions, please refer to [INSTALLATION.

-**Pre-Requisites and Caveats:**
+**Pre-Requisites:**
- u-blox GNSS receiver connected to the workstation via USB or UART port¹².
- **NOTE:** u-blox introduced a [new configuration mechanism](https://github.com/semuconsulting/pyubx2#configinterface) in 9th generation devices (**ROM protocol >=23.01**). Some UBX configuration commands (e.g. `CFG-PRT`, `CFG-RATE`, `CFG-MSG`) will only work with legacy devices (e.g. NEO-M6S, NEO-M8P); others (e.g. `CFG-VALSET`, `CFG-VALDEL`) will only work with newer devices (e.g. NEO-M9, ZED-F9P, ZED-X20P).
@@ -222,12 +222,6 @@ The UBX Configuration Dialog currently provides the following UBX configuration
1. UBX Legacy Command configuration panel providing structured updates for a range of legacy CFG-* configuration commands (*legacy protocols only*). Note: 'X' (byte) type attributes can be entered as integers or hexadecimal strings e.g. 522125312 or 0x1f1f0000. Once a command is selected, the configuration is polled and the current values displayed. The user can then amend these values as required and send the updated configuration. Some polls require input arguments (e.g. portID) - these are highlighted and will be set at default values initially (e.g. portID = 0), but can be amended by the user and re-polled using the  button.
1. Preset Commands widget supports a variety of user-defined UBX commands and queries - see [user-defined presets](#userdefined).
-An icon to the right of each 'SEND'
- button indicates the confirmation status of the configuration command;
-(pending i.e. awaiting confirmation ,
-confirmed  or
-warning ).
-
---
## NMEA Configuration Facilities
@@ -235,8 +229,7 @@ warning  in later releases (*contributions welcome*).
+- Receiver capable of being configured via proprietary NMEA sentences, connected to the workstation via USB or UART port. The facility includes support for a wide range of Quectel LG and LC series receivers via `PQTM*`, `PSTM*` and `PAIR*` sentences¹ ². Additional types may be supported in the underlying NMEA parser library [pynmeagps](https://github.com/semuconsulting/pynmeagps) in later releases (*contributions welcome*).
¹ Note that Quectel receivers implement a bewildering array of different configuration protocols, based on a mixture of proprietary NMEA `PQTM*`, `PSTM*` and `PAIR*` message types. Implementation depends on the *specific model variant and firmware version*, and several models (e.g. LG290 and LC29) exist in a wide range of variants. Refer to the GNSS Protocol Guide for your *specific* variant for details on the available configuration commands.
@@ -250,39 +243,35 @@ The NMEA Configuration Dialog currently provides the following NMEA configuratio
1. Preset Commands widget supports a variety of user-defined NMEA commands and queries - see [user defined presets](#userdefined).
1. Preset commands, once selected, can be edited or overwritten in the 'Commands' field before sending, but commands must observe the format `; ; ; ` (e.g. `P; QTMCFGUART; W,115200; 1` - see [user-defined presets](#userdefined)).
-An icon to the right of each 'SEND'
- button indicates the confirmation status of the configuration command;
-(pending i.e. awaiting confirmation ,
-confirmed  or
-warning ).
-
---
## TTY Configuration Facilities

-Version panel shows current device hardware/firmware versions (*Double-left-click to refresh*).
+**Pre-Requisites:**
-The TTY Commands dialog provides a facility to send user-defined ASCII TTY configuration commands (e.g. `AT+` style commands) to the connected serial device. Commands can be entered manually or selected from a list of user-defined presets. The dialog can be accessed via the TTY Config button or Menu..Options..TTY Commands.
-- CRLF checkbox - if ticked, a CRLF (`b"\x0d\x0a"`) terminator will be added to the command string.
-- Echo checkbox - if ticked, outgoing TTY commands will be echoed on the console with the marker `"TTY<<"`.
-- Delay checkbox - if ticked, a small delay will be added between each outgoing command to allow time for the receiving device to process the command.
+- Receiver capable of being configured via ASCII text messages over a USB or UART port. This includes, for example, most Septentrio and Unicore GNSS devices.
+- Some GNSS devices (e.g. Septentrio X5 or Unicore UM980) can use separate UART ports for configuration and navigation/monitoring - ensure you are using the appropriate UART port when sending TTY commands. Septentrio devices also require an 'Initialise Command Mode' sequence (`SSSSSSSSSS`) to be sent before accepting TTY commands.
-Preset commands can be set up by adding appropriate semicolon-delimited message descriptions and payload definitions to the `"ttypresets_l":` list in your json configuration file. See [User Defined Presets](#user-defined-presets) above and [example provided](https://github.com/semuconsulting/PyGPSClient/blob/master/pygpsclient.json#L302).
+**Instructions:**
-**NOTE:** Some GNSS devices (e.g. Septentrio X5 or Unicore UM980) can use separate UART ports for configuration and navigation/monitoring - ensure you are using the appropriate UART port when sending TTY commands. Septentrio devices also require an 'Initialise Command Mode' sequence (`SSSSSSSSSS`) to be sent before accepting TTY commands.
+- Version panel shows current device hardware/firmware versions (*Double-left-click to refresh*).
-The following example illustrates a series of ASCII configuration commands being sent to a Septentrio Mosaic X5 receiver, followed by successful acknowledgements:
+- The TTY Commands dialog provides a facility to send user-defined ASCII TTY configuration commands (e.g. `AT+` style commands) to the connected serial device. Commands can be entered manually or selected from a list of user-defined presets. The dialog can be accessed via the TTY Config button or Menu..Options..TTY Commands.
+ - CRLF checkbox - if ticked, a CRLF (`b"\x0d\x0a"`) terminator will be added to the command string.
+ - Echo checkbox - if ticked, outgoing TTY commands will be echoed on the console with the marker `"TTY<<"`.
+ - Delay checkbox - if ticked, a small delay will be added between each outgoing command to allow time for the receiving device to process the command.
-
+- Preset commands can be set up by adding appropriate semicolon-delimited message descriptions and payload definitions to the `"ttypresets_l":` list in your json configuration file. See [User Defined Presets](#user-defined-presets) above and [example provided](https://github.com/semuconsulting/PyGPSClient/blob/master/pygpsclient.json#L302).
---
## Configuration Command Load/Save/Record Facility

-The Configuration Command Load/Save/Record facility supports the following functionality:
-1. It allows users to record  a sequence of UBX, NMEA or TTY configuration commands as they are sent to a device, and to save  this recording to a binary file.
+To display the Configuration Command Load/Save/Record Dialog, select Menu..Options..Configuration Commmand Recorder.
+
+1. The Configuration Command Load/Save/Record facility allows users to record  a sequence of UBX, NMEA or TTY configuration commands as they are sent to a device, and to save  this recording to a binary file.
1. Saved recordings can be reloaded  and the configuration commands replayed . This provides a means to easily reproduce a given sequence of configuration commands, or copy a saved configuration between compatible devices.
1. Recorded commands of a similar type (UBX, NMEA or TTY) can also be imported  into PyGPSClient's json configuration file as [user defined presets](#user-defined-presets) - you will be prompted to enter a preset description (*defaults to current timestamp if blank*). The preset can then be replayed from the Presets panel via a single click.
1. The Configuration Load facility can accept configuration files in either UBX/NMEA binary (\*.bin), TTY (\*.tty) or u-center UBX text format (\*.txt) (as also used by [Ardusimple](https://www.ardusimple.com/configuration-files/?wmc-currency=EUR)).
@@ -341,13 +330,18 @@ By default, the server/caster binds to the host address '0.0.0.0' (IPv4) or '::'
**Pre-Requisites:**
-1. Running in NTRIP CASTER mode is predicated on the host being connected to an RTK-compatible GNSS receiver **operating in Base Station mode** (either `FIXED` or `SURVEY_IN`) and outputting the requisite RTCM3 message types (1005/6, 1077, 1087, 1097, etc.).
+1. Running in NTRIP CASTER mode is predicated on the host being connected to an RTK-compatible GNSS receiver **operating in Base Station mode** (either `FIXED` or `SURVEY_IN`) and outputting the requisite RTCM3 message types (1005/6, 1077, 1087, 1097, etc.). PyGPSClient supports 'one click' base station configuration for the following receiver types:
+ - u-blox - any modern u-blox RTK model (e.g. ZED-F9P, ZED-X20P) configured by binary UBX CFG-VALSET commands.
+ - Septentrio - most Septentrio Mosaic series (e.g. Mosaic X5) configured by ASCII TTY commands.
+ - Unicore - most UM9* series (e.g. UM981S) configured by ASCII TTY commands.
+ - Quectel LG Series - most LG series (e.g. LG290, LG580) configured by NMEA PQTM commands.
+ - Quectel LC Series - most LC series (e.g. LC29H) configured by NMEA PAIR commands.
1. It may be necessary to add a firewall rule and/or enable port-forwarding on the host machine or router to allow remote traffic on the specified address:port.
1. The server supports encrypted TLS (HTTPS) connections. The TLS server private key / certificate location can be set via environment variable `PYGNSSUTILS_PEMPATH`; the default is `$HOME/pygnssutils.pem`. A self-signed pem file suitable for test and demonstration purposes can be created interactively thus:
```shell
openssl req -x509 -newkey rsa:4096 -keyout pygnssutils.pem -out pygnssutils.pem -sha256 -days 3650 -nodes
```
- The TLS Client will only require the certificate from this file, which can be set via environment variable `PYGNSSUTILS_CRTPATH`; the default is `$HOME\pygnssutils.crt`.
+ The TLS Client will only require the certificate from this file, which can be set via environment variable `PYGNSSUTILS_CRTPATH`; the default is `$HOME/pygnssutils.crt`.
**Instructions:**
@@ -371,13 +365,6 @@ By default, the server/caster binds to the host address '0.0.0.0' (IPv4) or '::'
### Base Station Configuration
-The current version of PyGPSClient supports 'one click' base station configuration for the following receiver types:
-1. u-blox - any modern u-blox RTK model (e.g. XED-F9P, ZED-X20P) configured by binary UBX CFG-VALSET commands.
-1. Septentrio - most Septentrio Mosaic series (e.g. Mosaic X5) configured by ASCII TTY commands.
-1. Unicore - most UM9* series (e.g. UM981S) configured by ASCII TTY commands.
-1. Quectel LG Series - most LG series (e.g. LG290, LG580) configured by NMEA PQTM commands.
-1. Quectel LC Series - most LC series (e.g. LC29H) configured by NMEA PAIR commands.
-
**NOTE:**
1. Some receivers (*e.g. Quectel LG Series*) will require one or more restarts to enable or disable Base Station mode. This may take several seconds.
1. Different receiver models support different RTCM3 message cohorts, as indicated by the pygnssutils sourcetable entry e.g. `1006(5),1013(5),1019(5),1020(5),1033(5),1077(1),1087(1),1097(1),1127(1),1230(1)`
@@ -409,7 +396,7 @@ Please refer to [SPARTN.md](https://github.com/semuconsulting/PyGPSClient/blob/m

-*GPX Track Viewer screenshot*
+To display the GPX Track Viewer Dialog, select Menu..Options..GPX Track Viewer.
The GPX Track Viewer can display any valid GPX file containing track point (`trkpt`), route point (`rtept`) or waypoint (`wpt`) elements against either an ["custom" offline map image](#custommap), or an online MapQuest "map", "sat" or "hyb" view. The "map", "sat" and "hyb" options require a free [MapQuest API key](#mapquestapi). The Y axis scales will reflect the current choice of units (metric or imperial). If the GPX track omits a time element, the time and speed axes will be flagged as nominal. GPX track metadata, including min, max, average (mean) and median elevation and speed values, is displayed in the selected units.
Click  to refresh the display after any changes (e.g. resizing, zooming or change of units). The location marker indicates the nominal center point of the track.
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index e8c94fc4..00d7e60c 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -1,8 +1,19 @@
# PyGPSClient Release Notes
+### RELEASE 1.6.6
+
+FIXES:
+
+1. Update nmeahandler to cater for mixed float/null NMEA GGA alt/sep data - Fixes #244.
+
+ENHANCEMENTS:
+
+1. Minor internal updates to NTRIP SPARTN handling - no longer requires a 'dummy' key for unencrypted Point Perfect Flex SPARTN NTRIP services. To decode SPARTN messages in the console, set `"spartndecode_b": 1` in your json configuration file.
+
### RELEASE 1.6.5
FIXES:
+
1. Fix custom map import spurious validation error.
ENHANCEMENTS:
diff --git a/src/pygpsclient/_version.py b/src/pygpsclient/_version.py
index 01eed04f..ba78b87f 100644
--- a/src/pygpsclient/_version.py
+++ b/src/pygpsclient/_version.py
@@ -8,4 +8,4 @@
:license: BSD 3-Clause
"""
-__version__ = "1.6.5"
+__version__ = "1.6.6"
diff --git a/src/pygpsclient/nmea_handler.py b/src/pygpsclient/nmea_handler.py
index 9d99fb8e..31326117 100644
--- a/src/pygpsclient/nmea_handler.py
+++ b/src/pygpsclient/nmea_handler.py
@@ -160,12 +160,14 @@ def _process_GGA(self, data: NMEAMessage):
:param pynmeagps.NMEAMessage data: parsed GGA sentence
"""
+ alt = data.alt if isinstance(data.alt, (float, int)) else 0
+ sep = data.sep if isinstance(data.sep, (float, int)) else 0
self.__app.gnss_status.utc = data.time # datetime.time
self.__app.gnss_status.sip = data.numSV
self.__app.gnss_status.lat = data.lat
self.__app.gnss_status.lon = data.lon
- self.__app.gnss_status.alt = data.alt
- self.__app.gnss_status.hae = data.sep + data.alt
+ self.__app.gnss_status.alt = alt
+ self.__app.gnss_status.hae = sep + alt
self.__app.gnss_status.hdop = data.HDOP
self.__app.gnss_status.fix = fix2desc("GGA", data.quality)
self.__app.gnss_status.diff_corr = 0 if data.diffAge == "" else 1
diff --git a/src/pygpsclient/ntrip_client_dialog.py b/src/pygpsclient/ntrip_client_dialog.py
index a6ef3a43..c193e8b3 100644
--- a/src/pygpsclient/ntrip_client_dialog.py
+++ b/src/pygpsclient/ntrip_client_dialog.py
@@ -559,8 +559,6 @@ def _get_settings(self):
self._settings["refsep"] = cfg.get("ntripclientrefsep_f")
self._settings["spartndecode"] = cfg.get("spartndecode_b")
self._settings["spartnkey"] = cfg.get("spartnkey_s")
- if self._settings["spartnkey"] == "":
- self._settings["spartndecode"] = 0
# if basedate is provided in config file, it must be an integer gnssTimetag
self._settings["spartnbasedate"] = cfg.get("spartnbasedate_n")
@@ -613,6 +611,11 @@ def _set_settings(self):
self._settings["reflon"] = float(self._ntrip_gga_lon.get())
self._settings["refalt"] = float(self._ntrip_gga_alt.get())
self._settings["refsep"] = float(self._ntrip_gga_sep.get())
+ self._settings["spartndecode"] = self.__app.configuration.get("spartndecode_b")
+ self._settings["spartnkey"] = self.__app.configuration.get("spartnkey_s")
+ self._settings["spartnbasedate"] = self.__app.configuration.get(
+ "spartnbasedate_n"
+ )
def update_sourcetable(self, stable: list):
"""
@@ -655,7 +658,8 @@ def _connect(self):
reflon=self._settings["reflon"],
refalt=self._settings["refalt"],
refsep=self._settings["refsep"],
- spartndecode=self._settings["spartndecode"],
+ # NTRIP SPARTN is unencrypted so key not needed
+ spartndecode=1, # self._settings["spartndecode"],
spartnkey=self._settings["spartnkey"],
spartnbasedate=self._settings["spartnbasedate"],
output=self.__app.ntrip_inqueue,
diff --git a/src/pygpsclient/recorder_dialog.py b/src/pygpsclient/recorder_dialog.py
index 372f0a1a..d906d93c 100644
--- a/src/pygpsclient/recorder_dialog.py
+++ b/src/pygpsclient/recorder_dialog.py
@@ -513,6 +513,7 @@ def _on_import(self):
self._importdesc.set(self._get_preset_desc())
self._lbl_activity.grid_forget()
self._ent_import.grid(column=0, row=2, columnspan=8, padx=3, sticky=EW)
+ self._ent_import.focus_set()
self._save_to_preset = True
self.update()