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
1 change: 0 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
"telemetry.feedback.enabled": false,
"python.analysis.addHoverSummaries": false,
"python-envs.defaultEnvManager": "ms-python.python:venv",
"python-envs.pythonProjects": [],
"python.venvPath": "${userHome}/pygpsclient/bin/python3",
"python.systemPath": "/usr/local/bin/python3.14",
"python.defaultInterpreterPath": "${userHome}/pygpsclient/bin/python3",
Expand Down
68 changes: 37 additions & 31 deletions README.md

Large diffs are not rendered by default.

13 changes: 12 additions & 1 deletion RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# PyGPSClient Release Notes

### RELEASE 1.6.5

FIXES:
1. Fix custom map import spurious validation error.

ENHANCEMENTS:
1. Add preset description entry field to Configuration Command Recorder import facility.
1. Make TTY Presets dialog fully resizeable.
1. Update NMEA config panel to allow preset commands to be edited or entered manually before sending.
1. Update NMEA config panel to recognise correct PAIR responses for some Quectel set and poll commands (e.g. a PAIR864 Set baud rate command corresponds to a PAIR865 poll response).

### RELEASE 1.6.4

FIXES:
Expand All @@ -8,7 +19,7 @@ FIXES:

ENHANCEMENTS:
1. Improve Base Station receiver configuration handling in the NTRIP Caster/Socket Server dialog.
1. Add connected device descriptor to status bar (e.g. "u-blox ZED-F9P", "Unicore UM981S", "Septentrio mosaic-X5" or "Quectel LG290AG03"). **FYI** This is based on a series of query messages (*one for each enabled protocol*) sent approximately 3 seconds after the connection is started (*you may see 'unknown protocol' warnings in response to some of these messages; these can be ignored)*. Failing this, a generic descriptor is displayed on receipt of a message protocol unique to a particular manufacturer (*e.g. "u-blox" on receipt of a UBX message, "Unicore" on receipt of a UNI message, etc.*). Note that some (mainly older) devices may not return a meaningful descriptor, in which case "N/A" will be displayed.
1. Add connected device descriptor to status bar (e.g. "u-blox ZED-F9P", "Unicore UM981S", "Septentrio mosaic-X5" or "Quectel LG290AG03"). **FYI** This is based on a poll of hardware information messages (*one for each enabled protocol*) sent approximately 3 seconds after the connection is started (*you may see 'unknown protocol' warnings in response to some of these messages; these can be ignored)*. Failing this, a generic descriptor is displayed on receipt of a message protocol unique to a particular manufacturer (*e.g. "u-blox" on receipt of a UBX message, "Unicore" on receipt of a UNI message, etc.*). Note that some (mainly older) devices may not return a meaningful descriptor, in which case "N/A" will be displayed. Note also that some receivers will not output hardware information or other status messages at low baud rates (< 38,400)
1. Minor updates to serial port configuration panel (additional timeout values).
1. Mininum pynmeagps version updated to v1.1.1.

Expand Down
Binary file modified images/app.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified images/nmeaconfig_widget.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified images/tty_dialog.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified images/ubxconfig_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>=9.0.0",
"pygnssutils>=1.1.22",
"pyunigps>=0.2.0",
"pynmeagps>=1.1.1",
"pynmeagps>=1.1.2",
]

[project.scripts]
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.4"
__version__ = "1.6.5"
2 changes: 1 addition & 1 deletion src/pygpsclient/dialog_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ def __init__(self):
DLGTTTY: {
CLASS: TTYPresetDialog,
DLG: None,
RESIZE: False,
RESIZE: True,
},
DLGTRECORD: {
CLASS: RecorderDialog,
Expand Down
111 changes: 51 additions & 60 deletions src/pygpsclient/dynamic_config_frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
END,
EW,
LEFT,
NE,
NSEW,
NW,
VERTICAL,
Expand All @@ -40,7 +41,6 @@
W,
)

from PIL import Image, ImageTk
from pynmeagps import (
NMEA_MSGIDS_PROP,
NMEA_PAYLOADS_POLL_PROP,
Expand All @@ -63,12 +63,6 @@

from pygpsclient.globals import (
ERRCOL,
ICON_CONFIRMED,
ICON_PENDING,
ICON_REDRAW,
ICON_SEND,
ICON_UNKNOWN,
ICON_WARNING,
INFOCOL,
NMEA_CFGOTHER,
OKCOL,
Expand Down Expand Up @@ -98,6 +92,8 @@
)
# alternative POLL dictionary names for where POLL command
# doesn't correspond to SET (fudge for Quectel)
# e.g. a PAIR864 (set baud rate) command corresponds to PAIR865 (poll baud rate)
# (for PAIR commands, the poll is typically one digit higher than the set)
ALT_POLL_NAMES = {
"AIR050": "AIR051",
"AIR058": "AIR059",
Expand All @@ -111,8 +107,8 @@
"AIR100": "AIR101",
"AIR104": "AIR105",
"AIR400": "AIR401",
"AIR410": "AIR421",
"AIR420": "AIR411",
"AIR410": "AIR411",
"AIR420": "AIR421",
"AIR432": "AIR433",
"AIR434": "AIR435",
"AIR436": "AIR437",
Expand Down Expand Up @@ -173,12 +169,6 @@ def __init__(self, app: Frame, parent: Frame, *args, **kwargs):

super().__init__(parent.container, *args, **kwargs)

self._img_send = ImageTk.PhotoImage(Image.open(ICON_SEND))
self._img_pending = ImageTk.PhotoImage(Image.open(ICON_PENDING))
self._img_confirmed = ImageTk.PhotoImage(Image.open(ICON_CONFIRMED))
self._img_warn = ImageTk.PhotoImage(Image.open(ICON_WARNING))
self._img_unknown = ImageTk.PhotoImage(Image.open(ICON_UNKNOWN))
self._img_refresh = ImageTk.PhotoImage(Image.open(ICON_REDRAW))
self._cfg_id = "" # identity of selected CFG command
self._cfg_atts = {} # this holds the attributes of the selected CFG command
self._expected_response = None
Expand Down Expand Up @@ -210,22 +200,22 @@ def _body(self):
self, orient=VERTICAL, command=self._lbx_cfg_cmd.yview
)
self._lbx_cfg_cmd.config(yscrollcommand=self._scr_cfg_cmd.set)
self._lbl_send_command = Label(self, image=self._img_pending)
self._lbl_send_command = Label(self, image=self.__container.img_none)
self._btn_send_command = Button(
self,
image=self._img_send,
image=self.__container.img_send,
width=50,
command=self._on_set_cfg,
font=self.__app.font_md,
)
self._btn_refresh = Button(
self,
image=self._img_refresh,
width=50,
image=self.__container.img_redraw,
width=40,
command=self._on_refresh,
font=self.__app.font_md,
)
self._lbl_command = Label(self, text="", width=30, anchor=W)
self._lbl_command = Label(self, text="", anchor=W)
self._frm_container = Frame(self)
self._can_container = Canvas(self._frm_container)
self._frm_attrs = Frame(self._can_container)
Expand All @@ -246,31 +236,29 @@ def _do_layout(self):
Layout widgets.
"""

self._lbl_cfg_dyn.grid(column=0, row=0, columnspan=4, padx=3, sticky=EW)
self._lbx_cfg_cmd.grid(
column=0, row=1, columnspan=2, rowspan=6, padx=3, pady=3, sticky=EW
)
self._scr_cfg_cmd.grid(column=1, row=1, rowspan=6, sticky=(N, S, E))
self._btn_send_command.grid(column=3, row=1, ipadx=3, ipady=3, sticky=W)
self._lbl_send_command.grid(column=3, row=2, ipadx=3, ipady=3, sticky=W)
self._btn_refresh.grid(column=3, row=3, ipadx=3, ipady=3, sticky=W)
self._lbl_command.grid(column=0, row=7, columnspan=4, padx=3, sticky=EW)
self._lbl_cfg_dyn.grid(column=0, row=0, columnspan=3, sticky=EW)
self._lbx_cfg_cmd.grid(column=0, row=1, columnspan=1, sticky=EW)
self._scr_cfg_cmd.grid(column=1, row=1, sticky=(N, S, W))
self._btn_send_command.grid(column=2, row=1, ipadx=3, ipady=3, sticky=NE)
self._lbl_send_command.grid(column=3, row=1, ipadx=3, ipady=3, sticky=NE)
self._btn_refresh.grid(column=4, row=1, ipadx=3, ipady=3, sticky=NE)
self._lbl_command.grid(column=0, row=2, columnspan=5, sticky=EW)
self._frm_container.grid(
column=0, row=8, columnspan=4, rowspan=15, padx=3, sticky=NSEW
column=0,
row=3,
columnspan=5,
rowspan=1,
sticky=NSEW,
)
self._can_container.grid(
column=0, row=0, columnspan=3, rowspan=15, padx=3, sticky=NSEW
column=0,
row=0,
columnspan=5,
rowspan=1,
sticky=NSEW,
)
self._scr_container_ver.grid(column=3, row=0, rowspan=15, sticky=(N, S, E))
self._scr_container_hor.grid(
column=0, row=15, columnspan=4, rowspan=15, sticky=EW
)

cols, rows = self.grid_size()
for i in range(cols):
self.grid_columnconfigure(i, weight=1)
for i in range(rows):
self.grid_rowconfigure(i, weight=1)
self._scr_container_ver.grid(column=5, row=0, sticky=(N, S, W))
self._scr_container_hor.grid(column=0, row=2, columnspan=5, sticky=EW)
self.option_add("*Font", self.__app.font_sm)

def _attach_events(self):
Expand All @@ -297,7 +285,7 @@ def reset(self):
self._lbx_cfg_cmd.insert(i, cmd)

self._clear_widgets()
self._lbl_send_command.config(image=self._img_unknown)
self._lbl_send_command.config(image=self.__container.img_unknown)

def _setscroll(self, event): # pylint: disable=unused-argument
"""
Expand Down Expand Up @@ -379,7 +367,7 @@ def _on_set_cfg(self, *args, **kwargs): # pylint: disable=unused-argument

# send message, update status and await response
self.__container.send_command(msg)
self._lbl_send_command.config(image=self._img_pending)
self._lbl_send_command.config(image=self.__container.img_pending)
self.__container.status_label = f"P{self._cfg_id} SET message sent"
for msgid in pendcfg:
self.__container.set_pending(msgid, penddlg)
Expand All @@ -406,7 +394,7 @@ def _do_poll_cfg(self, *args, **kwargs): # pylint: disable=unused-argument
return

msg = penddlg = pendcfg = None
# use alternate names for some NMEA PAIR/PQTM POLL commands
# use alternate name for some NMEA PAIR/PQTM POLL commands
cfg_id = ALT_POLL_NAMES.get(self._cfg_id, self._cfg_id)
# set any POLL arguments to specified or default values
# e.g. portid = "1", msgname="RMC"
Expand All @@ -428,13 +416,13 @@ def _do_poll_cfg(self, *args, **kwargs): # pylint: disable=unused-argument
if msg is not None:
self.__container.send_command(msg)
self.__container.status_label = f"{cp}{cfg_id} POLL message sent"
self._lbl_send_command.config(image=self._img_pending)
self._lbl_send_command.config(image=self.__container.img_pending)
for msgid in pendcfg:
self.__container.set_pending(msgid, penddlg)
self._expected_response = POLL
else: # CFG cannot be POLLed
self.__container.status_label = f"{cp}{cfg_id} No POLL available"
self._lbl_send_command.config(image=self._img_unknown)
self._lbl_send_command.config(image=self.__container.img_unknown)

def _do_poll_args(self, cfg_id: str) -> dict:
"""
Expand Down Expand Up @@ -477,23 +465,26 @@ def _do_poll_args(self, cfg_id: str) -> dict:
pass
return args

def update_status(self, msg: object):
def update_status(self, msg: NMEAMessage | UBXMessage):
"""
UBXHandler or NMEAHandler module has received expected command response
and forwarded it to this module, entry widgets are pre-populated with
current configuration values and confirmation status is updated.

:param object msg: UBXMessage or NMEAMessage response
:param NMEAMessage | UBXMessage msg: UBXMessage or NMEAMessage response
"""

# self.logger.debug(f"{msg.identity=}")
ok = False
# strip off any variant suffix from cfg_id
# e.g. "QTMCFGUART_CURR" -> "PQTMCFGUART"
cfg_id = (
"P" + self._cfg_id.rsplit("_", 1)[0]
if self._protocol == NMEA
else self._cfg_id
)
if self._protocol == NMEA:
# use alternate name for some NMEA PAIR/PQTM POLL commands
# e.g. a PAIR864 (set baud rate) command corresponds to PAIR865 (poll baud rate)
cfg_id = ALT_POLL_NAMES.get(self._cfg_id, self._cfg_id)
# strip off any variant suffix from cfg_id
# e.g. "QTMCFGUART_CURR" -> "PQTMCFGUART"
cfg_id = "P" + cfg_id.rsplit("_", 1)[0]
else:
cfg_id = self._cfg_id

# if this message identity matches an expected response
if msg.identity in (cfg_id, ACK, NAK):
Expand All @@ -513,10 +504,10 @@ def update_status(self, msg: object):
f"{cfg_id} message acknowledged",
OKCOL,
)
self._lbl_send_command.config(image=self._img_confirmed)
self._lbl_send_command.config(image=self.__container.img_confirmed)
else:
self.__container.status_label = (f"{cfg_id} message rejected", ERRCOL)
self._lbl_send_command.config(image=self._img_warn)
self._lbl_send_command.config(image=self.__container.img_warn)
self.update()

def _clear_widgets(self):
Expand All @@ -530,13 +521,13 @@ def _clear_widgets(self):
wdg.destroy()
wdg = None
Label(self._frm_attrs, text="Attribute", width=12, anchor=W).grid(
column=0, row=0, padx=3, sticky=(W)
column=0, row=0, padx=3, sticky=W
)
Label(self._frm_attrs, text="Value", width=20, anchor=W).grid(
column=1, row=0, padx=3, sticky=(W)
column=1, row=0, padx=3, sticky=W
)
Label(self._frm_attrs, text="Type", width=5, anchor=W).grid(
column=2, row=0, padx=3, sticky=(W)
column=2, row=0, padx=3, sticky=W
)

def _add_widgets(self, pdict: dict, row: int, index: int) -> int:
Expand Down
2 changes: 2 additions & 0 deletions src/pygpsclient/globals.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,8 @@
VALDMY = 8
VALLEN = 9
VALBOOL = 10
VALREGEX = 11
VALCUSTOM = 12
WAYPOINT = "waypoint"
WIDGETU1 = (200, 200) # small widget size
WIDGETU2 = (300, 200) # medium widget size
Expand Down
39 changes: 16 additions & 23 deletions src/pygpsclient/hardware_info_frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
:license: BSD 3-Clause
"""

from tkinter import Frame, Label, W
from tkinter import EW, Frame, Label, W

from PIL import Image, ImageTk

Expand Down Expand Up @@ -62,38 +62,31 @@ def _body(self):
"""

self._lbl_hwverl = Label(self, text="Hardware")
self._lbl_hwver = Label(self)
self._lbl_hwver = Label(self, anchor=W)
self._lbl_swverl = Label(self, text="Software")
self._lbl_swver = Label(self)
self._lbl_swver = Label(self, anchor=W)
self._lbl_fwverl = Label(self, text="Firmware")
self._lbl_fwver = Label(self)
self._lbl_fwver = Label(self, anchor=W)
self._lbl_romverl = Label(self, text="Protocol")
self._lbl_romver = Label(self)
self._lbl_romver = Label(self, anchor=W)
self._lbl_gnssl = Label(self, text="GNSS/AS")
self._lbl_gnss = Label(self)
self._lbl_gnss = Label(self, anchor=W)

def _do_layout(self):
"""
Layout widgets.
"""

self._lbl_hwverl.grid(column=0, row=0, padx=2, sticky=W)
self._lbl_hwver.grid(column=1, row=0, columnspan=2, padx=2, sticky=W)
self._lbl_swverl.grid(column=3, row=0, padx=2, sticky=W)
self._lbl_swver.grid(column=4, row=0, columnspan=2, padx=2, sticky=W)
self._lbl_fwverl.grid(column=0, row=1, padx=2, sticky=W)
self._lbl_fwver.grid(column=1, row=1, columnspan=2, padx=2, sticky=W)
self._lbl_romverl.grid(column=3, row=1, padx=2, sticky=W)
self._lbl_romver.grid(column=4, row=1, columnspan=2, padx=2, sticky=W)
self._lbl_gnssl.grid(column=0, row=2, columnspan=1, padx=2, sticky=W)
self._lbl_gnss.grid(column=1, row=2, columnspan=4, padx=2, sticky=W)

cols, rows = self.grid_size()
for i in range(cols):
self.grid_columnconfigure(i, weight=1)
for i in range(rows):
self.grid_rowconfigure(i, weight=1)
# self.option_add("*Font", self.__app.font_sm)
self._lbl_hwverl.grid(column=0, row=0, padx=3, sticky=W)
self._lbl_hwver.grid(column=1, row=0, columnspan=2, padx=3, sticky=EW)
self._lbl_swverl.grid(column=3, row=0, padx=3, sticky=W)
self._lbl_swver.grid(column=4, row=0, columnspan=2, padx=3, sticky=EW)
self._lbl_fwverl.grid(column=0, row=1, padx=3, sticky=W)
self._lbl_fwver.grid(column=1, row=1, columnspan=2, padx=3, sticky=EW)
self._lbl_romverl.grid(column=3, row=1, padx=3, sticky=W)
self._lbl_romver.grid(column=4, row=1, columnspan=2, padx=3, sticky=EW)
self._lbl_gnssl.grid(column=0, row=2, padx=3, sticky=W)
self._lbl_gnss.grid(column=1, row=2, columnspan=4, padx=3, sticky=EW)

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