pygnssutils package

Submodules

pygnssutils.exceptions module

UBX Custom Exception Types

Created on 27 Sep 2020

author:

semuadmin (Steve Smith)

copyright:

semuadmin © 2020

license:

BSD 3-Clause

exception pygnssutils.exceptions.ParameterError[source]

Bases: Exception

Parameter Error Class.

exception pygnssutils.exceptions.GNSSError[source]

Bases: Exception

Master GNSS Error Class.

Any other GNSS exceptions defined here should inherit from this.

exception pygnssutils.exceptions.RINEXError[source]

Bases: Exception

Master RINEX Error Class.

Any other RINEX conversion exceptions defined here should inherit from this.

exception pygnssutils.exceptions.GNSSStreamError[source]

Bases: GNSSError

Generic GNSS Stream Error Class.

exception pygnssutils.exceptions.RINEXProcessingError[source]

Bases: RINEXError

Generic RINEX Conversion Error Class.

pygnssutils.globals module

Global variables for pygnssutils.

Created on 26 May 2022

author:

semuadmin (Steve Smith)

copyright:

semuadmin © 2022

license:

BSD 3-Clause

pygnssutils.globals.DEFAULT_BUFSIZE = 4096

Default socket buffer size

pygnssutils.globals.EARTH_RADIUS = 6371

Earth radius in km

pygnssutils.globals.ENV_NTRIP_PASSWORD = 'PYGPSCLIENT_PASSWORD'

Environment variable for NTRIP password

pygnssutils.globals.ENV_NTRIP_USER = 'PYGPSCLIENT_USER'

Environment variable for NTRIP user

pygnssutils.globals.ENV_MQTT_CLIENTID = 'MQTTCLIENTID'

Environment variable for MQTT Client ID

pygnssutils.globals.ENV_MQTT_KEY = 'MQTTKEY'

Environment variable for MQTT SPARTN decryption key

pygnssutils.globals.ENCODE_NONE = 0

No socket encoding

pygnssutils.globals.ENCODE_CHUNKED = 1

chunked socket encoding

pygnssutils.globals.ENCODE_GZIP = 2

gzip socket encoding

pygnssutils.globals.ENCODE_COMPRESS = 4

compress socket encoding

pygnssutils.globals.ENCODE_DEFLATE = 8

deflate socket encoding

pygnssutils.globals.FORMAT_PARSED = 1

parsed format

pygnssutils.globals.FORMAT_BINARY = 2

binary (raw) format

pygnssutils.globals.FORMAT_HEX = 4

hexadecimal string format

pygnssutils.globals.FORMAT_HEXTABLE = 8

tabular hexadecimal format

pygnssutils.globals.FORMAT_PARSEDSTRING = 16

parsed as string format

pygnssutils.globals.FORMAT_JSON = 32

JSON format

pygnssutils.globals.INPUT_NONE = 0

No input medium

pygnssutils.globals.INPUT_NTRIP_RTCM = 1

NTRIP RTCM input

pygnssutils.globals.INPUT_NTRIP_SPARTN = 2

NTRIP SPARTN input

pygnssutils.globals.INPUT_MQTT_SPARTN = 3

MQTT SPARTN input

pygnssutils.globals.INPUT_SERIAL = 4

Serial input (e.g. RXM-PMP from D9S SPARTN L-band receiver)

pygnssutils.globals.INPUT_FILE = 5

File input (e.g. CFG-VALSET commands)

pygnssutils.globals.MAXPORT = 65535

Maximum permissible port number

pygnssutils.globals.NTRIP1 = '1.0'

NTRIP version 1.0 descriptor

pygnssutils.globals.NTRIP2 = '2.0'

NTRIP version 2.0 descriptor

pygnssutils.globals.OUTPORT = 50010

Default socket server port

pygnssutils.globals.OUTPORT_NTRIP = 2101

Default NTRIP caster port

pygnssutils.globals.OUTPUT_NONE = 0

No output medium

pygnssutils.globals.OUTPUT_FILE = 1

Binary file output

pygnssutils.globals.OUTPUT_SERIAL = 2

Serial output

pygnssutils.globals.OUTPUT_SOCKET = 3

Socket output

pygnssutils.globals.OUTPUT_SOCKET_TLS = 6

Socket output with TLS

pygnssutils.globals.OUTPUT_HANDLER = 4

Custom output handler

pygnssutils.globals.OUTPUT_TEXT_FILE = 5

Text file output

pygnssutils.globals.VERBOSITY_CRITICAL = -1

Verbosity critical

pygnssutils.globals.VERBOSITY_LOW = 0

Verbosity error

pygnssutils.globals.VERBOSITY_MEDIUM = 1

Verbosity warning

pygnssutils.globals.VERBOSITY_HIGH = 2

Verbosity info

pygnssutils.globals.VERBOSITY_DEBUG = 3

Verbosity debug

pygnssutils.globals.UBXSIMULATOR = 'UBXSIMULATOR'

UBX simulator

pygnssutils.globals.LOGGING_LEVELS = {-1: 'CRITICAL', 0: 'ERROR', 1: 'WARNING', 2: 'INFO', 3: 'DEBUG'}

Logging level descriptors

pygnssutils.globals.DISCONNECTED = 0

Disconnected

pygnssutils.globals.CONNECTED = 1

Connected

pygnssutils.globals.MAXCONNECTION = 5

Maximum connections reached (for socket server)

pygnssutils.globals.LOGFORMAT = '{asctime}.{msecs:.0f} - {levelname} - {name} - {message}'

Logging format

pygnssutils.globals.LOGLIMIT = 10485760

Logfile limit

pygnssutils.globals.NOGGA = -1

No GGA sentence to be sent (for NTRIP caster)

pygnssutils.globals.EPILOG = 2022 semuadmin (Steve Smith) BSD 3-Clause license - https://github.com/semuconsulting/pygnssutils/'

CLI argument parser epilog

pygnssutils.globals.GNSSLIST = {0: 'GPS', 1: 'SBAS', 2: 'Galileo', 3: 'BeiDou', 4: 'IMES', 5: 'QZSS', 6: 'GLONASS'}

GNSS identifiers

pygnssutils.globals.FIXES = {'2D': 1, '3D': 1, 'DR': 6, 'GNSS+DR': 1, 'GPS + DR': 1, 'NO FIX': 0, 'RTK': 5, 'RTK FIXED': 4, 'RTK FLOAT': 5, 'TIME ONLY': 0}

Fix enumeration

pygnssutils.globals.FIXTYPE_GGA = {0: 'NO FIX', 1: '3D', 2: '3D', 4: 'RTK FIXED', 5: 'RTK FLOAT', 6: 'DR'}

NMEA GGA fixtype decode

pygnssutils.globals.HTTPCODES = {200: 'OK', 400: 'Bad Request', 401: 'Unauthorized', 403: 'Forbidden', 404: 'Not Found', 405: 'Method Not Allowed', 406: 'Not Acceptable', 408: 'Request Timeout', 409: 'Conflict', 429: 'Too Many Requests', 500: 'Internal Server Error', 501: 'Not Implemented', 503: 'Service Unavailable'}

HTTP response codes used by NTRIP

pygnssutils.globals.RTCMTYPES = {'1002': 1, '1006': 5, '1010': 1, '1077': 1, '1087': 1, '1097': 1, '1127': 1, '1230': 1, '4072_0': 1, '4072_1': 1}

RTCM3 message types output in NTRIP caster mode

pygnssutils.globals.RTCMSTR = '1002(1),1006(5),1010(1),1077(1),1087(1),1097(1),1127(1),1230(1),4072_0(1),4072_1(1)'

RTCM3 types sourcetable entry for NTRIP caster

pygnssutils.globals.PYGPSMP = 'pygnssutils'

Name of NTRIP caster mountpoint

pygnssutils.globals.PYGNSSUTILS_CRT = '/Users/steve/pygnssutils.crt'

Name of default TLS CRT file

pygnssutils.globals.PYGNSSUTILS_CRTPATH = 'PYGNSSUTILS_CRTPATH'

Name of environment variable containing path to TLS CRT file

pygnssutils.globals.PYGNSSUTILS_PEM = '/Users/steve/pygnssutils.pem'

Name of default TLS PEM file

pygnssutils.globals.PYGNSSUTILS_PEMPATH = 'PYGNSSUTILS_PEMPATH'

Name of environment variable containing path to TLS PEM file

pygnssutils.gnssmqttclient module

gnssmqttclient.py

MQTT SPARTN client class, retrieving correction data from an IP (MQTT) source and (optionally) sending the data to a designated writeable output medium (serial, file, socket, queue).

Calling app, if defined, can implement the following methods:

  • set_event() - create <<spartn_read>> event

  • dialog() - return reference to MQTT client configuration dialog

Can utilise the following environment variables:

  • MQTTKEY - SPARTN payload decription key (valid for 4 weeks)

  • MQTTCRT - MQTT server (PointPerfect) TLS certificate

  • MQTTPEM - MQTT server (PointPerfect) TLS key

  • MQTTCLIENTID - MQTT server client ID

Credentials can be download from:

Thingstream > Location Services > PointPerfect Thing > Credentials

Default location for key files is user’s HOME directory

Created on 20 Feb 2023

author:

semuadmin (Steve Smith)

copyright:

semuadmin © 2023

license:

BSD 3-Clause

class pygnssutils.gnssmqttclient.GNSSMQTTClient(app=None, **kwargs)[source]

Bases: object

SPARTN MQTT client class.

__init__(app=None, **kwargs)[source]

Constructor.

Parameters:

app (object) – application from which this class is invoked (None)

property settings

Getter for SPARTN IP settings.

property connected

Connection status getter.

start(**kwargs) int[source]

Start MQTT handler thread.

Returns:

return code

Return type:

int

stop()[source]

Stop MQTT handler thread.

static on_connect(client, userdata, flags, rcd)[source]

The callback for when the client receives a CONNACK response from the server.

Parameters:
  • client (object) – client

  • userdata (list) – list of user defined data items

  • flags (list) – optional flags

  • rcd (int) – return status code

static on_connect_fail(client, userdata, rcd)[source]

The callback for when the client fails to connect to the server.

Parameters:
  • client (object) – client

  • userdata (list) – list of user defined data items

  • rcd (int) – return status code

static on_disconnect(client, userdata, rcd)[source]

The callback for when the client disconnects from the server.

Parameters:
  • client (object) – client

  • userdata (list) – list of user defined data items

  • rcd (int) – return status code

static on_message(client, userdata, msg)[source]

The callback for when a PUBLISH message is received from the server. Some MQTT topics may contain more than one UBX or SPARTN message in a single payload.

Parameters:
  • client (object) – MQTT client

  • userdata (list) – list of user defined data items

  • msg (object) – SPARTN or UBX message topic content

static on_error(userdata: dict, err: object)[source]

Report return code back to any calling application.

Parameters:
  • userdata (dict) – user defined data dict

  • rcd (object) – return code (int or str)

pygnssutils.gnssmqttclient_cli module

gnssmqttclient_cli.py

CLI wrapper for GNSSMQTTClient class.

Created on 24 Jul 2024

author:

semuadmin (Steve Smith)

copyright:

semuadmin © 2023

license:

BSD 3-Clause

pygnssutils.gnssmqttclient_cli.runclient(**kwargs)[source]

Start MQTT client with CLI parameters.

pygnssutils.gnssmqttclient_cli.main()[source]

CLI Entry point.

pygnssutils.gnssntripclient module

gnssntripclient.py

NTRIP client class; essentially an HTTP client capable of retrieving sourcetable and RTCM3 or SPARTN correction data from an NTRIP server and (optionally) sending the correction data to a designated writeable output medium (serial, file, socket, queue).

Can also transmit client position back to NTRIP server at specified intervals via formatted NMEA GGA sentences.

Calling app, if defined, can implement the following methods:

  • set_event() - create <<ntrip_read>> event

  • dialog() - return reference to NTRIP config client dialog

  • get_coordinates() - return coordinates from receiver

NB: This utility is used by PyGPSClient - do not change footprint of any public methods without first checking impact on PyGPSClient - https://github.com/semuconsulting/PyGPSClient.

Created on 03 Jun 2022

author:

semuadmin (Steve Smith)

copyright:

semuadmin © 2022

license:

BSD 3-Clause

class pygnssutils.gnssntripclient.GNSSNTRIPClient(app=None, **kwargs)[source]

Bases: object

NTRIP client class.

__init__(app=None, **kwargs)[source]

Constructor.

Parameters:
  • app (object) – application from which this class is invoked (None)

  • retries (int) – (kwarg) maximum failed connection retries (5)

  • retryinterval (int) – (kwarg) retry interval in seconds (10)

  • timeout (int) – (kwarg) inactivity timeout in seconds (10)

  • tlscrtpath (str) – (kwarg) Path to self-sign TLS certificate (“pygnssutils.crt”)

run(**kwargs) bool[source]

Open NTRIP client connection.

If calling application implements a “get_coordinates” method to obtain live coordinates (i.e. from GNSS receiver), the method will use these instead of fixed reference coordinates.

User login credentials can be obtained from environment variables PYGPSCLIENT_USER and PYGPSCLIENT_PASSWORD, or passed as kwargs.

Parameters:
  • server (str) – (kwarg) NTRIP server URL (“”)

  • port (int) – (kwarg) NTRIP port (2101)

  • https (bool) – (kwarg) Enable HTTPS (TLS) connection? (0)

  • selfsign (bool) – (kwarg) Allow self-sign TLS certificate (0)

  • mountpoint (str) – (kwarg) NTRIP mountpoint (“”, leave blank to get sourcetable)

  • datatype (Literal["RTCM","SPARTN"]) – (kwarg) Data type - RTCM or SPARTN (“RTCM”)

  • version (Literal["1.0","2.0"]) – (kwarg) NTRIP protocol version (“2.0”)

  • ntripuser (str) – (kwarg) NTRIP authentication user (“anon”)

  • ntrippassword (str) – (kwarg) NTRIP authentication password (“password”)

  • ggainterval (int) – (kwarg) GGA sentence transmission interval (-1 = None)

  • ggamode (Literal[0,1]) – (kwarg) GGA pos source; 0 = live from receiver, 1 = fixed reference (0)

  • reflat (str) – (kwarg) reference latitude (0.0)

  • reflon (str) – (kwarg) reference longitude (0.0)

  • refalt (str) – (kwarg) reference altitude (0.0)

  • refsep (str) – (kwarg) reference separation (0.0)

  • spartndecode (bool) – (kwarg) decode SPARTN messages (0)

  • spartnkey (str | NoneType) – (kwarg) SPARTN decryption key (None)

  • datetime (str | datetime) – (kwarg) SPARTN decryption basedate (now(utc))

  • output (object) – (kwarg) writeable output medium (serial, file, socket, queue) (None)

  • stopevent (Event) – (kwarg) stopevent to terminate run() (internal Event())

Returns:

boolean flag 0 = stream terminated, 1 = streaming data

Return type:

bool

stop()[source]

Close NTRIP server connection.

property settings

Getter for NTRIP settings.

property connected

Connection status getter.

property responseok: bool

Response OK indicator (i.e. 200 OK).

Returns:

True/False

Return type:

bool

property status: dict

Get response status e.g. {protocol: “HTTP/1.1”, code: 200, description: “OK”}.

Returns:

dict of protocol, status code, status description

Return type:

dict

property content_type: str

Get content type e.g. “text/html” or “gnss/data”.

Returns:

content type

Return type:

str

property response_body: object

Get response body if available.

Returns:

response body as bytes or string, depending on encoding

Return type:

object

property encoding: int

Get response transfer-encoding settings (chunked, deflate, compress, gzip).

Returns:

OR’d transfer-encoding value

Return type:

int

property is_gnssdata: bool

Check if response is NTRIP data stream (RTCM or SPARTN).

Returns:

gnss/data True/False

Return type:

bool

property is_sourcetable: bool

Check if response is NTRIP sourcetable.

Returns:

gnss/sourcetable True/False

Return type:

bool

property stopevent: Event

Getter for stop event.

Returns:

stop event

Return type:

Event

pygnssutils.gnssntripclient_cli module

gnssntripclient_cli.py

CLI wrapper for GNSSNTRIPClient class.

Created on 24 Jul 2024

author:

semuadmin (Steve Smith)

copyright:

semuadmin © 2022

license:

BSD 3-Clause

pygnssutils.gnssntripclient_cli.runclient(**kwargs)[source]

Start NTRIP client with CLI parameters.

pygnssutils.gnssntripclient_cli.main()[source]

CLI Entry point.

pygnssutils.gnssreader module

gnssreader.py

Generic GNSS class.

Reads and parses individual UBX, SBF, QGC, NMEA or RTCM3 messages from any viable data stream which supports a read(n) -> bytes method.

It is essentially an amalgamation of the Reader classes in the separate pyubx2, pynmeagps, pyrtcm, pysbf2 and pyqgc packages.

Returns both the raw binary data (as bytes) and the parsed data.

  • ‘protfilter’ governs which protocols (NMEA, UBX, SBF, QGC or RTCM3) are processed

  • ‘quitonerror’ governs how errors are handled

  • ‘msgmode’ indicates the type of UBX datastream (output GET, input SET, query POLL). If msgmode is set to SETPOLL, input/query mode will be automatically detected by parser.

Created on 6 Oct 2025

author:

semuadmin (Steve Smith)

copyright:

semuadmin © 2020

license:

BSD 3-Clause

pygnssutils.gnssreader.NMEA_PROTOCOL = 1

NMEA Protocol

pygnssutils.gnssreader.UBX_PROTOCOL = 2

UBX Protocol (u-blox)

pygnssutils.gnssreader.RTCM3_PROTOCOL = 4

RTCM3 Protocol

pygnssutils.gnssreader.SBF_PROTOCOL = 8

RTCM3 Protocol (Septentrio)

pygnssutils.gnssreader.QGC_PROTOCOL = 16

QGC Protocol (Quectel)

pygnssutils.gnssreader.UNI_PROTOCOL = 32

UNI Protocol (Unicore)

class pygnssutils.gnssreader.GNSSReader(datastream, msgmode: Literal[0, 1, 2] = 0, validate: int = 1, protfilter: int = 63, quitonerror: Literal[0, 1, 2] = 1, parsebitfield: bool = True, labelmsm: Literal[0, 1] = 1, bufsize: int = 4096, parsing: bool = True, errorhandler: LambdaType | None = None)[source]

Bases: object

GNSSReader class.

__init__(datastream, msgmode: Literal[0, 1, 2] = 0, validate: int = 1, protfilter: int = 63, quitonerror: Literal[0, 1, 2] = 1, parsebitfield: bool = True, labelmsm: Literal[0, 1] = 1, bufsize: int = 4096, parsing: bool = True, errorhandler: LambdaType | None = None)[source]

Constructor.

Parameters:
  • stream (datastream) – input data stream

  • msgmode (Literal[0,1,2]) – 0=GET, 1=SET, 2=POLL, 3=SETPOLL (0)

  • validate (int) – VALCKSUM (1) = Validate checksum, VALNONE (0) = ignore invalid checksum (1)

  • protfilter (int) – NMEA_PROTOCOL (1), UBX_PROTOCOL (2), RTCM3_PROTOCOL (4), SBF_PROTOCOL (8), QGC_PROTOCOL (16), UNI_PROTOCOL (32). Can be OR’d (7)

  • quitonerror (Literal[0,1,2]) – ERR_IGNORE (0) = ignore errors, ERR_LOG (1) = log continue, ERR_RAISE (2) = (re)raise (1)

  • parsebitfield (bool) – 1 = parse bitfields, 0 = leave as bytes (1)

  • labelmsm (Literal[0,1]) – RTCM3 MSM label type 1 = RINEX, 2 = BAND (1)

  • bufsize (int) – socket recv buffer size (4096)

  • parsing (bool) – True = parse data, False = don’t parse data (output raw only) (True)

  • errorhandler (FunctionType | NoneType) – error handling object or function (None)

Raises:

UBXStreamError (if mode is invalid)

read() tuple[source]

Read a single NMEA, UBX, SBF, QGC or RTCM3 message from the stream buffer and return both raw and parsed data.

‘protfilter’ determines which protocols are parsed. ‘quitonerror’ determines whether to raise, log or ignore parsing errors.

Returns:

tuple of (raw_data as bytes, parsed_data as NMEAMessage, UBXMessage, SBFMessage, QGCMessage or RTCMMessage)

Return type:

tuple

Raises:

Exception (if invalid or unrecognised protocol in data stream)

property datastream: object

Getter for stream.

Returns:

data stream

Return type:

object

pygnssutils.gnssserver module

gnssserver.py

This is a simple implementation of a TCP Socket Server or NTRIP Server which reads the binary data stream from a connected GNSS receiver and broadcasts the data to any TCP socket or NTRIP client running on a local or remote machine.

Created on 24 May 2022

author:

semuadmin (Steve Smith)

copyright:

semuadmin © 2022

license:

BSD 3-Clause

class pygnssutils.gnssserver.GNSSSocketServer(app=None, stream: object = None, ipprot: Literal['IPv4', 'IPv6'] = 'IPv4', hostip: str = '0.0.0.0', outport: int = 50010, tls: bool = False, maxclients: int = 5, ntripmode: Literal[0, 1] = 0, ntripversion: Literal['1.0', '2.0'] = '2.0', ntripuser: str = 'anon', ntrippassword: str = 'password', tlspempath: str = '/Users/steve/pygnssutils.pem', ntriprtcmstr: str = '1002(1),1006(5),1010(1),1077(1),1087(1),1097(1),1127(1),1230(1),4072_0(1),4072_1(1)', **kwargs)[source]

Bases: object

GNSS Socket Server Class.

__init__(app=None, stream: object = None, ipprot: Literal['IPv4', 'IPv6'] = 'IPv4', hostip: str = '0.0.0.0', outport: int = 50010, tls: bool = False, maxclients: int = 5, ntripmode: Literal[0, 1] = 0, ntripversion: Literal['1.0', '2.0'] = '2.0', ntripuser: str = 'anon', ntrippassword: str = 'password', tlspempath: str = '/Users/steve/pygnssutils.pem', ntriprtcmstr: str = '1002(1),1006(5),1010(1),1077(1),1087(1),1097(1),1127(1),1230(1),4072_0(1),4072_1(1)', **kwargs)[source]

Context manager constructor.

Example of usage:

gnssserver inport=COM3 hostip=192.168.0.20 outport=50010 ntripmode=0

Parameters:
  • app (object) – application from which this class is invoked (None)

  • stream (object) – input datastream

  • ipprot (Literal["IPv4", "IPv6"]) – IP protocol IPv4/IPv6 (“IPv4”)

  • hostip (int) – host ip address (0.0.0.0)

  • outport (int) – TCP port (50010)

  • tls (bool) – Enable TLS (HTTPS) (False)

  • maxclients (int) – maximum number of connected clients (5)

  • ntripmode (Literal[0,1]) – 0 = socket server, 1 - NTRIP server (0)

  • ntripversion (Literal["1.0","2.0"]) – NTRIP version “1.0”/”2.0” (“2.0”)

  • ntripuser (str) – NTRIP caster authentication user (“anon”)

  • ntrippassword (str) – NTRIP caster authentication password (“password”)

  • tlspempath (str) – Path to TLS PEM file (“pygnssutils.pem”)

  • ntriprtcmstr (str) – NTRIP caster RTCM types sourcetable entry e.g. ‘1006(5),1077(1),…’

  • kwargs (dict) – optional keyword arguments to pass to GNSSStreamer

run() int[source]

Run server.

Returns:

rc 0 = fail, 1 = ok

Return type:

int

stop()[source]

Shutdown server.

pygnssutils.gnssserver_cli module

gnssserver_cli.py

CLI wrapper for GNSSSocketServer class.

Created on 24 Jul 2024

author:

semuadmin (Steve Smith)

copyright:

semuadmin © 2022

license:

BSD 3-Clause

pygnssutils.gnssserver_cli.main()[source]

CLI Entry point.

pygnssutils.gnssstreamer module

pygnssutils - gnssstreamer.py

GNSS streaming application which supports bidirectional communication with a GNSS datastream (e.g. an NMEA or UBX GNSS receiver serial port) via designated input and output handlers.

Created on 27 Jul 2023

author:

semuadmin (Steve Smith)

copyright:

semuadmin © 2023

license:

BSD 3-Clause

class pygnssutils.gnssstreamer.GNSSStreamer(app: object, stream: object, validate: int = 1, msgmode: Literal[0, 1, 2] = 0, parsebitfield: bool = True, outformat: int = 1, quitonerror: Literal[0, 1, 2] = 2, protfilter: int = 31, msgfilter: str = '', limit: int = 0, outqueue: Queue | None = None, inqueue: Queue | None = None, outputhandler: LambdaType | None = None, inputhandler: LambdaType | None = None, stopevent: Event | None = None, verbosity: Literal[-1, 0, 1, 2, 3] = 1, logtofile: str = '', **kwargs)[source]

Bases: object

Skeleton GNSS application class which supports bidirectional communication with a GNSS datastream (e.g. an NMEA or UBX GNSS receiver serial port) via designated input and output handlers.

  • user-defined output and input handlers (callbacks).

  • flexible protocol and message filtering options.

  • flexible output formatting options e.g. parsed, binary, hex, JSON.

  • supports external inputs to datastream, e.g. from RTK data source (NTRIP or SPARTN) or a configuration file.

  • implements a context manager e.g. with GNSSStreamer as gns:

The class implements public methods which can be used by other pygnssutils classes:

  • get_coordinates(), returns current GNSS status.

  • status property, returns current GNSS status.

To utilise logging, invoke and configure logging.getLogger(“pygnssutils”) in the calling hierarchy.

__init__(app: object, stream: object, validate: int = 1, msgmode: Literal[0, 1, 2] = 0, parsebitfield: bool = True, outformat: int = 1, quitonerror: Literal[0, 1, 2] = 2, protfilter: int = 31, msgfilter: str = '', limit: int = 0, outqueue: Queue | None = None, inqueue: Queue | None = None, outputhandler: LambdaType | None = None, inputhandler: LambdaType | None = None, stopevent: Event | None = None, verbosity: Literal[-1, 0, 1, 2, 3] = 1, logtofile: str = '', **kwargs)[source]

Constructor.

Parameters:
  • app (object) – name of any calling application

  • stream (object) – GNSS datastream (e.g. Serial, File or Socket)

  • validate (bool) – 1 = validate checksum, 0 = do not validate (1)

  • msgmode (Literal[0,1,2]) – 0 = GET, 1 = SET, 2 = POLL (0)

  • parsebitfield (bool) – 1 = parse UBX ‘X’ attributes as bitfields, 0 = leave as bytes (1)

  • outformat (int) – output format 1 = parsed, 2 = raw, 4 = hex, 8 = tabulated hex, 16 = parsed as string, 32 = JSON (can be OR’d) (1)

  • quitonerror (Literal[0,1,2]) – 0 = ignore errors, 1 = log errors and continue, 2 = (re)raise errors (1)

  • protfilter (int) – 1 = NMEA, 2 = UBX, 4 = RTCM3, 8 = SBF, 16 = QGC (can be OR’d) (31)

  • msgfilter (str) – comma-separated string of message identities to include in output e.g. ‘NAV-PVT,GNGSA’. A periodicity clause can be added e.g. NAV-SAT(10), signifying the minimum period in seconds between successive messages of this type (“”)

  • limit (int) – maximum number of messages to read (0 = unlimited)

  • outqueue (Queue | NoneType) – queue for data from datastream (None)

  • inqueue (Queue | NoneType) – queue for data to datastream (None)

  • outputhandler (FunctionType | NoneType) – output callback function (do_output())

  • inputhandler (FunctionType | NoneType) – input callback function (do_input())

  • stopevent (Event | NoneType) – stopevent to terminate run() (internal Event())

  • verbosity (Literal[-1,0,1,2,3]) – log message verbosity -1 = critical, 0 = error, 1 = warning, 2 = info, 3 = debug (1)

  • logtofile (str) – fully qualified path to logfile (”” = no logfile)

  • kwargs (dict) – user-defined keyword arguments to pass to custom input/output handlers

Raises:

ValueError – If invalid arguments

run()[source]

Run GNSS reader/writer.

stop()[source]

Stop GNSS reader/writer.

get_coordinates() dict[source]

DEPRECATED - use status property instead. Return current GNSS status. (method used by certain pygnssutils classes)

Returns:

dict of GNSS status attributes

Return type:

dict

property status: dict

Return current GNSS status.

Returns:

dict of GNSS status attributes

Return type:

dict

property stream: object

Return GNSS datastream.

Returns:

GNSS datastream

Return type:

object

static do_output(raw_data: bytes, formatted_data: list, outqueue: Queue, **kwargs)[source]
Default output handler callback.
  • logs output data type

  • sends output to out queue (if defined)

Parameters:
  • raw_data (bytes) – raw data

  • formatted_data (list) – list formatted data e.g. [NMEAMessage]

  • outqueue (Queue) – queue containing output from GNSS datastream

static do_input(datastream: object, inqueue: Queue, **kwargs)[source]

Default input handler callback.

  • receives data from in queue (if defined)

  • if bytes data (e.g. RTK), send to datastream

  • logs received data type

Queued data may be a tuple or a single object. If tuple, content may be:

  • (raw: bytes, parsed: object) e.g. RTK data

  • (sourcetable: list, nearest mountpoint, distance: tuple) e.g. NTRIP Sourcetable data

In the default input handler, only bytes data is written to datastream, but this may be overriden by user to handle other data types.

Parameters:
  • datastream (object) – bidirectional GNSS datastream

  • inqueue (Queue) – queue containing data to be sent to GNSS datastream

Raises:

ParameterError

pygnssutils.gnssstreamer_cli module

gnssstreamer_cli.py

CLI wrapper for GNSSStreamer class.

Supported GNSS datastreams:
  • serial

  • socket

  • file

  • generic stream

Supported output channels:
  • terminal (stdout)

  • socket

  • binary file

  • text file

  • lambda expression

Supported input channels:
  • NTRIP RTCM client

  • NTRIP SPARTN client

  • MQTT SPARTN client

  • serial stream

  • file stream

Created on 24 Jul 2024

author:

semuadmin (Steve Smith)

copyright:

semuadmin © 2022

license:

BSD 3-Clause

pygnssutils.gnssstreamer_cli.main()[source]

CLI Entry point.

pygnssutils.helpers module

Collection of GNSS related helper methods.

Created on 26 May 2022

author:

semuadmin (Steve Smith)

copyright:

semuadmin © 2020

license:

BSD 3-Clause

pygnssutils.helpers.cliclock(stopevent: Event, msg: str, delay: float = 0.25)[source]

THREADED Display clock timer on console.

Parameters:
  • stopevent (Event) – stop event

  • msg (str) – msg prompt

  • delay (float) – delay in seconds

pygnssutils.helpers.prog_callback(progress: int, inc: int = 50)[source]

Callback function to display progress bar at console.

Parameters:
  • progress (int) – % complete

  • inc (int) – number of increments (50)

pygnssutils.helpers.progbar(i: int, lim: int, inc: int = 50)[source]

Display progress bar on console.

pygnssutils.helpers.parse_config(configfile: str) dict[source]

Parse config file.

Parameters:

configfile (str) – fully qualified path to config file

Returns:

config as kwargs, or None if file not found

Return type:

dict

Raises:

FileNotFoundError

Raises:

ValueError

pygnssutils.helpers.set_common_args(name: str, ap: ArgumentParser, logname: str = 'pygnssutils', logdefault: int = 1) dict[source]

Set common argument parser and logging args.

Parameters:
  • name (str) – name of CLI utility e.g. “gnssstreamer”

  • ArgumentParserap – argument parser instance

  • logname (str) – logger name

  • logdefault (int) – default logger verbosity level

Returns:

parsed arguments as kwargs

Return type:

dict

pygnssutils.helpers.set_logging(logger: Logger, verbosity: int = 1, logtofile: str = '', logform: str = '{asctime}.{msecs:.0f} - {levelname} - {name} - {message}', limit: int = 10485760)[source]

Set logging format and level.

Parameters:
  • logger (logging.Logger) – module log handler

  • verbosity (int) – verbosity level -1,0,1,2,3 (2 - MEDIUM)

  • logtofile (str) – fully qualified log file name (“”)

  • logform (str) – logging format (datetime - level - name)

  • limit (int) – maximum logfile size in bytes (10MB)

pygnssutils.helpers.get_mp_distance(lat: float, lon: float, mp: list) float[source]

Get distance to mountpoint from current location (if known).

Predicated on the sourcetable being formatted as a list of sourcetable entries, where for each entry: entry[0] = mountpoint name entry[8] = mountpoint latitude entry[9] = mountpoint longitude

Parameters:
  • lat (float) – current latitude

  • lon (float) – current longitude

  • mp (list) – sourcetable mountpoint entry

Returns:

distance to mountpoint in km, or None if n/a

Return type:

float or None

pygnssutils.helpers.find_mp_distance(lat: float, lon: float, sourcetable: list, name: str = '') tuple[source]

Find distance to named mountpoint. If mountpoint name is not provided, find closest mountpoint in sourcetable.

Predicated on the sourcetable being formatted as a list of sourcetable entries, where for each entry: entry[0] = mountpoint name entry[8] = mountpoint latitude entry[9] = mountpoint longitude

Parameters:
  • lat (float) – reference latitude

  • lon (float) – reference longitude

  • sourcetable (list) – sourcetable as list

  • name (str) – (optional) mountpoint name (None)

Returns:

tuple of (name of closest mountpoint, distance in km)

Return type:

tuple

pygnssutils.helpers.cel2cart(elevation: float, azimuth: float) tuple[source]

Convert celestial coordinates (degrees) to Cartesian coordinates.

Parameters:
  • elevation (float) – elevation

  • azimuth (float) – azimuth

Returns:

cartesian x,y coordinates

Return type:

tuple

pygnssutils.helpers.format_dates() tuple[source]

Format response header dates.

Returns:

tuple of (http_date, server_date)

Return type:

tuple

pygnssutils.helpers.format_json(message: object) str[source]

Format object as JSON document.

Returns:

JSON document as string

Return type:

str

pygnssutils.helpers.format_conn(family: int, server: str, port: int, flowinfo: int = 0, scopeid: int = 0) tuple[source]

Return formatted socket connection string.

Parameters:
  • family (int) – IP family (AF_INET, AF_INET6)

  • server (str) – server

  • port (int) – port

  • flowinfo (int) – flow info (0)

  • scopeid (int) – scope ID (0)

Returns:

connection tuple

Return type:

tuple

pygnssutils.helpers.ipprot2int(family: str) int[source]

Convert IP family string to integer.

Parameters:

family (str) – family string (“IPv4”, “IPv6”)

Returns:

value as int AF_INET, AF_INET6

Return type:

int

pygnssutils.helpers.ipprot2str(family: int) str[source]

Convert IP family integer to string.

Parameters:

family (str) – family int (AF_INET, AF_INET6)

Returns:

value as str (“IPv4”, “IPv6”)

Return type:

int

pygnssutils.helpers.gtype(data: object) str[source]

Get type of GNSS data as user-friendly string.

Parameters:

data (object) – data

Returns:

type e.g. “UBX”

Return type:

str

pygnssutils.helpers.parse_url(url: str) tuple[source]

Parse URL. If protocol, port or path not specified, they default to ‘http’, 80 and ‘/’.

Parameters:

url (str) – full URL e.g. ‘https://example.com:443/path

Returns:

tuple of (protocol, hostname, port, path)

Return type:

tuple

pygnssutils.helpers.check_pemfile() tuple[source]

Check TLS PEM (Certificate/Key) file for HTTPS server connection.

Returns:

tuple of (path to PEM file, exists flag)

Return type:

tuple

pygnssutils.helpers.check_crtfile() tuple[source]

Check TLS CRT (Certificate) file for HTTPS client connection.

If CRT path does not exist, try PEM path.

Returns:

tuple of (path to CRT file, exists flag)

Return type:

tuple

pygnssutils.mqttmessage module

mqttmessage.py

MQTTMessage container class for MQTT topics with json payloads.

Created on 1 Sep 2023

author:

semuadmin (Steve Smith)

copyright:

semuadmin © 2020

license:

BSD 3-Clause

class pygnssutils.mqttmessage.MQTTMessage(topic: str, payload: bytes)[source]

Bases: object

Container class for MQTT topics with json payloads.

__init__(topic: str, payload: bytes)[source]

Constructor

Parameters:
  • topic (str) – MQTT topic e.g. “\pp\frequencies\Lb”

  • payload (bytes) – MQTT topic json payload as bytes

Raises:

ValueError if payload is invalid json

pygnssutils.rawnav module

rawnav.py

Raw navigation data container and reader classes.

The RawNavReader class implements methods to facilitate acquisition of NAV subframe data from UBX RXM-SFRBX messages.

The RawNav class parses and stores the individual attributes (ephemerides, ionospheric & clock corrections, etc.) of one or more raw GNSS NAV subframes, e.g. as a precursor to RINEX conversion.

Once a RawNav object is instantiated, the parse function can be invoked repeatedly to collate data from separate sequential subframes e.g. for GPS LNAV, subframe 1 contains clock corrections, subframes 2 & 3 contain ephemerides and subframe 4 page 18 contains ionospheric corrections.

An subframeacq bitfield signifies which subframe/page IDs have been acquired, and hence whether or not the RawNav frame contains sufficient information to be converted to a NAV record.

A boolean sequence argument determines whether subframes are processed as a contiguous sequence e.g. for GNSS where MSB and LSB attributes are held in separate, sequential subframes.

The objective is to handle any GNSS subframe format for which:

  • data is available as a raw, unpadded little-endian integer.

  • data definition dictionary has been transcribed from the relevant GNSS ICD (Interface Control Document) with standardized ascii field names e.g. omegadot, sqrta, cus, etc.

Format of data definition dictionary:

dict[field_name, tuple[offset, length, encoding, scaling]

where offset and length are in bits (see, for example, rinex_subframes_gps.py).

MSB and LSB field names MUST be suffixed “_msb” and “_lsb” respectively - the parse function will automatically combine them.

RXM-SFRBX structures for each GNSS are documented in section 3.15.1 Broadcast navigation data:

https://www.u-blox.com/sites/default/files/ZED-F9P_IntegrationManual_UBX-18010802.pdf

NB: Alpha Support currently limited to:
  • GPS LNAV, CNAV

  • GAL FNAV, INAV

  • BDS D1

… pending transcription of other GNSS ICDs.

Created on 20 Apr 2026

author:

semuadmin (Steve Smith)

copyright:

semuadmin © 2026

license:

BSD 3-Clause

pygnssutils.rawnav.IS1 = '_is1'

IS1 field name suffix (between MSB and ISB)

pygnssutils.rawnav.ISB = '_isb'

ISB field name suffix (between MSB/IS1 and LSB)

pygnssutils.rawnav.LSB = '_lsb'

LSB field name suffix

pygnssutils.rawnav.MSB = '_msb'

MSB field name suffix

pygnssutils.rawnav.TOC = 'toc'

TOC (time of clock) field name - used to establish epoch

pygnssutils.rawnav.TOW = 'tow'

HOW TOW field name

pygnssutils.rawnav.SID = 'sid'

subframe id field name

pygnssutils.rawnav.SPID = 'spid'

subframe page id field name

pygnssutils.rawnav.WN = 'wn'

WN (week number) field name - used to establish epoch

class pygnssutils.rawnav.RawNav(gnss: Literal['G', 'R', 'E', 'C', 'J', 'S', 'I'], svid: int, sigid: str, **kwargs)[source]

Bases: object

Raw Navigation Class.

__init__(gnss: Literal['G', 'R', 'E', 'C', 'J', 'S', 'I'], svid: int, sigid: str, **kwargs)[source]

Constructor.

Parameters:
  • gnss (Literal["G","R","E","C","J","S","I"]) – GNSS code

  • svid (int) – RINEX SV id e.g. 14

  • sigid (str) – RINEX signal id e.g. “1C”

  • kwargs (dict) – optional keyword arguments

parse(data: int, subframedef: dict[str, tuple[int, int, str, int]], subframeacq: int, sequence: bool = True)[source]

Parse raw subframe data into its constituent attributes.

Parameters:
  • data (int) – raw, unpadded input data

  • subframedef (dict[str, tuple[int, int, str, int]]) – subframe definition dictionary (from GNSS ICD)

  • subframeacq (int) – subframe acquisition bitmask

  • sequence (bool) – process subframe as part of a contiguous sequence (False)

Raises:

RINEXProcessingError

property identity: str

Getter for identity.

Returns:

identity

Return type:

str

property gnss: str

Getter for GNSS code.

Returns:

gnss

Return type:

str

property svid: int

Getter for SV id.

Returns:

svid

Return type:

int

property svcode: str

Getter for SV code (gnss & prn).

Returns:

svcode e.g. “G14”

Return type:

str

property sigid: str

Getter for signal id in RINEX format.

Returns:

signal id

Return type:

str

property subframeacq: int

Getter for subframe acquisition status.

Bitfield signifying which subframe IDs have been acquired:

  • subframeacq & 0b001 => page 1

  • subframeacq & 0b010 => page 2,

  • subframeacq & 0b100 => page 3, etc.

Returns:

subframe acquisition status.

Return type:

int

class pygnssutils.rawnav.RawNavReader(**kwargs)[source]

Bases: object

Raw Navigation Reader Class.

__init__(**kwargs)[source]

Constructor.

Parameters:

kwargs (dict) – optional keyword arguments

process_rxm_sfrbx(data: UBXMessage) dict[str, str | int | float | None][source]

Reassemble subframe from individual UBX RXM-SFRBX dwrds.

Parameters:

data (UBXMessage) – parsed UBX RXM-SFRBX message

Returns:

dict of subframe attributes

Return type:

dict[str, str | int | float | NoneType]

Raises:

RINEXProcessingError

pygnssutils.rinex_conv module

rinex_conv.py

RINEX Conversion Common class.

A preliminary implementation of a RINEX conversion utility for observation, navigation and meteorological data.

Functionality will be extended in future versions - contributions welcome.

Created on 6 Oct 2025

author:

semuadmin (Steve Smith)

copyright:

semuadmin © 2025

license:

BSD 3-Clause

class pygnssutils.rinex_conv.RinexConverter(app, rinex_version: str, rinex_types: list[str], gnssfilter: list[str], obsfilter: list[str], datasource: list[str], starttime: datetime | str, minobs: int, marker: list[str], antenna: list[str], receiver: list[str], observer: str, comments: list[str], protfilter: int = 7, verbosity: Literal[-1, 0, 1, 2, 3] = 1, logtofile: str = '', **kwargs)[source]

Bases: object

Rinex Common Convertor Class.

__init__(app, rinex_version: str, rinex_types: list[str], gnssfilter: list[str], obsfilter: list[str], datasource: list[str], starttime: datetime | str, minobs: int, marker: list[str], antenna: list[str], receiver: list[str], observer: str, comments: list[str], protfilter: int = 7, verbosity: Literal[-1, 0, 1, 2, 3] = 1, logtofile: str = '', **kwargs)[source]

Constructor.

Parameters:
  • app (object) – application from which this class is invoked (None)

  • rinex_version (str) – RINEX protocol version (3.05)

  • rinex_type (list[str]) – RINEX output type(s) e.g. [“O”,”N”]

  • gnssfilter (list[str]) – List of GNSS codes to process (or None for all) e.g. [GPS,GAL]

  • obsfilter (list[str]) – List of observation codes to process (or None for all) e.g. [“1C”,”2B”]

  • datasource (list[str]) – List of datasources for each rinex type e.g. [“R”,”R”,”R”]

  • starttime (datetime | str) – Approximate start time of RTCM3 (e.g. NTRIP) data in format “YYYYMMDDHHMMSS±ZZZZ e.g. “20260508090123+0100” (defaults to current UTC datetime)

  • minobs (int) – Minimum observations per observation type (0)

  • marker (list[str]) – marker details (name, number, type)

  • antenna (list[str]) – antenna details (number, type)

  • receiver (list[str] |) – receiver details (number, type, version)

  • observer (str) – observer details

  • comments (list[str]) – user comments

  • protfilter (int) – input message protocol mask NMEA=1, UBX=2, RTCM3=4. Can be OR’d (7)

  • verbosity (Literal[-1,0,1,2,3]) – log message verbosity -1 = critical, 0 = error, 1 = warning, 2 = info, 3 = debug (1)

  • logtofile (str) – fully qualified path to logfile (”” = no logfile)

  • kwargs (dict) – user-defined keyword arguments to pass to conversion functions

process_input(infile: Path | str, stopevent: Event | None = None, progcallback: LambdaType | MethodType | None = None, **kwargs) int[source]

Process binary input file containing UBX, RTCM or NMEA GNSS messages.

Parameters:
  • infile (Path | str) – input binary file path

  • stopevent (Event | NoneType) – stop event for remote cancellation (None)

  • progcallback (FunctionType | MethodType | NoneType) – progress update callback function or method (None)

Returns:

return code (0 = success, >0 = error)

Return type:

int

process_input_data(rinextypes: list[str], instream: BufferedReader, outputpath: Path, stopevent: Event | None = None, progcallback: LambdaType | MethodType | None = None, **kwargs) int[source]

Process binary data stream.

Parameters:
  • rinextypes (list[str]) – RINEX conversion type (O, N, M)

  • instream (BufferedReader) – input binary file stream

  • outstream (TextIOWrapper) – output text file stream

  • stopevent (Event | NoneType) – stop event for remote cancellation (None)

  • progcallback (FunctionType | MethodType | NoneType) – progress update callback function or method (None)

Returns:

return code (0 = success, >0 = error)

Return type:

int

process_output_data(rinextypes: list[str])[source]

Process any accumulated data for each RINEX category (OBS, NAV, MET).

Parameters:

rinextypes (list[str]) – rinex type(s)

format_header_common(rinextype: Literal['O', 'N', 'M']) str[source]

Format common header lines.

Parameters:

rinextype (Literal["O","N","M"]) – rinextype

Returns:

formatted string

Return type:

str

output(data: str | Any | None, rinextype: Literal['O', 'N', 'M'])[source]

Write formatted data to designated RINEX output file.

Parameters:
  • data (str | Any | NoneType) – RINEX formatted string

  • rinextype (Literal["O", "N", "M"]) – rinex output type

get_current_epoch(rt: str) datetime[source]

Getter for current epoch.

Returns:

current epoch

Return type:

datetime

set_current_epoch(epoch: datetime, rt: str)[source]

Setter for current epoch.

Sets observation frequency (interval) and first and last observation epochs.

Parameters:

epoch (datetime) – current epoch

get_start_epoch(rt: str) datetime[source]

Getter for start epoch (first observation).

Returns:

start epoch

Return type:

datetime

get_end_epoch(rt: str) datetime[source]

Getter for end epoch (last observation).

Returns:

end epoch

Return type:

datetime

get_interval(rt: str) int | float[source]

Getter for observation interval (frequency).

Returns:

observation interval in seconds

Return type:

int | float

property outputs: dict[str, tuple[Path, int]]

Getter for conversion outputs. For each rinex type: (filename, number of records processed)

Returns:

outputs

Return type:

dict[str, tuple[Path, int]]

pygnssutils.rinex_conv_cli module

rinex_conv_cli.py

CLI wrapper for RinexConvertor class.

Created on 6 Oct 2025

author:

semuadmin (Steve Smith)

copyright:

semuadmin © 2025

license:

BSD 3-Clause

pygnssutils.rinex_conv_cli.main()[source]

CLI Entry point.

pygnssutils.rinex_conv_met module

rinex_conv_met.py

RINEX Conversion Meterology class.

A preliminary implementation of a RINEX meteorology conversion utility.

Converts NMEA MWD and XDR messages to RINEX Meteorology text format.

Meteorology data comprises meteorological sensor readings such as temperature, pressure, wind speed and direction, rain levels, etc.

Functionality may be extended in future versions - contributions welcome.

Created on 6 Oct 2025

author:

semuadmin (Steve Smith)

copyright:

semuadmin © 2025

license:

BSD 3-Clause

class pygnssutils.rinex_conv_met.RinexConverterMeteorology(app: Any, rinex_version: str, gnssfilter: list[str], obsfilter: list[str], datasource: Literal['R', 'S', 'N', 'U'], minobs: int, marker: list[str], verbosity: Literal[-1, 0, 1, 2, 3] = 1, logtofile: str = '', **kwargs)[source]

Bases: object

Rinex Meteorology Converter Class.

__init__(app: Any, rinex_version: str, gnssfilter: list[str], obsfilter: list[str], datasource: Literal['R', 'S', 'N', 'U'], minobs: int, marker: list[str], verbosity: Literal[-1, 0, 1, 2, 3] = 1, logtofile: str = '', **kwargs)[source]

Constructor.

Parameters:
  • app (Any) – application from which this class is invoked

  • rinex_version (str) – RINEX protocol version (3.05)

  • gnssfilter (list[str]) – List of GNSS codes to process (or blank for ALL) e.g. [GPS,GAL]

  • obsfilter (list[str]) – List of observation codes to process (or blank for ALL) e.g. [“1C”,”2B”]

  • source (Literal["R","S","N","U"]) – data source (R)

  • minobs (int) – Minimum observations per observation type (10)

  • marker (list[str] | str) – marker details (name, number, type)

  • verbosity (Literal[-1,0,1,2,3]) – log message verbosity -1 = critical, 0 = error, 1 = warning, 2 = info, 3 = debug (1)

  • logtofile (str) – fully qualified path to logfile (”” = no logfile)

  • kwargs (dict) – user-defined keyword arguments to pass to conversion functions

process_input_data(parsed: UBXMessage | RTCMMessage | NMEAMessage) int[source]

Process parsed GNSS message(s) containing relevant meteorology data.

Parameters:

parsed (UBXMessage | RTCMMessage | NMEAMessage) – parsed message

Returns:

number of messages processed

Return type:

int

process_output_file()[source]

Process RINEX meteorology file.

convert_nmea_mwd(data: NMEAMessage)[source]

Extract relevant information from NMEA MDW GNSS message.

See RINEX_METOBS for list of supported met observation types.

Parameters:

data (NMEAMessage) – parsed NMEA MWD message

convert_nmea_xdr(data: NMEAMessage)[source]

Extract relevant information from NMEA XDR GNSS message.

See RINEX_METOBS for list of supported met observation types.

(NB: some proprietary XDR implementations use non-standard unit of measurement codes which are not in the public domain)

Parameters:

data (NMEAMessage) – parsed NMEA XDR message

get_nmea_epoch(data: NMEAMessage)[source]

Get current epoch from NMEA RMC navigation message.

Parameters:

data (NMEAMessage) – parsed NMEA message

pygnssutils.rinex_conv_nav module

rinex_conv_nav.py

RINEX Conversion Navigation class.

Converts NAV message data to RINEX Navigation text format.

NB: Alpha release currently limited to following data sources:

  • RawNav objects containing data collated from UBX RXM-SFRBX messages (GPS LNAV/CNAV, GAL FNAV,INAV, BDS D1 only)

  • RTCM3 ephemerides messages 1019, 1020, 1041-1046 e.g. from RTK receiver or NTRIP data stream

Created on 6 Oct 2025

author:

semuadmin (Steve Smith)

copyright:

semuadmin © 2025

license:

BSD 3-Clause

class pygnssutils.rinex_conv_nav.RinexConverterNavigation(app: Any, rinex_version: str, gnssfilter: list[str], obsfilter: list[str], datasource: Literal['R', 'S', 'N', 'U'], minobs: int, verbosity: Literal[-1, 0, 1, 2, 3] = 1, logtofile: str = '', **kwargs)[source]

Bases: object

Rinex Navigation Converter Class.

__init__(app: Any, rinex_version: str, gnssfilter: list[str], obsfilter: list[str], datasource: Literal['R', 'S', 'N', 'U'], minobs: int, verbosity: Literal[-1, 0, 1, 2, 3] = 1, logtofile: str = '', **kwargs)[source]

Constructor.

Parameters:
  • app (Any) – application from which this class is invoked

  • rinex_version (str) – RINEX protocol version (3.05)

  • gnssfilter (list[str]) – List of GNSS codes to process (or blank for ALL) e.g. [GPS,GAL]

  • obsfilter (list[str]) – List of observation codes to process (or blank for ALL) e.g. [“1C”,”2B”]

  • datasource (Literal["R","S","N","U"]) – data source (R)

  • minobs (int) – Minimum observations per observation type (10)

  • verbosity (Literal[-1,0,1,2,3]) – log message verbosity -1 = critical, 0 = error, 1 = warning, 2 = info, 3 = debug (1)

  • logtofile (str) – fully qualified path to logfile (”” = no logfile)

  • kwargs (dict) – user-defined keyword arguments to pass to conversion functions

process_input_data(parsed: UBXMessage | RTCMMessage | NMEAMessage | RawNav) int[source]

Process parsed GNSS message(s) containing relevant navigation data.

Parameters:

parsed (UBXMessage | RTCMMessage | NMEAMessage | RawNav) – parsed message

Returns:

number of messages processed

Return type:

int

process_output_file()[source]

Process RINEX navigation file.

pygnssutils.rinex_conv_obs module

rinex_conv_obs.py

RINEX Conversion Observation class.

A preliminary implementation of a RINEX observation conversion utility.

Converts UBX RXM-RAWX messages to RINEX Observation text format.

Observation data comprises pseudorange, (carrier) phaserange, Doppler shift and signal strength.

Functionality will be extended in future versions - contributions welcome.

Created on 6 Oct 2025

author:

semuadmin (Steve Smith)

copyright:

semuadmin © 2025

license:

BSD 3-Clause

class pygnssutils.rinex_conv_obs.RinexConverterObservation(app: Any, rinex_version: str, gnssfilter: list[str], obsfilter: list[str], minobs: int, datasource: Literal['R', 'S', 'N', 'U'], marker: list[str], antenna: list[str], receiver: list[str], observer: str = '', verbosity: Literal[-1, 0, 1, 2, 3] = 1, logtofile: str = '', **kwargs)[source]

Bases: object

Rinex Observation Converter Class.

__init__(app: Any, rinex_version: str, gnssfilter: list[str], obsfilter: list[str], minobs: int, datasource: Literal['R', 'S', 'N', 'U'], marker: list[str], antenna: list[str], receiver: list[str], observer: str = '', verbosity: Literal[-1, 0, 1, 2, 3] = 1, logtofile: str = '', **kwargs)[source]

Constructor.

Parameters:
  • app (Any) – application from which this class is invoked

  • rinex_version (str) – RINEX protocol version (3.05)

  • gnssfilter (list[str]) – List of GNSS codes to process (or blank for ALL) e.g. [GPS,GAL]

  • obsfilter (list[str]) – List of observation codes to process (or blank for ALL) e.g. [“1C”,”2B”]

  • datasource (Literal["R","S","N","U"]) – data source (R)

  • minobs (int) – Minimum observations per observation type (10)

  • marker (list[str]) – marker details (name, number, type)

  • antenna (list[str]) – antenna details (number, type)

  • receiver (list[str]) – receiver details (number, type, version)

  • observer (str) – observer details

  • verbosity (Literal[-1,0,1,2,3]) – log message verbosity -1 = critical, 0 = error, 1 = warning, 2 = info, 3 = debug (1)

  • logtofile (str) – fully qualified path to logfile (”” = no logfile)

  • kwargs (dict) – user-defined keyword arguments to pass to conversion functions

process_input_data(parsed: UBXMessage | RTCMMessage | NMEAMessage) int[source]

Process parsed GNSS message(s) containing relevant observation data.

Parameters:

parsed (UBXMessage | RTCMMessage | NMEAMessage) – parsed message

Returns:

number of messages processed

Return type:

int

process_output_file()[source]

Process RINEX observation file.

get_ubx_pos(data: UBXMessage)[source]

Get approx marker position from UBX navigation message.

Parameters:

data (UBXMessage) – parsed UBX message

get_nmea_pos(data: NMEAMessage)[source]

Get approx marker position from NMEA navigation message.

Parameters:

data (NMEAMessage) – parsed NMEA message

convert_ubx_rxmrawx(data: UBXMessage)[source]

Extract relevant information from individual GNSS message and add to obsdata dictionary, which will be used in the _format_observations function to populate the RINEX observation output file.

Parameters:

data (UBXMessage) – parsed UBX RXM-RAWX message

pygnssutils.rinex_globals module

rinex_globals.py

RINEX global constants, decodes and enumerations.

https://files.igs.org/pub/data/format/rinex305.pdf

Created on 6 Oct 2025

author:

semuadmin (Steve Smith)

copyright:

semuadmin © 2025

license:

BSD 3-Clause

pygnssutils.rinex_globals.ALLGNSS = ['G', 'R', 'E', 'C', 'S', 'J', 'I']

All Available GNSS Codes.

pygnssutils.rinex_globals.ALLOBS = ['O', 'N', 'M']

All Available Observation Codes.

pygnssutils.rinex_globals.RINEXTYPE = {'M': 'meteorology', 'N': 'navigation', 'O': 'observation'}

RINEX File Types.

pygnssutils.rinex_globals.RINEXGNSSR = {'C': 'BDS', 'E': 'GAL', 'G': 'GPS', 'I': 'IRN', 'J': 'QZS', 'M': 'MIXED', 'N': 'N/A', 'R': 'GLO', 'S': 'SBS'}

RINEX GNSS Codes.

pygnssutils.rinex_globals.RINEXOBSPREFIX = {'C': 'Pseudo Range', 'D': 'Doppler Shift', 'L': 'Carrier Phase', 'S': 'Signal Strength'}

RINEX Observation Code Prefixes.

pygnssutils.rinex_globals.GNSS_FREQUENCIES = {'B1': 1561.098, 'B1A': 1575.42, 'B1C': 1575.42, 'B2 (BDS-2)': 1207.14, 'B2a': 1176.45, 'B2a+B2b (BDS-3)': 1191.795, 'B2b (BDS-3)': 1207.14, 'B3': 1268.52, 'B3A (BDS-3)': 1268.52, 'E1': 1575.42, 'E5(A+B)': 1191.795, 'E5A': 1176.45, 'E5B': 1207.14, 'E6': 1278.75, 'G1': 1602, 'G1a': 1600.995, 'G2': 1246, 'G2a': 1248.06, 'G3': 1202.025, 'L1': 1575.42, 'L2': 1227.6, 'L5': 1176.45, 'L5S': 1176.45, 'L6': 1278.75, 'S': 2492.028}

GNSS Signal Code -> Frequency Lookup.

  • key is RINEX Frequency Band

  • value is Frequency in kHz

pygnssutils.rinex_globals.EVENT_TYPE = {0: 'observation', 1: 'observation', 2: 'new site occupation', 3: 'site move event', 4: 'header records follow', 5: 'external event', 6: 'cycle slip'}

RINEX Epoch Flag Description Lookup

pygnssutils.rinex_globals.UBXRINEXGNSS = {0: 'G', 1: 'S', 2: 'E', 3: 'C', 5: 'J', 6: 'R', 7: 'I'}

UBX GNSS code -> RINEX GNSS Code Lookup.

  • key is msg.gnss where msg = UBX-RXM-RAWX

  • value is RINEX GNSS code (‘GNSSR’)

pygnssutils.rinex_globals.UBXRINEXOBSCODE = {(0, 0): '1C', (0, 1): '1S', (0, 2): '1L', (0, 3): '2L', (0, 4): '2S', (0, 6): '5I', (0, 7): '5Q', (1, 0): '1C', (2, 0): '1C', (2, 1): '1B', (2, 3): '5I', (2, 4): '5Q', (2, 5): '7I', (2, 6): '7Q', (2, 8): '6B', (2, 9): '6C', (3, 0): '2I', (3, 1): '2I', (3, 2): '7I', (3, 3): '7I', (3, 4): '6I', (3, 5): '1P', (3, 6): '1D', (3, 7): '5P', (3, 8): '5D', (3, 10): '6I', (5, 0): '1C', (5, 1): '1Z', (5, 4): '2S', (5, 5): '2L', (5, 8): '5I', (5, 9): '5Q', (5, 12): '1B', (6, 0): '1C', (6, 2): '2C', (7, 0): '5A'}

UBX Signal ID -> RINEX Observation Code Lookup. TODO CHECK THIS MAPPING!!!

  • key is (msg.gnss, msg.sigId) where msg = UBX-RXM-RAWX

  • value is RINEX Observation Code, minus prefix (see RINEXOBSPREFIX for appropriate prefix)

pygnssutils.rinex_globals.RINEX_PHASE_ALIGNMENT = {('C', 'L1D'): ('B1C', 'Data (D)', 'REF'), ('C', 'L1L'): ('B1A', 'Pilot(P)', ' L1S'), ('C', 'L1P'): ('B1C', 'Pilot(P)', ' L1D'), ('C', 'L1S'): ('B1A', 'Data (D)', 'REF'), ('C', 'L1X'): ('B1C', 'D+P', ' L1D'), ('C', 'L1Z'): ('B1A', 'D+P', ' L1S'), ('C', 'L2I'): ('B1', 'I', 'REF'), ('C', 'L2Q'): ('B1', 'Q', ' L2I'), ('C', 'L2X'): ('B1', 'I+Q', ' L2I'), ('C', 'L5D'): ('B2a', 'Data (D)', 'REF'), ('C', 'L5P'): ('B2a', 'Pilot(P)', ' L5D'), ('C', 'L5X'): ('B2a', 'D+P', ' L5D'), ('C', 'L6D'): ('B3A (BDS-3)', 'Data (D)', 'REF'), ('C', 'L6I'): ('B3', 'I', 'REF'), ('C', 'L6P'): ('B3A (BDS-3)', 'Pilot (P)', ' L6D'), ('C', 'L6Q'): ('B3', 'Q', ' L6I'), ('C', 'L6X'): ('B3', 'I+Q', ' L6I'), ('C', 'L6Z'): ('B3A (BDS-3)', 'D+P', ' L6D'), ('C', 'L7D'): ('B2b (BDS-3)', 'Data (D)', 'REF'), ('C', 'L7I'): ('B2 (BDS-2)', 'I', 'REF'), ('C', 'L7P'): ('B2b (BDS-3)', 'Pilot(P)', ' L7D'), ('C', 'L7Q'): ('B2 (BDS-2)', 'Q', ' L7I'), ('C', 'L7X'): ('B2 (BDS-2)', 'I+Q', ' L7I'), ('C', 'L7Z'): ('B2b (BDS-3)', 'D+P', ' L7D'), ('C', 'L8D'): ('B2a+B2b (BDS-3)', 'Data (D)', 'REF'), ('C', 'L8P'): ('B2a+B2b (BDS-3)', 'Pilot(P)', ' L8D'), ('C', 'L8X'): ('B2a+B2b (BDS-3)', 'D+P', ' L8D'), ('E', 'L1B'): ('E1', 'B I/NAV OS/CS/SoL', 'REF'), ('E', 'L1C'): ('E1', 'C no data', ' L1B'), ('E', 'L1X'): ('E1', 'B+C', ' L1B'), ('E', 'L5I'): ('E5A', 'I', 'REF'), ('E', 'L5Q'): ('E5A', 'Q', ' L5I'), ('E', 'L5X'): ('E5A', 'I+Q', ' L5I'), ('E', 'L6B'): ('E6', 'B', 'REF'), ('E', 'L6C'): ('E6', 'C', ' L6B'), ('E', 'L6X'): ('E6', 'B+C', ' L6B'), ('E', 'L7I'): ('E5B', 'I', 'REF'), ('E', 'L7Q'): ('E5B', 'Q', ' L7I'), ('E', 'L7X'): ('E5B', 'I+Q', ' L7I'), ('E', 'L8I'): ('E5(A+B)', 'I', 'REF'), ('E', 'L8Q'): ('E5(A+B)', 'Q', ' L8I'), ('E', 'L8X'): ('E5(A+B)', 'I+Q', ' L8I'), ('G', 'L1C'): ('L1', 'C/A', 'REF'), ('G', 'L1L'): ('L1', 'L1C-P', ' L1C'), ('G', 'L1N'): ('L1', 'Codeless', ' L1C'), ('G', 'L1P'): ('L1', 'P', ' L1C'), ('G', 'L1R'): ('L2', 'M (RMP)', 'Restricted'), ('G', 'L1S'): ('L1', 'L1C-D', ' L1C'), ('G', 'L1W'): ('L1', 'Z-tracking', ' L1C'), ('G', 'L1X'): ('L1', 'L1C-(D+P)', ' L1C'), ('G', 'L2C'): ('L2', 'C/A', 'None/L2P'), ('G', 'L2D'): ('L2', 'Semi-codeless', 'None'), ('G', 'L2L'): ('L2', 'L2C(L)', ' L2P'), ('G', 'L2N'): ('L2', 'Codeless', 'None'), ('G', 'L2P'): ('L2', 'P', 'REF'), ('G', 'L2S'): ('L2', 'L2C(M)', ' L2P'), ('G', 'L2W'): ('L2', 'Z-tracking', 'None'), ('G', 'L2X'): ('L2', 'L2C(M+L)', ' L2P'), ('G', 'L5I'): ('L5', 'I', 'REF'), ('G', 'L5Q'): ('L5', 'Q', ' L5I'), ('G', 'L5X'): ('L5', 'I+Q', ' L5I'), ('I', 'L1D'): ('L1', 'D', 'REF'), ('I', 'L1P'): ('L1', 'P', ' L1D'), ('I', 'L1X'): ('L1', 'D+P', ' L1D'), ('I', 'L5A'): ('L5', 'A SPS', 'REF'), ('I', 'L5B'): ('L5', 'B RS(D)', 'Restricted'), ('I', 'L5C'): ('L5', 'C RS(P)', 'None'), ('I', 'L5X'): ('L5', 'B+C', ' L5A'), ('I', 'L9A'): ('S', 'A SPS', 'REF'), ('I', 'L9B'): ('S', 'B RS(D)', 'Restricted'), ('I', 'L9C'): ('S', 'C RS(P)', 'None'), ('I', 'L9X'): ('S', 'B+C', ' L9A'), ('J', 'L1B'): ('L1', 'L1Sb', 'N/A'), ('J', 'L1C'): ('L1', 'C/A', 'REF'), ('J', 'L1E'): ('L1', 'C/B', 'REF'), ('J', 'L1L'): ('L1', 'L1C (P)', ' L1C/L1E'), ('J', 'L1S'): ('L1', 'L1C (D)', ' L1C/L1E'), ('J', 'L1X'): ('L1', 'L1C-(D+P)', ' L1C/L1E'), ('J', 'L1Z'): ('L1', 'L1S', 'N/A'), ('J', 'L2L'): ('L2', 'L2C (L)', ' L2S'), ('J', 'L2S'): ('L2', 'L2C (M)', 'REF'), ('J', 'L2X'): ('L2', 'L2C (M+L)', ' L2S'), ('J', 'L5D'): ('L5S', 'I', 'REF'), ('J', 'L5I'): ('L5', 'I', 'REF'), ('J', 'L5P'): ('L5S', 'Q', ' L5D'), ('J', 'L5Q'): ('L5', 'Q', ' L5I'), ('J', 'L5X'): ('L5', 'I+Q', ' L5I'), ('J', 'L5Z'): ('L5S', 'I+Q', ' L5D'), ('J', 'L6E'): ('L6', 'L6E', 'None'), ('J', 'L6L'): ('L6', 'L6P', 'None'), ('J', 'L6S'): ('L6', 'L6D', 'REF'), ('J', 'L6X'): ('L6', 'L6(D+P)', 'None'), ('J', 'L6Z'): ('L6', 'L6(D+E)', 'None'), ('R', 'L1C'): ('G1', 'C/A', 'REF'), ('R', 'L1P'): ('G1', 'P', ' L1C'), ('R', 'L2C'): ('G2', 'C/A', 'REF'), ('R', 'L2P'): ('G2', 'P', ' L2C'), ('R', 'L3I'): ('G3', 'I', 'REF'), ('R', 'L3Q'): ('G3', 'Q', ' L3I'), ('R', 'L3X'): ('G3', 'I+Q', ' L3I'), ('R', 'L4A'): ('G1a', 'L1OCd', 'REF'), ('R', 'L4B'): ('G1a', 'L1OCp', 'None'), ('R', 'L4X'): ('G1a', 'L1OCd+ L1OCd', 'None'), ('R', 'L6A'): ('G2a', 'L2CSI', 'REF'), ('R', 'L6B'): ('G2a', 'L2OCp', 'None'), ('R', 'L6X'): ('G2a', 'L2CSI+ L2OCp', 'None')}

RINEX GNSS Code/Observation Code -> Signal and Phase Alignment Lookup.

  • key is (RINEX GNSS Code, RINEX Carrier Phase Observation Code)

  • value is (Frequency Band, Signal, Phase Alignment Frequency Band)

pygnssutils.rinex_globals.EPHNAVTYPES = {('C', 'B1C'): 'CNV1', ('C', 'B1I_GEO'): 'D2', ('C', 'B1I_MEOIGSO'): 'D1', ('C', 'B2I_GEO'): 'D2', ('C', 'B2I_MEOIGSO'): 'D1', ('C', 'B2a'): 'CNV2', ('C', 'B2b'): 'CNV3', ('C', 'B3I_GEO'): 'D2', ('C', 'B3I_MEOIGSO'): 'D1', ('E', 'E1'): 'INAV', ('E', 'E5a'): 'FNAV', ('E', 'E5b'): 'INAV', ('G', 'L1 C/A'): 'LNAV', ('G', 'L1C'): 'CNV2', ('G', 'L2C'): 'CNAV', ('G', 'L5'): 'CNAV', ('I', 'L1'): 'L1NV', ('I', 'L5/S SPS'): 'LNAV', ('J', 'L1 C/A'): 'LNAV', ('J', 'L1 C/B'): 'LNAV', ('J', 'L1C'): 'CNV2', ('J', 'L2C'): 'CNAV', ('J', 'L5'): 'CNAV', ('R', 'L1 C/A'): 'FDMA', ('R', 'L1OC'): 'L1OC', ('R', 'L3OC'): 'L3OC', ('S', 'L1'): 'SBAS'}

EPH Navigation Message Types.

  • key is (RINEX GNSS Code, Signal Code)

pygnssutils.rinex_globals.RINEX_METOBS = {'HI': 'Hail indicator non-zero: Hail detected since last measurement', 'HR': 'Relative humidity (percent)', 'PR': 'Pressure (mbar)', 'RI': 'Rain increment (1/10 mm): Rain accumulation since last measurement', 'TD': 'Dry temperature (deg Celsius)', 'WD': 'Wind azimuth (deg) from where the wind blows', 'WS': 'Wind speed (m/s)', 'ZD': 'Dry component of zen.path delay (mm)', 'ZT': 'Total zenith path delay (mm)', 'ZW': 'Wet zenith path delay (mm), (for WVR data)'}

RINEX Meteorology Observation Codes.

pygnssutils.rinex_helpers module

rinex_helpers.py

RINEX conversion static helper methods.

Mainly header string formatting.

Created on 6 Oct 2025

author:

semuadmin (Steve Smith)

copyright:

semuadmin © 2025

license:

BSD 3-Clause

pygnssutils.rinex_helpers.FRNX(num: float | int | str, length: int, dp: int) str[source]

Format float to RINEX FORTAN-style string.

Parameters:
  • num (float | int | str) – number

  • length (int) – length

  • dp (int) – decimal places

Returns:

formatted string

Return type:

str

pygnssutils.rinex_helpers.DRNX(num: float | int | str, length: int, sig: int) str[source]

Format float to RINEX SCIENTIFIC-style string.

Parameters:
  • num (float | int | str) – number

  • length (int) – length

  • sig (int) – significant digits

Returns:

formatted string

Return type:

str

pygnssutils.rinex_helpers.get_epoch(wno: int, tow: int, gnss: Literal['G', 'E', 'C', 'J', 'I']) tuple[datetime, int][source]

Get epoch and non-modular week number for given modular wno and tow.

Parameters:
  • wno (int) – modular week number

  • two (int) – time of week in seconds

  • gnss (Literal['G', 'E', 'C', 'J', 'I']) – gnss code

Returns:

epoch, non-modular wno

Return type:

tuple[datetime, int]

pygnssutils.rinex_helpers.get_fithours(iodc: int, fit: int, gnss: str) int | str[source]

Get FIT interval in hours for given IODC and fit flag.

Parameters:
  • iodc (int) – iodc

  • fit (int) – fit flag (0/1)

  • gnss (str) – gnss code

Returns:

fit interval in hourse

Return type:

int | str

pygnssutils.rinex_helpers.adjust_time_units(value: float) tuple[int, str][source]

Adjust time units to keep to 2 integers, as required in RINEX filename period and frequency fields.

Parameters:

value (float) – time value in seconds

Returns:

adjusted value and time units

Return type:

tuple[int, str]

pygnssutils.rinex_helpers.get_svcode_ubx(gnss: int, prn: int) str[source]

Convert GNSS and prn values to RINEX svid string e.g “G29”, “E 4”

Parameters:
  • gnss (int) – GNSS code

  • prn (int) – prn code

Returns:

svid as string

Return type:

str

pygnssutils.rinex_helpers.get_svcode_rtcm(gnssr: str, prn: int, freqno: int | None = None) str[source]

Convert GNSS and prn values to RINEX svid string e.g “G29”, “E 4”

Parameters:
  • gnss (str) – GNSS code

  • prn (int) – prn code

  • freqno (int | NoneType) – GLONASS frequency no

Returns:

svid as string

Return type:

str

pygnssutils.rinex_helpers.get_obscode(gnss: int, sigid: int) str[source]

Convert GNSS and Signal ID to RINEX observation code e.g. “1C”, “2B”

pygnssutils.rinex_helpers.get_ssi(cno: float) int[source]

Convert CNO in dbHz to RINEX signal strength indicator (SSI) value

Parameters:

cno (float) – CNO in dbHz

Returns:

SSI as integer

Return type:

int

pygnssutils.rinex_helpers.format_filename(rinextype: Literal['O', 'N', 'M'], gnssfilter: list[str], startepoch: datetime, endepoch: datetime, interval: int | float, outputpath: Path = PosixPath('.'), source: Literal['R', 'S', 'N', 'U'] = 'R', form: Literal['IGS', 'GNSS'] = 'GNSS', site: str = 'SITE', marker: int = 0, receiver: int = 0, country: str = 'USA') Path[source]

Format output file name using RINEX long filename format.

e.g. pygpsdata_R_202604101416_30M_01S_MO.rnx

Parameters:
  • rinextype (Literal["O","N","M"]) – RINEX type

  • gnssfilter (list[str]) – list of GNSS systems included (G, E, etc.)

  • startepoch (datetime) – first observation epoch

  • endepoch (datetime) – last observation epoch

  • interval (int | float) – observation interval in seconds

  • outputpath (Path | str) – fully-qualified file path (“.”)

  • source (Literal["R","S","N","U"]) – source of observations (R = Receiver)

  • form (Literal["IGS","GNSS"]) – filename format to use

  • site (str) – site (for IGS format only)

  • marker (int) – marker number (for IGS format only)

  • receiver (int) – receiver number (for IGS format only)

  • country (str) – country code (for IGS format only)

Returns:

fully-qualified output file path

Return type:

Path

pygnssutils.rinex_helpers.format_comments(comments: list | str = '') str[source]

Format comments.

Parameters:

comments (list | str) – comments

Returns:

formatted string

Return type:

str

pygnssutils.rinex_helpers.format_version(rinexver: str, rinextype: Literal['O', 'N', 'M'], gnssr: list[str] | str) str[source]

Format RINEX version.

Parameters:
  • rinexver (str) – RINEX version e.g. “3.05”

  • rinextype (Literal["O","N","M"]) – RINEX filetype

  • gnssr (list[str] | str) – RINEX GNSS code(s)

Returns:

formatted string

Return type:

str

pygnssutils.rinex_helpers.format_runby(pgm: str = 'pyrinexconv 0.1.2 Alpha', runby: str = 'steve', rundate: datetime | str = '') str[source]

Format Name of program/agency creating current file and run time.

Parameters:
  • version (str) – programme version

  • runby (str) – runtime user

  • rundate (datetime | str) – run date & timezone (now if blank)

Returns:

formatted string

Return type:

str

pygnssutils.rinex_helpers.format_marker(mkrname: str = '', mkrnum: str = '', mkrtype: str = '') str[source]

Format antenna marker name, number (optional) and type.

Parameters:
  • name (str) – marker name

  • name – marker number

  • name – marker type

Returns:

formatted string

Return type:

str

pygnssutils.rinex_helpers.format_observer(observer: str = '') str[source]

Format Name of observer / agency.

Parameters:

observer (str) – observer / agency

Returns:

formatted string

Return type:

str

pygnssutils.rinex_helpers.format_rcvrtype(rcvrnum: str = '', rcvrtype: str = '', rcvrver: str = '') str[source]

Format Receiver number, type, and version.

Parameters:
  • rcvrnum (str) – record number

  • rcvrtype (str) – record type

  • rcvrver (str) – record version

Returns:

formatted string

Return type:

str

pygnssutils.rinex_helpers.format_antennatype(antnum: str = '', anttype: str = '') str[source]

Format antenna type.

Parameters:
  • antenna (str) – antenna number

  • antenna – antenna type

Returns:

formatted string

Return type:

str

pygnssutils.rinex_helpers.format_approxpos(approxpos: list[float] | str = '') str[source]

Format approx ECEF marker position (prefer ITRS) (optional for moving platforms).

Parameters:

approxpos (list[float] | str) – approx ECEF pos [X,Y,Z]

Returns:

formatted string

Return type:

str

pygnssutils.rinex_helpers.format_antennadeltahen(height: float | str = '', eccen: list[float] | str = '') str[source]

Format Antenna height: Height and Horizontal eccentricity of the antenna reference point (ARP) relative to the marker.

Parameters:
  • height (float | str) – height relative to marker (m)

  • eccen (list[float] | str) – eccentricity (E,N) (m)

Returns:

formatted string

Return type:

str

pygnssutils.rinex_helpers.format_antennadeltaxyz(deltaxyz: list[float] | str = '') str[source]

Format Position of antenna reference point for antenna on vehicle (optional).

Parameters:

deltaxyz (list[float] | str) – approx pos [X,Y,Z]

Returns:

formatted string

Return type:

str

pygnssutils.rinex_helpers.format_sys_antennaphasecentre(antphasecentres: dict | str = '') str[source]

Format Average phase center position with respect to antenna reference point (optional)

Format of antphasecentres dict:

antphasecentres = {
    gnssr (str) : {
        "obstype": obstype (str),
        "phasecenter": [x (float, y (float), z (float)],
    },
    gnssr (str) : {
        "obstype": obstype (str),
        "phasecenter": [x (float, y (float), z (float)],
    },
}
Parameters:

antphasecentres (dict | str) – antenna phase centres

Returns:

formatted string

Return type:

str

pygnssutils.rinex_helpers.format_antennabsight(bsight: list[float] | str = '') str[source]

Format Direction of the “vertical” antenna axis towards the GNSS satellites (optional).

:param list[float] | str bsight : direction of antenna axis (X,Y,Z) :return: formatted string :rtype: str

pygnssutils.rinex_helpers.format_antennazerodirazi(azi: float | str = '') str[source]

Format Azimuth of the zero-direction of a fixed antenna (optional).

Parameters:

azi (float | str) – azimuth

Returns:

formatted string

Return type:

str

pygnssutils.rinex_helpers.format_antennazerodirxyz(zerodir: list[float] | str = '') str[source]

Format Zero-direction of antenna Antenna on vehicle (optional).

Parameters:

zerodir (list[float] | str) – zero-direction of antenna (X,Y,Z)

Returns:

formatted string

Return type:

str

pygnssutils.rinex_helpers.format_centermass(centermass: list[float] | str = '') str[source]

Format Current center of mass (X,Y,Z, meters) of vehicle in body-fixed coordinate system (optional).

Parameters:

centermass (list[float] | str) – centermass [x,y,z]

Returns:

formatted string

Return type:

str

pygnssutils.rinex_helpers.format_obstypes(obstypes: dict | str = '') str[source]

Format observation type(s).

Format of obstypes dict:

obstypes = {
    gnssr (str) : {
        BDS+obstype: num (int),
        "L"+obstype: num (int),
        "D"+obstype: num (int),
        "S"+obstype: num (int),
    },
    gnssr (str) : {
        BDS+obstype: num (int),
        "L"+obstype: num (int),
        "D"+obstype: num (int),
        "S"+obstype: num (int),
    },
}
Parameters:

obstypes (dict | str) – observation types

Returns:

formatted string

Return type:

str

pygnssutils.rinex_helpers.format_cnrunit(unit: str = 'dbHz') str[source]

Format Unit of the carrier to noise ratio observables (optional).

Parameters:

unit (str) – CNo unit

Returns:

formatted string

Return type:

str

pygnssutils.rinex_helpers.format_interval(interval: float | str = '') str[source]

Format Observation interval in seconds (optional).

Parameters:

interval (float | str) – interval in seconds

Returns:

formatted string

Return type:

str

pygnssutils.rinex_helpers.format_obstime(utc: datetime, source: str = 'GPS') str[source]

Format observation time and source.

Parameters:
  • utc (datetime) – utc time

  • source (str) – time source e.g. “GPS”

Returns:

formatted time string

Return type:

str

pygnssutils.rinex_helpers.format_timefirstlast(tim: datetime, mode: Literal['FIRST', 'LAST']) str[source]

Format header observation first and last time markers (optional).

Parameters:
  • tim (datetime) – time

  • "LAST"] (Literal["FIRST",) – mode

Returns:

formatted string

Return type:

str

pygnssutils.rinex_helpers.format_clockoffset(offset: int | str = '') str[source]

Format Epoch, code, and phase are corrected by applying the real-time-derived receiver clock offset (optional).

Parameters:

str (int |) – offset flag

Returns:

formatted string

Return type:

str

pygnssutils.rinex_helpers.format_sys_dcbsapplied(dcbsapplied: dict | str = '') str[source]

Format Program name used to apply differential code bias corrections (optional).

Format of dcbsapplied dict:

dcbsapplied = {
    gnssr (str) ) {
        "pgmname": pgmname (str),
        "source": source (str),
    },
    gnssr (str) ) {
        "pgmname": pgmname (str),
        "source": source (str),
    },
}
Parameters:

dcbsapplied (dict | str) – dcbs applied

Returns:

formatted string

Return type:

str

pygnssutils.rinex_helpers.format_sys_pcvsapplied(pcvsapplied: dict | str = '') str[source]

Format Program name used to apply phase center variation corrections (optional).

Format of pcvsapplied dict:

pcvsapplied = {
    gnssr (str) ) {
        "pgmname": pgmname (str),
        "source": source (str),
    },
    ...
}
Parameters:

pcvsapplied (dict | str) – pcvs applied

Returns:

formatted string

Return type:

str

pygnssutils.rinex_helpers.format_sys_scalefactor(scalefactors: dict | str = '') str[source]

Format Factor to divide stored observations with before (optional).

Format of scalefactors dict:

scale_factors = {
    gnssr (str) : {
        "scale": scale (float),
        "obstypes": [obstype (str), obstype (str), ...] # ["C01", "C02", etc.]
    },
    ...
}
Parameters:

scalefactors (dict | str) – gnssr scale factors

Returns:

formatted string

Return type:

str

pygnssutils.rinex_helpers.format_sys_phaseshift(phaseshifts: dict | str = '') str[source]

Format Phase shift correction used to generate phases consistent with respect to cycle shifts.

Format of phaseshifts dict:

phaseshifts = {
    gnssr (str) : {
        "obscode": obscode (str),
        "correction": correction (float)
        "sats": [svcode (str), svcode (str), svcode (str), ...] # ["E03", "E04", etc.]
    },
    ...
}
Parameters:

phaseshifts (dict | str) – phase shifts

Returns:

formatted string

Return type:

str

pygnssutils.rinex_helpers.format_glonassfrq(sats: dict | str = '') str[source]

Format GLONASS slot and frequency numbers (mandatory).

Format of sats dict:

sats = {
    svcode (str): freq (int),
    svcode (str): freq (int),
}
Parameters:

sats (dict | str) – dict of {svcode: frq}

Returns:

formatted string

Return type:

str

pygnssutils.rinex_helpers.format_glonassphasebias(corrs: dict | str = '') str[source]

Format GLONASS Phase bias correction used to align code and phase observations (mandatory).

Format of corrs dict:

corrs = {
    code (str) : bias (float),
    code (str) : bias (float),
}
Parameters:

corrs (dict | NoneType) – dict of {code: phase bias correction}

Returns:

formatted string

Return type:

str

pygnssutils.rinex_helpers.format_leapseconds(epoch: datetime | str = '', gnssfilter: list[str] | str = '') str[source]

Format Current Number of leap seconds (optional).

TODO check intention here - not clear from specs which date this refers to; have assumed first observation date

Parameters:
  • epoch (datetime | str) – epoch to which leapsecond refers

  • gnssfilter (list[str] | str) – list of gnss included

Returns:

formatted string

Return type:

str

pygnssutils.rinex_helpers.format_numsats(numsats: int | str = '') str[source]

Format Number of satellites, for which observations are stored in the file (optional).

Parameters:

numsats (int | str) – number of satellites

Returns:

formatted string

Return type:

str

pygnssutils.rinex_helpers.format_iono_corr(svid: int, corrtype: str, timemark: str, **kwargs) str[source]

Format Ionospheric Corrections (RINEX 3).

Parameters:
  • svid (str) – SV id

  • corrtype (str) – correction type

  • timemark (str) – time mark

  • kwargs (dict) – ionospheric correction parameters

Returns:

formatted string

Return type:

str

pygnssutils.rinex_helpers.format_ion(svcode: str, msgtype: str, msgsubtype: str, epoch: datetime, **kwargs) str[source]

Format Ionospheric Correction record (RINEX 4).

param str svcode: SV code param str msgtype: message type param str msgsubtype: sub message type param datetime epoch: epoch :param dict kwargs: ionospheric correction parameters :return: formatted string :rtype: str

pygnssutils.rinex_helpers.format_eop(svcode: str, msgtype: str, msgsubtype: str, epoch: datetime, tom: float, xp: float, dxpdt: float, dxpdt2: float, yp: float, dypdt: float, dypdt2: float, deltaut1: float, ddeltaut1dt: float, d2deltaut1dt2: float) str[source]

Format Earth Orientation record (RINEX 4).

param str svcode: SV code param str msgtype: message type param str msgsubtype: sub message type param datetime epoch: epoch

pygnssutils.rinex_helpers.format_time_corr(svcode: str, corrtype: str, timeref: int, weekno: int, source: str, a0: float, a1: float) str[source]

Format Time System Offset (RINEX 3).

Parameters:
  • svcode (str) – SV code

  • corrtype (str) – correction type

  • timeref (int) – time reference

  • weekno (int) – week number

  • source (str) – time source

  • a0 (float) – a0 clock offset

  • a1 (float) – a1 clock offset

Returns:

formatted string

Return type:

str

pygnssutils.rinex_helpers.format_sto(svcode: str, msgtype: str, msgsubtype: str, epoch: datetime, timecode: str, sbasid: str, utcid: str, tot: float, a0: float, a1: float, a2: float) str[source]

Format System Time Offset (RINEX 4).

param str svcode: SV code param str msgtype: message type param str msgsubtype: sub message type param datetime epoch: epoch param str timecode: timecode param str sbasid: sbas id (blank if not sbas) param str utcid: UTC id param float tot: time of transmission param float a0: clock offset coefficient a0 param float a1: clock offset coefficient a1 param float a2: clock offset coefficient a2

pygnssutils.rinex_helpers.format_nav_epoch(epoch: datetime) str[source]

Format nav epoch as string.

Parameters:

epoch (datetime) – epoch

pygnssutils.rinex_helpers.format_met_obstypes(sensortypes: dict[str, dict] | str) str[source]

Format Number of different meteorological observation types.

Parameters:

sensortypes (dict[str,dict] | str) – sensor types

Returns:

formatted string

Return type:

str

pygnssutils.rinex_helpers.format_met_sensortype(sensortypes: dict[str, dict] | str) str[source]

Format Description of the met sensor.

Parameters:

sensortypes (dict[str,dict] | str) – sensor types

Returns:

formatted string

Return type:

str

pygnssutils.rinex_helpers.format_met_sensorpos(senspos: list[float] | str, obstype: str = 'PR') str[source]

Format Approximate position of the met sensor.

Parameters:

sensortypes (list[float] | str) – sensor types

Returns:

formatted string

Return type:

str

pygnssutils.rinex_helpers.format_doi(doi: str = '') str[source]

Format Digital Object Identifier (DOI).

e.g. “https://doi.org/10.1000/182

Parameters:

doi (str) – digital object identifier

Returns:

formatted string

Return type:

str

pygnssutils.rinex_helpers.format_licenseofuse(lou: str = '') str[source]

Format License of Use.

e.g. “CC BY 04 ; https://creativecommons.org/licenses/by/4.0/

Parameters:

lou (str) – license of use descriptor

Returns:

formatted string

Return type:

str

pygnssutils.rinex_helpers.format_stationinfo(station: str = '') str[source]

Format Station Information.

Parameters:

station (str) – station info

Returns:

formatted string

Return type:

str

pygnssutils.rinex_helpers.format_nav_typesvmssg(rectyp: str, sv: str, msgtyp: str, msgsubtyp: str = '') str[source]

Format navigation record type, SV and message type/subtype.

Parameters:
  • rectyp (str) – record type e.g. EPH

  • sv (str) – SV e.g. G04

  • msgtyp (str) – message type e.g. LNAV

  • msgsubtyp (str) – message subtype

Returns:

formatted string

Return type:

str

pygnssutils.rinex_helpers.gpsura2m(ura) float[source]

Derive user-range accuracy in meters from URA code. (GPS ICD section 20.3.3.3.1.3)

Parameters:

ura (int) – ura code

Returns:

ura as meters

Return type:

float

pygnssutils.rinex_helpers.format_headerend() str[source]

Format header end.

Returns:

formatted string

Return type:

str

pygnssutils.rinex_helpers.format_fileend()[source]

Format file end.

pygnssutils.rinex_helpers.listify(arg: list[str] | str | None) list[str][source]

Convert comma-separated CLI argument str to list type.

Parm list[str] | str | NoneType arg:

argument

Returns:

argument as list

Return type:

list[str]

pygnssutils.rinex_subframes_bds module

rinex_subframes_bds.py

Beidou NAV Subframe definitions.

B1I: http://en.beidou.gov.cn/SYSTEMS/Officialdocument/201902/P020190227601370045731.pdf B1C: http://en.beidou.gov.cn/SYSTEMS/Officialdocument/201806/P020180608525871869457.pdf B2a: http://en.beidou.gov.cn/SYSTEMS/Officialdocument/201806/P020180608525870555377.pdf B3I: http://en.beidou.gov.cn/SYSTEMS/Officialdocument/201806/P020180608525869304359.pdf Open Service: http://en.beidou.gov.cn/SYSTEMS/ICD/201806/P020180608523308843290.pdf

D1 is the BDS-2/3 legacy navigation message on MEO/IGSO satellites (obscode 2I, 6I, 7I). D2 is the BDS-2/3 legacy navigation message on GEO satellites (obscode 2I, 6I, 7I). CNV1 is the navigation message on the Beidou-3 B1C signal (obscode 1D). CNV2 is the navigation message on the Beidou-3 B2a signal (obscode 5D).

These are provided as the basis of a capability to parse and store the payloads of raw NAV subframe messages, via the associated pygnssutils.RawNav class defined in rawnav.py.

NB:

  • MSB, intermediate bit and LSB fields MUST be suffixed ‘_msb’, ‘_isb’ and ‘_lsb’ respectively.

  • Non-data bits (reserved, parity, non) MUST be prefixed ‘_’.

  • Avoid the following reserved field names: gnss, svid, sigid, subframeacq, epoch

Created on 6 Oct 2025

author:

semuadmin (Steve Smith)

copyright:

semuadmin © 2025

license:

BSD 3-Clause

pygnssutils.rinex_subframes_gal module

rinex_subframes_gal.py

Galileo NAV Subframe definitions.

https://galileognss.eu/wp-content/uploads/2021/01/Galileo_OS_SIS_ICD_v2.0.pdf

These are provided as the basis of a capability to parse and store the payloads of raw NAV subframe messages, via the associated pygnssutils.RawNav class defined in rawnav.py.

NB:

  • MSB and LSB fields MUST be suffixed ‘_msb’ and ‘_lsb’ respectively.

  • Non-data bits (reserved, parity, non) MUST be prefixed ‘_’.

  • Avoid the following reserved field names: gnss, svid, sigid, subframeacq, epoch

Created on 6 Oct 2025

author:

semuadmin (Steve Smith)

copyright:

semuadmin © 2025

license:

BSD 3-Clause

pygnssutils.rinex_subframes_gps module

rinex_subframes_gps.py

GPS NAV Subframe definitions.

https://archive.gps.gov/technical/icwg/IS-GPS-200N.pdf

These are provided as the basis of a capability to parse and store the payloads of raw NAV subframe messages, via the associated pygnssutils.RawNav class defined in rawnav.py.

NB:

  • MSB and LSB fields MUST be suffixed ‘_msb’ and ‘_lsb’ respectively.

  • Non-data bits (reserved, parity, non) MUST be prefixed ‘_’.

  • Avoid the following reserved field names: gnss, svid, sigid, subframeacq, epoch

Created on 6 Oct 2025

author:

semuadmin (Steve Smith)

copyright:

semuadmin © 2025

license:

BSD 3-Clause

pygnssutils.socket_server module

TCP socket server for GNSS applications.

Reads raw data from GNSS receiver message queue and outputs this to multiple TCP socket clients.

NB: This utility is used by PyGPSClient - do not change footprint of any public methods without first checking impact on PyGPSClient - https://github.com/semuconsulting/PyGPSClient.

Provides two client request handler classes:

  • ClientHandler - HTTP connection

  • ClientHandlerTLS - HTTPS (TLS) connection

TLS requires a valid TLS certificate/key pair (in pem format) to be located at a path set in argument ‘tlspempath’ or environment variable PYGNSSUTILS_PEMPATH. The default path is $HOME/pygnssutils.pem.

A pem file suitable for demo and test purposes can be created thus:

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

For TLS (HTTPS) operation, instantiate SocketServer using a request handler of ClientHandlerTLS rather than ClientHander.

Operates in either of two modes according to ntripmode setting:

  • ntripmode=0. Open socket server mode - will stream GNSS data to any connected client without authentication.

  • ntripmode=1. NTRIP caster mode - implements NTRIP server protocol and will respond to NTRIP client authentication, sourcetable and RTCM3 data stream requests. NB: THIS ASSUMES THE CONNECTED GNSS RECEIVER IS OPERATING IN BASE STATION MODE (SURVEY-IN OR FIXED) AND OUTPUTTING THE RELEVANT RTCM3 MESSAGES.

For NTRIP caster mode, authorization credentials can be supplied via keyword arguments or set via environment variables PYGPSCLIENT_USER and PYGPSCLIENT_PASSWORD

Created on 16 May 2022

author:

semuadmin (Steve Smith)

copyright:

semuadmin © 2022

license:

BSD 3-Clause

class pygnssutils.socket_server.SocketServer(app, ntripmode: Literal[0, 1], maxclients: int, msgqueue: Queue, *args, **kwargs)[source]

Bases: ThreadingTCPServer

Socket server class.

This instantiates a daemon ClientHandler thread for each connected client.

__init__(app, ntripmode: Literal[0, 1], maxclients: int, msgqueue: Queue, *args, **kwargs)[source]

Overridden constructor.

Parameters:
  • app (Frame) – reference to main application class (if any)

  • ntripmode (Literal[0,1]) – 0 = open socket server, 1 = NTRIP server

  • maxclients (int) – max no of clients allowed

  • msgqueue (Queue) – queue containing raw GNSS messages

  • ipprot (Literal["IPv4","IPv6"]) – (kwarg) IP protocol family

  • ntripversion (Literal["1.0","2.0"]) – (kwarg) NTRIP version

  • ntripuser (str) – (kwarg) NTRIP authentication user name

  • ntrippassword (str) – (kwarg) NTRIP authentication password

  • tlspempath (str) – (kwarg) Path to TLS PEM file

  • ntriprtcmstr (str) – (kwarg) RTCM types sourcetable entry e.g. “1006(5),1077(1),…”

  • verbosity (int) – (kwarg) log verbosity (1 = medium)

  • logtofile (str) – (kwarg) fully qualifed log file name (‘’)

server_close()[source]

Overridden server close routine.

handle_error(request, client_address)[source]

Handle client exception.

Parameters:
  • request – request object

  • address – client address

verify_request(request, client_address) bool[source]

Verify client request.

Parameters:
  • request – request object

  • address – client address

Returns:

verified y/n

Return type:

bool

stop_read_thread()[source]

Stop GNSS message reader thread.

notify(address: tuple, status: int)[source]

Alert calling app on client connection or disconnection.

Parameters:
  • address (tuple) – client address

  • status (int) – 0 = disconnected, 1 = connected, 2 = maxconnections

property credentials: bytes

Getter for basic authorization credentials.

property connections

Getter for client connections.

property ntripmode: int

Getter for ntrip mode.

Returns:

0 = open socket server, 1 = ntrip mode

Return type:

int

property latlon: tuple

Get current lat / lon from receiver.

Return=:

tuple of (lat, lon)

Return type:

tuple

class pygnssutils.socket_server.ClientHandler(request, client_address, server)[source]

Bases: StreamRequestHandler

Threaded TCP client connection handler class.

__init__(request, client_address, server)[source]

Overridden constructor.

setup(*args, **kwargs)[source]

Overridden client handler setup routine. Allocates available message queue to client.

finish(*args, **kwargs)[source]

Overridden client handler finish routine. De-allocates message queue from client.

handle()[source]

Overridden main client handler.

If in NTRIP server mode, will respond to NTRIP client authentication and sourcetable requests and, if valid, stream relevant RTCM3 data from the input message queue to the socket.

If in open socket server mode, will simply stream content of input message queue to the socket.

class pygnssutils.socket_server.ClientHandlerTLS(request, client_address, server)[source]

Bases: ClientHandler

Threaded TCP client connection handler class with TLS (HTTPS).

__init__(request, client_address, server)[source]

Overridden constructor.

pygnssutils.socket_server.runserver(host: str, port: int, mq: Queue, ntripmode: int = 0, maxclients: int = 5, tls: bool = False, ntripversion: str = '2.0', **kwargs)[source]

THREADED Socket server function to be run as thread.

Parameters:
  • host (str) – host IP

  • port (int) – port

  • mq (Queue) – output message queue

  • ntripmode (int) – 0 = basic, 1 = ntrip caster

  • maxclients (int) – max concurrent clients (5)

  • tls (bool) – Enable TLS (HTTPS) (0) (remember to set PYGNSSUTILS_PEMPATH)

  • ntripversion (str) – NTRIP version 1.0 or 2.0

Module contents

Created on 27 Sep 2020

author:

semuadmin (Steve Smith)

copyright:

semuadmin © 2020

license:

BSD 3-Clause