Skip to content
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
4 changes: 2 additions & 2 deletions .github/workflows/checkpr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13"]
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]

steps:
- uses: actions/checkout@v4
Expand All @@ -25,4 +25,4 @@ jobs:
- name: Scan security vulnerabilities with bandit
run: bandit -c pyproject.toml -r .
- name: Generate coverage report
run: pytest
run: pytest
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13"]
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]

steps:
- uses: actions/checkout@v4
Expand Down
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,6 @@
"python.analysis.addHoverSummaries": false,
"python-envs.defaultEnvManager": "ms-python.python:venv",
"python-envs.pythonProjects": [],
"python.defaultInterpreterPath": "${userHome}/pygpsclient/bin/python3"
"python.defaultInterpreterPath": "${userHome}/pygpsclient/bin/python3",
"python.testing.unittestEnabled": false
}
18 changes: 5 additions & 13 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ If you're intending to make significant changes, please raise them in the [Discu

Being one of our contributors, you agree and confirm that:

* The work is all your own.
* The work is all your own. For the avoidance of doubt, this means **no AI coding agents such as Copilot**.
* Your work will be distributed under a BSD 3-Clause License once your pull request is merged.
* You submitted work fulfils or mostly fulfils our coding conventions, styles and standards.

Expand All @@ -15,29 +15,21 @@ Please help us keep our issue list small by adding fixes: #{$ISSUE_NO} to the co
## Coding conventions

* This is open source software. Code should be as simple and transparent as possible. Favour clarity over brevity.
* The code should be compatible with Python >= 3.9.
* The code should be compatible with Python >= 3.10.
* Avoid external library dependencies unless there's a compelling reason not to.
* We use and recommend [Visual Studio Code](https://code.visualstudio.com/) with the [Python Extension](https://marketplace.visualstudio.com/items?itemName=ms-python.python) for development and testing.
* Code should be documented in accordance with [Sphinx](https://www.sphinx-doc.org/en/master/) docstring conventions.
* Code should formatted using [black](https://pypi.org/project/black/) (>= 24.4).
* We use and recommend [pylint](https://pypi.org/project/pylint/) (>=3.0.1) for code analysis.
* We use and recommend [bandit](https://pypi.org/project/bandit/) (>=1.7.5) for security vulnerability analysis.
* Code should formatted using [black](https://pypi.org/project/black/).
* We use and recommend [pylint](https://pypi.org/project/pylint/) for code analysis.
* We use and recommend [bandit](https://pypi.org/project/bandit/) for security vulnerability analysis.
* Commits must be [signed](https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits).

## Testing

While we endeavour to test on as wide a variety of u-blox devices as possible, as a volunteer project we only have a limited number of devices available. We particularly welcome testing contributions relating to specialised devices (e.g. high precision HP, real-time kinematics RTK, automotive dead-reckoning ADR, etc.).

We use python's native pytest framework for local unit testing, complemented by the GitHub Actions automated build and testing workflow.

Please write pytest examples for new code you create and add them to the `/tests` folder following the naming convention `test_*.py`.

We test on the following platforms using a variety of u-blox devices from Generation 7 throught Generation 10:
* Windows 11
* MacOS (Ventura & Sonoma, Intel & Apple Silicon)
* Linux (Ubuntu 22.04 LTS Jammy Jellyfish, 24.04 LTS Noble Numbat)
* Raspberry Pi OS (32-bit & 64-bit)

## Submitting changes

Please send a [GitHub Pull Request to pygnssutils](https://github.com/semuconsulting/pygnssutils/pulls) with a clear list of what you've done (read more about [pull requests](https://docs.github.com/en/free-pro-team@latest/github/collaborating-with-issues-and-pull-requests/about-pull-requests)). Please follow our coding conventions (above) and make sure all of your commits are atomic (one feature per commit).
Expand Down
28 changes: 27 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pygnssutils
[gnssserver CLI](#gnssserver) |
[gnssntripclient CLI](#gnssntripclient) |
[gnssmqttclient CLI](#gnssmqttclient) |
[socketserver](#socketserver) |
[RTK Demonstration](#rtkdemo) |
[Troubleshooting](#troubleshooting) |
[Graphical Client](#gui) |
Expand All @@ -33,6 +34,7 @@ designated output stream.
1. `GNSSMQTTClient` class and its associated [`gnssmqttclient`](#gnssmqttclient) CLI utility. This implements
a simple SPARTN IP (MQTT) Client which receives SPARTN correction data from an SPARTN IP location service and (optionally) sends this to a
designated output stream.
1. `SocketServer` class based on the native Python `ThreadingTCPServer`. Capable of operating in two modes - Socket Server or NTRIP Caster. Provides two alternate client request handler classes - `ClientHandler` (HTTP) or `ClientHandlerTLS` (HTTPS).

The pygnssutils homepage is located at [https://github.com/semuconsulting/pygnssutils](https://github.com/semuconsulting/pygnssutils).

Expand Down Expand Up @@ -456,6 +458,31 @@ gnssmqttclient -h

Refer to the [pyspartn documentation](https://github.com/semuconsulting/pyspartn?tab=readme-ov-file#reading) for further details on decrypting encrypted (`eaf=1`) SPARTN payloads.

---
## <a name="socketserver">SocketServer</a>

```
class pygnssutils.socketserver.SocketServer(app, ntripmode, maxclients, msgqueue, **kwargs)
```

A helper class based on the native Python [`ThreadingTCPServer`](https://docs.python.org/3/library/socketserver.html) class, which streams GNSS data from an inbound message queue `msgqueue` to a maximum of `maxclients` TCP clients.

Capable of operating in either of two modes, according to the `ntripmode` argument:
1. ntripmode = 0 - Socket Server; streams incoming GNSS data to any TCP client, without authentication.
2. ntripmode = 1 - NTRIP Caster; acts as a simple NTRIP Server/Caster, streaming incoming RTCM3 data to any authenticated NTRIP client.

Provides two client request handler classes:
- `ClientHandler` - unencrypted HTTP connection.
- `ClientHandlerTLS` - encrypted HTTPS (TLS) connection.

**NB:** HTTPS requires a valid x509 TLS certificate/key pair (in pem format) to be located at a path designated by environment variable `PYGNSSUTILS_PEMPATH`. The default path is `$HOME\pygnssutils.pem`. The following openssl command can be used to create a suitable pem file for test and demonstration purposes:

```shell
openssl req -x509 -newkey rsa:4096 -keyout pygnssutils.pem -out pygnssutils.pem -sha256 -days 3650 -nodes
```

Refer to the [Sphinx API documentation](https://www.semuconsulting.com/pygnssutils/pygnssutils.html#module-pygnssutils.socket_server) for further details.

---
## <a name="rtkdemo">NTRIP RTK demonstration using `gnssserver` and `gnssntripclient`</a>

Expand Down Expand Up @@ -531,7 +558,6 @@ gnssntripclient --server 192.168.0.27 --port 2101 --https 0 --mountpoint pygnssu

Refer to [cryptography installation README.md](https://github.com/semuconsulting/pyspartn/blob/main/cryptography_installation/README.md).


---
## <a name="gui">Graphical Client</a>

Expand Down
13 changes: 13 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
# pygnssutils

### RELEASE 1.1.19

ENHANCEMENTS:

1. Add support for TLS connections in SocketServer. Introduces two alternative client request handler classes - ClientHandler (HTTP) or ClientHandlerTLS (HTTPS). TLS operation requires a suitable TLS certificate/key pair (in pem format) to be located at a path designated by
environment variable `PYGNSSUTILS_PEMPATH` - the default path is $HOME/pygnssutils.pem. See Sphinx documentation for details.

A self-signed pem file suitable for test and demonstration purposes can be created thus:
```shell
openssl req -x509 -newkey rsa:4096 -keyout pygnssutils.pem -out pygnssutils.pem -sha256 -days 3650 -nodes
```

### RELEASE 1.1.18

ENHANCEMENTS:

1. Add gnssreader class.
1. Add support for Quectel QGC protocol.

### RELEASE 1.1.17
Expand Down
10 changes: 5 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@ dependencies = [
"certifi>=2025.0.0",
"paho-mqtt>=2.1.0",
"pyserial>=3.5",
"pyspartn>=1.0.7",
"pyubx2>=1.2.58",
"pysbf2>=1.0.0",
"pyubxutils>=1.0.3",
"pyqgc>=0.1.1",
"pyspartn>=1.0.8",
"pyubx2>=1.2.59",
"pysbf2>=1.0.3",
"pyubxutils>=1.0.5",
"pyqgc>=0.1.2",
]

[project.scripts]
Expand Down
8 changes: 8 additions & 0 deletions src/pygnssutils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@
from pygnssutils.globals import *
from pygnssutils.gnssmqttclient import GNSSMQTTClient
from pygnssutils.gnssntripclient import GNSSNTRIPClient
from pygnssutils.gnssreader import (
NMEA_PROTOCOL,
QGC_PROTOCOL,
RTCM3_PROTOCOL,
SBF_PROTOCOL,
UBX_PROTOCOL,
GNSSReader,
)
from pygnssutils.gnssserver import GNSSSocketServer
from pygnssutils.gnssstreamer import GNSSStreamer
from pygnssutils.helpers import *
Expand Down
2 changes: 1 addition & 1 deletion src/pygnssutils/_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@
:license: BSD 3-Clause
"""

__version__ = "1.1.18"
__version__ = "1.1.19"
15 changes: 12 additions & 3 deletions src/pygnssutils/globals.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
:license: BSD 3-Clause
"""

OKCOL = "green"
ERRCOL = "salmon"
INFOCOL = "steelblue2"

CLIAPP = "CLI"
DEFAULT_BUFSIZE = 4096 # buffer size for NTRIP client
"""Default socket buffer size"""
Expand Down Expand Up @@ -74,6 +78,8 @@
"""Serial output"""
OUTPUT_SOCKET = 3
"""Socket output"""
OUTPUT_SOCKET_TLS = 6
"""Socket output with TLS"""
OUTPUT_HANDLER = 4
"""Custom output handler"""
OUTPUT_TEXT_FILE = 5
Expand Down Expand Up @@ -102,7 +108,7 @@
"""Disconnected"""
CONNECTED = 1
"""Connected"""
MAXCONNECTION = 2
MAXCONNECTION = 5
"""Maximum connections reached (for socket server)"""
LOGFORMAT = "{asctime}.{msecs:.0f} - {levelname} - {name} - {message}"
"""Logging format"""
Expand All @@ -111,7 +117,7 @@
NOGGA = -1
"""No GGA sentence to be sent (for NTRIP caster)"""
EPILOG = (
"© 2022 SEMU Consulting BSD 3-Clause license"
"© 2022 semuadmin (Steve Smith) BSD 3-Clause license"
" - https://github.com/semuconsulting/pygnssutils/"
)
"""CLI argument parser epilog"""
Expand Down Expand Up @@ -195,7 +201,10 @@
MINMMEA_ID = [b"\xf0\x00", b"\xf0\x02", b"\xf0\x03", b"\xf0\x04", b"\xf0\x05"]
ALLUBX_CLS = [b"\x01"]
MINUBX_ID = [b"\x01\x04", b"\x01\x07", b"\x01\x35"]

PYGNSSUTILS_PEM = "pygnssutils.pem"
"""Name of default TLS PEM file"""
PYGNSSUTILS_PEMPATH = "PYGNSSUTILS_PEMPATH"
"""Name of environment variable containing path to TLS PEM file"""
TOPIC_KEY = "/pp/ubx/0236/{}"
TOPIC_ASSIST = "/pp/ubx/mga"
TOPIC_DATA = "/pp/{}/{}"
Expand Down
22 changes: 17 additions & 5 deletions src/pygnssutils/gnssmqttclient_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,14 @@
ENV_MQTT_CLIENTID,
ENV_MQTT_KEY,
EPILOG,
MAXCONNECTION,
NTRIP2,
OUTPORT_SPARTN,
OUTPUT_FILE,
OUTPUT_NONE,
OUTPUT_SERIAL,
OUTPUT_SOCKET,
OUTPUT_SOCKET_TLS,
SPARTN_PPSERVER,
)
from pygnssutils.gnssmqttclient import TIMEOUT, GNSSMQTTClient
Expand Down Expand Up @@ -195,10 +198,17 @@ def main():
f"CLI output type {OUTPUT_NONE} = none, "
f"{OUTPUT_FILE} = binary file, "
f"{OUTPUT_SERIAL} = serial port, "
f"{OUTPUT_SOCKET} = TCP socket server"
f"{OUTPUT_SOCKET} = TCP socket server, "
f"{OUTPUT_SOCKET_TLS} = TCP socket server with TLS"
),
type=int,
choices=[OUTPUT_NONE, OUTPUT_FILE, OUTPUT_SERIAL, OUTPUT_SOCKET],
choices=[
OUTPUT_NONE,
OUTPUT_FILE,
OUTPUT_SERIAL,
OUTPUT_SOCKET,
OUTPUT_SOCKET_TLS,
],
default=OUTPUT_NONE,
)
ap.add_argument(
Expand All @@ -208,7 +218,8 @@ def main():
"Output medium as formatted string. "
f"If clioutput = {OUTPUT_FILE}, format = file name (e.g. '/home/myuser/spartn.log'); "
f"If clioutput = {OUTPUT_SERIAL}, format = port@baudrate (e.g. '/dev/tty.ACM0@38400'); "
f"If clioutput = {OUTPUT_SOCKET}, format = hostip:port (e.g. '0.0.0.0:50010'). "
f"If clioutput = {OUTPUT_SOCKET} or {OUTPUT_SOCKET_TLS}, "
"format = hostip:port (e.g. '0.0.0.0:50010'). "
"NB: gnssmqttclient will have exclusive use of any serial or server port."
),
default=None,
Expand All @@ -231,14 +242,15 @@ def main():
with Serial(port, int(baud), timeout=3) as output:
kwargs["output"] = output
runclient(**kwargs)
elif cliout == OUTPUT_SOCKET:
elif cliout in (OUTPUT_SOCKET, OUTPUT_SOCKET_TLS):
host, port = kwargs["output"].split(":")
tls = cliout == OUTPUT_SOCKET_TLS
kwargs["output"] = Queue()
# socket server runs as background thread, piping
# output from mqtt client via a message queue
Thread(
target=runserver,
args=(host, int(port), kwargs["output"]),
args=(host, int(port), kwargs["output"], 0, MAXCONNECTION, tls, NTRIP2),
daemon=True,
).start()
runclient(**kwargs)
Expand Down
Loading