Skip to content

Commit

Permalink
Merge pull request #84 from semuconsulting/RELEASE-CANDIDATE-1.0.32
Browse files Browse the repository at this point in the history
RELEASE CANDIDATE 1.0.32
  • Loading branch information
semuadmin committed Aug 12, 2024
2 parents 48fdca3 + 3a316d8 commit e1ec253
Show file tree
Hide file tree
Showing 30 changed files with 792 additions and 642 deletions.
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
"python3.8InterpreterPath": "/Library/Frameworks/Python.framework/Versions/3.8/bin/python3.8",
"modulename": "${workspaceFolderBasename}",
"distname": "${workspaceFolderBasename}",
"moduleversion": "1.0.31"
"moduleversion": "1.0.32"
}
17 changes: 13 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -318,15 +318,24 @@ For help and full list of optional arguments, type:
### EXPERIMENTAL
Provides a simple simulation of a GNSS serial stream by generating synthetic UBX or NMEA messages based on parameters defined in a json configuration file. Can simulate a motion vector based on a specified course over ground and speed.
Provides a simple simulation of a GNSS serial stream by generating synthetic UBX or NMEA messages based on parameters defined in a json configuration file. Can simulate a motion vector based on a specified course over ground and speed. Location of configuration file can be set via environment variable `UBXSIMULATOR`.
Example usage::
Example usage:
```shell
ubxsimulator --simconfigfile "/home/myuser/ubxsimulator.json" --interval 1000 --timeout 3 --verbosity 3
```
```python
from pygnssutils import UBXSimulator
from os import getenv
from pygnssutils import UBXSimulator, UBXSIMULATOR
from pyubx2 import UBXReader
with UBXSimulator(configfile="/home/myuser/ubxsimulator.json", interval=1, timeout=3) as stream:
with UBXSimulator(
configfile=getenv(UBXSIMULATOR, "/home/myuser/ubxsimulator.json"),
interval=1000,
timeout=3,
) as stream:
ubr = UBXReader(stream)
for raw, parsed in ubr:
print(parsed)
Expand Down
29 changes: 29 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,34 @@
# pygnssutils Release Notes

### RELEASE 1.0.32

ENHANCEMENTS:

1. Add configuration file option to all CLI utilities via `-C` or `--config` argument. Default location of configuration file can be specified in environment variable `{utility}_CONF` e.g. `GNSSDUMP_CONF`, `GNSSNTRIPCLIENT_CONF`, etc. Config files are text files containing key-value pairs which mirror the existing CLI arguments, e.g.
```shell
gnssdump -C gnssdump.conf
```
where gnssdump.conf contains...

filename=pygpsdata-MIXED3.log
verbosity=3
format=2
clioutput=1
output=testfile.bin

is equivalent to:
```shell
gnssdump --filename pygpsdata-MIXED3.log --verbosity 3 --format 2 --clioutput 1 --output testfile.bin
```
2. Streamline logging. CLI usage unchanged; to use pygnssutils logging within calling application, invoke `logging.getLogger("pygnssutils")` in calling module.
3. Internal enhancements to experimental UBXSimulator to add close() and in_waiting() methods; recognise incoming RTCM data.
4. GGA message sent to NTRIP Caster in GGALIVE mode will include additional live attributes (siv, hdop, quality, diffage, diffstation). Thanks to @yydgis for contribution.

FIXES:

1. gnssntripclient - update HTTP GET request for better NTRIP 2.0 compliance
1. issue with delay on gnssntripclient retry limit

### RELEASE 1.0.31

ENHANCEMENTS:
Expand Down
185 changes: 122 additions & 63 deletions examples/gnssapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
data from a receiver until the stop Event is set or stop() method invoked. Assumes
receiver is connected via serial USB or UART1 port.
The app also implements basic methods needed by certain pygnssutils classes.
The app also implements public methods used by certain pygnssutils classes:
- get_coordinates()
Optional keyword arguments:
Expand All @@ -29,15 +30,14 @@
:license: BSD 3-Clause
"""

# pylint: disable=invalid-name, too-many-instance-attributes

from argparse import ArgumentDefaultsHelpFormatter, ArgumentParser
from logging import getLogger
from queue import Empty, Queue
from threading import Event, Thread
from time import sleep

from pynmeagps import NMEAMessageError, NMEAParseError
from pyrtcm import RTCMMessage, RTCMMessageError, RTCMParseError
from pyrtcm import RTCMMessageError, RTCMParseError
from pyubx2 import (
CARRSOLN,
FIXTYPE,
Expand All @@ -51,8 +51,32 @@
)
from serial import Serial

from pygnssutils import UBXSIMULATOR, VERBOSITY_MEDIUM, UBXSimulator, set_common_args

DISCONNECTED = 0
CONNECTED = 1
FIXTYPE_GGA = {
0: "NO FIX",
1: "3D",
2: "3D",
4: "RTK FIXED",
5: "RTK FLOAT",
6: "DR",
}
DIFFAGE_PVT = {
0: 0,
1: 1,
2: 2,
3: 5,
4: 10,
5: 15,
6: 20,
7: 30,
8: 45,
9: 60,
10: 90,
11: 120,
}


class GNSSSkeletonApp:
Expand All @@ -72,13 +96,15 @@ def __init__(
:param Event stopevent: stop event
"""

self.verbosity = kwargs.get("verbosity", VERBOSITY_MEDIUM)
# configure logger with name "pygnssutils" in calling module
self.logger = getLogger("pygnssutils.gnssapp")
self.port = port
self.baudrate = baudrate
self.timeout = timeout
self.stopevent = stopevent
self.recvqueue = kwargs.get("recvqueue", None)
self.sendqueue = kwargs.get("sendqueue", None)
self.verbosity = kwargs.get("verbosity", 1)
self.enableubx = kwargs.get("enableubx", True)
self.showstatus = kwargs.get("showstatus", True)
self.stream = None
Expand All @@ -90,6 +116,11 @@ def __init__(
self.alt = 0
self.sep = 0
self.hacc = 0
self.sip = 0
self.fix = "NO FIX"
self.hdop = 0
self.diffage = 0
self.diffstation = 0

def __enter__(self):
"""
Expand All @@ -112,9 +143,14 @@ def run(self):
Run GNSS reader/writer.
"""

self.logger.info("Starting GNSS reader/writer...")
self.enable_ubx(self.enableubx)

self.stream = Serial(self.port, self.baudrate, timeout=self.timeout)
if self.port.upper() == UBXSIMULATOR:
self.stream = UBXSimulator()
self.stream.start()
else:
self.stream = Serial(self.port, self.baudrate, timeout=self.timeout)
self.connected = CONNECTED
self.stopevent.clear()

Expand All @@ -139,6 +175,7 @@ def stop(self):
self.connected = DISCONNECTED
if self.stream is not None:
self.stream.close()
self.logger.info("GNSS reader/writer stopped")

def _read_loop(
self, stream: Serial, stopevent: Event, recvqueue: Queue, sendqueue: Queue
Expand All @@ -163,10 +200,8 @@ def _read_loop(
raw_data, parsed_data = ubr.read()
if parsed_data:
self._extract_data(parsed_data)
if self.verbosity == 1:
print(f"GNSS>> {parsed_data.identity}")
elif self.verbosity == 2:
print(parsed_data)
self.logger.info(f"GNSS>> {parsed_data.identity}")
self.logger.debug(parsed_data)
if recvqueue is not None:
# place data on receive queue
recvqueue.put((raw_data, parsed_data))
Expand All @@ -182,7 +217,7 @@ def _read_loop(
RTCMMessageError,
RTCMParseError,
) as err:
print(f"Error parsing data stream {err}")
self.logger.critical(f"Error parsing data stream {err}")
continue

def _extract_data(self, parsed_data: object):
Expand All @@ -193,17 +228,30 @@ def _extract_data(self, parsed_data: object):
"""

if hasattr(parsed_data, "fixType"):
self.fix = FIXTYPE[parsed_data.fixType]
self.fix = FIXTYPE.get(parsed_data.fixType, "NO FIX")
if hasattr(parsed_data, "carrSoln"):
self.fix = f"{self.fix} {CARRSOLN[parsed_data.carrSoln]}"
if parsed_data.carrSoln != 0: # NO RTK
self.fix = f"{CARRSOLN.get(parsed_data.carrSoln, self.fix)}"
if hasattr(parsed_data, "quality"):
self.fix = FIXTYPE_GGA.get(parsed_data.quality, "NO FIX")
if hasattr(parsed_data, "numSV"):
self.siv = parsed_data.numSV
self.sip = parsed_data.numSV
if hasattr(parsed_data, "lat"):
self.lat = parsed_data.lat
if hasattr(parsed_data, "lon"):
self.lon = parsed_data.lon
if hasattr(parsed_data, "alt"):
self.alt = parsed_data.alt
if hasattr(parsed_data, "HDOP"):
self.hdop = parsed_data.HDOP
if hasattr(parsed_data, "hDOP"):
self.hdop = parsed_data.hDOP
if hasattr(parsed_data, "diffAge"):
self.diffage = parsed_data.diffAge
if hasattr(parsed_data, "lastCorrectionAge"):
self.diffage = DIFFAGE_PVT.get(parsed_data.lastCorrectionAge, 0)
if hasattr(parsed_data, "diffStation"):
self.diffstation = parsed_data.diffStation
if hasattr(parsed_data, "hMSL"): # UBX hMSL is in mm
self.alt = parsed_data.hMSL / 1000
if hasattr(parsed_data, "sep"):
Expand All @@ -214,9 +262,9 @@ def _extract_data(self, parsed_data: object):
unit = 1 if parsed_data.identity == "PUBX00" else 1000
self.hacc = parsed_data.hAcc / unit
if self.showstatus:
print(
f"fix {self.fix}, siv {self.siv}, lat {self.lat},",
f"lon {self.lon}, alt {self.alt:.3f} m, hAcc {self.hacc:.3f} m",
self.logger.info(
f"fix {self.fix}, sip {self.sip}, lat {self.lat}, "
f"lon {self.lon}, alt {self.alt:.3f} m, hAcc {self.hacc:.3f} m"
)

def _send_data(self, stream: Serial, sendqueue: Queue):
Expand All @@ -233,10 +281,8 @@ def _send_data(self, stream: Serial, sendqueue: Queue):
while not sendqueue.empty():
data = sendqueue.get(False)
raw_data, parsed_data = data
if self.verbosity == 1:
print(f"GNSS<< {parsed_data.identity}")
elif self.verbosity == 2:
print(parsed_data)
self.logger.info(f"GNSS<< {parsed_data.identity}")
self.logger.debug(f"{parsed_data}")
stream.write(raw_data)
sendqueue.task_done()
except Empty:
Expand All @@ -246,6 +292,8 @@ def enable_ubx(self, enable: bool):
"""
Enable UBX output and suppress NMEA.
NB: only works for Gen 9+ receivers e.g. F9P.
:param bool enable: enable UBX and suppress NMEA output
"""

Expand All @@ -263,68 +311,79 @@ def enable_ubx(self, enable: bool):
msg = UBXMessage.config_set(layers, transaction, cfg_data)
self.sendqueue.put((msg.serialize(), msg))

def get_coordinates(self) -> tuple:
def get_coordinates(self) -> dict:
"""
Return current receiver navigation solution.
(method needed by certain pygnssutils classes)
:return: tuple of (connection status, lat, lon, alt and sep)
:rtype: tuple
:return: dict
:rtype: dict
"""

return (self.connected, self.lat, self.lon, self.alt, self.sep)


if __name__ == "__main__":
arp = ArgumentParser(
formatter_class=ArgumentDefaultsHelpFormatter,
)
arp.add_argument(
"-P", "--port", required=False, help="Serial port", default="/dev/ttyACM1"
)
arp.add_argument(
"-B", "--baudrate", required=False, help="Baud rate", default=38400, type=int
)
arp.add_argument(
"-T", "--timeout", required=False, help="Timeout in secs", default=3, type=float
)
arp.add_argument(
"--verbosity",
required=False,
help="Verbosity",
default=1,
choices=[0, 1, 2],
type=int,
)
arp.add_argument(
"--enableubx", required=False, help="Enable UBX output", default=1, type=int
)
arp.add_argument(
"--showstatus", required=False, help="Show GNSS status", default=1, type=int
)
return {
"connection": self.connected,
"lat": self.lat,
"lon": self.lon,
"alt": self.alt,
"sep": self.sep,
"sip": self.sip,
"fix": self.fix,
"hdop": self.hdop,
"diffage": self.diffage,
"diffstation": self.diffstation,
}


def main(**kwargs):
"""
Main routine - CLI entry point.
"""

args = arp.parse_args()
recv_queue = Queue() # set to None to print data to stdout
send_queue = Queue()
stop_event = Event()

try:
print("Starting GNSS reader/writer...\n")

with GNSSSkeletonApp(
args.port,
int(args.baudrate),
float(args.timeout),
kwargs.get("port", "/dev/ttyACM0"),
int(kwargs.get("baudrate", 38400)),
float(kwargs.get("timeout", 3)),
stop_event,
recvqueue=recv_queue,
sendqueue=send_queue,
verbosity=int(args.verbosity),
enableubx=int(args.enableubx),
showstatus=int(args.showstatus),
verbosity=int(kwargs.get("verbosity", VERBOSITY_MEDIUM)),
enableubx=int(kwargs.get("enableubx", 1)),
showstatus=int(kwargs.get("showstatus", 1)),
) as gna:
gna.run()
while True:
sleep(1)

except KeyboardInterrupt:
stop_event.set()
print("Terminated by user")


if __name__ == "__main__":

ap = ArgumentParser(
formatter_class=ArgumentDefaultsHelpFormatter,
)
ap.add_argument(
"-P", "--port", required=False, help="Serial port", default="/dev/ttyACM1"
)
ap.add_argument(
"-B", "--baudrate", required=False, help="Baud rate", default=38400, type=int
)
ap.add_argument(
"-T", "--timeout", required=False, help="Timeout in secs", default=3, type=float
)
ap.add_argument(
"--enableubx", required=False, help="Enable UBX output", default=1, type=int
)
ap.add_argument(
"--showstatus", required=False, help="Show GNSS status", default=1, type=int
)
args = set_common_args(ap)

main(**args)
Loading

0 comments on commit e1ec253

Please sign in to comment.