Skip to content
Draft
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
4 changes: 2 additions & 2 deletions .github/workflows/checkpr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ jobs:
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]

steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v6
uses: actions/setup-python@v7
with:
python-version: ${{ matrix.python-version }}
- name: Install deploy dependencies
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ jobs:
release-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- name: Set up Python
uses: actions/setup-python@v6
uses: actions/setup-python@v7
with:
python-version: 3.13

Expand All @@ -23,7 +23,7 @@ jobs:
python -m build

- name: Upload distributions # upload build artifacts
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: release-dists
path: dist/
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ jobs:
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]

steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v6
uses: actions/setup-python@v7
with:
python-version: ${{ matrix.python-version }}
- name: Install deploy dependencies
Expand Down
9 changes: 8 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,12 @@
"python.systemPath": "/usr/local/bin/python3.14",
"python.defaultInterpreterPath": "${userHome}/pygpsclient/bin/python3",
//"python.defaultInterpreterPath": "C:/Users/steve/pygpsclient/Scripts/python.exe",
"python.testing.unittestEnabled": false
"python.testing.unittestEnabled": false,
"python-envs.pythonProjects": [
{
"path": ".",
"envManager": "ms-python.python:venv",
"packageManager": "ms-python.python:pip"
}
]
}
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ For more comprehensive installation instructions, please refer to [INSTALLATION.
|![sysmon widget](https://github.com/semuconsulting/PyGPSClient/blob/master/images/sysmon_widget.png?raw=true)| System Monitor widget showing device cpu, memory and I/O utilisation (*GNSS receiver must be capable of outputting UBX MON-SYS/MON-COMMS or SBF ReceiverStatus messages*). Tick checkbox to toggle between actual (cumulative) I/O stats and pending I/O. Primarily intended for u-blox modules, but can display limited system information for other devices. |
|![scatterplot widget](https://github.com/semuconsulting/PyGPSClient/blob/master/images/scatterplot_widget.png?raw=true)| Scatterplot widget showing variability in position reporting over time. (Optional) Enter fixed reference position. Select Average to center plot on dynamic average position (*displayed at top left*), or Fixed to center on fixed reference position (*if entered*). Check Autorange to set plot range automatically. Set the update interval (e.g. 4 = every 4th navigation solution). Use the range slider or mouse wheel to adjust plot range. Right-click to set fixed reference point to the current mouse cursor position. Double-click to clear the existing data. |
|![rover widget](https://github.com/semuconsulting/PyGPSClient/blob/master/images/rover_widget.png?raw=true) | Relative Position (formerly 'Rover') widget plots relative 2D bearing, distance, track and status information for the following configurations: (i) NAV-RELPOSNED - position of roving receiver relative to a fixed or moving RTK base (including an NTRIP mountpoint); (ii) RTCM 1005/6 - position of roving receiver relative to a fixed RTK NTRIP antenna reference point (ARP); (iii) NAV-DAHEADING - static heading ('yaw') from a dual-antenna receiver e.g. u-blox ZED-X20D. Double-click to clear existing plot. |
|![chart view](https://github.com/semuconsulting/PyGPSClient/blob/master/images/chart_widget.png?raw=true) | Chart widget acts as a multi-channel "plotter", allowing the user to plot a series of named numeric data attributes from any parsed GNSS data source, with configurable y (value) and x (time) axes. By default, the number of channels is set to 4, but this can be manually edited by the user via the json configuration file setting `chartsettings_d["numchn_n"]`. For each channel, user can select: (*optional*) identity of message source e.g. `NAV-PVT`; attribute name e.g. `hAcc`; scaling factor (divisor) e.g. 1000; y axis range e.g. 0 - 5. Wildcards are available for attribute groups - "\*" (average of group values), "+" (maximum of group values), "-" (minimum of group values) e.g. `cno*` will plot the average `cno` value for a group of satellites. Double-click to clear the existing data. Double-right-click to save the current chart data to the clipboard in CSV format, which can be directly pasted into a spreadsheet application. |
|![chart view](https://github.com/semuconsulting/PyGPSClient/blob/master/images/chart_widget.png?raw=true) | Chart widget acts as a multi-channel "plotter", allowing the user to plot a series of named numeric data attributes from any parsed GNSS data source, with configurable y (value) and x (time) axes. By default, the number of channels is set to 4, but this can be manually edited by the user via the json configuration file setting `chartsettings_d["numchn_n"]` or Menu..Options..App Configuration dialog (*save configuration and restart after any changes*). For each channel, user can select: (*optional*) identity of message source e.g. `NAV-PVT`; attribute name e.g. `hAcc`; scaling factor (divisor) e.g. 1000; y axis range e.g. 0 - 5. Wildcards are available for attribute groups - "\*" (average of group values), "+" (maximum of group values), "-" (minimum of group values) e.g. `cno*` will plot the average `cno` value for a group of satellites. Double-click to clear the existing data. Double-right-click to save the current chart data to the clipboard in CSV format, which can be directly pasted into a spreadsheet application. |
|![attitude widget](https://github.com/semuconsulting/PyGPSClient/blob/master/images/attitude_widget.png?raw=true) | Attitude Monitor widget (*formerly "IMU Monitor"*) showing current orientation/attitude (roll, pitch, yaw *aka 'static heading'*) and status from a variety of IMU, Dead Reckoning, Dual Antenna or other 2D/3D attitude message sources. Select range in degrees (from ±1 to ±180 degrees). |

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

### RELEASE 1.7.6

1. Add Python 3.15 classifier to pyproject.toml.
1. Update github actions to latest versions.
1. Add Chart plotter channels to App Configuration dialog.

### RELEASE 1.7.5

FIXES:
Expand Down
8 changes: 6 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ build-backend = "setuptools.build_meta:__legacy__"
[project]
name = "pygpsclient"
dynamic = ["version"]
authors = [{ name = "Steve Smith", email = "semuadmin@semuconsulting.com" }]
maintainers = [{ name = "Steve Smith", email = "semuadmin@semuconsulting.com" }]
authors = [{ name = "Steve Smith" }, { email = "semuadmin@semuconsulting.com" }]
maintainers = [
{ name = "Steve Smith" },
{ email = "semuadmin@semuconsulting.com" },
]
description = "GNSS Diagnostic and UBX Configuration GUI Application"
license = "BSD-3-Clause"
license-files = ["LICENSE"]
Expand Down Expand Up @@ -44,6 +47,7 @@ classifiers = [
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Programming Language :: Python :: 3.15",
"Topic :: Utilities",
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: Scientific/Engineering :: GIS",
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.5"
__version__ = "1.7.6"
58 changes: 45 additions & 13 deletions src/pygpsclient/app_config_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
)
MAP_INTERVALS = ("1", "2", "5", "10", "30", "60", "120", "240", "360")
LOG_SIZES = ("1", "2", "5", "10", "20", "50", "100", "200", "500", "1000")
PLOT_CHANS = [str(i) for i in range(4, 21, 2)]


class AppConfigDialog(ToplevelDialog):
Expand All @@ -89,6 +90,7 @@ def __init__(self, app: Tk, *args, **kwargs):
self._maprefresh = StringVar()
self._mapkey = StringVar()
self._logsize = StringVar()
self._plotchans = StringVar()
self._resizedialog = BooleanVar()
self._transientdialog = BooleanVar()

Expand Down Expand Up @@ -149,6 +151,17 @@ def _body(self):
wrap=True,
textvariable=self._logsize,
)
self._lbl_plotchans = Label(
self._frm_body, text="Chart Plotter Channels", anchor=W
)
self._spn_plotchans = Spinbox(
self._frm_body,
values=PLOT_CHANS,
width=6,
state=READONLY,
wrap=True,
textvariable=self._plotchans,
)
self._lbl_resizedialog = Label(
self._frm_body, text="Resizeable TopLevel Dialogs", anchor=W
)
Expand Down Expand Up @@ -181,21 +194,23 @@ def _do_layout(self):
self._lbl_guirefresh.grid(column=0, row=0, sticky=W)
self._spn_guirefresh.grid(column=1, row=0, sticky=W)
self._lbl_guirefreshu.grid(column=2, row=0, padx=2, sticky=W)
self._lbl_maprefresh.grid(column=0, row=1, sticky=W)
self._spn_maprefresh.grid(column=1, row=1, sticky=W)
self._lbl_maprefreshu.grid(column=2, row=1, padx=2, sticky=W)
self._lbl_logsize.grid(column=0, row=2, sticky=W)
self._spn_logsize.grid(column=1, row=2, sticky=W)
self._lbl_logsizeu.grid(column=2, row=2, padx=2, sticky=W)
self._frm_mapkey.grid(column=0, row=3, sticky=EW)
self._lbl_logsize.grid(column=0, row=1, sticky=W)
self._spn_logsize.grid(column=1, row=1, sticky=W)
self._lbl_logsizeu.grid(column=2, row=1, padx=2, sticky=W)
self._lbl_plotchans.grid(column=0, row=2, sticky=W)
self._spn_plotchans.grid(column=1, row=2, sticky=W)
self._lbl_maprefresh.grid(column=0, row=3, sticky=W)
self._spn_maprefresh.grid(column=1, row=3, sticky=W)
self._lbl_maprefreshu.grid(column=2, row=3, padx=2, sticky=W)
self._frm_mapkey.grid(column=0, row=4, sticky=EW)
self._lbl_mapkey.grid(column=0, row=0, sticky=W)
self._btn_mapkey.grid(column=1, row=0, sticky=E)
self._ent_mapkey.grid(column=1, row=3, columnspan=2, sticky=EW)
self._lbl_resizedialog.grid(column=0, row=4, sticky=W)
self._chk_resizedialog.grid(column=1, row=4, columnspan=2, sticky=W)
self._lbl_transientdialog.grid(column=0, row=5, sticky=W)
self._chk_transientdialog.grid(column=1, row=5, columnspan=2, sticky=W)
self._btn_save.grid(column=1, row=6, columnspan=2, padx=4, pady=3, sticky=E)
self._ent_mapkey.grid(column=1, row=4, columnspan=2, sticky=EW)
self._lbl_resizedialog.grid(column=0, row=5, sticky=W)
self._chk_resizedialog.grid(column=1, row=5, columnspan=2, sticky=W)
self._lbl_transientdialog.grid(column=0, row=6, sticky=W)
self._chk_transientdialog.grid(column=1, row=6, columnspan=2, sticky=W)
self._btn_save.grid(column=1, row=7, columnspan=2, padx=4, pady=3, sticky=E)
self._frm_body.columnconfigure(2, weight=1)

def _attach_events(self):
Expand All @@ -208,6 +223,7 @@ def _attach_events(self):
self._guirefresh,
self._maprefresh,
self._logsize,
self._plotchans,
self._resizedialog,
self._transientdialog,
self._mapkey,
Expand All @@ -224,6 +240,9 @@ def _reset(self):
)
self._maprefresh.set(self.__app.configuration.get("mapupdateinterval_n"))
self._logsize.set(int(self.__app.configuration.get("logsize_n") / 1048576))
self._plotchans.set(
int(self.__app.configuration.get("chartsettings_d")["numchn_n"])
)
self._mapkey.set(self.__app.configuration.get("mqapikey_s"))
self._resizedialog.set(int(self.__app.configuration.get("resizeable_dialog_b")))
self._transientdialog.set(
Expand All @@ -250,6 +269,10 @@ def _on_update(self, var, index, mode):
self.__app.configuration.get("logsize_n")
!= int(self._logsize.get()) * 1048576
)
| (
self.__app.configuration.get("chartsettings_d")["numchn_n"]
!= int(self._plotchans.get())
)
| (self.__app.configuration.get("mqapikey_s") != self._mapkey.get())
| (
self.__app.configuration.get("resizeable_dialog_b")
Expand Down Expand Up @@ -295,6 +318,15 @@ def _on_save_updates(self):
)
self.__app.configuration.set("mapupdateinterval_n", int(self._maprefresh.get()))
self.__app.configuration.set("logsize_n", int(self._logsize.get()) * 1048576)
chartsettings = self.__app.configuration.get("chartsettings_d")
self.__app.configuration.set(
"chartsettings_d",
{
"numchn_n": int(self._plotchans.get()),
"timrng_n": chartsettings["timrng_n"],
"maxpoints_n": chartsettings["maxpoints_n"],
},
)
self.__app.configuration.set("mqapikey_s", self._mapkey.get())
self.__app.configuration.set(
"resizeable_dialog_b", int(self._resizedialog.get())
Expand Down
6 changes: 3 additions & 3 deletions src/pygpsclient/sqlite_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,15 +187,15 @@ def open(
return SQLOK
except AttributeError as err:
self.__app.set_status_label(DLGDBSQLERR.format(err), errcol)
self.logger.debug(traceback.format_exc())
# self.logger.debug(traceback.format_exc())
return NOEXT # extensions not supported
except sqlite3.OperationalError as err:
self.__app.set_status_label(DLGDBSQLERR.format(err), errcol)
self.logger.debug(traceback.format_exc())
# self.logger.debug(traceback.format_exc())
return NOMODS # no mod_spatial extension found
except sqlite3.Error as err:
self.__app.set_status_label(DLGDBSQLERR.format(err), errcol)
self.logger.debug(traceback.format_exc())
# self.logger.debug(traceback.format_exc())
return SQLERR # other sqlite error

def close(self):
Expand Down