Skip to content

Commit

Permalink
Merge pull request #59 from semuconsulting/RC-1.0.22
Browse files Browse the repository at this point in the history
RELEASE CANDIDATE 1.0.22
  • Loading branch information
semuadmin committed Mar 18, 2024
2 parents 2bce0f8 + 15ac670 commit 7a0779e
Show file tree
Hide file tree
Showing 7 changed files with 83 additions and 24 deletions.
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@
"editor.formatOnSave": true,
"modulename": "${workspaceFolderBasename}",
"distname": "${workspaceFolderBasename}",
"moduleversion": "1.0.21"
"moduleversion": "1.0.22"
}
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Originally developed in support of the [PyGPSClient](https://github.com/semucons
1. `GNSSStreamer` class and its associated [`gnssdump`](#gnssdump) CLI utility. This is essentially a configurable input/output wrapper around the [`pyubx2.UBXReader`](https://github.com/semuconsulting/pyubx2#reading) class with flexible message formatting and filtering options for NMEA, UBX and RTCM3 protocols.
1. `GNSSSocketServer` class and its associated [`gnssserver`](#gnssserver) CLI utility. This implements a TCP Socket Server for GNSS data streams which is also capable of being run as a simple NTRIP Server.
1. `GNSSNTRIPClient` class and its associated [`gnssntripclient`](#gnssntripclient) CLI utility. This implements
a simple NTRIP Client which receives RTCM3 correction data from an NTRIP Server and (optionally) sends this to a
a simple NTRIP Client which receives RTCM3 or SPARTN correction data from an NTRIP Server and (optionally) sends this to a
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 sends this to a
Expand Down Expand Up @@ -242,7 +242,7 @@ Refer to the [Sphinx API documentation](https://www.semuconsulting.com/pygnssuti
class pygnssutils.gnssntripclient.GNSSNTRIPClient(app=None, **kwargs)
```
The `GNSSNTRIPClient` class provides a basic NTRIP Client capability and forms the basis of a [`gnssntripclient`](#gnssntripclient) CLI utility. It receives RTCM3 correction data from an NTRIP server and (optionally) sends this to a designated output stream. NTRIP server login credentials are set via command line arguments or environment variables `PYGPSCLIENT_USER` and `PYGPSCLIENT_PASSWORD`.
The `GNSSNTRIPClient` class provides a basic NTRIP Client capability and forms the basis of a [`gnssntripclient`](#gnssntripclient) CLI utility. It receives RTCM3 or SPARTN correction data from an NTRIP server and (optionally) sends this to a designated output stream. NTRIP server login credentials are set via command line arguments or environment variables `PYGPSCLIENT_USER` and `PYGPSCLIENT_PASSWORD`.
### CLI Usage:
Expand All @@ -251,7 +251,7 @@ Assuming the Python 3 scripts (bin) directory is in your PATH, the CLI utility m
To retrieve the sourcetable and determine the closest available mountpoint to the reference lat/lon, leave the mountpoint argument blank (the port defaults to 2101):
```shell
> gnssntripclient --server rtk2go.com --ggamode 1 --reflat 37.23 --reflon 115.81 --ntripuser myuser --ntrippassword mypassword
> gnssntripclient --server rtk2go.com --protocol RTCM --ggamode 1 --reflat 37.23 --reflon 115.81 --ntripuser myuser --ntrippassword mypassword
2022-06-03 20:15:54.510294: Closest mountpoint to reference location 37.23,-115.81 = WW6RY, 351.51 km
Complete sourcetable follows...
Expand Down
8 changes: 7 additions & 1 deletion RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
# pygnssutils Release Notes

### RELEASE CANDIDATE 1.0.21
### RELEASE 1.0.22

ENHANCEMENTS:

1. Add support for u-blox PointPerfect NTRIP SPARTN service in gnssntripclient.

### RELEASE 1.0.21

ENHANCEMENTS:

Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ name = "pygnssutils"
authors = [{ name = "semuadmin", email = "[email protected]" }]
maintainers = [{ name = "semuadmin", email = "[email protected]" }]
description = "GNSS Command Line Utilities"
version = "1.0.21"
version = "1.0.22"
license = { file = "LICENSE" }
readme = "README.md"
requires-python = ">=3.8"
Expand Down Expand Up @@ -37,7 +37,7 @@ dependencies = [
"certifi>=2024.0.0",
"paho-mqtt>=1.6.0",
"pyserial>=3.5",
"pyspartn>=0.2.0",
"pyspartn>=0.2.1",
"pyubx2>=1.2.39",
]

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.0.21"
__version__ = "1.0.22"
1 change: 1 addition & 0 deletions src/pygnssutils/globals.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

OUTPORT = 50010
OUTPORT_NTRIP = 2101
DEFAULT_TLS_PORTS = (443, 2102)
MIN_NMEA_PAYLOAD = 3 # minimum viable length of NMEA message payload
EARTH_RADIUS = 6371 # km
DEFAULT_BUFSIZE = 4096 # buffer size for NTRIP client
Expand Down
84 changes: 68 additions & 16 deletions src/pygnssutils/gnssntripclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,15 @@
from certifi import where as findcacerts
from pynmeagps import GET, NMEAMessage
from pyrtcm import RTCMMessageError, RTCMParseError, RTCMTypeError
from pyspartn import SPARTNMessageError, SPARTNParseError, SPARTNReader, SPARTNTypeError
from pyubx2 import ERR_IGNORE, RTCM3_PROTOCOL, UBXReader
from serial import Serial

from pygnssutils._version import __version__ as VERSION
from pygnssutils.exceptions import ParameterError
from pygnssutils.globals import (
DEFAULT_BUFSIZE,
DEFAULT_TLS_PORTS,
EPILOG,
HTTPERR,
LOGLIMIT,
Expand All @@ -61,6 +63,8 @@
GGALIVE = 0
GGAFIXED = 1
DLGTNTRIP = "NTRIP Configuration"
RTCM = "RTCM"
SPARTN = "SPARTN"


class GNSSNTRIPClient:
Expand All @@ -87,11 +91,13 @@ def __init__(self, app=None, **kwargs):
"ipprot": socket.AF_INET,
"server": "",
"port": 2101,
"https": 0,
"flowinfo": 0,
"scopeid": 0,
"mountpoint": "",
"distance": "",
"version": "2.0",
"datatype": RTCM,
"ntripuser": "anon",
"ntrippassword": "password",
"ggainterval": "None",
Expand Down Expand Up @@ -178,9 +184,11 @@ def run(self, **kwargs) -> bool:
:param str ipprot: (kwarg) IP protocol IPv4/IPv6 ("IPv4")
:param str server: (kwarg) NTRIP server URL ("")
:param int port: (kwarg) NTRIP port (2101)
:param int https: (kwarg) HTTPS (TLS) connection? 0 = HTTP 1 = HTTPS (0)
:param int flowinfo: (kwarg) flowinfo for IPv6 (0)
:param int scopeid: (kwarg) scopeid for IPv6 (0)
:param str mountpoint: (kwarg) NTRIP mountpoint ("", leave blank to get sourcetable)
:param str datatype: (kwarg) Data type - RTCM or SPARTN ("RTCM")
:param str version: (kwarg) NTRIP protocol version ("2.0")
:param str ntripuser: (kwarg) NTRIP authentication user ("anon")
:param str ntrippassword: (kwarg) NTRIP authentication password ("password")
Expand All @@ -200,12 +208,16 @@ def run(self, **kwargs) -> bool:
self._last_gga = datetime.fromordinal(1)

ipprot = kwargs.get("ipprot", "IPv4")
self.settings["ipprot"] = ipprot2int(ipprot)
self._settings["ipprot"] = ipprot2int(ipprot)
self._settings["server"] = server = kwargs.get("server", "")
self._settings["port"] = port = int(kwargs.get("port", OUTPORT_NTRIP))
self._settings["https"] = int(
kwargs.get("https", 1 if port in DEFAULT_TLS_PORTS else 0)
)
self._settings["flowinfo"] = int(kwargs.get("flowinfo", 0))
self._settings["scopeid"] = int(kwargs.get("scopeid", 0))
self._settings["mountpoint"] = mountpoint = kwargs.get("mountpoint", "")
self._settings["datatype"] = kwargs.get("datatype", RTCM).upper()
self._settings["version"] = kwargs.get("version", "2.0")
self._settings["ntripuser"] = kwargs.get(
"ntripuser", os.getenv("PYGPSCLIENT_USER", "user")
Expand Down Expand Up @@ -451,15 +463,16 @@ def _read_thread(
try:
server = settings["server"]
port = int(settings["port"])
https = int(settings["https"])
flowinfo = int(settings["flowinfo"])
scopeid = int(settings["scopeid"])
mountpoint = settings["mountpoint"]
ggainterval = int(settings["ggainterval"])
datatype = settings["datatype"]

conn = format_conn(settings["ipprot"], server, port, flowinfo, scopeid)
with socket.socket(settings["ipprot"], socket.SOCK_STREAM) as self._socket:
if port == 443:
# context = ssl.create_default_context()
if https:
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
context.load_verify_locations(findcacerts())
self._socket = context.wrap_socket(
Expand All @@ -473,9 +486,11 @@ def _read_thread(
self._send_GGA(ggainterval, output)
while not stopevent.is_set():
rc = self._do_header(self._socket, stopevent, output)
if rc == "0": # streaming RTMC3 data from mountpoint
if rc == "0": # streaming RTMC3/SPARTN data from mountpoint
self._do_log(f"Using mountpoint {mountpoint}\n")
self._do_data(self._socket, stopevent, ggainterval, output)
self._do_data(
self._socket, datatype, stopevent, ggainterval, output
)
elif rc == "1": # retrieved sourcetable
stopevent.set()
self._connected = False
Expand Down Expand Up @@ -553,32 +568,48 @@ def _do_header(self, sock: socket, stopevent: Event, output: object) -> str:
return "0"

def _do_data(
self, sock: socket, stopevent: Event, ggainterval: int, output: object
self,
sock: socket,
datatype: str,
stopevent: Event,
ggainterval: int,
output: object,
):
"""
THREADED
Read and parse incoming NTRIP RTCM3 data stream.
:param socket sock: socket
:param str datatype: RTCM or SPARTN
:param Event stopevent: stop event
:param int ggainterval: GGA transmission interval seconds
:param object output: output stream for RTCM3 messages
"""

# UBXReader will wrap socket as SocketStream
ubr = UBXReader(
sock,
protfilter=RTCM3_PROTOCOL,
quitonerror=ERR_IGNORE,
bufsize=DEFAULT_BUFSIZE,
labelmsm=True,
)

parser = None
raw_data = None
parsed_data = None

# parser will wrap socket as SocketStream
if datatype == SPARTN:
parser = SPARTNReader(
sock,
quitonerror=ERR_IGNORE,
bufsize=DEFAULT_BUFSIZE,
decode=False,
)
else:
parser = UBXReader(
sock,
protfilter=RTCM3_PROTOCOL,
quitonerror=ERR_IGNORE,
bufsize=DEFAULT_BUFSIZE,
labelmsm=True,
)

while not stopevent.is_set():
try:
raw_data, parsed_data = ubr.read()
raw_data, parsed_data = parser.read()
if raw_data is not None:
self._do_write(output, raw_data, parsed_data)
self._send_GGA(ggainterval, output)
Expand All @@ -587,6 +618,9 @@ def _do_data(
RTCMMessageError,
RTCMParseError,
RTCMTypeError,
SPARTNMessageError,
SPARTNParseError,
SPARTNTypeError,
) as err:
parsed_data = f"Error parsing data stream {err}"
self._do_write(output, raw_data, parsed_data)
Expand Down Expand Up @@ -709,6 +743,14 @@ def main():
ap.add_argument(
"-P", "--port", required=False, help="NTRIP port", type=int, default=2101
)
ap.add_argument(
"--https",
required=False,
help=f"HTTPS (TLS) connection? 0 = HTTP, 1 = HTTPS (defaults to 1 if port in {DEFAULT_TLS_PORTS})",
type=int,
choices=[0, 1],
default=0,
)
ap.add_argument(
"--flowinfo", required=False, help="Flow info for IPv6", type=int, default=0
)
Expand All @@ -729,6 +771,13 @@ def main():
help="NTRIP protocol version",
default="2.0",
)
ap.add_argument(
"--datatype",
required=False,
help="Data type (RTCM or SPARTN)",
choices=[RTCM, "rtcm", SPARTN, "spartn"],
default=RTCM,
)
ap.add_argument(
"--waittime",
required=False,
Expand Down Expand Up @@ -807,6 +856,9 @@ def main():
args = ap.parse_args()
kwargs = vars(args)

# assume HTTPS if port is 443 or 2102 (PointPerfect NTRIP TLS port)
kwargs["https"] = 1 if kwargs["port"] in DEFAULT_TLS_PORTS else kwargs["https"]

try:
with GNSSNTRIPClient(None, **kwargs) as gnc:
streaming = gnc.run(**kwargs)
Expand Down

0 comments on commit 7a0779e

Please sign in to comment.