pygnssutils 1.2.3__tar.gz → 1.2.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. {pygnssutils-1.2.3/src/pygnssutils.egg-info → pygnssutils-1.2.4}/PKG-INFO +7 -7
  2. {pygnssutils-1.2.3 → pygnssutils-1.2.4}/README.md +4 -4
  3. {pygnssutils-1.2.3 → pygnssutils-1.2.4}/pyproject.toml +2 -2
  4. {pygnssutils-1.2.3 → pygnssutils-1.2.4}/src/pygnssutils/_version.py +1 -1
  5. {pygnssutils-1.2.3 → pygnssutils-1.2.4}/src/pygnssutils/rawnav.py +16 -12
  6. {pygnssutils-1.2.3 → pygnssutils-1.2.4}/src/pygnssutils/rinex_conv.py +1 -1
  7. {pygnssutils-1.2.3 → pygnssutils-1.2.4}/src/pygnssutils/rinex_conv_nav.py +123 -21
  8. {pygnssutils-1.2.3 → pygnssutils-1.2.4}/src/pygnssutils/rinex_globals.py +6 -1
  9. {pygnssutils-1.2.3 → pygnssutils-1.2.4}/src/pygnssutils/rinex_helpers.py +56 -1
  10. pygnssutils-1.2.4/src/pygnssutils/rinex_subframes_glo.py +136 -0
  11. {pygnssutils-1.2.3 → pygnssutils-1.2.4/src/pygnssutils.egg-info}/PKG-INFO +7 -7
  12. {pygnssutils-1.2.3 → pygnssutils-1.2.4}/src/pygnssutils.egg-info/SOURCES.txt +1 -0
  13. {pygnssutils-1.2.3 → pygnssutils-1.2.4}/src/pygnssutils.egg-info/requires.txt +2 -2
  14. {pygnssutils-1.2.3 → pygnssutils-1.2.4}/tests/test_rinex.py +78 -57
  15. {pygnssutils-1.2.3 → pygnssutils-1.2.4}/tests/test_rinex_defs.py +28 -16
  16. {pygnssutils-1.2.3 → pygnssutils-1.2.4}/LICENSE +0 -0
  17. {pygnssutils-1.2.3 → pygnssutils-1.2.4}/setup.cfg +0 -0
  18. {pygnssutils-1.2.3 → pygnssutils-1.2.4}/src/pygnssutils/__init__.py +0 -0
  19. {pygnssutils-1.2.3 → pygnssutils-1.2.4}/src/pygnssutils/exceptions.py +0 -0
  20. {pygnssutils-1.2.3 → pygnssutils-1.2.4}/src/pygnssutils/globals.py +0 -0
  21. {pygnssutils-1.2.3 → pygnssutils-1.2.4}/src/pygnssutils/gnssmqttclient.py +0 -0
  22. {pygnssutils-1.2.3 → pygnssutils-1.2.4}/src/pygnssutils/gnssmqttclient_cli.py +0 -0
  23. {pygnssutils-1.2.3 → pygnssutils-1.2.4}/src/pygnssutils/gnssntripclient.py +0 -0
  24. {pygnssutils-1.2.3 → pygnssutils-1.2.4}/src/pygnssutils/gnssntripclient_cli.py +0 -0
  25. {pygnssutils-1.2.3 → pygnssutils-1.2.4}/src/pygnssutils/gnssreader.py +0 -0
  26. {pygnssutils-1.2.3 → pygnssutils-1.2.4}/src/pygnssutils/gnssserver.py +0 -0
  27. {pygnssutils-1.2.3 → pygnssutils-1.2.4}/src/pygnssutils/gnssserver_cli.py +0 -0
  28. {pygnssutils-1.2.3 → pygnssutils-1.2.4}/src/pygnssutils/gnssstreamer.py +0 -0
  29. {pygnssutils-1.2.3 → pygnssutils-1.2.4}/src/pygnssutils/gnssstreamer_cli.py +0 -0
  30. {pygnssutils-1.2.3 → pygnssutils-1.2.4}/src/pygnssutils/helpers.py +0 -0
  31. {pygnssutils-1.2.3 → pygnssutils-1.2.4}/src/pygnssutils/mqttmessage.py +0 -0
  32. {pygnssutils-1.2.3 → pygnssutils-1.2.4}/src/pygnssutils/rinex_conv_cli.py +0 -0
  33. {pygnssutils-1.2.3 → pygnssutils-1.2.4}/src/pygnssutils/rinex_conv_met.py +0 -0
  34. {pygnssutils-1.2.3 → pygnssutils-1.2.4}/src/pygnssutils/rinex_conv_obs.py +0 -0
  35. {pygnssutils-1.2.3 → pygnssutils-1.2.4}/src/pygnssutils/rinex_subframes_bds.py +0 -0
  36. {pygnssutils-1.2.3 → pygnssutils-1.2.4}/src/pygnssutils/rinex_subframes_gal.py +0 -0
  37. {pygnssutils-1.2.3 → pygnssutils-1.2.4}/src/pygnssutils/rinex_subframes_gps.py +0 -0
  38. {pygnssutils-1.2.3 → pygnssutils-1.2.4}/src/pygnssutils/socket_server.py +0 -0
  39. {pygnssutils-1.2.3 → pygnssutils-1.2.4}/src/pygnssutils.egg-info/dependency_links.txt +0 -0
  40. {pygnssutils-1.2.3 → pygnssutils-1.2.4}/src/pygnssutils.egg-info/entry_points.txt +0 -0
  41. {pygnssutils-1.2.3 → pygnssutils-1.2.4}/src/pygnssutils.egg-info/top_level.txt +0 -0
  42. {pygnssutils-1.2.3 → pygnssutils-1.2.4}/tests/test_cli.py +0 -0
  43. {pygnssutils-1.2.3 → pygnssutils-1.2.4}/tests/test_gnssstreamer.py +0 -0
  44. {pygnssutils-1.2.3 → pygnssutils-1.2.4}/tests/test_rawnav.py +0 -0
  45. {pygnssutils-1.2.3 → pygnssutils-1.2.4}/tests/test_socketwrapper.py +0 -0
  46. {pygnssutils-1.2.3 → pygnssutils-1.2.4}/tests/test_sourcetable.py +0 -0
  47. {pygnssutils-1.2.3 → pygnssutils-1.2.4}/tests/test_static.py +0 -0
  48. {pygnssutils-1.2.3 → pygnssutils-1.2.4}/tests/test_stream.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pygnssutils
3
- Version: 1.2.3
3
+ Version: 1.2.4
4
4
  Summary: GNSS Command Line Utilities
5
5
  Author-email: Steve Smith <semuadmin@semuconsulting.com>
6
6
  Maintainer-email: Steve Smith <semuadmin@semuconsulting.com>
@@ -35,10 +35,10 @@ Requires-Dist: paho-mqtt>=2.1.0
35
35
  Requires-Dist: pyserial>=3.5
36
36
  Requires-Dist: pyspartn>=1.0.8
37
37
  Requires-Dist: pyubx2>=1.3.3
38
- Requires-Dist: pynmeagps>=1.1.4
38
+ Requires-Dist: pynmeagps>=1.1.5
39
39
  Requires-Dist: pysbf2>=1.0.4
40
40
  Requires-Dist: pyubxutils>=1.0.6
41
- Requires-Dist: pyqgc>=0.2.2
41
+ Requires-Dist: pyqgc>=1.0.0
42
42
  Requires-Dist: pyunigps>=1.0.0
43
43
  Dynamic: license-file
44
44
 
@@ -81,7 +81,7 @@ designated output stream.
81
81
  a simple SPARTN IP (MQTT) Client which receives SPARTN correction data from an SPARTN IP location service and (optionally) sends this to a
82
82
  designated output stream.
83
83
  1. `SocketServer` class based on the native Python `ThreadingTCPServer`. Capable of operating in two modes - Socket Server or NTRIP Caster. Provides two alternate client request handler classes - `ClientHandler` (HTTP) or `ClientHandlerTLS` (HTTPS).
84
- 1. `RinexConverter` class and its associated [`pyrinexconv`](#rinexconvert) CLI utility. This implements a binary GNSS data log file to RINEX text file conversion facility. **NB: this initial ALPHA release has limited functionality.**
84
+ 1. `RinexConverter` class and its associated [`pyrinexconv`](#rinexconvert) CLI utility. This implements a binary GNSS data log file to RINEX text file conversion facility. **NB: RINEX conversion is currently an experimental work in progress (*contributions and feedback welcome*)**
85
85
 
86
86
  The pygnssutils homepage is located at [https://github.com/semuconsulting/pygnssutils](https://github.com/semuconsulting/pygnssutils).
87
87
 
@@ -543,15 +543,15 @@ class pygnssutils.rinex_conv.RinexConvertor(app, rinex_version, rinex_type, gnss
543
543
 
544
544
  A command line utility and Python class `RinexConverter` to convert binary GNSS data logs to RINEX text file format.
545
545
 
546
- **NB: The current ALPHA release implements the following functionality:**
546
+ **NB: RINEX conversion is an experimental work in progress (*contributions and feedback welcome*). The current ALPHA release implements the following functionality:**
547
547
 
548
548
  1. RINEX versions 3.05 and 4.02.
549
549
  1. Convert binary UBX RXM-RAW or RXM-RAWX (raw observation) data from u-blox receivers (e.g. ZED-F9P) to RINEX Observation file format.
550
- 1. Convert binary RXM-SFRBX (navigation subframe) data from u-blox receivers to RINEX Navigation file format. **Currently supports GPS LNAV/CNAV, GAL FNAV/INAV, BDS D1/D2**, but the underlying `RinexConverterNavigation` class will be enhanced in future releases.
550
+ 1. Convert binary RXM-SFRBX (navigation subframe) data from u-blox receivers to RINEX Navigation file format. **Currently supports GPS LNAV/CNAV, GAL FNAV/INAV, BDS D1/D2 & GLO L1OF**, but the underlying `RinexConverterNavigation` class will be enhanced in future releases.
551
551
  1. Convert RTCM3 Ephemerides messages (1019, 1020, 1041-1046) from any source (including NTRIP caster or RTK base station receiver) to RINEX Navigation file format.
552
552
  1. Convert NMEA MWD (wind speed and direction) and XDR (temperature and pressure) sensor data to RINEX Meteorology file format.
553
553
 
554
- A Graphical User Interface for this utility will be added to [PyGPSClient](https://github.com/semuconsulting/PyGPSClient).
554
+ A Graphical User Interface for this utility has been added to [PyGPSClient](https://github.com/semuconsulting/PyGPSClient).
555
555
 
556
556
  Assuming the u-blox receiver is already configured to output raw observation (UBX-RXM-RAWX) and navigation subframe (UBX-RXM-SFRBX) data, a suitable input log file can be created using the pygnssutils `gnssstreamer` CLI utility e.g. ...
557
557
 
@@ -37,7 +37,7 @@ designated output stream.
37
37
  a simple SPARTN IP (MQTT) Client which receives SPARTN correction data from an SPARTN IP location service and (optionally) sends this to a
38
38
  designated output stream.
39
39
  1. `SocketServer` class based on the native Python `ThreadingTCPServer`. Capable of operating in two modes - Socket Server or NTRIP Caster. Provides two alternate client request handler classes - `ClientHandler` (HTTP) or `ClientHandlerTLS` (HTTPS).
40
- 1. `RinexConverter` class and its associated [`pyrinexconv`](#rinexconvert) CLI utility. This implements a binary GNSS data log file to RINEX text file conversion facility. **NB: this initial ALPHA release has limited functionality.**
40
+ 1. `RinexConverter` class and its associated [`pyrinexconv`](#rinexconvert) CLI utility. This implements a binary GNSS data log file to RINEX text file conversion facility. **NB: RINEX conversion is currently an experimental work in progress (*contributions and feedback welcome*)**
41
41
 
42
42
  The pygnssutils homepage is located at [https://github.com/semuconsulting/pygnssutils](https://github.com/semuconsulting/pygnssutils).
43
43
 
@@ -499,15 +499,15 @@ class pygnssutils.rinex_conv.RinexConvertor(app, rinex_version, rinex_type, gnss
499
499
 
500
500
  A command line utility and Python class `RinexConverter` to convert binary GNSS data logs to RINEX text file format.
501
501
 
502
- **NB: The current ALPHA release implements the following functionality:**
502
+ **NB: RINEX conversion is an experimental work in progress (*contributions and feedback welcome*). The current ALPHA release implements the following functionality:**
503
503
 
504
504
  1. RINEX versions 3.05 and 4.02.
505
505
  1. Convert binary UBX RXM-RAW or RXM-RAWX (raw observation) data from u-blox receivers (e.g. ZED-F9P) to RINEX Observation file format.
506
- 1. Convert binary RXM-SFRBX (navigation subframe) data from u-blox receivers to RINEX Navigation file format. **Currently supports GPS LNAV/CNAV, GAL FNAV/INAV, BDS D1/D2**, but the underlying `RinexConverterNavigation` class will be enhanced in future releases.
506
+ 1. Convert binary RXM-SFRBX (navigation subframe) data from u-blox receivers to RINEX Navigation file format. **Currently supports GPS LNAV/CNAV, GAL FNAV/INAV, BDS D1/D2 & GLO L1OF**, but the underlying `RinexConverterNavigation` class will be enhanced in future releases.
507
507
  1. Convert RTCM3 Ephemerides messages (1019, 1020, 1041-1046) from any source (including NTRIP caster or RTK base station receiver) to RINEX Navigation file format.
508
508
  1. Convert NMEA MWD (wind speed and direction) and XDR (temperature and pressure) sensor data to RINEX Meteorology file format.
509
509
 
510
- A Graphical User Interface for this utility will be added to [PyGPSClient](https://github.com/semuconsulting/PyGPSClient).
510
+ A Graphical User Interface for this utility has been added to [PyGPSClient](https://github.com/semuconsulting/PyGPSClient).
511
511
 
512
512
  Assuming the u-blox receiver is already configured to output raw observation (UBX-RXM-RAWX) and navigation subframe (UBX-RXM-SFRBX) data, a suitable input log file can be created using the pygnssutils `gnssstreamer` CLI utility e.g. ...
513
513
 
@@ -39,10 +39,10 @@ dependencies = [
39
39
  "pyserial>=3.5",
40
40
  "pyspartn>=1.0.8",
41
41
  "pyubx2>=1.3.3",
42
- "pynmeagps>=1.1.4",
42
+ "pynmeagps>=1.1.5",
43
43
  "pysbf2>=1.0.4",
44
44
  "pyubxutils>=1.0.6",
45
- "pyqgc>=0.2.2",
45
+ "pyqgc>=1.0.0",
46
46
  "pyunigps>=1.0.0",
47
47
  ]
48
48
 
@@ -8,4 +8,4 @@ Created on 2 Oct 2020
8
8
  :license: BSD 3-Clause
9
9
  """
10
10
 
11
- __version__ = "1.2.3"
11
+ __version__ = "1.2.4"
@@ -47,11 +47,11 @@ navigation data:
47
47
 
48
48
  https://www.u-blox.com/sites/default/files/ZED-F9P_IntegrationManual_UBX-18010802.pdf
49
49
 
50
- NB: Alpha Support currently limited to:
50
+ NB: Alpha release currently implements:
51
51
  - GPS LNAV, CNAV
52
52
  - GAL FNAV, INAV
53
- - BDS D1
54
- ... pending transcription of other GNSS ICDs.
53
+ - BDS D1, D2
54
+ - GLO L1OF
55
55
 
56
56
  Created on 20 Apr 2026
57
57
 
@@ -136,9 +136,10 @@ class RawNav:
136
136
  self._gnss = gnss
137
137
  self._svid = svid
138
138
  self._sigcode = sigcode
139
- self.wn = -1
140
- self.toc = -1
141
- self.tow = -1
139
+ if gnss != "R":
140
+ self.wn = -1
141
+ self.toc = -1
142
+ self.tow = -1
142
143
  self._subframeacq = 0
143
144
  self._msb = {}
144
145
  self._isb = {}
@@ -603,16 +604,18 @@ class RawNavReader:
603
604
  subframe = 0
604
605
  subframeid = 0
605
606
  subframepageid = 0
607
+ freqid = data.freqId
606
608
 
607
- # for GLO, subframe = 85 bits, 3 * 32 bit dwrds, plus
608
- # a receiver-generated 4th 32 dwrd containing subframe and page ids
609
- if sigcode in ("1C", "2C"): # GLO L1,L2
609
+ # for GLO, subframe = 85 bits,
610
+ # 3 * 32 bit dwrds with 11 bits padding at end,
611
+ # plus a receiver-generated 4th dwrd containing superframe and frame ids
612
+ if sigcode in ("1C",): # GLO L1OF
610
613
  for i in range(numw):
611
614
  wrd = getattr(data, f"dwrd_{i+1:02d}")
612
615
  subframe += wrd << (32 * (numw - 1 - i))
613
- subframepageid = (subframe >> 16) & 0xFFFF
614
- subframeid = subframe & 0b11111111
615
- subframe = (subframe >> 43) & 0x1FFFFFFFFFFFFFFFFFFFFF # strip 4th dwrd
616
+ # strip 4th dwrd, leaving 85 bit subframe
617
+ subframe = (subframe >> 43) & 0x1FFFFFFFFFFFFFFFFFFFFF # 2**85-1
618
+ subframeid = (subframe >> 80) & 0b01111
616
619
 
617
620
  return {
618
621
  "gnss": gnss,
@@ -621,6 +624,7 @@ class RawNavReader:
621
624
  "subframeid": subframeid,
622
625
  "subframepageid": subframepageid,
623
626
  "subframe": subframe,
627
+ "freqid": freqid,
624
628
  }
625
629
 
626
630
  def _process_rxm_sfrbx_sba(
@@ -327,7 +327,7 @@ class RinexConverter:
327
327
  TypeError,
328
328
  ValueError,
329
329
  ):
330
- self.logger.exception(f"RINEX NAV Conversion error")
330
+ self.logger.exception("RINEX NAV Conversion error")
331
331
  res = RINEX_ERROR
332
332
  except KeyboardInterrupt:
333
333
  self.logger.warning("Terminated by user")
@@ -8,7 +8,6 @@ Converts NAV message data to RINEX Navigation text format.
8
8
  NB: Alpha release currently limited to following data sources:
9
9
 
10
10
  - RawNav objects containing data collated from UBX RXM-SFRBX messages
11
- (GPS LNAV/CNAV, GAL FNAV,INAV, BDS D1 only)
12
11
  - RTCM3 ephemerides messages 1019, 1020, 1041-1046 e.g. from RTK receiver
13
12
  or NTRIP data stream
14
13
 
@@ -52,6 +51,7 @@ from pygnssutils.rinex_globals import (
52
51
  ION,
53
52
  IRN,
54
53
  KLOB,
54
+ L1OF,
55
55
  LNAV,
56
56
  NAV,
57
57
  NEQUICK,
@@ -75,12 +75,15 @@ from pygnssutils.rinex_helpers import ( # format_timefirstlast,
75
75
  format_sto,
76
76
  format_time_corr,
77
77
  get_epoch,
78
+ get_epoch_glo,
78
79
  get_fithours,
79
80
  get_svcode,
81
+ glotk2sec,
80
82
  gpsura2m,
81
83
  )
82
84
  from pygnssutils.rinex_subframes_bds import BDS_SUBFRAMEACQ_MAP
83
85
  from pygnssutils.rinex_subframes_gal import GAL_SUBFRAMEACQ_MAP
86
+ from pygnssutils.rinex_subframes_glo import GLO_SUBFRAMEACQ_MAP
84
87
  from pygnssutils.rinex_subframes_gps import GPS_SUBFRAMEACQ_MAP
85
88
 
86
89
  CLKBIAS = "clkbias"
@@ -268,6 +271,11 @@ class RinexConverterNavigation:
268
271
  sfrmap = BDS_SUBFRAMEACQ_MAP[D2]
269
272
  formatter = self._format_rawnav_bds_d1d2
270
273
  kwargs = {"d1d2": sfrdata.get("d1d2", 0)}
274
+ elif gnss == GLO:
275
+ if sigcode in ("1C",):
276
+ sfrmap = GLO_SUBFRAMEACQ_MAP[L1OF]
277
+ formatter = self._format_rawnav_glo_l1of
278
+ kwargs = {"freqid": sfrdata.get("freqid", 0)}
271
279
  # elif other gnss/sigcode, as and when I get to it TODO
272
280
 
273
281
  if sfrmap is None or formatter is None:
@@ -874,8 +882,8 @@ class RinexConverterNavigation:
874
882
  raw NAV subframe sources.
875
883
  """
876
884
 
877
- self._navdata[(data.svcode, data.iodc)] = {}
878
- nvd = self._navdata[(data.svcode, data.iodc)]
885
+ self._navdata[(data.svcode, data.iode)] = {}
886
+ nvd = self._navdata[(data.svcode, data.iode)]
879
887
 
880
888
  epoch, wn_cont = get_epoch(wno=data.wn, tow=data.tow, gnss=data.gnss)
881
889
  self.__app.set_current_epoch(epoch, NAV)
@@ -927,7 +935,7 @@ class RinexConverterNavigation:
927
935
 
928
936
  if self._rinex_version < RINEX4:
929
937
  if self._timecorrflag:
930
- self._format_timecorr_3(data)
938
+ self._format_timecorr_3(data, 2)
931
939
  if self._ionocorrflag:
932
940
  self._format_ionocorr_3(data)
933
941
  else: # RINEX 4.02
@@ -953,7 +961,7 @@ class RinexConverterNavigation:
953
961
  raw NAV subframe sources.
954
962
  """
955
963
 
956
- self._navdata[(data.svcode, data.top)] = {} # is top equivalent to iodc here?
964
+ self._navdata[(data.svcode, data.top)] = {} # have assumed top => iode
957
965
  nvd = self._navdata[(data.svcode, data.top)]
958
966
 
959
967
  epoch, wn_cont = get_epoch(wno=data.wn, tow=data.tow, gnss=data.gnss)
@@ -1016,7 +1024,7 @@ class RinexConverterNavigation:
1016
1024
 
1017
1025
  if self._rinex_version < RINEX4:
1018
1026
  if self._timecorrflag:
1019
- self._format_timecorr_3(data)
1027
+ self._format_timecorr_3(data, 2)
1020
1028
  if self._ionocorrflag:
1021
1029
  self._format_ionocorr_3(data)
1022
1030
  else: # RINEX 4.02
@@ -1129,7 +1137,7 @@ class RinexConverterNavigation:
1129
1137
 
1130
1138
  if self._rinex_version < RINEX4:
1131
1139
  if self._timecorrflag:
1132
- self._format_timecorr_3(data)
1140
+ self._format_timecorr_3(data, 5)
1133
1141
  if self._ionocorrflag:
1134
1142
  self._format_ionocorr_3(data)
1135
1143
  else: # RINEX 4.02
@@ -1157,8 +1165,8 @@ class RinexConverterNavigation:
1157
1165
  """
1158
1166
 
1159
1167
  d1d2 = kwargs.get("d1d2", 0)
1160
- self._navdata[(data.svcode, data.aodc)] = {}
1161
- nvd = self._navdata[(data.svcode, data.aodc)]
1168
+ self._navdata[(data.svcode, data.aode)] = {}
1169
+ nvd = self._navdata[(data.svcode, data.aode)]
1162
1170
 
1163
1171
  epoch, wn_cont = get_epoch(wno=data.wn, tow=data.tow, gnss=data.gnss)
1164
1172
  self.__app.set_current_epoch(epoch, NAV)
@@ -1210,7 +1218,7 @@ class RinexConverterNavigation:
1210
1218
 
1211
1219
  if self._rinex_version < RINEX4:
1212
1220
  if self._timecorrflag:
1213
- self._format_timecorr_3(data)
1221
+ self._format_timecorr_3(data, 7)
1214
1222
  if self._ionocorrflag:
1215
1223
  self._format_ionocorr_3(data)
1216
1224
  else: # RINEX 4.02
@@ -1230,24 +1238,107 @@ class RinexConverterNavigation:
1230
1238
  data=data,
1231
1239
  )
1232
1240
 
1233
- def _format_timecorr_3(self, data: RawNav):
1241
+ def _format_rawnav_glo_l1of(self, data: RawNav, **kwargs):
1242
+ """
1243
+ Format RawNav GLO L1OF (FDMA) broadcast orbit blocks.
1244
+
1245
+ :param RawNav data: RawNav object containing data \
1246
+ collated from UBX RXM-SFRBX messages or other \
1247
+ raw NAV subframe sources.
1248
+ """
1249
+
1250
+ freqid = kwargs.get("freqid", 0)
1251
+ self._navdata[(data.svcode, data.tb)] = {} # have assumed tb => iode
1252
+ nvd = self._navdata[(data.svcode, data.tb)]
1253
+
1254
+ epoch = get_epoch_glo(data.nt, data.n4, data.tk)
1255
+ nd = (epoch.weekday() + 1) % 7 # GPS week Sunday = 0
1256
+ self.__app.set_current_epoch(epoch, NAV)
1257
+ nvd[EPOCH] = epoch
1258
+ nvd[RECTYPE] = "FDMA"
1259
+ nvd[CLKBIAS] = -data.tauntb # clock bias (sec)
1260
+ nvd[CLKDRIFT] = data.gammantb # clock relative freq bias (sec)
1261
+ nvd[CLKRATE] = glotk2sec(data.tk) + (nd * 86400) # - msg timeframe (s)
1262
+ nvd[BOD] = []
1263
+ nvb = nvd[BOD]
1264
+ for _ in range(4): # broadcast orbit data blocks * 7
1265
+ nvb.append(["", "", "", ""]) # 4X,4D19.12
1266
+ # BROADCAST ORBIT - 1
1267
+ nvb[0][0] = data.xntb # - pos (km)
1268
+ nvb[0][1] = data.xntbdot # - vel (km/s)
1269
+ nvb[0][2] = data.xntbdot2 # - acc (km/s2)
1270
+ nvb[0][3] = (data.bn >> 2) & 0b1 # - health
1271
+ # BROADCAST ORBIT - 2
1272
+ nvb[1][0] = data.yntb # - pos (km)
1273
+ nvb[1][1] = data.yntbdot # - vel (km/s)
1274
+ nvb[1][2] = data.yntbdot2 # - acc (km/s2)
1275
+ nvb[1][3] = freqid # - freq id
1276
+ # BROADCAST ORBIT - 3
1277
+ nvb[2][0] = data.zntb # - pos (km)
1278
+ nvb[2][1] = data.zntbdot # - vel (km/s)
1279
+ nvb[2][2] = data.zntbdot2 # - acc (km/s2)
1280
+ nvb[2][3] = data.en # - age of operation info (days)
1281
+ health = (
1282
+ (data.m << 6)
1283
+ + (data.p4 << 5)
1284
+ + (data.p3 << 4)
1285
+ + (data.p2 << 3)
1286
+ + (data.p1 << 1)
1287
+ + data.p
1288
+ )
1289
+ nvb[3][0] = health # - health status flags
1290
+ nvb[3][1] = data.deltataun # - L1/L2 group delay diff (sec)
1291
+ nvb[3][2] = data.ft # - URAI (GLO-M/K only)
1292
+ nvb[3][3] = data.ln << 2 # - health flags
1293
+
1294
+ if self._rinex_version < RINEX4:
1295
+ if self._timecorrflag:
1296
+ self._format_timecorr_3(data, 3)
1297
+ # if self._ionocorrflag:
1298
+ # self._format_ionocorr_3(data)
1299
+ else: # RINEX 4.02
1300
+ if self._timecorrflag:
1301
+ nvd[STO] = self._format_timecorr_4(
1302
+ msgtype="FDMA",
1303
+ msgsubtype="",
1304
+ timecode="GLUT",
1305
+ utcid="UTC(SU)",
1306
+ data=data,
1307
+ )
1308
+ # if self._ionocorrflag:
1309
+ # nvd[ION] = self._format_ionocorr_4(
1310
+ # msgtype="IFNV", msgsubtype="", model=NEQUICK, data=data
1311
+ # )
1312
+
1313
+ def _format_timecorr_3(self, data: RawNav, source: int = 0):
1234
1314
  """
1235
1315
  Format RINEX 3 ime correction blocks.
1236
1316
 
1237
1317
  RINEX 3 places these as TIME SYSTEM CORR header lines.
1238
1318
 
1239
1319
  :param RawNav data: data containing time corrections
1320
+ :param int source: time correction source
1240
1321
  """
1241
1322
 
1323
+ if data.gnss == GLO:
1324
+ a0 = -data.tauc
1325
+ a1 = 0
1326
+ timeref = 0
1327
+ weekno = 0
1328
+ else:
1329
+ a0 = data.a0
1330
+ a1 = data.a1
1331
+ timeref = data.toc
1332
+ weekno = data.wn
1242
1333
  timecode = f"{RINEXGNSSR[data.gnss][0:2]}UT"
1243
1334
  self._timecorr[timecode] = format_time_corr(
1244
1335
  corrtype=timecode,
1245
1336
  svcode=data.svcode,
1246
- source="0",
1247
- timeref=data.toc,
1248
- weekno=data.wn,
1249
- a0=data.a0,
1250
- a1=data.a1,
1337
+ source=source,
1338
+ timeref=timeref,
1339
+ weekno=weekno,
1340
+ a0=a0,
1341
+ a1=a1,
1251
1342
  )
1252
1343
 
1253
1344
  def _format_timecorr_4(
@@ -1267,7 +1358,18 @@ class RinexConverterNavigation:
1267
1358
  :rtype: str
1268
1359
  """
1269
1360
 
1270
- epoch, _ = get_epoch(wno=data.wn, tow=data.tow, gnss=data.gnss)
1361
+ if data.gnss == GLO:
1362
+ a0 = -data.tauc
1363
+ a1 = 0
1364
+ a2 = 0
1365
+ epoch = get_epoch_glo(data.nt, data.n4, data.tk)
1366
+ tot = 0
1367
+ else:
1368
+ a0 = data.a0
1369
+ a1 = data.a1
1370
+ a2 = getattr(data, "a2", 0)
1371
+ epoch, _ = get_epoch(wno=data.wn, tow=data.tow, gnss=data.gnss)
1372
+ tot = data.toc
1271
1373
  return format_sto(
1272
1374
  svcode=data.svcode,
1273
1375
  msgtype=msgtype,
@@ -1276,10 +1378,10 @@ class RinexConverterNavigation:
1276
1378
  timecode=timecode,
1277
1379
  sbasid="",
1278
1380
  utcid=utcid,
1279
- tot=data.toc,
1280
- a0=data.a0,
1281
- a1=data.a1,
1282
- a2=getattr(data, "a2", 0),
1381
+ tot=tot,
1382
+ a0=a0,
1383
+ a1=a1,
1384
+ a2=a2,
1283
1385
  )
1284
1386
 
1285
1387
  def _format_ionocorr_3(self, data: RawNav):
@@ -34,6 +34,7 @@ EPOCH0_GPS = datetime(1980, 1, 6, 0, 0, 0, tzinfo=timezone.utc)
34
34
  EPOCH0_IRN = datetime(1999, 8, 22, 0, 0, 0, tzinfo=timezone.utc)
35
35
  EPOCHMAX = datetime(9999, 12, 31, tzinfo=timezone.utc)
36
36
  EPOCHMIN = datetime(1900, 1, 1, tzinfo=timezone.utc)
37
+ FDMA = "FDMA"
37
38
  FNAV = "FNAV"
38
39
  GAL = "E"
39
40
  GLO = "R"
@@ -42,6 +43,8 @@ INAV = "INAV"
42
43
  ION = "ION"
43
44
  IRN = "I"
44
45
  KLOB = "KLOBUCHAR"
46
+ L1OF = "L1OF"
47
+ L2OF = "L2OF"
45
48
  LEAPS0 = datetime(1900, 1, 1, 0, 0, 0, tzinfo=timezone.utc)
46
49
  LNAV = "LNAV"
47
50
  MET = "M"
@@ -51,7 +54,7 @@ NAV = "N"
51
54
  NEQUICK = "NEQUICK"
52
55
  OBS = "O"
53
56
  OMEGADOTREF = -2.6e-9
54
- PYRINEXCONV_VERSION = "0.1.3 Alpha"
57
+ PYRINEXCONV_VERSION = "0.1.4 Alpha"
55
58
  QZS = "J"
56
59
  RINEX_CANCELLED = 2
57
60
  RINEX_ERROR = 99
@@ -83,6 +86,7 @@ P2_N5 = 0.03125 # 2**-5
83
86
  P2_N6 = 0.015625 # 2**-6
84
87
  P2_N8 = 0.00390625 # 2**-8
85
88
  P2_N9 = 0.001953125 # 2**-9
89
+ P2_N11 = 0.00048828125 # 2**-11
86
90
  P2_N14 = 6.103515625e-05 # 2**-14
87
91
  P2_N15 = 3.0517578125e-05 # 2**-15
88
92
  P2_N16 = 1.52587890625e-05 # 2**-16
@@ -104,6 +108,7 @@ P2_N34 = 5.820766091346741e-11 # 2**-34
104
108
  P2_N35 = 2.9103830456733704e-11 # 2**-35
105
109
  P2_N37 = 7.275957614183426e-12 # 2**-37
106
110
  P2_N38 = 3.637978807091713e-12 # 2**-38
111
+ P2_N40 = 9.094947017729282e-13 # 2**-40
107
112
  P2_N43 = 1.1368683772161603e-13 # 2**-43
108
113
  P2_N44 = 5.684341886080802e-14 # 2**-44
109
114
  P2_N46 = 1.4210854715202004e-14 # 2**-46
@@ -14,7 +14,7 @@ Created on 6 Oct 2025
14
14
 
15
15
  # pylint: disable=invalid-name, too-many-arguments, too-many-positional-arguments
16
16
 
17
- from datetime import datetime, timezone
17
+ from datetime import datetime, timedelta, timezone
18
18
  from getpass import getuser
19
19
  from pathlib import Path
20
20
  from types import NoneType
@@ -108,6 +108,61 @@ def get_epoch(
108
108
  return epoch, wn
109
109
 
110
110
 
111
+ def glotk2sec(tk: int) -> int:
112
+ """
113
+ Convert GLONASS tk value to seconds.
114
+
115
+ :param int tk: GLONASS time
116
+ :return: seconds
117
+ :rtype: int
118
+ """
119
+
120
+ hour = (tk >> 7) & 0b11111
121
+ minute = (tk >> 1) & 0b111111
122
+ seconds = (tk & 0b1) * 30
123
+ return (hour * 3600) + (minute * 60) + seconds
124
+
125
+
126
+ def get_epoch_glo(nt: int, n4: int, tk: int) -> datetime:
127
+ """
128
+ Get epoch from GLONASS day and time attributes.
129
+
130
+ See GLONASS ICD A.3.1.3
131
+
132
+ :param int nt: number of days in 4 year cycle
133
+ :param int n4: 4 year cycle number
134
+ :param int tk: time
135
+ :return: epoch
136
+ :rtype: datetime
137
+ """
138
+
139
+ j = 0
140
+ md = 365
141
+ if 1 <= nt <= 366:
142
+ j = 1
143
+ md = 366
144
+ elif 367 <= nt <= 731:
145
+ j = 2
146
+ elif 732 <= nt <= 1096:
147
+ j = 3
148
+ elif 1097 <= nt <= 1461:
149
+ j = 4
150
+ year = 1996 + (4 * (n4 - 1)) + (j - 1)
151
+ basedate = datetime(year, 1, 1, tzinfo=timezone.utc) + timedelta(days=(nt % md) - 2)
152
+ hour = ((tk >> 7) & 0b11111) - 3 # GLO time 3 hours ahead of UTC
153
+ minute = (tk >> 1) & 0b111111
154
+ seconds = (tk & 0b1) * 30
155
+ return datetime(
156
+ basedate.year,
157
+ basedate.month,
158
+ basedate.day,
159
+ hour,
160
+ minute,
161
+ seconds,
162
+ tzinfo=timezone.utc,
163
+ )
164
+
165
+
111
166
  def get_fithours(iodc: int, fit: int, gnss: str) -> int | str:
112
167
  """
113
168
  Get FIT interval in hours for given IODC and fit flag.
@@ -0,0 +1,136 @@
1
+ """
2
+ rinex_subframes_glo.py
3
+
4
+ GLONASS NAV Subframe definitions.
5
+
6
+ https://web.archive.org/web/20161020203029/http://russianspacesystems.ru/wp-content/uploads/2016/08/ICD_GLONASS_eng_v5.1.pdf
7
+
8
+ These are provided as the basis of a capability to parse and store
9
+ the payloads of raw NAV subframe messages, via the associated
10
+ `pygnssutils.RawNav` class defined in `rawnav.py`.
11
+
12
+ NB:
13
+
14
+ - MSB and LSB fields MUST be suffixed '_msb' and '_lsb' respectively.
15
+ - Non-data bits (reserved, parity, non) MUST be prefixed '_'.
16
+ - Avoid the following reserved field names: gnss, svid, sigid, subframeacq, epoch
17
+
18
+ Created on 6 Oct 2025
19
+
20
+ :author: semuadmin (Steve Smith)
21
+ :copyright: semuadmin © 2025
22
+ :license: BSD 3-Clause
23
+ """
24
+
25
+ from pygnssutils.rawnav import SID, S, U
26
+ from pygnssutils.rinex_globals import (
27
+ L1OF,
28
+ P2_N11,
29
+ P2_N20,
30
+ P2_N30,
31
+ P2_N31,
32
+ P2_N40,
33
+ START,
34
+ TARGET,
35
+ )
36
+
37
+ # **********************************************************************
38
+ # L1OF (FDMA) - "1C" (same as L2OF - "2C")
39
+ # **********************************************************************
40
+
41
+ # GLONASS ICD refers to subframes as 'strings'
42
+
43
+ GLO_L1OF_SUBFRAME_TLM = {
44
+ "_idle": (0, 1, U, 0),
45
+ SID: (1, 4, U, 0), # subframe (string) number
46
+ }
47
+
48
+ GLO_L1OF_SUBFRAME_END = {
49
+ "_hamming": (77, 8, U, 0),
50
+ }
51
+
52
+ # content of 4 32-bit dwrds in RXM-SFRBX payload:
53
+ GLO_SUPERFRAME = {
54
+ **GLO_L1OF_SUBFRAME_TLM,
55
+ "data": (5, 72, U, 0),
56
+ **GLO_L1OF_SUBFRAME_END,
57
+ "_padding1": (85, 11, U, 0),
58
+ "superframeid": (96, 16, U, 0),
59
+ "_padding2": (112, 8, U, 0),
60
+ "frameid": (120, 8, U, 0),
61
+ }
62
+
63
+ # attribute_name: (bit offset, bit length, bit encoding, scaling)
64
+ GLO_L1OF_SUBFRAME_1 = {
65
+ **GLO_L1OF_SUBFRAME_TLM,
66
+ "_reserved1": (5, 2, U, 0),
67
+ "p1": (7, 2, U, 1),
68
+ "tk": (9, 12, U, 0),
69
+ # "tk_hours": (9, 5, U, 0),
70
+ # "tk_mins": (14, 6, U, 0),
71
+ # "tk_secs": (20, 1, U, 30),
72
+ "xntbdot": (21, 24, S, P2_N20),
73
+ "xntbdot2": (45, 5, S, P2_N30),
74
+ "xntb": (50, 27, S, P2_N11),
75
+ **GLO_L1OF_SUBFRAME_END,
76
+ }
77
+ GLO_L1OF_SUBFRAME_2 = {
78
+ **GLO_L1OF_SUBFRAME_TLM,
79
+ "bn": (5, 3, U, 0),
80
+ "p2": (8, 1, U, 1),
81
+ "tb": (9, 7, U, 15),
82
+ "_reserved1": (16, 5, U, 0),
83
+ "yntbdot": (21, 24, S, P2_N20),
84
+ "yntbdot2": (45, 5, S, P2_N30),
85
+ "yntb": (50, 27, S, P2_N11),
86
+ **GLO_L1OF_SUBFRAME_END,
87
+ }
88
+ GLO_L1OF_SUBFRAME_3 = {
89
+ **GLO_L1OF_SUBFRAME_TLM,
90
+ "p3": (5, 1, U, 1),
91
+ "gammantb": (6, 11, S, P2_N40),
92
+ "_reserved1": (17, 1, U, 0),
93
+ "p": (18, 2, U, 1),
94
+ "ln": (20, 1, U, 1),
95
+ "zntbdot": (21, 24, S, P2_N20),
96
+ "zntbdot2": (45, 5, S, P2_N30),
97
+ "zntb": (50, 27, S, P2_N11),
98
+ **GLO_L1OF_SUBFRAME_END,
99
+ }
100
+ GLO_L1OF_SUBFRAME_4 = {
101
+ **GLO_L1OF_SUBFRAME_TLM,
102
+ "tauntb": (5, 22, S, P2_N30),
103
+ "deltataun": (27, 5, S, P2_N30),
104
+ "en": (32, 5, U, 1),
105
+ "_reserved1": (37, 14, U, 0),
106
+ "p4": (51, 1, U, 1),
107
+ "ft": (52, 4, U, 0),
108
+ "_reserved2": (56, 3, U, 0),
109
+ "nt": (59, 11, U, 0),
110
+ "n": (70, 5, U, 0),
111
+ "m": (75, 2, U, 0), # 0 = GLONASS, 1 = GLONASS-M
112
+ **GLO_L1OF_SUBFRAME_END,
113
+ }
114
+ GLO_L1OF_SUBFRAME_5 = {
115
+ **GLO_L1OF_SUBFRAME_TLM,
116
+ "na": (5, 11, S, P2_N30),
117
+ "tauc": (16, 32, S, P2_N31),
118
+ "_reserved1": (48, 1, U, 0),
119
+ "n4": (49, 5, U, 1),
120
+ "taugps": (54, 22, S, P2_N30),
121
+ "ln": (76, 1, U, 0),
122
+ **GLO_L1OF_SUBFRAME_END,
123
+ }
124
+
125
+ # mapping for (subframe, page) acquisition mask subframeacq
126
+ GLO_SUBFRAMEACQ_MAP = {
127
+ L1OF: {
128
+ TARGET: 0b11111, # subframes 1,2,3,4,5
129
+ START: 1,
130
+ (1, 0): (GLO_L1OF_SUBFRAME_1, 1),
131
+ (2, 0): (GLO_L1OF_SUBFRAME_2, 2),
132
+ (3, 0): (GLO_L1OF_SUBFRAME_3, 4),
133
+ (4, 0): (GLO_L1OF_SUBFRAME_4, 8),
134
+ (5, 0): (GLO_L1OF_SUBFRAME_5, 16),
135
+ },
136
+ }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pygnssutils
3
- Version: 1.2.3
3
+ Version: 1.2.4
4
4
  Summary: GNSS Command Line Utilities
5
5
  Author-email: Steve Smith <semuadmin@semuconsulting.com>
6
6
  Maintainer-email: Steve Smith <semuadmin@semuconsulting.com>
@@ -35,10 +35,10 @@ Requires-Dist: paho-mqtt>=2.1.0
35
35
  Requires-Dist: pyserial>=3.5
36
36
  Requires-Dist: pyspartn>=1.0.8
37
37
  Requires-Dist: pyubx2>=1.3.3
38
- Requires-Dist: pynmeagps>=1.1.4
38
+ Requires-Dist: pynmeagps>=1.1.5
39
39
  Requires-Dist: pysbf2>=1.0.4
40
40
  Requires-Dist: pyubxutils>=1.0.6
41
- Requires-Dist: pyqgc>=0.2.2
41
+ Requires-Dist: pyqgc>=1.0.0
42
42
  Requires-Dist: pyunigps>=1.0.0
43
43
  Dynamic: license-file
44
44
 
@@ -81,7 +81,7 @@ designated output stream.
81
81
  a simple SPARTN IP (MQTT) Client which receives SPARTN correction data from an SPARTN IP location service and (optionally) sends this to a
82
82
  designated output stream.
83
83
  1. `SocketServer` class based on the native Python `ThreadingTCPServer`. Capable of operating in two modes - Socket Server or NTRIP Caster. Provides two alternate client request handler classes - `ClientHandler` (HTTP) or `ClientHandlerTLS` (HTTPS).
84
- 1. `RinexConverter` class and its associated [`pyrinexconv`](#rinexconvert) CLI utility. This implements a binary GNSS data log file to RINEX text file conversion facility. **NB: this initial ALPHA release has limited functionality.**
84
+ 1. `RinexConverter` class and its associated [`pyrinexconv`](#rinexconvert) CLI utility. This implements a binary GNSS data log file to RINEX text file conversion facility. **NB: RINEX conversion is currently an experimental work in progress (*contributions and feedback welcome*)**
85
85
 
86
86
  The pygnssutils homepage is located at [https://github.com/semuconsulting/pygnssutils](https://github.com/semuconsulting/pygnssutils).
87
87
 
@@ -543,15 +543,15 @@ class pygnssutils.rinex_conv.RinexConvertor(app, rinex_version, rinex_type, gnss
543
543
 
544
544
  A command line utility and Python class `RinexConverter` to convert binary GNSS data logs to RINEX text file format.
545
545
 
546
- **NB: The current ALPHA release implements the following functionality:**
546
+ **NB: RINEX conversion is an experimental work in progress (*contributions and feedback welcome*). The current ALPHA release implements the following functionality:**
547
547
 
548
548
  1. RINEX versions 3.05 and 4.02.
549
549
  1. Convert binary UBX RXM-RAW or RXM-RAWX (raw observation) data from u-blox receivers (e.g. ZED-F9P) to RINEX Observation file format.
550
- 1. Convert binary RXM-SFRBX (navigation subframe) data from u-blox receivers to RINEX Navigation file format. **Currently supports GPS LNAV/CNAV, GAL FNAV/INAV, BDS D1/D2**, but the underlying `RinexConverterNavigation` class will be enhanced in future releases.
550
+ 1. Convert binary RXM-SFRBX (navigation subframe) data from u-blox receivers to RINEX Navigation file format. **Currently supports GPS LNAV/CNAV, GAL FNAV/INAV, BDS D1/D2 & GLO L1OF**, but the underlying `RinexConverterNavigation` class will be enhanced in future releases.
551
551
  1. Convert RTCM3 Ephemerides messages (1019, 1020, 1041-1046) from any source (including NTRIP caster or RTK base station receiver) to RINEX Navigation file format.
552
552
  1. Convert NMEA MWD (wind speed and direction) and XDR (temperature and pressure) sensor data to RINEX Meteorology file format.
553
553
 
554
- A Graphical User Interface for this utility will be added to [PyGPSClient](https://github.com/semuconsulting/PyGPSClient).
554
+ A Graphical User Interface for this utility has been added to [PyGPSClient](https://github.com/semuconsulting/PyGPSClient).
555
555
 
556
556
  Assuming the u-blox receiver is already configured to output raw observation (UBX-RXM-RAWX) and navigation subframe (UBX-RXM-SFRBX) data, a suitable input log file can be created using the pygnssutils `gnssstreamer` CLI utility e.g. ...
557
557
 
@@ -26,6 +26,7 @@ src/pygnssutils/rinex_globals.py
26
26
  src/pygnssutils/rinex_helpers.py
27
27
  src/pygnssutils/rinex_subframes_bds.py
28
28
  src/pygnssutils/rinex_subframes_gal.py
29
+ src/pygnssutils/rinex_subframes_glo.py
29
30
  src/pygnssutils/rinex_subframes_gps.py
30
31
  src/pygnssutils/socket_server.py
31
32
  src/pygnssutils.egg-info/PKG-INFO
@@ -3,8 +3,8 @@ paho-mqtt>=2.1.0
3
3
  pyserial>=3.5
4
4
  pyspartn>=1.0.8
5
5
  pyubx2>=1.3.3
6
- pynmeagps>=1.1.4
6
+ pynmeagps>=1.1.5
7
7
  pysbf2>=1.0.4
8
8
  pyubxutils>=1.0.6
9
- pyqgc>=0.2.2
9
+ pyqgc>=1.0.0
10
10
  pyunigps>=1.0.0
@@ -13,7 +13,6 @@ Created on 26 May 2022
13
13
  import unittest
14
14
  from datetime import datetime, timedelta, timezone
15
15
  from pathlib import Path
16
- from platform import system
17
16
  from time import sleep
18
17
 
19
18
  from pygnssutils.rinex_conv import RinexConverter
@@ -21,9 +20,6 @@ from pygnssutils.rinex_globals import BDS, EPOCH0_GPS, EPOCHMAX, EPOCHMIN, GAL,
21
20
  from pygnssutils.rinex_helpers import (
22
21
  DRNX,
23
22
  FRNX,
24
- get_epoch,
25
- get_svcode_ubx,
26
- get_svcode,
27
23
  adjust_time_units,
28
24
  format_antennabsight,
29
25
  format_antennadeltahen,
@@ -36,24 +32,27 @@ from pygnssutils.rinex_helpers import (
36
32
  format_clockoffset,
37
33
  format_cnrunit,
38
34
  format_comments,
35
+ format_eop,
39
36
  format_fileend,
40
37
  format_filename,
41
38
  format_glonassfrq,
42
39
  format_glonassphasebias,
43
40
  format_headerend,
44
41
  format_interval,
45
- format_eop,
42
+ format_ion,
46
43
  format_iono_corr,
47
44
  format_leapseconds,
48
45
  format_marker,
49
46
  format_met_obstypes,
50
47
  format_met_sensorpos,
51
48
  format_met_sensortype,
49
+ format_nav_typesvmssg,
52
50
  format_numsats,
53
51
  format_observer,
54
52
  format_obstypes,
55
53
  format_rcvrtype,
56
54
  format_runby,
55
+ format_sto,
57
56
  format_sys_antennaphasecentre,
58
57
  format_sys_dcbsapplied,
59
58
  format_sys_pcvsapplied,
@@ -62,11 +61,13 @@ from pygnssutils.rinex_helpers import (
62
61
  format_time_corr,
63
62
  format_timefirstlast,
64
63
  format_version,
65
- format_nav_typesvmssg,
66
- listify,
67
- format_sto,
68
- format_ion,
64
+ get_epoch,
65
+ get_epoch_glo,
66
+ get_svcode,
67
+ get_svcode_ubx,
69
68
  gpsura2m,
69
+ glotk2sec,
70
+ listify,
70
71
  )
71
72
 
72
73
  SENSORTYPES = {
@@ -133,7 +134,7 @@ SENSORTYPES = {
133
134
  }
134
135
 
135
136
  # only run RINEX file tests locally
136
- RINEXFILETEST = False # system() == "Darwin"
137
+ RINEXFILETEST = False # system() == "Darwin"
137
138
 
138
139
 
139
140
  class StaticTest(unittest.TestCase):
@@ -166,22 +167,22 @@ class StaticTest(unittest.TestCase):
166
167
  self.assertEqual(DRNX(" ", 14, 8), " ")
167
168
 
168
169
  def testgetsvcodeubx(self):
169
- self.assertEqual(get_svcode_ubx(0,3),"G03")
170
- self.assertEqual(get_svcode_ubx(0,3,False),"G 3")
171
- self.assertEqual(get_svcode_ubx(2,12),"E12")
172
- self.assertEqual(get_svcode_ubx(2,12,False),"E12")
173
- self.assertEqual(get_svcode_ubx(1,112),"S12")
174
- self.assertEqual(get_svcode_ubx(1,112,False),"S12")
175
- self.assertEqual(get_svcode_ubx(5,194),"J02")
176
- self.assertEqual(get_svcode_ubx(5,194,False),"J 2")
170
+ self.assertEqual(get_svcode_ubx(0, 3), "G03")
171
+ self.assertEqual(get_svcode_ubx(0, 3, False), "G 3")
172
+ self.assertEqual(get_svcode_ubx(2, 12), "E12")
173
+ self.assertEqual(get_svcode_ubx(2, 12, False), "E12")
174
+ self.assertEqual(get_svcode_ubx(1, 112), "S12")
175
+ self.assertEqual(get_svcode_ubx(1, 112, False), "S12")
176
+ self.assertEqual(get_svcode_ubx(5, 194), "J02")
177
+ self.assertEqual(get_svcode_ubx(5, 194, False), "J 2")
177
178
 
178
179
  def testgetsvcodertcm(self):
179
- self.assertEqual(get_svcode("G",3),"G03")
180
- self.assertEqual(get_svcode("G",3,None, False),"G 3")
181
- self.assertEqual(get_svcode("S",112-100),"S12")
182
- self.assertEqual(get_svcode("S",112-100,None, False),"S12")
183
- self.assertEqual(get_svcode("J",194-192),"J02")
184
- self.assertEqual(get_svcode("J",194-192,None, False),"J 2")
180
+ self.assertEqual(get_svcode("G", 3), "G03")
181
+ self.assertEqual(get_svcode("G", 3, None, False), "G 3")
182
+ self.assertEqual(get_svcode("S", 112 - 100), "S12")
183
+ self.assertEqual(get_svcode("S", 112 - 100, None, False), "S12")
184
+ self.assertEqual(get_svcode("J", 194 - 192), "J02")
185
+ self.assertEqual(get_svcode("J", 194 - 192, None, False), "J 2")
185
186
 
186
187
  def testformat_filename(self):
187
188
  firstobs = datetime(2026, 3, 14, 12, 4, 6)
@@ -206,13 +207,13 @@ class StaticTest(unittest.TestCase):
206
207
  )
207
208
  firstobs = datetime(2026, 3, 14, 12, 4, 6)
208
209
  lastobs = firstobs + timedelta(minutes=60)
209
- interval=15
210
+ interval = 15
210
211
  res = format_filename(
211
- rinextype="O",
212
+ rinextype="O",
212
213
  gnssfilter=[GPS],
213
- startepoch=firstobs,
214
+ startepoch=firstobs,
214
215
  endepoch=lastobs,
215
- interval=interval,
216
+ interval=interval,
216
217
  outputpath=Path("/Users/steve/Downloads"),
217
218
  form="IGS",
218
219
  site="SITE",
@@ -226,9 +227,30 @@ class StaticTest(unittest.TestCase):
226
227
  )
227
228
 
228
229
  def test_getepoch(self):
229
- res = get_epoch(366,411634,"G")
230
+ res = get_epoch(366, 411634, "G")
231
+ # print(res)
232
+ self.assertEqual(
233
+ res, (datetime(2026, 4, 16, 18, 20, 34, tzinfo=timezone.utc), 2414)
234
+ )
235
+
236
+ def test_glotk2sec(self):
237
+ res = glotk2sec(1584)
238
+ # print(res)
239
+ self.assertEqual(res, 44640)
240
+ res = glotk2sec(1585)
241
+ # print(res)
242
+ self.assertEqual(res, 44670)
243
+ res = glotk2sec(1586)
230
244
  # print(res)
231
- self.assertEqual(res, (datetime(2026, 4, 16, 18, 20, 34, tzinfo=timezone.utc), 2414))
245
+ self.assertEqual(res, 44700)
246
+
247
+ def test_getepochglo(self):
248
+ res = get_epoch_glo(884, 8, 1584)
249
+ # print(res)
250
+ self.assertEqual(res, datetime(2026, 6, 2, 9, 24, 0, tzinfo=timezone.utc))
251
+ res = get_epoch_glo(884, 8, 1646)
252
+ # print(res)
253
+ self.assertEqual(res, (datetime(2026, 6, 2, 9, 55, 0, tzinfo=timezone.utc)))
232
254
 
233
255
  def testformat_antennabsight(self):
234
256
  res = format_antennabsight()
@@ -346,7 +368,7 @@ class StaticTest(unittest.TestCase):
346
368
  "GPSB 1.2481e-07 5.0391e-06 2.3771e-07 1.2346e-13 B 14 IONOSPHERIC CORR\n"
347
369
  )
348
370
  res = format_iono_corr(
349
- svid=2,
371
+ svid=2,
350
372
  timemark="A",
351
373
  corrtype="GPSA",
352
374
  parm1=0.00000012481234567890,
@@ -355,7 +377,7 @@ class StaticTest(unittest.TestCase):
355
377
  parm4=0.00000000000012345678909,
356
378
  )
357
379
  res += format_iono_corr(
358
- svid=14,
380
+ svid=14,
359
381
  timemark="B",
360
382
  corrtype="GPSB",
361
383
  parm1=0.00000012481234567890,
@@ -369,7 +391,7 @@ class StaticTest(unittest.TestCase):
369
391
  def testformat_ion(self):
370
392
  EXPECTED_RESULT = (
371
393
  "> ION G24 LNAV XXXX\n"
372
- " 2026 05 13 08 34 02 1.234567000000e-12 1.234567000000e-12-1.234567000000e-12\n"
394
+ " 2026 05 13 08 34 02 1.234567000000e-12 1.234567000000e-12-1.234567000000e-12\n"
373
395
  " 1.234567000000e-12 1.234567000000e-12 1.234567000000e-12-1.234567000000e-12\n"
374
396
  " 1.234567000000e-12\n"
375
397
  )
@@ -377,7 +399,7 @@ class StaticTest(unittest.TestCase):
377
399
  svcode="G24",
378
400
  msgtype="LNAV",
379
401
  msgsubtype="XXXX",
380
- epoch=datetime(2026,5,13,8,34,2,tzinfo=timezone.utc),
402
+ epoch=datetime(2026, 5, 13, 8, 34, 2, tzinfo=timezone.utc),
381
403
  a0=1.234567e-12,
382
404
  a1=1.234567e-12,
383
405
  a2=-1.234567e-12,
@@ -387,13 +409,13 @@ class StaticTest(unittest.TestCase):
387
409
  b2=-1.234567e-12,
388
410
  b3=1.234567e-12,
389
411
  )
390
- #print(res)
412
+ # print(res)
391
413
  self.assertEqual(res, EXPECTED_RESULT)
392
414
 
393
415
  def testformat_eop(self):
394
416
  EXPECTED_RESULT = (
395
417
  "> EOP G24 LNAV XXXX\n"
396
- " 2026 05 13 08 34 02 2.082471847534e-01-6.551742553711e-04 0.000000000000e+00\n"
418
+ " 2026 05 13 08 34 02 2.082471847534e-01-6.551742553711e-04 0.000000000000e+00\n"
397
419
  " 3.444433212280e-01-9.121894836426e-04 0.000000000000e+00\n"
398
420
  " 1.729860000000e+05-1.754972934723e-01 5.635917186737e-04 0.000000000000e+00\n"
399
421
  )
@@ -401,17 +423,17 @@ class StaticTest(unittest.TestCase):
401
423
  svcode="G24",
402
424
  msgtype="LNAV",
403
425
  msgsubtype="XXXX",
404
- epoch=datetime(2026,5,13,8,34,2,tzinfo=timezone.utc),
405
- tom=1.729860000000e+05,
426
+ epoch=datetime(2026, 5, 13, 8, 34, 2, tzinfo=timezone.utc),
427
+ tom=1.729860000000e05,
406
428
  xp=2.082471847534e-01,
407
429
  dxpdt=-6.551742553711e-04,
408
- dxpdt2=0.000000000000e+00,
430
+ dxpdt2=0.000000000000e00,
409
431
  yp=3.444433212280e-01,
410
432
  dypdt=-9.121894836426e-04,
411
- dypdt2=0.000000000000e+00,
433
+ dypdt2=0.000000000000e00,
412
434
  deltaut1=-1.754972934723e-01,
413
435
  ddeltaut1dt=5.635917186737e-04,
414
- d2deltaut1dt2=0.000000000000e+00,
436
+ d2deltaut1dt2=0.000000000000e00,
415
437
  )
416
438
  print(res)
417
439
  self.assertEqual(res, EXPECTED_RESULT)
@@ -425,33 +447,32 @@ class StaticTest(unittest.TestCase):
425
447
  weekno=1849,
426
448
  source="5",
427
449
  a0=0.000000003725290298,
428
- a1=0.00000000000000532907052
450
+ a1=0.00000000000000532907052,
429
451
  )
430
452
  # print(res)
431
453
  self.assertEqual(res, EXPECTED_RESULT)
432
454
 
433
-
434
455
  def testformat_sto(self):
435
456
  EXPECTED_RESULT = (
436
457
  "> STO G24 LNAV XXXX\n"
437
- " 2026 05 13 08 34 02 GPUT SSSS UTC(USNO) \n"
458
+ " 2026 05 13 08 34 02 GPUT SSSS UTC(USNO) \n"
438
459
  " 4.567890000000e+05 1.234567000000e-23 1.234567000000e-23-1.234567800000e-12\n"
439
460
  )
440
461
  res = format_sto(
441
462
  svcode="G24",
442
463
  msgtype="LNAV",
443
464
  msgsubtype="XXXX",
444
- epoch=datetime(2026,5,13,8,34,2,tzinfo=timezone.utc),
465
+ epoch=datetime(2026, 5, 13, 8, 34, 2, tzinfo=timezone.utc),
445
466
  timecode="GPUT",
446
467
  sbasid="SSSS",
447
468
  utcid="UTC(USNO)",
448
469
  tot=456789,
449
470
  a0=1.234567e-23,
450
471
  a1=1.234567e-23,
451
- a2=-1.2345678e-12
472
+ a2=-1.2345678e-12,
452
473
  )
453
474
  # print(res)
454
- self.assertEqual(res,EXPECTED_RESULT)
475
+ self.assertEqual(res, EXPECTED_RESULT)
455
476
 
456
477
  def testformat_met_obstypes(self):
457
478
  EXPECTED_RESULT = (
@@ -514,16 +535,16 @@ class StaticTest(unittest.TestCase):
514
535
 
515
536
  def testgpsura2m(self):
516
537
 
517
- self.assertEqual(gpsura2m(1),2.8)
518
- self.assertEqual(gpsura2m(2),4.0)
519
- self.assertEqual(gpsura2m(3),5.7)
520
- self.assertEqual(gpsura2m(5),11.3)
521
- self.assertEqual(gpsura2m(8),64)
522
- self.assertEqual(gpsura2m(14),4096)
523
- self.assertEqual(gpsura2m(15),0)
524
- self.assertEqual(gpsura2m(-16),0)
525
- self.assertEqual(gpsura2m(-8),0.1)
526
-
538
+ self.assertEqual(gpsura2m(1), 2.8)
539
+ self.assertEqual(gpsura2m(2), 4.0)
540
+ self.assertEqual(gpsura2m(3), 5.7)
541
+ self.assertEqual(gpsura2m(5), 11.3)
542
+ self.assertEqual(gpsura2m(8), 64)
543
+ self.assertEqual(gpsura2m(14), 4096)
544
+ self.assertEqual(gpsura2m(15), 0)
545
+ self.assertEqual(gpsura2m(-16), 0)
546
+ self.assertEqual(gpsura2m(-8), 0.1)
547
+
527
548
  def testrinexnav(self):
528
549
  EXPECTED_RESULT_OBS = [
529
550
  r" 3.05 O: OBSERVATION M: MIXED RINEX VERSION / TYPE\n",
@@ -6,17 +6,17 @@ length are consistent with ICD definition.
6
6
 
7
7
  Created on 26 May 2022
8
8
 
9
- *** NB: must be saved in UTF-8 format ***
10
-
11
9
  @author: semuadmin
12
10
  """
13
11
 
14
12
  import unittest
15
13
 
16
- from pygnssutils.rawnav import VALPREAMBLE
17
- import pygnssutils.rinex_subframes_gps as gps
18
- import pygnssutils.rinex_subframes_gal as gal
19
14
  import pygnssutils.rinex_subframes_bds as bds
15
+ import pygnssutils.rinex_subframes_gal as gal
16
+ import pygnssutils.rinex_subframes_glo as glo
17
+ import pygnssutils.rinex_subframes_gps as gps
18
+ from pygnssutils.rawnav import VALPREAMBLE
19
+
20
20
 
21
21
  class StaticTest(unittest.TestCase):
22
22
  def setUp(self):
@@ -48,7 +48,7 @@ class StaticTest(unittest.TestCase):
48
48
  gps.GPS_LNAV_SUBFRAME_4_P18,
49
49
  )
50
50
  sfrlen = 300
51
- self.scandefs(sfrdefs,sfrlen)
51
+ self.scandefs(sfrdefs, sfrlen)
52
52
 
53
53
  def testGPSCNAVdefs(self):
54
54
  sfrdefs = (
@@ -66,10 +66,10 @@ class StaticTest(unittest.TestCase):
66
66
  gps.GPS_CNAV_SUBFRAME_35,
67
67
  gps.GPS_CNAV_SUBFRAME_36,
68
68
  gps.GPS_CNAV_SUBFRAME_37,
69
- gps.GPS_CNAV_SUBFRAME_40
69
+ gps.GPS_CNAV_SUBFRAME_40,
70
70
  )
71
71
  sfrlen = 300
72
- self.scandefs(sfrdefs,sfrlen)
72
+ self.scandefs(sfrdefs, sfrlen)
73
73
 
74
74
  def testGALFNAV(self):
75
75
 
@@ -79,16 +79,16 @@ class StaticTest(unittest.TestCase):
79
79
  gal.GAL_FNAV_SUBFRAME_3,
80
80
  gal.GAL_FNAV_SUBFRAME_4,
81
81
  gal.GAL_FNAV_SUBFRAME_5,
82
- gal.GAL_FNAV_SUBFRAME_6
82
+ gal.GAL_FNAV_SUBFRAME_6,
83
83
  )
84
84
  sfrlen = 244
85
- self.scandefs(sfrdefs,sfrlen)
85
+ self.scandefs(sfrdefs, sfrlen)
86
86
 
87
87
  def testGALINAV(self):
88
-
88
+
89
89
  sfrdefs = (gal.GAL_INAV_SUBFRAME,)
90
90
  sfrlen = 256
91
- self.scandefs(sfrdefs,sfrlen)
91
+ self.scandefs(sfrdefs, sfrlen)
92
92
 
93
93
  sfrdefs = (
94
94
  gal.GAL_INAV_WORD_1,
@@ -101,10 +101,10 @@ class StaticTest(unittest.TestCase):
101
101
  gal.GAL_INAV_WORD_8,
102
102
  gal.GAL_INAV_WORD_9,
103
103
  gal.GAL_INAV_WORD_10,
104
- gal.GAL_INAV_WORD_16
104
+ gal.GAL_INAV_WORD_16,
105
105
  )
106
106
  sfrlen = 128
107
- self.scandefs(sfrdefs,sfrlen)
107
+ self.scandefs(sfrdefs, sfrlen)
108
108
 
109
109
  def testBDSD1(self):
110
110
 
@@ -116,7 +116,7 @@ class StaticTest(unittest.TestCase):
116
116
  bds.BDS_D1_SUBFRAME_5_P10,
117
117
  )
118
118
  sfrlen = 300
119
- self.scandefs(sfrdefs,sfrlen)
119
+ self.scandefs(sfrdefs, sfrlen)
120
120
 
121
121
  def testBDSD2(self):
122
122
 
@@ -133,4 +133,16 @@ class StaticTest(unittest.TestCase):
133
133
  bds.BDS_D2_SUBFRAME_1_P10,
134
134
  )
135
135
  sfrlen = 300
136
- self.scandefs(sfrdefs,sfrlen)
136
+ self.scandefs(sfrdefs, sfrlen)
137
+
138
+ def testGLOL1OF(self):
139
+
140
+ sfrdefs = (
141
+ glo.GLO_L1OF_SUBFRAME_1,
142
+ glo.GLO_L1OF_SUBFRAME_2,
143
+ glo.GLO_L1OF_SUBFRAME_3,
144
+ glo.GLO_L1OF_SUBFRAME_4,
145
+ glo.GLO_L1OF_SUBFRAME_5,
146
+ )
147
+ sfrlen = 85
148
+ self.scandefs(sfrdefs, sfrlen)
File without changes
File without changes