Skip to content
Open
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
17 changes: 8 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,14 +177,15 @@ For more comprehensive installation instructions, please refer to [INSTALLATION.

#### <a name="refreshrate">GUI refresh rate setting</a>

30. PyGPSClient processes all incoming GNSS data in 'real time' but, by default, the GUI is only refreshed every 0.5 seconds. The refresh rate can be manually configured via the `guiupdateinterval_f` setting in the json configuration file. **NB:** PyGPSClient may become unresponsive on slower platforms (e.g. Raspberry Pi) at high message rates if the GUI update interval is less than 0.2 seconds.
30. PyGPSClient processes all incoming GNSS data in 'real time' but, by default, the GUI is only refreshed every 0.5 seconds. The refresh rate can be manually configured via the `guiupdateinterval_f` setting in the json configuration file or Menu..Options..GUI Configuration dialog (*save configuration and restart after any changes*). **NB:** PyGPSClient may become unresponsive on slower platforms (e.g. Raspberry Pi) at high message rates if the GUI update interval is less than 0.2 seconds.

#### <a name="transient">Toplevel ('pop-up') dialog setting</a>

31. The behaviour of Toplevel ('pop-up') dialogs will depend on the screen resolution and 'transient' setting. If the width or height of a Toplevel dialog exceeds the screen resolution, the dialog will be displayed in a scrollable, resizeable window. Otherwise, the dialog is displayed as a fixed, non-resizeable panel.
- A boolean configuration setting `transient_dialog_b` governs whether Toplevel dialogs are 'transient' (i.e. always on top of main application dialog) or not. Changing this setting to `0` allows Toplevel dialogs to be minimised independently of the main application window, but be mindful that some dialogs may end up hidden behind others e.g. "Open file/folder" dialogs. **If a file open button appears unresponsive, check that the "Open file/folder" panel isn't already open but obscured**.
- If you're accessing the desktop via a VNC session (e.g. to a headless Raspberry Pi) it is recommended to keep the setting at the default `1`, as VNC may not recognise keystrokes on overlaid non-transient windows.
- A boolean configuration setting `resizeable_dialog_b` governs whether *all* Toplevel dialogs are resizeable, irrespective of the default setting in `DialogState`. Setting this to '1' provides a workaround for issues with some scaled Linux Wayland displays.
- Settings can be updated via the Menu..Options..GUI Configuration dialog (*save configuration and restart after any changes*).

#### <a name="widgets">User-selectable widgets</a>
---
Expand Down Expand Up @@ -235,11 +236,9 @@ For more comprehensive installation instructions, please refer to [INSTALLATION.
**Legacy UBX:**

1. Version panel shows current device hardware/firmware versions (*Double-left-click to refresh*).
1. Protocol Configuration panel (CFG-PRT) sets baud rate and inbound/outbound protocols across all available ports (*legacy protocols only*).
1. Solution Rate panel (CFG-RATE) sets navigation solution interval in ms (e.g. 1000 = 1/second) and measurement ratio (ratio between the number of measurements and the number of navigation solutions, e.g. 5 = five measurements per navigation solution) (*legacy protocols only*).
1. For each of the panels above, clicking anywhere in the panel background will refresh the displayed information with the current configuration.
1. Message Rate panel (CFG-MSG) sets message rates per port for UBX and NMEA messages (*legacy protocols only*). Message rate is relative to navigation solution frequency e.g. a message rate of '4' means 'every 4th navigation solution' (higher = less frequent).
1. UBX Legacy Command configuration panel providing structured updates for a range of legacy CFG-* configuration commands for *legacy protocols only*. Note: 'X' (byte) type attributes can be entered as integers or hexadecimal strings e.g. 522125312 or 0x1f1f0000. Once a command is selected, the configuration is polled and the current values displayed. The user can then amend these values as required and send the updated configuration. Some polls require input arguments (e.g. portID) - these are highlighted and will be set at default values initially (e.g. portID = 0), but can be amended by the user and re-polled using the ![refresh](https://github.com/semuconsulting/PyGPSClient/blob/master/src/pygpsclient/resources/iconmonstr-refresh-lined-24.png?raw=true) button.
1. Protocol Configuration panel (CFG-PRT) sets baud rate and inbound/outbound protocols across all available ports. Clicking anywhere in the panel background will refresh the displayed information with the current configuration.
1. Message Rate panel (CFG-MSG) sets message rates per port for UBX and NMEA messages. Message rate is relative to navigation solution frequency e.g. a message rate of '4' means 'every 4th navigation solution' (higher = less frequent).
1. UBX Legacy Command configuration panel providing structured updates for a range of legacy CFG-* configuration commands. Note: 'X' (byte) type attributes can be entered as integers or hexadecimal strings e.g. 522125312 or 0x1f1f0000. Once a command is selected, the configuration is polled and the current values displayed. The user can then amend these values as required and send the updated configuration. Some polls require input arguments (e.g. portID) - these are highlighted and will be set at default values initially (e.g. portID = 0), but can be amended by the user and re-polled using the ![refresh](https://github.com/semuconsulting/PyGPSClient/blob/master/src/pygpsclient/resources/iconmonstr-refresh-lined-24.png?raw=true) button.

---
## <a name="nmeaconfig">NMEA Configuration Facilities</a>
Expand Down Expand Up @@ -454,12 +453,12 @@ facility is *not* intended to be used for real time navigational purposes.

Once you have received the API key (a 32-character alphanumeric string), you can (in order of precedence):

1. Copy it to the `"mqapikey_s":` value in your json configuration file (see example provided).
1. Copy it to the `"mqapikey_s":` value in your json configuration file (see example provided), or via the Menu..Options..GUI Configuration dialog.
2. Create an environment variable named `MQAPIKEY` (all upper case) and set this to the API key value. It is recommended
that this is a User variable rather than a System/Global variable.
3. Pass it via command line argument `--mqapikey`.

*The web map refresh rate can be amended if required by changing the `mapupdateinterval_n:` value in your json configuration file.
\*The web map refresh rate can be amended if required by changing the `mapupdateinterval_n:` value in your json configuration file, or via the Menu..Options..GUI Configuration Options dialog (*save configuration and restart after any changes*).

---
## <a name="userdefined">User Defined Presets</a>
Expand Down Expand Up @@ -514,7 +513,7 @@ For further details, refer to the `pygnssutils` homepage at [https://github.com/

1. There is a known issue with PyGPSClient GUI refreshes becoming progressively slower on certain platforms if the app is left unattended (_i.e. no user interaction_) for an extended period - typically 30 minutes or more. The issue is more pronounced on low-end SBC platforms like the Raspberry Pi. **Underlying processing (including message parsing and datalogging) is unaffected**, and the GUI can generally be 'woken up' within a few seconds via a simple user interaction e.g. resizing the main panel. The root cause of this issue is under investigation, but as a workaround, users can try a) increasing the `guiupdateinterval_f` setting in the json configuration file, or b) hiding some or all user-selectable widgets until needed.

2. **NB:** The latest version of Python for MacOS (>=3.14.5) comes with a new version of tkinter (9.0). There appear to be fairly serious performance issues with this version on MacOS Tahoe which render the PyGPSClient GUI somewhat sluggish. For the time being, it is recommended that users use >=3.14.4. This issue does *not* affect other operating systems or Python apps not using tkinter.
2. **NB:** The latest version of Python for MacOS (>=3.14.5) comes with a new version of tkinter (9.0). Early iterations of this version (3.14.5, 3.14.6) displayed fairly serious performance issues on MacOS Sonoma & Tahoe. The issue appears to have been resolved in version >=3.14.7 (tkinter 9.0.4), but if you experience any compatibility or performance issues with tkinter 9.0, consider reverting to Python <=3.14.4. This issue does *not* affect other operating systems or Python apps not using tkinter.

3. If you encounter persistent `WARNING>>Error parsing data stream Serial stream terminated unexpectedly` messages in the console, this may be indicative of insufficient serial port bandwidth (baudrate or timeout) for the current output message cohort (*particularly if this includes raw Ephemerides or Observation data*). Try increasing the baudrate in the first instance.

Expand Down
9 changes: 9 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# PyGPSClient Release Notes

### RELEASE 1.7.4

ENHANCEMENTS:

1. Add GUI Configuration Options panel to set internal GUI configuration parameters (*previously only available via manual edits of json file*). **NB**: User must save configuration and restart app after any changes.
1. Add "Toggle Settings" button to Banner panel for quick access to Settings (docked or undocked).
1. Streamline UBX Legacy Configuration panel for improved access on low res displays.
1. Enhanced validation messaging in UBX and NMEA configuration panels.

### RELEASE 1.7.3

FIXES:
Expand Down
16 changes: 8 additions & 8 deletions docs/pygpsclient.rst
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,14 @@ pygpsclient.gpx\_dialog module
:undoc-members:
:show-inheritance:

pygpsclient.gui\_config\_dialog module
--------------------------------------

.. automodule:: pygpsclient.gui_config_dialog
:members:
:undoc-members:
:show-inheritance:

pygpsclient.hardware\_info\_frame module
----------------------------------------

Expand Down Expand Up @@ -500,14 +508,6 @@ pygpsclient.ubx\_preset\_frame module
:undoc-members:
:show-inheritance:

pygpsclient.ubx\_solrate\_frame module
--------------------------------------

.. automodule:: pygpsclient.ubx_solrate_frame
:members:
:undoc-members:
:show-inheritance:

pygpsclient.uni\_handler module
-------------------------------

Expand Down
Binary file modified images/ubxconfiglegacy_widget.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ dependencies = [
"Pillow>=12.0.0",
"pygnssutils>=1.2.7",
"pyunigps>=1.0.0",
"pynmeagps>=1.1.5",
"pynmeagps>=1.1.7",
"pyubx2>=1.3.5",
]

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.7.3"
__version__ = "1.7.4"
95 changes: 59 additions & 36 deletions src/pygpsclient/about_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,18 @@

import logging
from platform import machine, python_version
from tkinter import Button, Checkbutton, Frame, IntVar, Label, Tcl
from tkinter import (
CENTER,
EW,
NSEW,
Button,
Checkbutton,
Frame,
IntVar,
Label,
Tcl,
ttk,
)
from webbrowser import open_new_tab

from PIL import Image, ImageTk
Expand All @@ -21,7 +32,6 @@
CLICK_CURSOR,
ERRCOL,
ICON_APP128,
ICON_GITHUB,
ICON_SPONSOR,
INFOCOL,
LICENSE_URL,
Expand Down Expand Up @@ -61,7 +71,6 @@ def __init__(self, app, *args, **kwargs): # pylint: disable=unused-argument
self.__app = app # Reference to main application class
self.logger = logging.getLogger(__name__)
self._img_icon = ImageTk.PhotoImage(Image.open(ICON_APP128).resize((64, 64)))
self._img_github = ImageTk.PhotoImage(Image.open(ICON_GITHUB).resize((32, 32)))
self._img_sponsor = ImageTk.PhotoImage(Image.open(ICON_SPONSOR))
self._checkonstartup = IntVar()
self._checkonstartup.set(self.__app.configuration.get("checkforupdate_b"))
Expand All @@ -79,16 +88,23 @@ def _body(self):
"""

self._frm_body = Frame(self.container)
self._lbl_icon = Label(self._frm_body, image=self._img_icon, borderwidth=0)
self._lbl_descs = []
for txt in ABOUTTXT:
self._lbl_descs.append(
Label(
self._frm_body,
text=txt,
borderwidth=0,
)
)
self._lbl_icon = Label(
self._frm_body, image=self._img_icon, borderwidth=0, anchor=CENTER
)
self._lbl_desc = Label(
self._frm_body,
text=ABOUTTXT,
wraplength=400,
justify=CENTER,
anchor=CENTER,
)
self._lbl_github = Label(
self._frm_body,
text=GITHUB_URL,
foreground=INFOCOL,
cursor=CLICK_CURSOR,
anchor=CENTER,
)
tkv = Tcl().call("info", "patchlevel")
self._lbl_python_version = Label(
self._frm_body,
Expand All @@ -97,14 +113,16 @@ def _body(self):
f"Python: {python_version()} Tk: {tkv} "
f"Spatial: {SQLSTATUS[self.__app.db_enabled]}"
),
anchor=CENTER,
)
self._lbl_lib_versions = []
for nam, ver in LIBVERSIONS.items():
self._lbl_lib_versions.append(
Label(
self._frm_body,
text=f"{nam}: {ver}",
borderwidth=0,
anchor=CENTER,
border=0,
highlightthickness=0,
)
)
Expand All @@ -123,44 +141,49 @@ def _body(self):
self._frm_body,
image=self._img_sponsor,
cursor=CLICK_CURSOR,
)
self._lbl_github = Label(
self._frm_body,
text=GITHUB_URL,
fg=INFOCOL,
cursor=CLICK_CURSOR,
anchor=CENTER,
)
self._lbl_copyright = Label(
self._frm_body,
text=COPYRIGHT,
foreground=INFOCOL,
cursor=CLICK_CURSOR,
anchor=CENTER,
)

def _do_layout(self):
"""
Arrange widgets in dialog.
"""

i = 0
self._frm_body.grid(column=0, row=0, padx=5, pady=5, ipadx=5, ipady=5)
self._lbl_icon.grid(column=0, row=1, columnspan=2, padx=3, pady=0)
for i, lbl in enumerate(self._lbl_descs):
lbl.grid(column=0, row=2 + i, columnspan=2, padx=3, pady=0)
self._lbl_python_version.grid(column=0, row=3 + i, columnspan=2, padx=3, pady=1)
n = 4 + i
self._frm_body.grid(column=0, row=0, ipadx=2, ipady=2, sticky=NSEW)
self._lbl_icon.grid(column=0, row=0, columnspan=2, padx=3, pady=0, sticky=EW)
self._lbl_desc.grid(column=0, row=1, columnspan=2, padx=3, pady=0, sticky=EW)
self._lbl_github.grid(column=0, row=2, columnspan=2, padx=3, pady=0, sticky=EW)
ttk.Separator(self._frm_body).grid(
column=0, row=3, columnspan=2, padx=3, pady=3, sticky=EW
)
self._lbl_python_version.grid(
column=0, row=4, columnspan=2, padx=3, pady=1, sticky=EW
)
for i, lbl in enumerate(self._lbl_lib_versions):
lbl.grid(column=0, row=n + i, columnspan=2, padx=2)
lbl.grid(column=0, row=5 + i, columnspan=2, padx=2, pady=0, sticky=EW)
lv = len(self._lbl_lib_versions)
self._btn_checkupdate.grid(
column=0, row=1 + n + i, ipadx=3, ipady=3, padx=3, pady=3
column=0, row=6 + lv, ipadx=3, ipady=3, padx=3, pady=3
)
self._chk_checkupdate.grid(
column=1, row=1 + n + i, ipadx=3, ipady=3, padx=3, pady=3
column=1, row=6 + lv, ipadx=3, ipady=3, padx=3, pady=3
)
ttk.Separator(self._frm_body).grid(
column=0, row=7 + lv, columnspan=2, padx=3, pady=3, sticky=EW
)
self._lbl_sponsoricon.grid(
column=0, row=2 + n + i, columnspan=2, padx=3, pady=3
column=0, row=8 + lv, columnspan=2, padx=3, pady=3, sticky=EW
)
self._lbl_copyright.grid(
column=0, row=9 + lv, columnspan=2, padx=3, pady=3, sticky=EW
)
self._lbl_github.grid(column=0, row=3 + n + i, columnspan=2, padx=3, pady=0)
self._lbl_copyright.grid(column=0, row=4 + n + i, columnspan=2, padx=3, pady=0)

def _attach_events(self):
"""
Expand Down Expand Up @@ -238,7 +261,7 @@ def _check_for_update(self, *args, **kwargs): # pylint: disable=unused-argument
txt += f" - Latest version is {latest}"
col = ERRCOL
self._lbl_lib_versions[i]["text"] = txt
self._lbl_lib_versions[i]["fg"] = col
self._lbl_lib_versions[i]["foreground"] = col
updates = [nam for (nam, current, latest) in versions if latest != current]
if len(updates) > 0:
self.set_status_label("Updates available", OKCOL)
Expand Down Expand Up @@ -272,10 +295,10 @@ def _set_update_btn_mode(self, update: bool):

if update:
self._btn_checkupdate["text"] = "UPDATE"
self._btn_checkupdate["fg"] = OKCOL
self._btn_checkupdate["foreground"] = OKCOL
self._btn_checkupdate.bind("<Button>", self._do_update)
else:
self._btn_checkupdate["text"] = "CHECK FOR UPDATES"
self._btn_checkupdate["fg"] = INFOCOL
self._btn_checkupdate["foreground"] = INFOCOL
self._btn_checkupdate.bind("<Button>", self._check_for_update)
self.__app.update_idletasks()
18 changes: 14 additions & 4 deletions src/pygpsclient/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,8 @@ def _do_layout(self):
# dynamically instantiate and position widgets in frm_widgets
col = 0
row = 0
for i, (name, state) in enumerate(self.widget_state.state.items()):
i = 0
for name, state in self.widget_state.state.items():
# setup widget
frm = self.widget_setup(state)
# set corresponding View menu label to 'Show' or 'Hide'
Expand All @@ -345,6 +346,7 @@ def _do_layout(self):
row += 1
else:
col += c
i += 1

# set column and row weights to control 'pack' behaviour of main layout
self.grid_columnconfigure(0, weight=1)
Expand Down Expand Up @@ -509,17 +511,20 @@ def set_event(self, evt: str):

self.event_generate(evt)

def load_config(self):
def load_config(self) -> str:
"""
Load configuration file menu option.

:return: error code
:rtype: str
"""

# Warn if Streaming, NTRIP or SPARTN clients are running
if self.conn_status == DISCONNECTED and self.rtk_conn_status == DISCONNECTED:
self.set_status_label("", OKCOL)
else:
self.set_status_label(DLGSTOPRTK, ERRCOL)
return
return "cancelled"

_, err = self.configuration.loadfile()
if err == "": # load succeeded
Expand All @@ -533,10 +538,14 @@ def load_config(self):
self._do_layout()
elif err == "cancelled":
pass
return err

def save_config(self):
def save_config(self) -> str:
"""
Save configuration file menu option.

:return: error code
:rtype: str
"""

# save current screen geometry
Expand All @@ -549,6 +558,7 @@ def save_config(self):
pass
else: # save failed
self.set_status_label(SAVECONFIGBAD.format(err), ERRCOL)
return err

def update_widgets(self):
"""
Expand Down
Loading