Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions INSTALLATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -243,11 +243,13 @@ TBC. Anyone conversant with PowerShell is welcome to contribute an equivalent in

## <a name="troubleshooting">Troubleshooting</a>

1. The optional `rasterio` package is only available as an [sdist](#sdist-vs-wheel) on some Linux / ARM platforms (including Raspberry Pi OS), and the consequent [GDAL](https://gdal.org/en/stable/) build and configuration requirements may be problematic e.g. `WARNING:root:Failed to get options via gdal-config`. Refer to [rasterio installation](https://rasterio.readthedocs.io/en/stable/installation.html) and [GDAL installation](https://gdal.org/en/stable/) for assistance but - *be warned* - the process is **not** for the faint-hearted.
1. `[Errno 13] could not open port /dev/tty**** [Errno 13] permission denied /dev/tty****` error on Linux when attempting to access serial port. Refer to [User Privileges](#userpriv).

1. The optional `rasterio` package is only available as an [sdist](#sdist-vs-wheel) on some Linux / ARM platforms and the consequent [GDAL](https://gdal.org/en/stable/) build and configuration requirements may be problematic e.g. `WARNING:root:Failed to get options via gdal-config`. Refer to [rasterio installation](https://rasterio.readthedocs.io/en/stable/installation.html) and [GDAL installation](https://gdal.org/en/stable/) for assistance but - *be warned* - the process is **not** for the faint-hearted.

In practice, `rasterio` is only required for automatic extents detection in PyGPSClient's Import Custom Map facility. As a workaround, extents can be entered manually, or you can try importing maps on a different platform and then copy-and-paste the relevant `usermaps_l` extents configuration to the target platform.

1. The optional `cryptography` package is only available as an [sdist](#sdist-vs-wheel) on some 32-bit Linux / ARM platforms (including Raspberry Pi OS), and the consequent OpenSSL build requirements may be problematic e.g. `Building wheel for cryptography (PEP 517): finished with status 'error'`. Refer to [cryptography installation](https://github.com/semuconsulting/pyspartn/blob/main/cryptography_installation/README.md) for assistance.
1. The optional `cryptography` package is only available as an [sdist](#sdist-vs-wheel) on some 32-bit Linux / ARM platforms and the consequent OpenSSL build requirements may be problematic e.g. `Building wheel for cryptography (PEP 517): finished with status 'error'`. Refer to [cryptography installation](https://github.com/semuconsulting/pyspartn/blob/main/cryptography_installation/README.md) for assistance.

---
## <a name="license">License</a>
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

PyGPSClient is a free, open-source, multi-platform graphical GNSS/GPS testing, diagnostic and configuration application written entirely by volunteers in Python and tkinter.
* Runs on any platform which supports a Python 3 interpreter (>=3.10) and tkinter (>=8.6) GUI framework, including Windows, MacOS, Linux and Raspberry Pi OS. Accommodates low resolution screens (>= 640x480) via resizable and/or scrollable panels.
* Supports NMEA, UBX, SBF, QGC, RTCM3, NTRIP, SPARTN, MQTT and TTY (ASCII) protocols¹.
* Supports NMEA, UBX (u-blox binary), SBF (Septentrio binary), UNI (Unicore binary), QGC (Quectel binary), RTCM3, NTRIP, SPARTN, MQTT and TTY (ASCII) protocols¹.
* Capable of reading from a variety of GNSS data streams: Serial (USB / UART), Socket (TCP / UDP), binary data stream (terminal or file capture) and binary recording (e.g. u-center \*.ubx).
* Provides [NTRIP](#ntripconfig) client facilities.
* Can serve as an [NTRIP base station](#basestation) with an RTK-compatible receiver (e.g. u-blox ZED-F9P/ZED-X20P, Quectel LG290P/LG580P/LC29H, Septentrio Mosaic G5/X5 or Unicore UM98n).
Expand Down Expand Up @@ -116,7 +116,7 @@ For more comprehensive installation instructions, please refer to [INSTALLATION.
1. To immediately disconnect and terminate all running threads, click Ctrl-K ("Kill Switch").
1. To exit the application, click
![exit icon](https://github.com/semuconsulting/PyGPSClient/blob/master/src/pygpsclient/resources/iconmonstr-door-6-24.png?raw=true), or press Ctrl-Q, or click the application window's close window icon.
1. Protocols Shown - Select which protocols to display; NMEA, UBX, SBF, QGC, RTCM3, SPARTN or TTY (NB: this only changes the displayed protocols - to change the actual protocols output by the receiver, use the [UBX Configuration Dialog](#ubxconfig)).
1. Protocols Shown - Select which protocols to display; NMEA, UBX (*u-blox binary*), SBF (*Septentrio binary*), UNI (*Unicore binary*), QGC (*Quectel binary*), RTCM3, SPARTN or TTY (*ASCII text*) (NB: this only changes the displayed protocols - to change the actual protocols output by the receiver, use the relevant configuration command(s)).
- **NB:** Serial connection must be stopped before changing to or from TTY (terminal) protocol.
- **NB:** Enabling TTY (terminal) mode will disable all other protocols.
1. Console Display - Select console display format (Parsed, Binary, Hex Tabular, Hex String, Parsed+Hex Tabular - see Console Widget below).
Expand Down Expand Up @@ -457,7 +457,7 @@ The `pygnssutils` and `pyubxutils` libraries which underpin many of the function

For further details, refer to the `pygnssutils` homepage at [https://github.com/semuconsulting/pygnssutils](https://github.com/semuconsulting/pygnssutils) or `pyubxutils` homepage at [https://github.com/semuconsulting/pyubxutils](https://github.com/semuconsulting/pyubxutils).

--
---
## <a name="knownissues">Known Issues</a>

1. Most budget USB-UART adapters (e.g. FT232, CH345, CP2102) have a bandwidth limit of around 3MB/s and may not work reliably above 115200 baud, even if the receiver supports higher baud rates. If you're using an adapter and notice significant message corruption, try reducing the baud rate to a maximum 115200.
Expand Down
4 changes: 4 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# PyGPSClient Release Notes

### RELEASE 1.6.3

1. Add support for Unicore UNI binary data output messages via `pyunigps>=0.1.3` and `pygnssutils>=1.1.22`.

### RELEASE 1.6.2

1. Add support for Unicore Secondary Antenna and Attitude (IMU) NMEA sentences e.g. UM98n "GGAH", "HPD" (requires pynmeagps>=1.1.0).
Expand Down
7 changes: 6 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,12 @@ classifiers = [
"Topic :: Scientific/Engineering :: GIS",
]

dependencies = ["requests>=2.28.0", "Pillow>=9.0.0", "pygnssutils>=1.1.21"]
dependencies = [
"requests>=2.28.0",
"Pillow>=9.0.0",
"pygnssutils>=1.1.22",
"pyunigps>=0.1.4",
]

[project.scripts]
pygpsclient = "pygpsclient.__main__:main"
Expand Down
2 changes: 1 addition & 1 deletion src/pygpsclient/_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@
:license: BSD 3-Clause
"""

__version__ = "1.6.2"
__version__ = "1.6.3"
9 changes: 5 additions & 4 deletions src/pygpsclient/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,14 @@
from types import NoneType

from pygnssutils import GNSSMQTTClient, GNSSNTRIPClient, MQTTMessage
from pygnssutils.gnssreader import ( # UNI_PROTOCOL, # TODO
from pygnssutils.gnssreader import (
NMEA_PROTOCOL,
POLL,
QGC_PROTOCOL,
RTCM3_PROTOCOL,
SBF_PROTOCOL,
UBX_PROTOCOL,
UNI_PROTOCOL,
)
from pygnssutils.socket_server import ClientHandler, ClientHandlerTLS, SocketServer
from pynmeagps import NMEAMessage
Expand All @@ -63,6 +64,7 @@
from pysbf2 import SBFMessage
from pyspartn import SPARTNMessage
from pyubx2 import UBXMessage
from pyunigps import UNIMessage
from serial import SerialException, SerialTimeoutException

from pygpsclient._version import __version__ as VERSION
Expand Down Expand Up @@ -97,7 +99,6 @@
STATUSPRIORITY,
TTY_PROTOCOL,
UNDO,
UNI_PROTOCOL,
)
from pygpsclient.gnss_status import GNSSStatus
from pygpsclient.helpers import (
Expand Down Expand Up @@ -913,8 +914,8 @@ def process_data(self, raw_data: bytes, parsed_data: object, marker: str = ""):
msgprot = SBF_PROTOCOL
elif isinstance(parsed_data, QGCMessage):
msgprot = QGC_PROTOCOL
# elif isinstance(parsed_data, UNIMessage):
# msgprot = UNI_PROTOCOL
elif isinstance(parsed_data, UNIMessage):
msgprot = UNI_PROTOCOL
elif isinstance(parsed_data, UBXMessage):
msgprot = UBX_PROTOCOL
elif isinstance(parsed_data, RTCMMessage):
Expand Down
3 changes: 3 additions & 0 deletions src/pygpsclient/chart_frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
BGCOL,
ERRCOL,
FGCOL,
INFOCOL,
PLOTCOLS,
READONLY,
TRACEMODE_WRITE,
Expand All @@ -48,6 +49,7 @@
WIDGETU6,
)
from pygpsclient.helpers import time2str
from pygpsclient.strings import CONTENTCOPIED

OL_WID = 1
LBLCOL = "white"
Expand Down Expand Up @@ -623,6 +625,7 @@ def _on_clipboard(self, event): # pylint: disable=unused-argument
self.__master.clipboard_clear()
self.__master.clipboard_append(csv)
self.__master.update()
self.__app.status_label = (CONTENTCOPIED.format("chart"), INFOCOL)

def _on_resize(self, event): # pylint: disable=unused-argument
"""
Expand Down
4 changes: 3 additions & 1 deletion src/pygpsclient/console_frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,10 @@
FORMAT_BOTH,
FORMAT_HEXSTR,
FORMAT_HEXTAB,
INFOCOL,
WIDGETU3,
)
from pygpsclient.strings import HALTTAGWARN
from pygpsclient.strings import CONTENTCOPIED, HALTTAGWARN

HALT = "HALT"
CONSOLELINES = 20
Expand Down Expand Up @@ -242,6 +243,7 @@ def _on_clipboard(self, event): # pylint: disable=unused-argument
self.__master.clipboard_clear()
self.__master.clipboard_append(self.txt_console.get("1.0", END))
self.__master.update()
self.__app.status_label = (CONTENTCOPIED.format("console"), INFOCOL)

def _on_resize(self, event): # pylint: disable=unused-argument
"""
Expand Down
27 changes: 23 additions & 4 deletions src/pygpsclient/globals.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@
PNTCOL = "#FF8000" # default plot point color

# Protocols to be used in protocol mask (others defined in pygnssutils.gnss_reader.py)
UNI_PROTOCOL = 32 # provisional - awaiting pygnssutils.gnssreader updates for Unicore
SPARTN_PROTOCOL = 256
MQTT_PROTOCOL = 512
TTY_PROTOCOL = 1024
Expand Down Expand Up @@ -252,7 +251,7 @@
TRACK = "track"
TRACEMODE_WRITE = "write"
TTYOK = ("OK", "$R:")
TTYERR = ("ERROR", "$R?")
TTYERR = ("ERROR", "$R?", "FAIL", "CAN'T FOUND DEVICE")
TTYMARKER = "TTY<<"
UBXPRESETS = "ubxpresets"
UBXSIMULATOR = "ubxsimulator"
Expand Down Expand Up @@ -424,11 +423,31 @@
"PVTGeodetic7": "RTK FIXED",
"PVTGeodetic8": "RTK FLOAT",
"PVTGeodetic10": "PPP",
"BESTNAV0": "NO FIX",
"BESTNAV1": "FIXEDPOS", # FIXEDPOS Position fixed by the FIX POSITION command
"BESTNAV2": "FIXEDHMSL", # FIXEDHEIGHT Not supported currently
"BESTNAV8": "DOPP", # DOPPLER_VELOCITY Velocity computed using instantaneous Doppler
"BESTNAV16": "3D", # SINGLE Single point positioning
"BESTNAV17": "RTK FLOAT", # SRDIFF Pseudorange differential solution
"BESTNAV18": "SBAS", # SBAS SBAS positioning
"BESTNAV32": "RTK FLOAT", # L1_FLOAT L1 float solution
"BESTNAV33": "RTK FLOAT", # IONOFREE_FLOAT Ionosphere-free float solution
"BESTNAV34": "RTK FLOAT", # NARROW_FLOAT Narrow-lane float solution
"BESTNAV48": "RTK FIXED", # L1_INT L1 fixed solution
"BESTNAV49": "RTK FIXED", # WIDE_INT Wide-lane fixed solution
"BESTNAV50": "RTK FIXED", # NARROW_INT Narrow-lane fixed solution
"BESTNAV52": "DR", # INS Inertial navigation solution
"BESTNAV53": "3D+DR", # INS_PSRSP Integrated solution of INS and single point pos
"BESTNAV54": "RTK+DR", # INS_PSRDIFF Integrated solution of INS and pseudorange diff pos
"BESTNAV55": "RTK FLOAT+DR", # INS_RTKFLOAT Integrated solution of INS and RTK float
"BESTNAV56": "RTK FIXED+DR", # INS_RTKFIXED Integrated solution of INS and RTK fix
"BESTNAV68": "RTK FIXED+DR", # INS_RTKFIXED Integrated solution of INS and RTK fix
"BESTNAV69": "PPP", # PPP
}
"""
Map of fix values to descriptions.
The keys in this map are a concatenation of NMEA/UBX
message identifier and attribute value e.g.
The keys in this map are a concatenation of
message identity and attribute value e.g.
GGA1: GGA + quality = 1
NAV-STATUS3: NAV-STATUS + gpsFix = 3
(valid for NMEA >=4)
Expand Down
14 changes: 10 additions & 4 deletions src/pygpsclient/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
from typing import Any, Literal

from pygnssutils import version as PGVERSION
from pynmeagps import WGS84_SMAJ_AXIS, NMEAMessage, haversine
from pynmeagps import WGS84_SMAJ_AXIS, NMEAMessage, haversine, leapsecond
from pynmeagps import version as NMEAVERSION
from pyqgc import version as QGCVERSION
from pyrtcm import version as RTCMVERSION
Expand All @@ -59,6 +59,7 @@
atttyp,
)
from pyubx2 import version as UBXVERSION
from pyunigps import version as UNIVERSION
from requests import get

from pygpsclient._version import __version__ as VERSION
Expand Down Expand Up @@ -107,6 +108,7 @@
"pygnssutils": PGVERSION,
"pyubx2": UBXVERSION,
"pysbf2": SBFVERSION,
"pyunigps": UNIVERSION,
"pyqgc": QGCVERSION,
"pynmeagps": NMEAVERSION,
"pyrtcm": RTCMVERSION,
Expand Down Expand Up @@ -1445,20 +1447,24 @@ def valid_geom(geom: str) -> bool:
return regexgeom.match(geom) is not None


def wnotow2date(wno: int, tow: int) -> datetime:
def wnotow2date(wno: int, tow: int, ls: int | NoneType = None) -> datetime:
"""
Get datetime from GPS Week number (Wno) and Time of Week (Tow).
Get datetime from GPS Week number (Wno), Time of Week (Tow)
and leapsecond offset.

GPS Epoch 0 = 6th Jan 1980

:param int wno: week number
:param int tow: time of week
:param int ls: leapsecond offset
:return: datetime
:rtype: datetime
"""

if ls is None:
ls = leapsecond(datetime.now())
dat = GPSEPOCH0 + timedelta(days=wno * 7)
dat += timedelta(seconds=tow)
dat += timedelta(seconds=tow - ls)
return dat


Expand Down
12 changes: 12 additions & 0 deletions src/pygpsclient/init_presets.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,20 @@
"Septentrio X5 Stop RTCM output;setDataInOut,COM1, ,none",
"Unicore UM98n Restore Factory Defaults CONFIRM; freset",
"Unicore UM98n Save Current Configuration to NVM CONFIRM; saveconfig",
"Unicore UM98n Force COLD reset CONFIRM; reset",
"Unicore UM98n Enable basic NMEA on current port; gpgsa 1; gpgga 1; gpgll 1; gpgsv 4; gpvtg 1; gprmc 1",
"Unicore UM98n Enable basic UNI on current port; PVTSLNB 1; SATSINFOB 4; BESTNAVB 1; STADOPB 1",
"Unicore UM98n Disable basic NMEA on current port; unlog gpgsa; unlog gpgga; unlog gpgll; unlog gpgsv; unlog gpvtg; unlog gprmc",
"Unicore UM98n Disable basic UNI on current port; unlog PVTSLNB; unlog SATSINFOB; unlog BESTNAVB; unlog STADOPB",
"Unicore UM98n Query Version; version",
"Unicore UM98n Query Config; config",
"Unicore UM98n Stop All Output on COM1; unlog COM1",
"Unicore UM98n Stop All Output on COM2; unlog COM2",
"Unicore UM98n Set COM1/2 Baud Rate to 115200; config com1 115200; config com2 115200",
"Unicore UM98n Set COM1/2 Baud Rate to 460800; config com1 460800; config com2 460800",
"Unicore UM98n Set Rover Mode CONFIRM; mode rover; saveconfig",
"Unicore UM98n Set Base Mode Survey-In CONFIRM; mode base time 60; rtcm1006 com1 10; rtcm1033 com1 10; rtcm1074 com1 1; rtcm1124 com1 1; rtcm1084 com1 1; rtcm1094 com1 1; saveconfig",
"Unicore UM98n Enable B2p PPP; CONFIG PPP ENABLE B2b-PPP; CONFIG PPP CONVERGE 10 20"
"Feyman IM19 Tilt Survey Setup; AT+LOAD_DEFAULT; AT+GNSS_PORT=PHYSICAL_UART2; AT+NASC_OUTPUT=UART1,ON; AT+LEVER_ARM2=0.0057,-0.0732,-0.0645; AT+CLUB_VECTOR=0,0,1.865; AT+INSTALL_ANGLE=0,180,0; AT+GNSS_CARD=OEM; AT+WORK_MODE=408; AT+CORRECT_HOLDER=ENABLE; AT+SET_PPS_EDGE=RISING; AT+AHRS=ENABLE; AT+MAG_AUTO_SAVE=ENABLE; AT+SAVE_ALL",
"Feyman IM19 System reset CONFIRM; AT+SYSTEM_RESET",
"Feyman IM19 Save the parameters CONFIRM; AT+SAVE_ALL",
Expand Down
13 changes: 7 additions & 6 deletions src/pygpsclient/settings_child_frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -456,11 +456,11 @@ def _do_layout(self):
self._chk_nmea.grid(column=1, row=0, padx=0, pady=0, sticky=W)
self._chk_ubx.grid(column=2, row=0, padx=0, pady=0, sticky=W)
self._chk_rtcm.grid(column=3, row=0, padx=0, pady=0, sticky=W)
self._chk_sbf.grid(column=1, row=1, padx=0, pady=0, sticky=W)
self._chk_qgc.grid(column=2, row=1, padx=0, pady=0, sticky=W)
self._chk_spartn.grid(column=3, row=1, padx=0, pady=0, sticky=W)
self._chk_tty.grid(column=1, row=2, padx=0, pady=0, sticky=W)
# self._chk_unicore.grid(column=2, row=2, padx=0, pady=0, sticky=W) # TODO
self._chk_unicore.grid(column=1, row=1, padx=0, pady=0, sticky=W)
self._chk_sbf.grid(column=2, row=1, padx=0, pady=0, sticky=W)
self._chk_qgc.grid(column=3, row=1, padx=0, pady=0, sticky=W)
self._chk_spartn.grid(column=1, row=2, padx=0, pady=0, sticky=W)
self._chk_tty.grid(column=2, row=2, padx=0, pady=0, sticky=W)
self._lbl_consoledisplay.grid(column=0, row=3, padx=2, pady=2, sticky=W)
self._spn_conformat.grid(
column=1, row=3, columnspan=2, padx=1, pady=2, sticky=W
Expand Down Expand Up @@ -536,6 +536,7 @@ def reset(self):
self._prot_ubx.set(cfg.get("ubxprot_b"))
self._prot_sbf.set(cfg.get("sbfprot_b"))
self._prot_qgc.set(cfg.get("qgcprot_b"))
self._prot_unicore.set(cfg.get("uniprot_b"))
self._prot_rtcm.set(cfg.get("rtcmprot_b"))
self._prot_spartn.set(cfg.get("spartnprot_b"))
self._prot_tty.set(cfg.get("ttyprot_b"))
Expand Down Expand Up @@ -588,7 +589,7 @@ def _on_update_qgcprot(self, var, index, mode):

def _on_update_uniprot(self, var, index, mode):
"""
Action on updating unicoreprot.
Action on updating uniprot.
"""

if not self._prot_tty.get():
Expand Down
11 changes: 2 additions & 9 deletions src/pygpsclient/skyview_frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,12 +110,9 @@ def update_frame(self):
"""

data = self.__app.gnss_status.gsv_data
show_unused = self.__app.configuration.get("unusedsat_b")
siv = len(data)
siv = siv if show_unused else siv - unused_sats(data)
sel = sum(1 for (_, _, ele, _, _, _) in data.values() if ele not in ("", None))
# ignore if cno are all zero and 'show_unused' is not set,
# or if elevation values are all null
# ignore if elevation values are all null
if siv <= 0 or sel <= 0:
return

Expand All @@ -125,11 +122,7 @@ def update_frame(self):
for val in sorted(data.values(), key=lambda x: x[4]): # sort by ascending C/N0
try:
gnssId, prn, ele, azi, cno, _ = val
if (
(cno == 0 and not show_unused)
or ele in ("", None)
or azi in ("", None)
):
if ele in ("", None) or azi in ("", None):
continue
x, y = self._canvas.d2xy(int(azi), int(ele))
_, ol_col = GNSS_LIST[gnssId]
Expand Down
Loading