From d3fe493b8b81f5eb8bc5d7f303b8220a3ebf201d Mon Sep 17 00:00:00 2001 From: SEMU Admin <28569967+semuadmin@users.noreply.github.com> Date: Sat, 13 Jun 2026 08:27:24 +0100 Subject: [PATCH 1/3] update rinex dialog --- README.md | 2 +- RELEASE_NOTES.md | 6 ++ pyproject.toml | 10 +- src/pygpsclient/__main__.py | 4 +- src/pygpsclient/_version.py | 2 +- src/pygpsclient/init_presets.py | 1 + src/pygpsclient/rinex_dialog.py | 175 +++++++++++++++++++++++--------- src/pygpsclient/strings.py | 1 + tests/test_static.py | 4 +- 9 files changed, 148 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index ebbd25e4..45d10567 100644 --- a/README.md +++ b/README.md @@ -430,7 +430,7 @@ The RINEX Conversion Dialog supports the conversion of raw observation, navigati 1. A previously-saved binary datalog containing raw observation (UBX RXM-RAWX), navigation (UBX RXM-SFRBX¹) and/or meteorology (NMEA) data or RTCM3 ephemerides (1019, 1020, 1041-1046) messages. A suitable datalog can be recorded using PyGPSClient's [binary datalogging](#datalog) facility. **NB**: The file should contain at least 15-30 minutes of continuous data. - ¹ Currently only GPS LNAV/CNAV, Galileo FNAV/INAV and Beidou D1/D2 data is supported by the [pygnssutils RINEX NAV conversion utility](https://github.com/semuconsulting/pygnssutils#rinexconvert). This will be enhanced in future releases. + ¹ The alpha release of the [pygnssutils RINEX NAV conversion utility](https://github.com/semuconsulting/pygnssutils#rinexconvert) currently implements GPS LNAV/CNAV, Galileo FNAV/INAV, Beidou D1/D2 and Glonass L1OF signal types. This will be enhanced in future releases. **Instructions:** diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 1917b699..b329eb09 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,5 +1,11 @@ # PyGPSClient Release Notes +### RELEASE 1.7.0 + +1. Minimum pyubx2 version updated to 1.3.3 (adds new firmware configuration database items for u-blox X20 HPG 2.10). +1. Default log level amended to '0' (ERROR) rather than '-1' (CRITICAL). Any logged exception tracebacks will now appear in terminal logs by default. +1. Updates to RINEX conversion dialog for pygnssutil>=1.2.3 (incorporates various bug fixes to RINEX conversion routines, but still an experimental facility). + ### RELEASE 1.6.10 1. Minor enhancements to UBX Config Dialog confirmation signalling. diff --git a/pyproject.toml b/pyproject.toml index 73892577..495b44ad 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,12 +50,12 @@ classifiers = [ ] dependencies = [ - "requests>=2.28.0", - "Pillow>=9.0.0", - "pygnssutils>=1.2.2", + "requests>=2.34.0", + "Pillow>=12.0.0", + "pygnssutils>=1.2.4", "pyunigps>=1.0.0", - "pynmeagps>=1.1.4", - "pyubx2>=1.3.1", + "pynmeagps>=1.1.5", + "pyubx2>=1.3.3", ] [project.scripts] diff --git a/src/pygpsclient/__main__.py b/src/pygpsclient/__main__.py index 076bc76d..fc951336 100644 --- a/src/pygpsclient/__main__.py +++ b/src/pygpsclient/__main__.py @@ -129,7 +129,7 @@ def main(): VERBOSITY_DEBUG, VERBOSITY_CRITICAL, ], - default=VERBOSITY_CRITICAL, + default=VERBOSITY_LOW, ) ap.add_argument( "--logtofile", @@ -139,7 +139,7 @@ def main(): kwargs = vars(ap.parse_args()) # set up global logging configuration - verbosity = int(kwargs.pop("verbosity", VERBOSITY_CRITICAL)) + verbosity = int(kwargs.pop("verbosity", VERBOSITY_LOW)) logtofile = kwargs.pop("logtofile", "") logger = getLogger(APPNAME) # "pygpsclient" logger_utils = getLogger("pygnssutils") diff --git a/src/pygpsclient/_version.py b/src/pygpsclient/_version.py index a97d72e2..32122df4 100644 --- a/src/pygpsclient/_version.py +++ b/src/pygpsclient/_version.py @@ -8,4 +8,4 @@ :license: BSD 3-Clause """ -__version__ = "1.6.10" +__version__ = "1.7.0" diff --git a/src/pygpsclient/init_presets.py b/src/pygpsclient/init_presets.py index cad62c19..e2a0b026 100644 --- a/src/pygpsclient/init_presets.py +++ b/src/pygpsclient/init_presets.py @@ -60,6 +60,7 @@ "Set UART1 Baudrate to 115200, CFG, CFG-VALSET, 000100000100524000c20100, 1", "Set UART2 Baudrate to 115200, CFG, CFG-VALSET, 000100000100534000c20100, 1", "Poll UART1/2 Baudrates, CFG, CFG-VALGET, 000000000100524001005340, 2", + "ZED-X20 Enable Galileo HAS, CFG, CFG-VALSET, 0001000001000d1000, 1, CFG, CFG-VALSET, 0001000002000d1001, 1", ], "nmeapresets_l": [ "Quectel LG* Restore Factory Defaults CONFIRM; P; QTMRESTOREPAR; ; 1", diff --git a/src/pygpsclient/rinex_dialog.py b/src/pygpsclient/rinex_dialog.py index 2d8e7b46..a9e970db 100644 --- a/src/pygpsclient/rinex_dialog.py +++ b/src/pygpsclient/rinex_dialog.py @@ -66,10 +66,11 @@ READONLY, TRACEMODE_WRITE, ) -from pygpsclient.helpers import VALCUSTOM, VALNONBLANK, validate +from pygpsclient.helpers import VALCUSTOM, VALFLOAT, VALNONBLANK, validate from pygpsclient.strings import ( DLGTRINEX, LBLRINEXANTENNA, + LBLRINEXANTENNAHED, LBLRINEXCOMMENT, LBLRINEXCOUNTRY, LBLRINEXDOI, @@ -142,11 +143,17 @@ def __init__(self, app, *args, **kwargs): # pylint: disable=unused-argument self._navsource = StringVar() self._metsource = StringVar() self._rinexver = StringVar() + self._antennah = StringVar() + self._antennae = StringVar() + self._antennad = StringVar() self._countrycode = StringVar() self._rinexobs = IntVar() self._rinexnav = IntVar() self._rinexmet = IntVar() self._rxgps = IntVar() + self._timecorrflag = IntVar() + self._ionocorrflag = IntVar() + self._eopcorrflag = IntVar() self._rxglonass = IntVar() self._rxgalileo = IntVar() self._rxbeidou = IntVar() @@ -333,6 +340,25 @@ def _body(self): variable=self._rxnavic, state=NORMAL, ) + self._lbl_corr = Label(self._frm_basic, text="Corrections:", anchor=W) + self._chk_timecorr = Checkbutton( + self._frm_basic, + text="Time", + variable=self._timecorrflag, + state=NORMAL, + ) + self._chk_ionocorr = Checkbutton( + self._frm_basic, + text="Iono", + variable=self._ionocorrflag, + state=NORMAL, + ) + self._chk_eopcorr = Checkbutton( + self._frm_basic, + text="EOP", + variable=self._eopcorrflag, + state=NORMAL, + ) self._lbl_obstypes = Label(self._frm_basic, text=LBLRINEXOBSTYPES) self._ent_obstypes = Entry( self._frm_basic, @@ -357,7 +383,7 @@ def _body(self): self._lbl_marker = Label(self._frm_advanced, text=LBLRINEXMARKER, anchor=W) self._ent_markernum = Entry( self._frm_advanced, - width=5, + width=10, textvariable=self._markernum, state=NORMAL, relief="sunken", @@ -372,7 +398,7 @@ def _body(self): self._spn_markertype = Spinbox( self._frm_advanced, values=RINEXMARKERTYPES, - width=20, + width=15, wrap=True, textvariable=self._markertype, state=NORMAL, @@ -382,7 +408,7 @@ def _body(self): self._lbl_antenna = Label(self._frm_advanced, text=LBLRINEXANTENNA, anchor=W) self._ent_antennanum = Entry( self._frm_advanced, - width=5, + width=10, textvariable=self._antennanum, state=NORMAL, relief="sunken", @@ -394,10 +420,34 @@ def _body(self): state=NORMAL, relief="sunken", ) + self._lbl_antennahed = Label( + self._frm_advanced, text=LBLRINEXANTENNAHED, anchor=W + ) + self._ent_antennah = Entry( + self._frm_advanced, + width=10, + textvariable=self._antennah, + state=NORMAL, + relief="sunken", + ) + self._ent_antennae = Entry( + self._frm_advanced, + width=10, + textvariable=self._antennae, + state=NORMAL, + relief="sunken", + ) + self._ent_antennad = Entry( + self._frm_advanced, + width=10, + textvariable=self._antennad, + state=NORMAL, + relief="sunken", + ) self._lbl_receiver = Label(self._frm_advanced, text=LBLRINEXRCVR, anchor=W) self._ent_rcvrnum = Entry( self._frm_advanced, - width=5, + width=10, textvariable=self._rcvrnum, state=NORMAL, relief="sunken", @@ -411,7 +461,7 @@ def _body(self): ) self._ent_rcvrversion = Entry( self._frm_advanced, - width=20, + width=15, textvariable=self._rcvrversion, state=NORMAL, relief="sunken", @@ -517,62 +567,73 @@ def _do_layout(self): self._chk_rxnavic.grid(column=6, row=9, padx=3, sticky=W) self._lbl_obstypes.grid(column=0, row=10, columnspan=7, padx=3, sticky=W) self._ent_obstypes.grid(column=0, row=11, columnspan=7, padx=3, sticky=EW) - self._pgb_elapsed.grid(column=0, row=12, columnspan=7, padx=3, sticky=EW) + self._lbl_corr.grid(column=0, row=12, columnspan=2, padx=3, sticky=W) + self._chk_timecorr.grid(column=2, row=12, padx=3, sticky=W) + self._chk_ionocorr.grid(column=3, row=12, padx=3, sticky=W) + self._pgb_elapsed.grid(column=0, row=13, columnspan=7, padx=3, sticky=EW) ttk.Separator(self._frm_basic).grid( - column=0, row=13, columnspan=7, padx=3, sticky=EW + column=0, row=14, columnspan=7, padx=3, sticky=EW ) - self._lbl_outputs.grid(column=0, row=14, columnspan=7, padx=3, pady=1, sticky=W) + self._lbl_outputs.grid(column=0, row=15, columnspan=7, padx=3, pady=1, sticky=W) for i, lbl in enumerate(self._lbl_output_labels.values()): - lbl.grid(column=0, row=15 + i, columnspan=7, padx=3, pady=1, sticky=EW) + lbl.grid(column=0, row=16 + i, columnspan=7, padx=3, pady=1, sticky=EW) - self._lbl_marker.grid(column=0, row=0, columnspan=3, padx=3, sticky=W) + self._lbl_marker.grid(column=0, row=0, columnspan=5, padx=3, sticky=W) self._ent_markernum.grid(column=0, row=1, padx=3, sticky=W) - self._ent_markername.grid(column=1, row=1, padx=3, sticky=W) - self._spn_markertype.grid(column=2, row=1, padx=3, sticky=W) - self._lbl_antenna.grid(column=0, row=2, columnspan=3, padx=3, sticky=W) + self._ent_markername.grid(column=1, row=1, columnspan=2, padx=3, sticky=W) + self._spn_markertype.grid(column=3, row=1, padx=3, sticky=W) + self._lbl_antenna.grid(column=0, row=2, columnspan=5, padx=3, sticky=W) self._ent_antennanum.grid(column=0, row=3, padx=3, sticky=W) - self._ent_antennatype.grid(column=1, row=3, padx=3, sticky=W) - self._lbl_receiver.grid(column=0, row=4, columnspan=3, padx=3, sticky=W) - self._ent_rcvrnum.grid(column=0, row=5, padx=3, sticky=W) - self._ent_rcvrtype.grid(column=1, row=5, padx=3, sticky=W) - self._ent_rcvrversion.grid(column=2, row=5, padx=3, sticky=W) - self._lbl_observer.grid(column=0, row=6, columnspan=3, padx=3, sticky=W) - self._lbl_country.grid(column=2, row=6, columnspan=3, padx=3, sticky=W) - self._ent_observer.grid(column=0, row=7, columnspan=2, padx=3, sticky=EW) - self._ent_country.grid(column=2, row=7, padx=3, sticky=W) - self._lbl_starttime.grid(column=0, row=8, columnspan=2, padx=3, sticky=W) - self._ent_starttime.grid(column=2, row=8, padx=3, sticky=W) - self._lbl_comments.grid(column=0, row=9, columnspan=3, padx=3, sticky=W) + self._ent_antennatype.grid(column=1, row=3, columnspan=2, padx=3, sticky=W) + self._lbl_antennahed.grid(column=0, row=4, columnspan=5, padx=3, sticky=W) + self._ent_antennah.grid(column=0, row=5, padx=3, sticky=W) + self._ent_antennae.grid(column=1, row=5, padx=3, sticky=W) + self._ent_antennad.grid(column=2, row=5, padx=3, sticky=W) + self._lbl_receiver.grid(column=0, row=6, columnspan=5, padx=3, sticky=W) + self._ent_rcvrnum.grid(column=0, row=7, padx=3, sticky=W) + self._ent_rcvrtype.grid(column=1, row=7, columnspan=2, padx=3, sticky=W) + self._ent_rcvrversion.grid(column=3, row=7, columnspan=2, padx=3, sticky=W) + self._lbl_observer.grid(column=0, row=8, columnspan=3, padx=3, sticky=W) + self._lbl_country.grid(column=3, row=8, columnspan=2, padx=3, sticky=W) + self._ent_observer.grid(column=0, row=9, columnspan=3, padx=3, sticky=EW) + self._ent_country.grid(column=3, row=9, padx=3, sticky=W) + self._lbl_starttime.grid(column=0, row=10, columnspan=2, padx=3, sticky=W) + self._ent_starttime.grid(column=2, row=10, columnspan=2, padx=3, sticky=W) + self._lbl_comments.grid(column=0, row=11, columnspan=5, padx=3, sticky=W) for i, ec in enumerate(self._entcomments): - ec.grid(column=0, row=10 + i, columnspan=3, padx=3, sticky=EW) + ec.grid(column=0, row=12 + i, columnspan=5, padx=3, sticky=EW) for col in range(7): # make columns equal width self._frm_basic.grid_columnconfigure(col, weight=1, uniform="col") - def _do_layout_rinex4(self, show: bool, startrow: int = 10 + USERCOMMENTS): + def _do_layout_rinex4(self, show: bool, startrow: int): """ Position optional RINEX 4 widgets in frame. + + :param bool show: show/hide + :param int startrow: starting grid row """ if show: self._lbl_doi.grid( - column=0, row=startrow + 1, columnspan=3, padx=3, sticky=W + column=0, row=startrow + 1, columnspan=5, padx=3, sticky=W ) self._ent_doi.grid( - column=0, row=startrow + 2, columnspan=3, padx=3, sticky=EW + column=0, row=startrow + 2, columnspan=5, padx=3, sticky=EW ) self._lbl_license.grid( - column=0, row=startrow + 3, columnspan=3, padx=3, sticky=W + column=0, row=startrow + 3, columnspan=5, padx=3, sticky=W ) self._ent_license.grid( - column=0, row=startrow + 4, columnspan=3, padx=3, sticky=EW + column=0, row=startrow + 4, columnspan=5, padx=3, sticky=EW ) self._lbl_station.grid( - column=0, row=startrow + 5, columnspan=3, padx=3, sticky=W + column=0, row=startrow + 5, columnspan=5, padx=3, sticky=W ) self._ent_station.grid( - column=0, row=startrow + 6, columnspan=3, padx=3, sticky=EW + column=0, row=startrow + 6, columnspan=5, padx=3, sticky=EW ) + self._chk_eopcorr.grid(column=4, row=12, padx=3, sticky=W) else: self._lbl_doi.grid_forget() self._ent_doi.grid_forget() @@ -580,6 +641,7 @@ def _do_layout_rinex4(self, show: bool, startrow: int = 10 + USERCOMMENTS): self._ent_license.grid_forget() self._lbl_station.grid_forget() self._ent_station.grid_forget() + self._chk_eopcorr.grid_forget() def _attach_events(self): """ @@ -622,6 +684,12 @@ def _reset(self): self._obssource.set(UBXSOURCE) self._navsource.set(UBXSOURCE) self._metsource.set(NMEASOURCE) + self._antennah.set("0.0") + self._antennae.set("0.0") + self._antennad.set("0.0") + self._timecorrflag.set(1) + self._ionocorrflag.set(1) + self._eopcorrflag.set(1) rcvrname = self.__app.gnss_status.version_data.get("hwversion", NA) self._rcvrname.set("" if rcvrname == NA else rcvrname) rcvrversion = self.__app.gnss_status.version_data.get("fwversion", NA) @@ -634,7 +702,7 @@ def _on_update_rinexver(self, var, index, mode): # pylint: disable=unused-argum Update RINEX version. """ - self._do_layout_rinex4(self._rinexver.get() >= "4.00", 10 + USERCOMMENTS) + self._do_layout_rinex4(self._rinexver.get() >= "4.00", 12 + USERCOMMENTS) def _on_update_config(self, var, index, mode): # pylint: disable=unused-argument """ @@ -735,19 +803,19 @@ def _toggle_advanced(self): self._frm_advanced.grid_forget() self._btn_toggle.config(image=self._img_expandh) - def _valid_settings(self) -> bool: - """ - Validate settings. + # def _valid_settings(self) -> bool: + # """ + # Validate settings. - :return: valid True/False - :rtype: bool - """ + # :return: valid True/False + # :rtype: bool + # """ - valid = True - if not valid: - self.status_label = ("ERROR - invalid settings", ERRCOL) + # valid = True + # if not valid: + # self.status_label = ("ERROR - invalid settings", ERRCOL) - return valid + # return valid def _validtime(self, val: str) -> bool: """ @@ -779,6 +847,9 @@ def _do_conversion(self, **kwargs): self._ent_starttime, valmode=VALCUSTOM, func=self._validtime ) valid = valid & validate(self._ent_infilepath, valmode=VALNONBLANK) + valid = valid & validate(self._ent_antennah, valmode=VALFLOAT) + valid = valid & validate(self._ent_antennae, valmode=VALFLOAT) + valid = valid & validate(self._ent_antennad, valmode=VALFLOAT) if not valid: self.status_label = ("Invalid Parameters", ERRCOL) return @@ -824,12 +895,20 @@ def _do_conversion(self, **kwargs): self._markertype.get(), ] antenna = [self._antennanum.get(), self._antennatype.get()] + antennahed = [ + self._antennah.get(), + self._antennae.get(), + self._antennad.get(), + ] receiver = [ self._rcvrnum.get(), self._rcvrname.get(), self._rcvrversion.get(), ] observer = self._observer.get() + timecorr = self._timecorrflag.get() + ionocorr = self._ionocorrflag.get() + eopcorr = self._eopcorrflag.get() doi = self._doi.get() licen = self._license.get() station = self._station.get() @@ -856,6 +935,10 @@ def _do_conversion(self, **kwargs): minobs=minobs, marker=marker, antenna=antenna, + antennahed=antennahed, + timecorr=timecorr, + ionocorr=ionocorr, + eopcorr=eopcorr, receiver=receiver, observer=observer, comments=comments, @@ -926,7 +1009,7 @@ def _process_input( elif res == RINEX_NORECS: self.status_label = ("No parsable records in input", ERRCOL) else: - self.status_label = ("Conversion failed", ERRCOL) + self.status_label = ("Conversion failed - see log", ERRCOL) def _prog_callback_threaded(self, progress: int): """ diff --git a/src/pygpsclient/strings.py b/src/pygpsclient/strings.py index c875759c..af997905 100644 --- a/src/pygpsclient/strings.py +++ b/src/pygpsclient/strings.py @@ -156,6 +156,7 @@ LBLPROTDISP = "Protocols" LBLPUBLICIP = "Public IP" LBLRINEXANTENNA = "Antenna Number / Type:" +LBLRINEXANTENNAHED = "Antenna Delta H / E / D:" LBLRINEXCOMMENT = "User Comments:" LBLRINEXCOUNTRY = "Country Code:" LBLRINEXDOI = "Digital Object Identifier:" diff --git a/tests/test_static.py b/tests/test_static.py index 90ed7898..5da933bc 100644 --- a/tests/test_static.py +++ b/tests/test_static.py @@ -164,7 +164,7 @@ def testloadfile(self): def testinitpresets(self): cfg = Configuration(DummyApp()) cfg.init_presets("ubx") - self.assertEqual(len(cfg.get("ubxpresets_l")), 46) + self.assertEqual(len(cfg.get("ubxpresets_l")), 47) cfg.set( "ubxpresets_l", [ @@ -175,7 +175,7 @@ def testinitpresets(self): ], ) cfg.init_presets("ubx") - self.assertEqual(len(cfg.get("ubxpresets_l")), 49) + self.assertEqual(len(cfg.get("ubxpresets_l")), 50) def testmakeval(self): self.assertEqual(makeval(""), 0.0) From 275e23c0fac6d8e9ade11a07f9c4c114938b0230 Mon Sep 17 00:00:00 2001 From: SEMU Admin <28569967+semuadmin@users.noreply.github.com> Date: Mon, 15 Jun 2026 11:41:12 +0100 Subject: [PATCH 2/3] add status message timeout --- src/pygpsclient/app.py | 17 +++++++++++++---- src/pygpsclient/globals.py | 3 ++- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/pygpsclient/app.py b/src/pygpsclient/app.py index 209887e1..efed2224 100644 --- a/src/pygpsclient/app.py +++ b/src/pygpsclient/app.py @@ -99,7 +99,8 @@ SOCKSERVER_MAX_CLIENTS, SPARTN_EVENT, SPARTN_PROTOCOL, - STATUSPRIORITY, + STATUS_PRIORITY, + STATUS_TIMEOUT, TTY_PROTOCOL, UNDO, ) @@ -225,7 +226,9 @@ def __init__(self, master, **kwargs): self.frm_settings = None self._conn_status = DISCONNECTED self._rtk_conn_status = DISCONNECTED - self._last_gui_update = datetime.now() + now = datetime.now() + self._last_gui_update = now + self._last_status_update = now self._socket_thread = None self._socket_server = None self.consoledata = [] @@ -969,7 +972,8 @@ def process_data(self, raw_data: bytes, parsed_data: object, marker: str = ""): self.consoledata.append((raw_data, parsed_data, marker)) # periodically update widgets if visible - if datetime.now() > self._last_gui_update + timedelta( + now = datetime.now() + if now > self._last_gui_update + timedelta( seconds=self.configuration.get("guiupdateinterval_f") ): self._refresh_widgets() @@ -978,6 +982,10 @@ def process_data(self, raw_data: bytes, parsed_data: object, marker: str = ""): self.sqlite_handler.load_data() self._last_gui_update = datetime.now() + # remove stale status messages + if now > self._last_status_update + timedelta(seconds=STATUS_TIMEOUT): + self.status_label = ("", INFOCOL) + # update GPX track file if enabled if self.configuration.get("recordtrack_b"): self.file_handler.update_gpx_track() @@ -1165,7 +1173,7 @@ def status_label(self, message: str | tuple[str, str]): """ def priority(col): - return STATUSPRIORITY.get(col, 0) + return STATUS_PRIORITY.get(col, 0) if isinstance(message, tuple): message, color = message @@ -1180,6 +1188,7 @@ def priority(col): color = INFOCOL if color == "blue" else color self.status_label.config(text=message, fg=color) self.status_label.update() + self._last_status_update = datetime.now() else: # defer message until frm_status is instantiated if isinstance(self._deferredmsg, tuple): defpty = priority(self._deferredmsg[1]) diff --git a/src/pygpsclient/globals.py b/src/pygpsclient/globals.py index c8b459b2..9d1e9fde 100644 --- a/src/pygpsclient/globals.py +++ b/src/pygpsclient/globals.py @@ -246,7 +246,8 @@ SPARTN_SOURCE_LB = 1 SPONSOR_URL = "https://buymeacoffee.com/semuconsulting" SQRT2 = 0.7071067811865476 # square root of 2 -STATUSPRIORITY = {INFOCOL: 0, "blue": 0, OKCOL: 2, "green": 1, ERRCOL: 3, "red": 3} +STATUS_PRIORITY = {INFOCOL: 0, "blue": 0, OKCOL: 2, "green": 1, ERRCOL: 3, "red": 3} +STATUS_TIMEOUT = 20 # timeout for stale status messages in seconds TIME0 = datetime(1970, 1, 1) # basedate for time() TIMEOUTS = ("0.1", "0.2", "0.5", "1", "2", "5", "10", "20", "None", "0") # map nmea talker to gnss_id From 6cdbd0844cfa0fe81cfe95795a9dfe32f7682b9f Mon Sep 17 00:00:00 2001 From: SEMU Admin <28569967+semuadmin@users.noreply.github.com> Date: Wed, 24 Jun 2026 08:21:47 +0100 Subject: [PATCH 3/3] add hae/sep toggle --- README.md | 10 +++---- RELEASE_NOTES.md | 3 +- images/dgps_status.png | Bin 11920 -> 6343 bytes pyproject.toml | 2 +- src/pygpsclient/about_dialog.py | 9 +++--- src/pygpsclient/banner_frame.py | 36 ++++++++++++++++++++---- src/pygpsclient/dynamic_config_frame.py | 3 ++ src/pygpsclient/globals.py | 3 +- src/pygpsclient/gpx_dialog.py | 3 ++ src/pygpsclient/importmap_dialog.py | 4 +++ src/pygpsclient/nmea_preset_frame.py | 2 ++ src/pygpsclient/ntrip_client_dialog.py | 3 ++ src/pygpsclient/recorder_dialog.py | 8 ++++++ src/pygpsclient/rinex_dialog.py | 9 ++++++ src/pygpsclient/serialconfig_frame.py | 3 ++ src/pygpsclient/serverconfig_dialog.py | 3 ++ src/pygpsclient/settings_child_frame.py | 11 ++++++++ src/pygpsclient/spartn_gnss_frame.py | 3 ++ src/pygpsclient/spartn_lband_frame.py | 4 +++ src/pygpsclient/spartn_mqtt_frame.py | 5 ++++ src/pygpsclient/toplevel_dialog.py | 2 ++ src/pygpsclient/tty_preset_dialog.py | 2 ++ src/pygpsclient/ubx_cfgval_frame.py | 2 ++ src/pygpsclient/ubx_msgrate_frame.py | 2 ++ src/pygpsclient/ubx_port_frame.py | 2 ++ src/pygpsclient/ubx_preset_frame.py | 2 ++ src/pygpsclient/ubx_solrate_frame.py | 2 ++ 27 files changed, 120 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 45d10567..12b57370 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ PyGPSClient is a free, open-source, multi-platform graphical GNSS/GPS testing, d * Provides [NTRIP client](#ntripconfig) facilities for both RTCM3 and SPARTN NTRIP services. * Can serve as an [NTRIP base station](#basestation) with an RTK-compatible receiver (e.g. u-blox ZED-F9P/ZED-X20P, Quectel LG/LC Series, Septentrio Mosaic Series or Unicore UM9** Series). * Supports GNSS (*and related*) device configuration via proprietary UBX, NMEA and ASCII TTY protocols, including most u-blox, Quectel, Septentrio, Unicore and Feyman GNSS devices. -* **New in v1.6.7** - Experimental support for [RINEX conversion](#rinex) of raw observation, navigation and meteorology data. +* **New in version >= 1.6.7** - Experimental support for [RINEX conversion](#rinex) of raw observation, navigation and meteorology data. * Can be installed using the standard `pip` Python package manager - see [installation instructions](#installation) below. This is an independent project and we have no affiliation whatsoever with any GNSS manufacturer or distributor. @@ -183,7 +183,7 @@ For more comprehensive installation instructions, please refer to [INSTALLATION. --- | Widget | To show or hide the various widgets, go to Menu..View and click on the relevant hide/show option. | |---------------------------|---------------------------------------------------------------------------------------------------| -|![banner widget](https://github.com/semuconsulting/PyGPSClient/blob/master/images/banner_widget.png?raw=true)| Expandable banner showing key navigation status information based on messages received from receiver. To expand or collapse the banner or serial port configuration widgets, click the ![expand icon](https://github.com/semuconsulting/PyGPSClient/blob/master/src/pygpsclient/resources/iconmonstr-arrow-80-16.png?raw=true)/![expand icon](https://github.com/semuconsulting/PyGPSClient/blob/master/src/pygpsclient/resources/iconmonstr-triangle-1-16.png?raw=true) buttons. Ordinarily 'track:' represents heading of motion (aka course over ground), but the field will display 'hdg:' where static heading (yaw) is available. **NB**: some fields (e.g. hdop/vdop, hacc/vacc) are only available from proprietary NMEA or UBX messages and may not be output by default. The minimum messages required to populate all available fields are: NMEA: GGA, GSA, GSV, RMC, UBX00 (proprietary); UBX: NAV-DOP, NAV-PVT, NAV-SAT; UNI: BESTNAV, SATSINFO, STADOP. | +|![banner widget](https://github.com/semuconsulting/PyGPSClient/blob/master/images/banner_widget.png?raw=true)| Expandable banner showing key navigation status information based on messages received from receiver. To expand or collapse the banner or serial port configuration widgets, click the ![expand icon](https://github.com/semuconsulting/PyGPSClient/blob/master/src/pygpsclient/resources/iconmonstr-arrow-80-16.png?raw=true)/![expand icon](https://github.com/semuconsulting/PyGPSClient/blob/master/src/pygpsclient/resources/iconmonstr-triangle-1-16.png?raw=true) buttons. Double-click the "hae" label or value to toggle between hae (height above ellipsoid) and sep (separation) values. **NB**: some fields (e.g. hdop/vdop, hacc/vacc) are only available from proprietary NMEA or UBX messages and may not be output by default. The minimum messages required to populate all available fields are: NMEA: GGA, GSA, GSV, RMC, UBX00 (proprietary); UBX: NAV-DOP, NAV-PVT, NAV-SAT; UNI: BESTNAV, SATSINFO, STADOP. | |![console widget](https://github.com/semuconsulting/PyGPSClient/blob/master/images/console_widget.png?raw=true)| Configurable serial console widget showing incoming GNSS data streams in either parsed, binary or tabular hexadecimal formats. Double-right-click to copy contents of console to the clipboard. The scroll behaviour and number of lines retained in the console can be configured via the settings panel. Supports user-configurable color tagging of selected strings for easy identification. Color tags are loaded from the `"colortag_b":` value (`0` = disable, `1` = enable) and `"colortags_l":` list (`[string, color]` pairs) in your json configuration file (see example provided). If color is set to "HALT", streaming will halt on any match and a warning displayed. NB: color tagging does impose a small performance overhead - turning it off will improve console response times at very high transaction rates.| |![skyview widget](https://github.com/semuconsulting/PyGPSClient/blob/master/images/skyview_widget.png?raw=true)| Skyview widget showing current satellite visibility and position (elevation / azimuth). Satellite icon borders are colour-coded to distinguish between different GNSS constellations. For consistency between NMEA and UBX data sources, will display GLONASS NMEA SVID (65-96) rather than slot (1-24). | |![levelsview widget](https://github.com/semuconsulting/PyGPSClient/blob/master/images/graphview_widget.png?raw=true)| Levels view widget showing current satellite carrier-to-noise (C/No) levels for each GNSS constellation. Double-click to toggle legend. Double-right-click to toggle levels where C/No = 0 dbHz. | @@ -322,7 +322,7 @@ The NTRIP Configuration utility allows users to receive and process NTRIP RTK Co 1. For NTRIP services which require client position data via NMEA GGA sentences, select the appropriate sentence transmission interval in seconds. The default is 'None' (no GGA sentences sent). A value of 10 or 60 seconds is typical. 1. If GGA sentence transmission is enabled, GGA sentences can either be populated from live navigation data (*assuming a receiver is connected and outputting valid position data*) or from fixed reference settings entered in the NTRIP configuration panel (latitude, longitude, elevation and geoid separation - all four reference settings must be provided). 1. To connect to the NTRIP server, click ![connect icon](https://github.com/semuconsulting/PyGPSClient/blob/master/src/pygpsclient/resources/iconmonstr-media-control-48-24.png?raw=true). To disconnect, click ![disconnect icon](https://github.com/semuconsulting/PyGPSClient/blob/master/src/pygpsclient/resources/iconmonstr-media-control-50-24.png?raw=true). -1. If NTRIP data is being successfully received, the banner '**corr:**' status indicator should change to 'YES' and indicate the age and reference station of the correction data (where available) ![dgps status](https://github.com/semuconsulting/PyGPSClient/blob/master/images/dgps_status.png?raw=true). Note that DGPS status is typically maintained for up to 60 seconds after loss of correction signal. +1. If NTRIP data is being successfully received, the banner '**corr:**' status indicator should change to '✓' and indicate the age and reference station of the correction data (where available) ![dgps status](https://github.com/semuconsulting/PyGPSClient/blob/master/images/dgps_status.png?raw=true). Note that CORR status is typically maintained for up to 60 seconds after loss of correction signal. 1. Some NTRIP services may output RTCM3 or SPARTN correction messages at a high rate, flooding the GUI console display. To suppress these messages in the console, de-select the 'RTCM' or'SPARTN' options in 'Protocols Shown' - the RTCM3 or SPARTN messages will continue to be processed in the background. Below is a illustrative NTRIP DGPS data log, showing: @@ -428,9 +428,7 @@ The RINEX Conversion Dialog supports the conversion of raw observation, navigati **Pre-Requisites:** -1. A previously-saved binary datalog containing raw observation (UBX RXM-RAWX), navigation (UBX RXM-SFRBX¹) and/or meteorology (NMEA) data or RTCM3 ephemerides (1019, 1020, 1041-1046) messages. A suitable datalog can be recorded using PyGPSClient's [binary datalogging](#datalog) facility. **NB**: The file should contain at least 15-30 minutes of continuous data. - - ¹ The alpha release of the [pygnssutils RINEX NAV conversion utility](https://github.com/semuconsulting/pygnssutils#rinexconvert) currently implements GPS LNAV/CNAV, Galileo FNAV/INAV, Beidou D1/D2 and Glonass L1OF signal types. This will be enhanced in future releases. +1. A previously-saved binary datalog containing raw observation (UBX RXM-RAWX), navigation (UBX RXM-SFRBX) and/or meteorology (NMEA) data or RTCM3 ephemerides (1019, 1020, 1041-1046) messages. A suitable datalog can be recorded using PyGPSClient's [binary datalogging](#datalog) facility. **NB**: The file should contain at least 15-30 minutes of continuous data. **Instructions:** diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index b329eb09..18ce089b 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -2,9 +2,10 @@ ### RELEASE 1.7.0 +1. Can now double-click to toggle between 'hae' (height above ellipsoid) and 'sep' (separation) values in banner panel. 1. Minimum pyubx2 version updated to 1.3.3 (adds new firmware configuration database items for u-blox X20 HPG 2.10). 1. Default log level amended to '0' (ERROR) rather than '-1' (CRITICAL). Any logged exception tracebacks will now appear in terminal logs by default. -1. Updates to RINEX conversion dialog for pygnssutil>=1.2.3 (incorporates various bug fixes to RINEX conversion routines, but still an experimental facility). +1. Updates to RINEX conversion dialog for pygnssutil>=1.2.5 (incorporates various bug fixes to RINEX conversion routines, but remains an experimental facility). ### RELEASE 1.6.10 diff --git a/images/dgps_status.png b/images/dgps_status.png index 28ab4f757fba30eeb9fb8462d1ac1a0a2e33fa81..24648058eb7911f3fde4af92ce5194ef6fb7e370 100644 GIT binary patch literal 6343 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 OtSqlCS9aGd=zjnM$7yf? literal 11920 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 bSupc!TXlXOQ!Sxiz+iw?byO;p9tQsp{N>g) diff --git a/pyproject.toml b/pyproject.toml index 495b44ad..776f1d65 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,7 +52,7 @@ classifiers = [ dependencies = [ "requests>=2.34.0", "Pillow>=12.0.0", - "pygnssutils>=1.2.4", + "pygnssutils>=1.2.5", "pyunigps>=1.0.0", "pynmeagps>=1.1.5", "pyubx2>=1.3.3", diff --git a/src/pygpsclient/about_dialog.py b/src/pygpsclient/about_dialog.py index f30f6624..6a764161 100644 --- a/src/pygpsclient/about_dialog.py +++ b/src/pygpsclient/about_dialog.py @@ -18,6 +18,7 @@ from PIL import Image, ImageTk from pygpsclient.globals import ( + CLICK_CURSOR, ERRCOL, ICON_APP128, ICON_GITHUB, @@ -112,7 +113,7 @@ def _body(self): self._frm_body, text="", width=16, - cursor="hand2", + cursor=CLICK_CURSOR, ) self._chk_checkupdate = Checkbutton( self._frm_body, @@ -122,18 +123,18 @@ def _body(self): self._lbl_sponsoricon = Label( self._frm_body, image=self._img_sponsor, - cursor="hand2", + cursor=CLICK_CURSOR, ) self._lbl_github = Label( self._frm_body, text=GITHUB_URL, fg=INFOCOL, - cursor="hand2", + cursor=CLICK_CURSOR, ) self._lbl_copyright = Label( self._frm_body, text=COPYRIGHT, - cursor="hand2", + cursor=CLICK_CURSOR, ) def _do_layout(self): diff --git a/src/pygpsclient/banner_frame.py b/src/pygpsclient/banner_frame.py index f6cf6770..5ef9d07b 100644 --- a/src/pygpsclient/banner_frame.py +++ b/src/pygpsclient/banner_frame.py @@ -19,6 +19,7 @@ from pygpsclient.globals import ( BGCOL, + CLICK_CURSOR, CONNECTED, CONNECTED_FILE, CONNECTED_NTRIP, @@ -94,6 +95,7 @@ def __init__(self, app: Frame, parent: Frame, *args, **kwargs): self._img_ntrip = ImageTk.PhotoImage(Image.open(ICON_NTRIPCONFIG)) self._img_spartn = ImageTk.PhotoImage(Image.open(ICON_SPARTNCONFIG)) self._img_blank = ImageTk.PhotoImage(Image.open(ICON_BLANK)) + self._sep = False super().__init__(parent, *args, **kwargs) @@ -143,12 +145,18 @@ def _body(self): height=22, command=self._toggle_advanced, image=self._img_expand, + cursor=CLICK_CURSOR, ) self._lbl_lhmsl = Label( self._frm_advanced, text="hmsl:", bg=BGCOL, fg=FGCOL, anchor=N ) self._lbl_lhae = Label( - self._frm_advanced, text="hae:", bg=BGCOL, fg=FGCOL, anchor=N + self._frm_advanced, + text="hae:", + bg=BGCOL, + fg=FGCOL, + anchor=N, + cursor=CLICK_CURSOR, ) self._lbl_lspd = Label( self._frm_advanced, text="speed:", bg=BGCOL, fg=FGCOL, anchor=N @@ -195,7 +203,12 @@ def _body(self): self._frm_advanced, bg=BGCOL, fg="orange", width=13, anchor=W ) self._lbl_hae = Label( - self._frm_advanced, bg=BGCOL, fg="orange", width=13, anchor=W + self._frm_advanced, + bg=BGCOL, + fg="orange", + width=13, + anchor=W, + cursor=CLICK_CURSOR, ) self._lbl_spd = Label( self._frm_advanced, bg=BGCOL, fg="deepskyblue", width=12, anchor=W @@ -283,6 +296,8 @@ def _attach_events(self): """ self.bind("", self._on_resize) + self._lbl_lhae.bind("", self._on_sep) + self._lbl_hae.bind("", self._on_sep) def _toggle_advanced(self): """ @@ -396,7 +411,8 @@ def _update_pos(self, pos_format, units): self._lbl_llat.config(text="lat:") self._lbl_llon.config(text="lon:") self._lbl_lhmsl.config(text="hmsl:") - self._lbl_lhae.config(text="hae:") + lhae = "sep:" if self._sep else "hae:" + self._lbl_lhae.config(text=lhae) alt_u = "ft" if units in (UI, UIK) else "m" try: @@ -423,7 +439,8 @@ def _update_pos(self, pos_format, units): self._lbl_lat.config(text=f"{lat:{deg_f}}") self._lbl_lon.config(text=f"{lon:{deg_f}}") self._lbl_hmsl.config(text=f"{alt:.4f} {alt_u}") - self._lbl_hae.config(text=f"{hae:.4f} {alt_u}") + haev = hae - alt if self._sep else hae + self._lbl_hae.config(text=f"{haev:.4f} {alt_u}") except (TypeError, ValueError): self._lbl_lat.config(text=NA) self._lbl_lon.config(text=NA) @@ -623,9 +640,18 @@ def _set_fontsize(self): ): ctl.config(font=scale_font(self.width, FONTBASE - 4, FONTSCALE)[0]) + def _on_sep(self, event): # pylint: disable=unused-argument + """ + Toggle hae/sep display. + + :param event event: mouse click event + """ + + self._sep = not self._sep + def _on_resize(self, event): # pylint: disable=unused-argument """ - Resize frame + Resize frame. :param event event: resize event """ diff --git a/src/pygpsclient/dynamic_config_frame.py b/src/pygpsclient/dynamic_config_frame.py index 1c783e24..fcc54698 100644 --- a/src/pygpsclient/dynamic_config_frame.py +++ b/src/pygpsclient/dynamic_config_frame.py @@ -62,6 +62,7 @@ ) from pygpsclient.globals import ( + CLICK_CURSOR, ERRCOL, INFOCOL, NMEA_CFGOTHER, @@ -207,6 +208,7 @@ def _body(self): width=50, command=self._on_set_cfg, font=self.__app.font_md, + cursor=CLICK_CURSOR, ) self._btn_refresh = Button( self, @@ -214,6 +216,7 @@ def _body(self): width=40, command=self._on_refresh, font=self.__app.font_md, + cursor=CLICK_CURSOR, ) self._lbl_command = Label(self, text="", anchor=W) self._frm_container = Frame(self) diff --git a/src/pygpsclient/globals.py b/src/pygpsclient/globals.py index 9d1e9fde..cb29ca6a 100644 --- a/src/pygpsclient/globals.py +++ b/src/pygpsclient/globals.py @@ -75,6 +75,7 @@ ) BSR = "backslashreplace" CLASS = "cls" +CLICK_CURSOR = "hand2" # cursor displayed for clickable widgets CMDINITDELAY = 3000 # initial delay before sending commands to new connection CMDPAUSE = 100 # pause between sent commands in ms COLORTAGS = "colortags" @@ -247,7 +248,7 @@ SPONSOR_URL = "https://buymeacoffee.com/semuconsulting" SQRT2 = 0.7071067811865476 # square root of 2 STATUS_PRIORITY = {INFOCOL: 0, "blue": 0, OKCOL: 2, "green": 1, ERRCOL: 3, "red": 3} -STATUS_TIMEOUT = 20 # timeout for stale status messages in seconds +STATUS_TIMEOUT = 15 # timeout for stale status messages in seconds TIME0 = datetime(1970, 1, 1) # basedate for time() TIMEOUTS = ("0.1", "0.2", "0.5", "1", "2", "5", "10", "20", "None", "0") # map nmea talker to gnss_id diff --git a/src/pygpsclient/gpx_dialog.py b/src/pygpsclient/gpx_dialog.py index b4944376..9d66c088 100644 --- a/src/pygpsclient/gpx_dialog.py +++ b/src/pygpsclient/gpx_dialog.py @@ -37,6 +37,7 @@ from pygpsclient.canvas_subclasses import CanvasGraph from pygpsclient.globals import ( BGCOL, + CLICK_CURSOR, CUSTOM, ERRCOL, FGCOL, @@ -170,6 +171,7 @@ def _body(self): image=self.img_load, width=40, command=self._on_load, + cursor=CLICK_CURSOR, ) self._lbl_maptype = Label(self._frm_controls, text="Map Type") self._spn_maptype = Spinbox( @@ -203,6 +205,7 @@ def _body(self): image=self.img_redraw, width=40, command=self._on_redraw, + cursor=CLICK_CURSOR, ) def _do_layout(self): diff --git a/src/pygpsclient/importmap_dialog.py b/src/pygpsclient/importmap_dialog.py index 55203ff1..3186e7f2 100644 --- a/src/pygpsclient/importmap_dialog.py +++ b/src/pygpsclient/importmap_dialog.py @@ -41,6 +41,7 @@ from pygpsclient.canvas_map import CanvasMap from pygpsclient.globals import ( BGCOL, + CLICK_CURSOR, ERRCOL, IMPORT, INFOCOL, @@ -92,18 +93,21 @@ def _body(self): image=self.img_load, width=40, command=self._on_load, + cursor=CLICK_CURSOR, ) self._btn_import = Button( self._frm_controls, image=self.img_send, width=40, command=self._on_import, + cursor=CLICK_CURSOR, ) self._btn_redraw = Button( self._frm_controls, image=self.img_redraw, width=40, command=self._on_redraw, + cursor=CLICK_CURSOR, ) self._chk_first = Checkbutton( self._frm_controls, text="First?", variable=self._first diff --git a/src/pygpsclient/nmea_preset_frame.py b/src/pygpsclient/nmea_preset_frame.py index 69083d86..30b2d182 100644 --- a/src/pygpsclient/nmea_preset_frame.py +++ b/src/pygpsclient/nmea_preset_frame.py @@ -36,6 +36,7 @@ from pygpsclient.confirm_box import ConfirmBox from pygpsclient.globals import ( + CLICK_CURSOR, ERRCOL, ICON_CONFIRMED, ICON_PENDING, @@ -131,6 +132,7 @@ def _body(self): image=self._img_send, width=50, command=self._on_send_preset, + cursor=CLICK_CURSOR, ) def _do_layout(self): diff --git a/src/pygpsclient/ntrip_client_dialog.py b/src/pygpsclient/ntrip_client_dialog.py index c193e8b3..511db6c3 100644 --- a/src/pygpsclient/ntrip_client_dialog.py +++ b/src/pygpsclient/ntrip_client_dialog.py @@ -51,6 +51,7 @@ from pygnssutils.helpers import find_mp_distance from pygpsclient.globals import ( + CLICK_CURSOR, CONNECTED_NTRIP, DISCONNECTED, ERRCOL, @@ -280,6 +281,7 @@ def _body(self): height=35, image=self.img_conn, command=lambda: self._connect(), + cursor=CLICK_CURSOR, ) self._btn_disconnect = Button( self._frm_body, @@ -287,6 +289,7 @@ def _body(self): height=35, image=self.img_disconn, command=lambda: self._disconnect(), + cursor=CLICK_CURSOR, state=DISABLED, ) diff --git a/src/pygpsclient/recorder_dialog.py b/src/pygpsclient/recorder_dialog.py index d906d93c..6ce3adf1 100644 --- a/src/pygpsclient/recorder_dialog.py +++ b/src/pygpsclient/recorder_dialog.py @@ -54,6 +54,7 @@ from pygpsclient.globals import ( BGCOL, + CLICK_CURSOR, ERRCOL, FGCOL, HOME, @@ -146,6 +147,7 @@ def _body(self): font=self.__app.font_md, highlightbackground=BGCOL, highlightthickness=2, + cursor=CLICK_CURSOR, ) self._btn_save = Button( self._frm_body, @@ -154,6 +156,7 @@ def _body(self): command=self._on_save, highlightbackground=BGCOL, highlightthickness=2, + cursor=CLICK_CURSOR, ) self._btn_import = Button( self._frm_body, @@ -162,6 +165,7 @@ def _body(self): command=self._on_import, highlightbackground=BGCOL, highlightthickness=2, + cursor=CLICK_CURSOR, ) self._btn_play = Button( self._frm_body, @@ -170,6 +174,7 @@ def _body(self): command=self._on_play, highlightbackground=BGCOL, highlightthickness=2, + cursor=CLICK_CURSOR, ) self._btn_record = Button( self._frm_body, @@ -178,6 +183,7 @@ def _body(self): command=self._on_record, highlightbackground=BGCOL, highlightthickness=2, + cursor=CLICK_CURSOR, ) self._btn_undo = Button( self._frm_body, @@ -186,6 +192,7 @@ def _body(self): command=self._on_undo, highlightbackground=BGCOL, highlightthickness=2, + cursor=CLICK_CURSOR, ) self._btn_delete = Button( self._frm_body, @@ -194,6 +201,7 @@ def _body(self): command=self._on_delete, highlightbackground=BGCOL, highlightthickness=2, + cursor=CLICK_CURSOR, ) self._lbl_memory = Label( self._frm_body, diff --git a/src/pygpsclient/rinex_dialog.py b/src/pygpsclient/rinex_dialog.py index a9e970db..79ec08f1 100644 --- a/src/pygpsclient/rinex_dialog.py +++ b/src/pygpsclient/rinex_dialog.py @@ -60,6 +60,7 @@ ) from pygpsclient.globals import ( + CLICK_CURSOR, ERRCOL, INFOCOL, OKCOL, @@ -215,6 +216,7 @@ def _body(self): height=35, image=self.img_load, command=lambda: self._on_load(), + cursor=CLICK_CURSOR, ) self._btn_convert = Button( self._frm_basic, @@ -222,6 +224,7 @@ def _body(self): height=35, image=self.img_conn, command=lambda: self._on_convert(), + cursor=CLICK_CURSOR, ) self._btn_cancel = Button( self._frm_basic, @@ -229,6 +232,7 @@ def _body(self): height=35, image=self.img_cancel, command=lambda: self._on_cancel(), + cursor=CLICK_CURSOR, ) self._btn_toggle = Button( self._frm_basic, @@ -236,6 +240,7 @@ def _body(self): height=28, command=self._toggle_advanced, image=self._img_expandh, + cursor=CLICK_CURSOR, ) self._lbl_rinexver = Label(self._frm_basic, text=LBLRINEXVER) self._spn_rinexver = Spinbox( @@ -615,6 +620,8 @@ def _do_layout_rinex4(self, show: bool, startrow: int): """ if show: + self._chk_timecorr.config(text="STO") + self._chk_ionocorr.config(text="ION") self._lbl_doi.grid( column=0, row=startrow + 1, columnspan=5, padx=3, sticky=W ) @@ -635,6 +642,8 @@ def _do_layout_rinex4(self, show: bool, startrow: int): ) self._chk_eopcorr.grid(column=4, row=12, padx=3, sticky=W) else: + self._chk_timecorr.config(text="Time") + self._chk_ionocorr.config(text="Iono") self._lbl_doi.grid_forget() self._ent_doi.grid_forget() self._lbl_license.grid_forget() diff --git a/src/pygpsclient/serialconfig_frame.py b/src/pygpsclient/serialconfig_frame.py index c46ad6e7..dce6e599 100644 --- a/src/pygpsclient/serialconfig_frame.py +++ b/src/pygpsclient/serialconfig_frame.py @@ -49,6 +49,7 @@ from serial.tools.list_ports import comports from pygpsclient.globals import ( + CLICK_CURSOR, ICON_CONTRACT, ICON_EXPAND, ICON_REFRESH, @@ -177,6 +178,7 @@ def _body(self): image=self._img_refresh, width=28, height=22, + cursor=CLICK_CURSOR, ) self._btn_toggle = Button( self._frm_basic, @@ -184,6 +186,7 @@ def _body(self): image=self._img_expand, width=28, height=22, + cursor=CLICK_CURSOR, ) self._frm_advanced = Frame(self) diff --git a/src/pygpsclient/serverconfig_dialog.py b/src/pygpsclient/serverconfig_dialog.py index a65368f2..9933e176 100644 --- a/src/pygpsclient/serverconfig_dialog.py +++ b/src/pygpsclient/serverconfig_dialog.py @@ -46,6 +46,7 @@ from pynmeagps import NMEAMessage, ecef2llh, llh2ecef from pygpsclient.globals import ( + CLICK_CURSOR, CMDPAUSE, DISCONNECTED, ERRCOL, @@ -279,6 +280,7 @@ def _body(self): image=self._img_expand, width=28, height=22, + cursor=CLICK_CURSOR, # state=DISABLED, ) self._frm_advanced = Frame(self._frm_body) @@ -313,6 +315,7 @@ def _body(self): image=self._img_send, width=40, height=22, + cursor=CLICK_CURSOR, ) self._spn_rcvrtype = Spinbox( self._frm_advanced, diff --git a/src/pygpsclient/settings_child_frame.py b/src/pygpsclient/settings_child_frame.py index 7b71b8e9..110cb8cd 100644 --- a/src/pygpsclient/settings_child_frame.py +++ b/src/pygpsclient/settings_child_frame.py @@ -41,6 +41,7 @@ from pygpsclient.globals import ( BPSRATES, + CLICK_CURSOR, CONNECTED, CONNECTED_FILE, CONNECTED_SOCKET, @@ -198,6 +199,7 @@ def _body(self): image=self._img_serial, command=lambda: self._on_connect(CONNECTED), state=NORMAL, + cursor=CLICK_CURSOR, ) self._lbl_connect = Label(self._frm_buttons, text="USB/UART") self._btn_connect_socket = Button( @@ -207,6 +209,7 @@ def _body(self): image=self._img_socket, command=lambda: self._on_connect(CONNECTED_SOCKET), state=NORMAL, + cursor=CLICK_CURSOR, ) self._lbl_connect_socket = Label(self._frm_buttons, text="TCP/UDP") self._btn_connect_file = Button( @@ -216,6 +219,7 @@ def _body(self): image=self._img_dataread, command=lambda: self._on_connect(CONNECTED_FILE), state=NORMAL, + cursor=CLICK_CURSOR, ) self._lbl_connect_file = Label(self._frm_buttons, text="FILE") self._btn_disconnect = Button( @@ -225,6 +229,7 @@ def _body(self): image=self._img_disconn, command=lambda: self._on_connect(DISCONNECTED), state=DISABLED, + cursor=CLICK_CURSOR, ) self._lbl_disconnect = Label(self._frm_buttons, text="STOP") self._btn_exit = Button( @@ -234,6 +239,7 @@ def _body(self): image=self._img_exit, command=lambda: self.__app.on_exit(), state=NORMAL, + cursor=CLICK_CURSOR, ) self._lbl_status_preset = Label( @@ -373,6 +379,7 @@ def _body(self): width=45, image=self._img_ubxconfig, command=lambda: self.__app.start_dialog(DLGTUBX), + cursor=CLICK_CURSOR, ) self._lbl_nmeaconfig = Label( self._frm_options_btns, @@ -384,6 +391,7 @@ def _body(self): image=self._img_nmeaconfig, command=lambda: self.__app.start_dialog(DLGTNMEA), state=NORMAL, + cursor=CLICK_CURSOR, ) self._lbl_ttyconfig = Label( self._frm_options_btns, @@ -395,6 +403,7 @@ def _body(self): image=self._img_ttyconfig, command=lambda: self.__app.start_dialog(DLGTTTY), state=NORMAL, + cursor=CLICK_CURSOR, ) self._lbl_ntripconfig = Label( self._frm_options_btns, @@ -406,6 +415,7 @@ def _body(self): image=self._img_ntripconfig, command=lambda: self.__app.start_dialog(DLGTNTRIP), state=NORMAL, + cursor=CLICK_CURSOR, ) self._lbl_serverconfig = Label( self._frm_options_btns, @@ -417,6 +427,7 @@ def _body(self): image=self._img_serverconfig, command=lambda: self.__app.start_dialog(DLGTSERVER), state=NORMAL, + cursor=CLICK_CURSOR, ) def _do_layout(self): diff --git a/src/pygpsclient/spartn_gnss_frame.py b/src/pygpsclient/spartn_gnss_frame.py index 25f3432e..db4d46dd 100644 --- a/src/pygpsclient/spartn_gnss_frame.py +++ b/src/pygpsclient/spartn_gnss_frame.py @@ -39,6 +39,7 @@ from pyubx2 import POLL, SET, SET_LAYER_RAM, TXN_NONE, U1, U2, U4, UBXMessage, val2bytes from pygpsclient.globals import ( + CLICK_CURSOR, CONNECTED, ERRCOL, ICON_BLANK, @@ -215,6 +216,7 @@ def _body(self): width=45, image=self._img_load, command=lambda: self._on_load_json(), + cursor=CLICK_CURSOR, ) self._chk_upload_keys = Checkbutton( self, @@ -237,6 +239,7 @@ def _body(self): fg=OKCOL, command=self._on_send_gnss_config, font=self.__app.font_md, + cursor=CLICK_CURSOR, ) def _do_layout(self): diff --git a/src/pygpsclient/spartn_lband_frame.py b/src/pygpsclient/spartn_lband_frame.py index 27f1d337..f3448bd4 100644 --- a/src/pygpsclient/spartn_lband_frame.py +++ b/src/pygpsclient/spartn_lband_frame.py @@ -38,6 +38,7 @@ from pygpsclient.globals import ( BPSRATES, + CLICK_CURSOR, CONNECTED, CONNECTED_SPARTNIP, CONNECTED_SPARTNLB, @@ -283,6 +284,7 @@ def _body(self): width=45, image=self._img_serial, command=lambda: self.on_connect(), + cursor=CLICK_CURSOR, ) self._btn_disconnect = Button( self, @@ -290,6 +292,7 @@ def _body(self): image=self._img_disconn, command=lambda: self.on_disconnect(), state=DISABLED, + cursor=CLICK_CURSOR, ) self._lbl_send = Label(self, image=self._img_blank) self._btn_send = Button( @@ -297,6 +300,7 @@ def _body(self): image=self._img_send, width=45, command=self._on_send_config, + cursor=CLICK_CURSOR, ) def _do_layout(self): diff --git a/src/pygpsclient/spartn_mqtt_frame.py b/src/pygpsclient/spartn_mqtt_frame.py index 4963507e..9e59744a 100644 --- a/src/pygpsclient/spartn_mqtt_frame.py +++ b/src/pygpsclient/spartn_mqtt_frame.py @@ -49,6 +49,7 @@ from pyubx2 import UBXMessage from pygpsclient.globals import ( + CLICK_CURSOR, CONNECTED_SPARTNIP, CONNECTED_SPARTNLB, DISCONNECTED, @@ -209,6 +210,7 @@ def _body(self): width=45, image=self._img_load, command=lambda: self._get_spartncerts("crt"), + cursor=CLICK_CURSOR, ) self._lbl_mqttcrt = Label(self, text="CRT File") self._ent_mqttcrt = Entry( @@ -223,6 +225,7 @@ def _body(self): width=45, image=self._img_load, command=lambda: self._get_spartncerts("pem"), + cursor=CLICK_CURSOR, ) self._lbl_mqttpem = Label(self, text="PEM File") self._ent_mqttpem = Entry( @@ -243,6 +246,7 @@ def _body(self): width=45, image=self._img_socket, command=lambda: self.on_connect(), + cursor=CLICK_CURSOR, ) self._btn_disconnect = Button( self, @@ -250,6 +254,7 @@ def _body(self): image=self._img_disconn, command=lambda: self.on_disconnect(), state=DISABLED, + cursor=CLICK_CURSOR, ) def _do_layout(self): diff --git a/src/pygpsclient/toplevel_dialog.py b/src/pygpsclient/toplevel_dialog.py index f908a35e..4181b0e0 100644 --- a/src/pygpsclient/toplevel_dialog.py +++ b/src/pygpsclient/toplevel_dialog.py @@ -36,6 +36,7 @@ from pygpsclient.canvas_subclasses import CanvasContainer from pygpsclient.globals import ( APPNAME, + CLICK_CURSOR, ERRCOL, ICON_BLANK, ICON_CANCEL, @@ -139,6 +140,7 @@ def _con_body(self, resizable: bool): width=45, fg=ERRCOL, command=self.on_exit, + cursor=CLICK_CURSOR, ) self._frm_status.grid(column=0, row=2, sticky=EW) self._lbl_status.grid(column=0, row=0, sticky=EW) diff --git a/src/pygpsclient/tty_preset_dialog.py b/src/pygpsclient/tty_preset_dialog.py index 67e8f396..4e4b240e 100644 --- a/src/pygpsclient/tty_preset_dialog.py +++ b/src/pygpsclient/tty_preset_dialog.py @@ -37,6 +37,7 @@ from pygpsclient.globals import ( ASCII, BSR, + CLICK_CURSOR, CMDPAUSE, CRLF, ERRCOL, @@ -148,6 +149,7 @@ def _body(self): image=self.img_send, width=50, command=self._on_send_command, + cursor=CLICK_CURSOR, ) def _do_layout(self): diff --git a/src/pygpsclient/ubx_cfgval_frame.py b/src/pygpsclient/ubx_cfgval_frame.py index 91ec1a28..5e2a8a7d 100644 --- a/src/pygpsclient/ubx_cfgval_frame.py +++ b/src/pygpsclient/ubx_cfgval_frame.py @@ -40,6 +40,7 @@ from pyubx2.ubxhelpers import attsiz, atttyp, cfgname2key from pygpsclient.globals import ( + CLICK_CURSOR, ERRCOL, ICON_BLANK, ICON_CONFIRMED, @@ -199,6 +200,7 @@ def _body(self): image=self._img_send, width=50, command=self._on_send_config, + cursor=CLICK_CURSOR, ) def _do_layout(self): diff --git a/src/pygpsclient/ubx_msgrate_frame.py b/src/pygpsclient/ubx_msgrate_frame.py index 7f65ca82..d19f918c 100644 --- a/src/pygpsclient/ubx_msgrate_frame.py +++ b/src/pygpsclient/ubx_msgrate_frame.py @@ -33,6 +33,7 @@ from pyubx2.ubxhelpers import key_from_val from pygpsclient.globals import ( + CLICK_CURSOR, ERRCOL, OKCOL, READONLY, @@ -146,6 +147,7 @@ def _body(self): fg=OKCOL, command=self._on_send_cfg_msg, font=self.__app.font_md, + cursor=CLICK_CURSOR, ) def _do_layout(self): diff --git a/src/pygpsclient/ubx_port_frame.py b/src/pygpsclient/ubx_port_frame.py index beabb39c..1692f4f9 100644 --- a/src/pygpsclient/ubx_port_frame.py +++ b/src/pygpsclient/ubx_port_frame.py @@ -27,6 +27,7 @@ from pygpsclient.globals import ( BPSRATES, + CLICK_CURSOR, CONNECTED, ERRCOL, OKCOL, @@ -127,6 +128,7 @@ def _body(self): width=50, command=self._on_send_port, font=self.__app.font_md, + cursor=CLICK_CURSOR, ) def _do_layout(self): diff --git a/src/pygpsclient/ubx_preset_frame.py b/src/pygpsclient/ubx_preset_frame.py index 0bd68b12..51d6ebd1 100644 --- a/src/pygpsclient/ubx_preset_frame.py +++ b/src/pygpsclient/ubx_preset_frame.py @@ -35,6 +35,7 @@ from pygpsclient.confirm_box import ConfirmBox from pygpsclient.globals import ( + CLICK_CURSOR, ERRCOL, OKCOL, UBX_PRESET, @@ -122,6 +123,7 @@ def _body(self): image=self.__container.img_send, width=50, command=self._on_send_preset, + cursor=CLICK_CURSOR, ) def _do_layout(self): diff --git a/src/pygpsclient/ubx_solrate_frame.py b/src/pygpsclient/ubx_solrate_frame.py index e42010f6..291c6081 100644 --- a/src/pygpsclient/ubx_solrate_frame.py +++ b/src/pygpsclient/ubx_solrate_frame.py @@ -15,6 +15,7 @@ from pyubx2 import POLL, SET, UBXMessage from pygpsclient.globals import ( + CLICK_CURSOR, CONNECTED, ERRCOL, OKCOL, @@ -102,6 +103,7 @@ def _body(self): width=50, command=self._on_send_rate, font=self.__app.font_md, + cursor=CLICK_CURSOR, ) def _do_layout(self):