diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 9956fcf..b994624 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,5 +1,15 @@ # pygnssutils +### RELEASE 1.2.6 + +1. Add `msgfilter` argument to GNSSReader - will only parse GNSS messages where msgid is in msgfilter. msgid is string type for NMEA, otherwise integer. +1. Enhance `parsing` argument to GNSSReader - permissible values: + - `PARSE_NONE` (0) - No message parsing, raw output only + - `PARSE_FULL` (1) - Full parsing of all message attributes (the default) + - `PARSE_META` (2) - Parse only basic metadata from messages (protocol, identity and length). Outputs a new generic `GNSSMessage` object. Approximately 100x faster than full parsing, but individual data attributes will no longer be available. +1. Further enhancements and bug fixes to RINEX conversion routines. +1. Extend support for raw almanac message parsing. + ### RELEASE 1.2.5 1. Further enhancements and bug fixes to RINEX conversion routines. diff --git a/examples/process_rxmsfrbx_frames.py b/examples/process_rxmsfrbx_frames.py index fa128e1..33257ab 100644 --- a/examples/process_rxmsfrbx_frames.py +++ b/examples/process_rxmsfrbx_frames.py @@ -26,22 +26,20 @@ from pygnssutils.rawnav_subframes_qzs import QZS_SUBFRAMEACQ_MAP from pygnssutils.rawnav_subframes_irn import IRN_SUBFRAMEACQ_MAP -# INFILE = "pygpsdata-rxmsfrbx.log" -# INFILE = "/Users/steve/Downloads/pygpsdata-20260602101842.log" -INFILE = "/Users/steve/Downloads/pygpsdata-20260611094239.ubx" +INFILE = "pygpsdata-rxmsfrbx.log" gps = 0 navframes = {} navstart = {} rxm = 0 -# sfrmap = GPS_SUBFRAMEACQ_MAP[LNAV] # subframe payload definitions -# sfrmap = GPS_SUBFRAMEACQ_MAP[CNAV] # subframe payload definitions +#sfrmap = GPS_SUBFRAMEACQ_MAP[LNAV] # subframe payload definitions +sfrmap = GPS_SUBFRAMEACQ_MAP[CNAV] # subframe payload definitions # sfrmap = GAL_SUBFRAMEACQ_MAP[FNAV] # subframe payload definitions # sfrmap = GAL_SUBFRAMEACQ_MAP[INAV] # subframe payload definitions # sfrmap = BDS_SUBFRAMEACQ_MAP[D1] # subframe payload definitions # sfrmap = BDS_SUBFRAMEACQ_MAP[D2] # subframe payload definitions # sfrmap = GLO_SUBFRAMEACQ_MAP[L1OF] # subframe payload definitions -sfrmap = SBA_SUBFRAMEACQ_MAP[L1CA] # subframe payload definitions +# sfrmap = SBA_SUBFRAMEACQ_MAP[L1CA] # subframe payload definitions # sfrmap = QZS_SUBFRAMEACQ_MAP[LNAV] # subframe payload definitions # sfrmap = QZS_SUBFRAMEACQ_MAP[CNAV] # subframe payload definitions # sfrmap = QZS_SUBFRAMEACQ_MAP[CNV2] # subframe payload definitions @@ -56,14 +54,14 @@ continue if parsed.identity == "RXM-SFRBX": rxm += 1 - # if parsed.gnssId == 0 and parsed.sigId in (0,): # GPS LNAV: - # if parsed.gnssId == 0 and parsed.sigId in (3,4,6,7,): # GPS CNAV: + #if parsed.gnssId == 0 and parsed.sigId in (0,): # GPS LNAV: + if parsed.gnssId == 0 and parsed.sigId in (3,4,6,7,): # GPS CNAV: # if parsed.gnssId == 2 and parsed.sigId in (3,): # GAL FNAV: # if parsed.gnssId == 2 and parsed.sigId in (1,5): # GAL INAV: # if parsed.gnssId == 3 and parsed.sigId in (0,2,4,): # BDS D1: # if parsed.gnssId == 3 and parsed.sigId in (1,3,10,): # BDS D2: # if parsed.gnssId == 6 and parsed.sigId in (0,): # GLO L1OF: - if parsed.gnssId == 1 and parsed.sigId in (0,): # SBA L1CA: + # if parsed.gnssId == 1 and parsed.sigId in (0,): # SBA L1CA: gps += 1 # extract the subframe from the RXM-SFRBX message sfrdata = rnr.process_rxm_sfrbx(parsed) @@ -87,6 +85,7 @@ navframes[sv] = navframes.get(sv, RawNav(gnss, svid, sigcode)) nav = navframes[sv] # parse the subframe into its constituent attributes + # sfracq = 0b1111 # override default subframe acquisition bitmask nav.parse(subframe, sfrdict, sfracq) # when all target subframes have been acquired, print complete frame if nav.subframeacq & target == target: diff --git a/pyproject.toml b/pyproject.toml index 427df4d..21a8223 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,7 +38,7 @@ dependencies = [ "paho-mqtt>=2.1.0", "pyserial>=3.5", "pyspartn>=1.0.8", - "pyubx2>=1.3.3", + "pyubx2>=1.3.6", "pynmeagps>=1.1.5", "pysbf2>=1.0.4", "pyubxutils>=1.0.6", diff --git a/src/pygnssutils/_version.py b/src/pygnssutils/_version.py index 8b74d79..7701d71 100644 --- a/src/pygnssutils/_version.py +++ b/src/pygnssutils/_version.py @@ -8,4 +8,4 @@ :license: BSD 3-Clause """ -__version__ = "1.2.5" +__version__ = "1.2.6" diff --git a/src/pygnssutils/globals.py b/src/pygnssutils/globals.py index b23c500..963ab51 100644 --- a/src/pygnssutils/globals.py +++ b/src/pygnssutils/globals.py @@ -206,10 +206,6 @@ ALLUBX = "allubx" MINNMEA = "minnmea" MINUBX = "minubx" -ALLNMEA_CLS = [b"\xf0", b"\xf1"] -MINMMEA_ID = [b"\xf0\x00", b"\xf0\x02", b"\xf0\x03", b"\xf0\x04", b"\xf0\x05"] -ALLUBX_CLS = [b"\x01"] -MINUBX_ID = [b"\x01\x04", b"\x01\x07", b"\x01\x35"] PYGNSSUTILS_CRT = f"{HOME}/pygnssutils.crt" """Name of default TLS CRT file""" PYGNSSUTILS_CRTPATH = "PYGNSSUTILS_CRTPATH" diff --git a/src/pygnssutils/gnssreader.py b/src/pygnssutils/gnssreader.py index be54698..5633800 100644 --- a/src/pygnssutils/gnssreader.py +++ b/src/pygnssutils/gnssreader.py @@ -1,18 +1,20 @@ """ gnssreader.py -Generic GNSS class. +Generic GNSS reader / parser class. -Reads and parses individual UBX, SBF, QGC, NMEA or RTCM3 messages from any viable -data stream which supports a read(n) -> bytes method. +Reads and parses individual UBX, SBF, QGC, UNI, NMEA or RTCM3 messages from any viable +GNSS data stream which supports a read(n) -> bytes method. It is essentially an amalgamation of the Reader classes in the separate pyubx2, pynmeagps, -pyrtcm, pysbf2 and pyqgc packages. +pyrtcm, pysbf2, pyqgc and pyunigps packages. Returns both the raw binary data (as bytes) and the parsed data. -- 'protfilter' governs which protocols (NMEA, UBX, SBF, QGC or RTCM3) are processed -- 'quitonerror' governs how errors are handled +- 'protfilter' governs which protocols (NMEA, UBX, SBF, QGC, UNI or RTCM3) are processed. +- 'msgfilter' governs which individual message types are processed. +- `parsing` governs whether payloads are fully or partially parsed. +- 'quitonerror' governs how errors are handled. - 'msgmode' indicates the type of UBX datastream (output GET, input SET, query POLL). If msgmode is set to SETPOLL, input/query mode will be automatically detected by parser. @@ -28,7 +30,7 @@ from logging import getLogger from socket import socket from types import FunctionType, NoneType -from typing import Literal +from typing import Any, Literal from pynmeagps import ( NMEA_HDR, @@ -81,6 +83,7 @@ UBXReader, UBXStreamError, UBXTypeError, + escapeall, ) from pyunigps import ( UNI_HDR, @@ -93,6 +96,7 @@ ) from pygnssutils.exceptions import GNSSStreamError +from pygnssutils.globals import DEFAULT_BUFSIZE NMEA_PROTOCOL = 1 """NMEA Protocol""" @@ -106,6 +110,108 @@ """QGC Protocol (Quectel)""" UNI_PROTOCOL = 32 """UNI Protocol (Unicore)""" +PARSE_NONE = 0 +"""No Parsing, raw output only""" +PARSE_FULL = 1 +"""Full parsing of all attributes""" +PARSE_META = 2 +"""Parse protocol and message Id only""" + +PROTOCOLS = { + NMEA_PROTOCOL: "NMEA", + UBX_PROTOCOL: "UBX", + RTCM3_PROTOCOL: "RTCM", + SBF_PROTOCOL: "SBF", + QGC_PROTOCOL: "QGC", + UNI_PROTOCOL: "UNI", +} + + +class GNSSMessage: + """ + Generic GNSSMessage class. + + Holds partially parsed GNSS binary messages (`parsing=2`). + """ + + def __init__(self, protocol: int, msgid: int | str, data: bytes): + """ + Constructor + + :param int protocol: GNSS protocol e.g. 2 (=UBX) + :param int | str msgid: GNSS message id e.g. 0x0215 or "GNGSA" + :param bytes data: raw data from original message + """ + + self._protocol = protocol + self._identity = msgid + self._data = data + + def __str__(self) -> str: + """ + Human readable representation. + + :return: human readable representation + :rtype: str + """ + + prot = PROTOCOLS.get(self._protocol, "UNKNOWN") + ident = ( + f"0x{self._identity:04x}" + if self._protocol in (UBX_PROTOCOL, QGC_PROTOCOL) + else self._identity + ) + return f"<{prot}({ident}, length={self.length}, data={escapeall(self._data)})>" + + def __repr__(self) -> str: + """ + Machine readable representation. + + eval(repr(obj)) = obj + + :return: machine readable representation + :rtype: str + + """ + + ident = ( + f"'{self._identity}'" + if isinstance(self._identity, str) + else f"{self._identity}" + ) + return f"GNSSMessage({self._protocol}, {ident}, {self._data})" + + @property + def protocol(self) -> int: + """ + Getter for protocol. + """ + + return self._protocol + + @property + def identity(self) -> int | str: + """ + Getter for identity. + """ + + return self._identity + + @property + def data(self) -> bytes: + """ + Getter for raw data. + """ + + return self._data + + @property + def length(self) -> int: + """ + Getter for length of original raw data. + """ + + return len(self._data) class GNSSReader: @@ -127,8 +233,9 @@ def __init__( quitonerror: Literal[0, 1, 2] = ERR_LOG, parsebitfield: bool = True, labelmsm: Literal[0, 1] = 1, - bufsize: int = 4096, - parsing: bool = True, + bufsize: int = DEFAULT_BUFSIZE, + parsing: Literal[0, 1, 2] = PARSE_FULL, + msgfilter: tuple | int | str = "", errorhandler: FunctionType | NoneType = None, ): """Constructor. @@ -138,13 +245,15 @@ def __init__( :param int validate: VALCKSUM (1) = Validate checksum, VALNONE (0) = ignore invalid checksum (1) :param int protfilter: NMEA_PROTOCOL (1), UBX_PROTOCOL (2), RTCM3_PROTOCOL (4), - SBF_PROTOCOL (8), QGC_PROTOCOL (16), UNI_PROTOCOL (32). Can be OR'd (7) + SBF_PROTOCOL (8), QGC_PROTOCOL (16), UNI_PROTOCOL (32). Can be OR'd (63) :param Literal[0,1,2] quitonerror: ERR_IGNORE (0) = ignore errors, \ ERR_LOG (1) = log continue, ERR_RAISE (2) = (re)raise (1) :param bool parsebitfield: 1 = parse bitfields, 0 = leave as bytes (1) :param Literal[0,1] labelmsm: RTCM3 MSM label type 1 = RINEX, 2 = BAND (1) :param int bufsize: socket recv buffer size (4096) - :param bool parsing: True = parse data, False = don't parse data (output raw only) (True) + :param Literal[0,1,2] parsing: PARSE_NONE (0) = no parsing (raw only), \ + PARSE_FULL (1) = full parsing, PARSE_META (2) = parse metadata only (1) + :param tuple | int | str msgfilter: parsed message filter ("" = ALL) :param FunctionType | NoneType errorhandler: error handling object or function (None) :raises: UBXStreamError (if mode is invalid) """ @@ -162,6 +271,10 @@ def __init__( self._labelmsm = labelmsm self._msgmode = msgmode self._parsing = parsing + self._msgfilter = ( + (msgfilter,) if not isinstance(msgfilter, tuple) else msgfilter + ) + self._filtermsg = self._msgfilter not in ((), ("",)) self._logger = getLogger(__name__) if self._msgmode not in (GET, SET, POLL, SETPOLL): @@ -174,12 +287,12 @@ def __iter__(self): return self - def __next__(self) -> tuple: + def __next__(self) -> tuple[bytes | NoneType, Any]: """ Return next item in iteration. :return: tuple of (raw_data as bytes, parsed_data as UBXMessage) - :rtype: tuple + :rtype: tuple[bytes | NoneType, Any] :raises: StopIteration """ @@ -187,22 +300,24 @@ def __next__(self) -> tuple: raw_data, parsed_data = self.read() if raw_data is None and parsed_data is None: raise StopIteration - return (raw_data, parsed_data) + return raw_data, parsed_data - def read(self) -> tuple: + def read(self) -> tuple[bytes | NoneType, Any]: """ - Read a single NMEA, UBX, SBF, QGC or RTCM3 message from the stream buffer + Read a single NMEA, UBX, SBF, QGC, UNI or RTCM3 message from the stream buffer and return both raw and parsed data. 'protfilter' determines which protocols are parsed. 'quitonerror' determines whether to raise, log or ignore parsing errors. :return: tuple of (raw_data as bytes, parsed_data as NMEAMessage, UBXMessage, - SBFMessage, QGCMessage or RTCMMessage) - :rtype: tuple + SBFMessage, QGCMessage, UNIMessage, RTCMMessage or (if `parsing=2`) GNSSMessage) + :rtype: tuple[bytes | NoneType, Any] :raises: Exception (if invalid or unrecognised protocol in data stream) """ + raw_data = None + parsed_data = None parsing = True while parsing: # loop until end of valid message or EOF try: @@ -274,7 +389,6 @@ def read(self) -> tuple: parsing = False else: continue - # unrecognised protocol header else: raise GNSSStreamError(f"Unknown protocol header {bytehdr}.") @@ -311,173 +425,209 @@ def read(self) -> tuple: self._do_error(err) continue - return (raw_data, parsed_data) + return raw_data, parsed_data - def _parse_ubx(self, hdr: bytes) -> tuple[bytes, UBXMessage | NoneType]: + def _parse_ubx( + self, hdr: bytes + ) -> tuple[bytes, UBXMessage | GNSSMessage | NoneType]: """ Parse UBX message (using pyubx2). :param bytes hdr: UBX header (b'\\xb5\\x62') :return: tuple of (raw_data as bytes, parsed_data as UBXMessage or None) - :rtype: tuple[bytes, UBXMessage | NoneType] + :rtype: tuple[bytes, UBXMessage | GNSSMessage | NoneType] """ # read the rest of the UBX message from the buffer byten = self._read_bytes(4) - clsid = byten[0:1] - msgid = byten[1:2] + msgid = byten[0:2] + msgidi = int.from_bytes(msgid, "big") # UBX msgids are documented in big endian lenb = byten[2:4] leni = int.from_bytes(lenb, "little", signed=False) - byten = self._read_bytes(leni + 2) - plb = byten[0:leni] - cksum = byten[leni : leni + 2] - raw_data = hdr + clsid + msgid + lenb + plb + cksum + paycrc = self._read_bytes(leni + 2) + raw_data = hdr + msgid + lenb + paycrc # only parse if we need to (filter passes UBX) - if (self._protfilter & UBX_PROTOCOL) and self._parsing: - parsed_data = UBXReader.parse( - raw_data, - validate=self._validate, - msgmode=self._msgmode, - parsebitfield=self._parsebf, - ) - else: - parsed_data = None - return (raw_data, parsed_data) - - def _parse_sbf(self, hdr: bytes) -> tuple[bytes, SBFMessage | NoneType]: + parsed_data = None + if self._protfilter & UBX_PROTOCOL and ( + not self._filtermsg or msgidi in self._msgfilter + ): + if self._parsing == PARSE_FULL: + parsed_data = UBXReader.parse( + raw_data, + validate=self._validate, + msgmode=self._msgmode, + parsebitfield=self._parsebf, + ) + elif self._parsing == PARSE_META: + parsed_data = GNSSMessage(UBX_PROTOCOL, msgidi, raw_data) + return raw_data, parsed_data + + def _parse_sbf( + self, hdr: bytes + ) -> tuple[bytes, SBFMessage | GNSSMessage | NoneType]: """ Parse SBF message (using pysbf2). :param bytes hdr: SBF header (b'\\x24\\x40') :return: tuple of (raw_data as bytes, parsed_data as SBFMessage or None) - :rtype: tuple[bytes, SBFMessage | NoneType] + :rtype: tuple[bytes, SBFMessage | GNSSMessage | NoneType] """ # read the rest of the SBF message from the buffer byten = self._read_bytes(6) crc = byten[0:2] msgid = byten[2:4] + msgidi = int.from_bytes(msgid, "little") & 0x1FFF lenb = byten[4:6] # lenb includes 8 byte header leni = int.from_bytes(lenb, "little", signed=False) - 8 plb = self._read_bytes(leni) raw_data = hdr + crc + msgid + lenb + plb # only parse if we need to (filter passes SBF) - if (self._protfilter & SBF_PROTOCOL) and self._parsing: - parsed_data = SBFReader.parse( - raw_data, - validate=self._validate, - parsebitfield=self._parsebf, - ) - else: - parsed_data = None - return (raw_data, parsed_data) - - def _parse_uni(self, hdr: bytes) -> tuple[bytes, UNIMessage | NoneType]: + parsed_data = None + if self._protfilter & SBF_PROTOCOL and ( + not self._filtermsg or msgidi in self._msgfilter + ): + if self._parsing == PARSE_FULL: + parsed_data = SBFReader.parse( + raw_data, + validate=self._validate, + parsebitfield=self._parsebf, + ) + elif self._parsing == PARSE_META: + parsed_data = GNSSMessage(SBF_PROTOCOL, msgidi, raw_data) + return raw_data, parsed_data + + def _parse_uni( + self, hdr: bytes + ) -> tuple[bytes, UNIMessage | GNSSMessage | NoneType]: """ Parse binary UNI message. :param bytes hdr: UNI header (b'\\xaa\\x44\\xb5') :return: tuple of (raw_data as bytes, parsed_data as UNIMessage or None) - :rtype: tuple[bytes, UNIMessage | NoneType] + :rtype: tuple[bytes, UNIMessage | GNSSMessage | NoneType] """ header = self._read_bytes(21) - lenp = int.from_bytes(header[3:5], "little") - payload = self._read_bytes(lenp + 4) + leni = int.from_bytes(header[3:5], "little") + payload = self._read_bytes(leni + 4) + msgidi = int.from_bytes(header[1:3], "little") raw_data = hdr + header + payload # only parse if we need to (filter passes UNI) - if (self._protfilter & UNI_PROTOCOL) and self._parsing: - parsed_data = UNIReader.parse( - raw_data, - msgmode=self._msgmode, - validate=self._validate, - parsebitfield=self._parsebf, - ) - else: - parsed_data = None - return (raw_data, parsed_data) - - def _parse_qgc(self, hdr: bytes) -> tuple[bytes, QGCMessage | NoneType]: + parsed_data = None + if self._protfilter & UNI_PROTOCOL and ( + not self._filtermsg or msgidi in self._msgfilter + ): + if self._parsing == PARSE_FULL: + parsed_data = UNIReader.parse( + raw_data, + validate=self._validate, + msgmode=self._msgmode, + parsebitfield=self._parsebf, + ) + elif self._parsing == PARSE_META: + parsed_data = GNSSMessage(UNI_PROTOCOL, msgidi, raw_data) + return raw_data, parsed_data + + def _parse_qgc( + self, hdr: bytes + ) -> tuple[bytes, QGCMessage | GNSSMessage | NoneType]: """ Parse QGC message (using pyqgc). :param bytes hdr: QGC header (b'\\x51\\x47') :return: tuple of (raw_data as bytes, parsed_data as QGCMessage or None) - :rtype: tuple[bytes, QGCMessage | NoneType] + :rtype: tuple[bytes, QGCMessage | GNSSMessage | NoneType] """ # read the rest of the QGC message from the buffer byten = self._read_bytes(4) - msggrp = byten[0:1] - msgid = byten[1:2] + msgid = byten[0:2] + msgidi = int.from_bytes(msgid, "big") # QCG msgids are documented in big endian lenb = byten[2:4] leni = int.from_bytes(lenb, "little", signed=False) - byten = self._read_bytes(leni + 2) - plb = byten[0:leni] - cksum = byten[leni : leni + 2] - raw_data = hdr + msggrp + msgid + lenb + plb + cksum + paycrc = self._read_bytes(leni + 2) + raw_data = hdr + msgid + lenb + paycrc # only parse if we need to (filter passes QGC) - if (self._protfilter & QGC_PROTOCOL) and self._parsing: - parsed_data = QGCReader.parse( - raw_data, - msgmode=self._msgmode, - validate=self._validate, - parsebitfield=self._parsebf, - ) - else: - parsed_data = None - return (raw_data, parsed_data) - - def _parse_nmea(self, hdr: bytes) -> tuple[bytes, NMEAMessage | NoneType]: + parsed_data = None + if self._protfilter & QGC_PROTOCOL and ( + not self._filtermsg or msgidi in self._msgfilter + ): + if self._parsing == PARSE_FULL: + parsed_data = QGCReader.parse( + raw_data, + validate=self._validate, + msgmode=self._msgmode, + parsebitfield=self._parsebf, + ) + elif self._parsing == PARSE_META: + parsed_data = GNSSMessage(QGC_PROTOCOL, msgidi, raw_data) + return raw_data, parsed_data + + def _parse_nmea( + self, hdr: bytes + ) -> tuple[bytes, NMEAMessage | GNSSMessage | NoneType]: """ Parse NMEA message (using pynmeagps). :param bytes hdr: NMEA header (b'\\x24\\x..') :return: tuple of (raw_data as bytes, parsed_data as NMEAMessage or None) - :rtype: tuple[bytes, NMEAMessage | NoneType] + :rtype: tuple[bytes, NMEAMessage | GNSSMessage | NoneType] """ # read the rest of the NMEA message from the buffer byten = self._read_line() # NMEA protocol is CRLF-terminated raw_data = hdr + byten + msgids = ( + raw_data[1:].decode("utf-8", errors="backslashreplace").split(",", 1)[0] + ) # only parse if we need to (filter passes NMEA) - if (self._protfilter & NMEA_PROTOCOL) and self._parsing: - # invoke pynmeagps parser - parsed_data = NMEAReader.parse( - raw_data, - validate=self._validate, - msgmode=self._msgmode, - ) - else: - parsed_data = None - return (raw_data, parsed_data) - - def _parse_rtcm3(self, hdr: bytes) -> tuple[bytes, RTCMMessage | NoneType]: + parsed_data = None + if self._protfilter & NMEA_PROTOCOL and ( + not self._filtermsg or msgids in self._msgfilter + ): + if self._parsing == PARSE_FULL: + parsed_data = NMEAReader.parse( + raw_data, + validate=self._validate, + msgmode=self._msgmode, + ) + elif self._parsing == PARSE_META: + parsed_data = GNSSMessage(NMEA_PROTOCOL, msgids, raw_data) + return raw_data, parsed_data + + def _parse_rtcm3( + self, hdr: bytes + ) -> tuple[bytes, RTCMMessage | GNSSMessage | NoneType]: """ Parse RTCM3 message (using pyrtcm). :param bytes hdr: first 2 bytes of RTCM3 header :return: tuple of (raw_data as bytes, parsed_stub as RTCMMessage) - :rtype: tuple[bytes, RTCMMessage | NoneType] + :rtype: tuple[bytes, RTCMMessage | GNSSMessage | NoneType] """ hdr3 = self._read_bytes(1) size = hdr3[0] | (hdr[1] << 8) payload = self._read_bytes(size) + msgidi = ((int.from_bytes(payload[0:2], "big")) >> 4) & 0xFFF crc = self._read_bytes(3) raw_data = hdr + hdr3 + payload + crc # only parse if we need to (filter passes RTCM) - if (self._protfilter & RTCM3_PROTOCOL) and self._parsing: - # invoke pyrtcm parser - parsed_data = RTCMReader.parse( - raw_data, - validate=self._validate, - labelmsm=self._labelmsm, - ) - else: - parsed_data = None - return (raw_data, parsed_data) + parsed_data = None + if self._protfilter & RTCM3_PROTOCOL and ( + not self._filtermsg or msgidi in self._msgfilter + ): + if self._parsing == PARSE_FULL: + parsed_data = RTCMReader.parse( + raw_data, + validate=self._validate, + labelmsm=self._labelmsm, + ) + elif self._parsing == PARSE_META: + parsed_data = GNSSMessage(RTCM3_PROTOCOL, msgidi, raw_data) + return raw_data, parsed_data def _read_bytes(self, size: int) -> bytes: """ diff --git a/src/pygnssutils/gnssstreamer.py b/src/pygnssutils/gnssstreamer.py index d0955df..ad9a42f 100644 --- a/src/pygnssutils/gnssstreamer.py +++ b/src/pygnssutils/gnssstreamer.py @@ -52,10 +52,12 @@ ) from pygnssutils.gnssreader import ( NMEA_PROTOCOL, + PARSE_FULL, QGC_PROTOCOL, RTCM3_PROTOCOL, SBF_PROTOCOL, UBX_PROTOCOL, + GNSSMessage, GNSSReader, ) from pygnssutils.helpers import format_json, set_logging @@ -101,6 +103,7 @@ def __init__( | SBF_PROTOCOL | QGC_PROTOCOL, msgfilter: str = "", + parsing: Literal[0, 1, 2] = PARSE_FULL, limit: int = 0, outqueue: Queue | NoneType = None, inqueue: Queue | NoneType = None, @@ -127,6 +130,8 @@ def __init__( :param str msgfilter: comma-separated string of message identities to include in output \ e.g. 'NAV-PVT,GNGSA'. A periodicity clause can be added e.g. NAV-SAT(10), signifying \ the minimum period in seconds between successive messages of this type ("") + :param Literal[0,1,2] parsing: PARSE_NONE (0) = no parsing (raw only), \ + PARSE_FULL (1) = full parsing, PARSE_META (2) = parse message id only :param int limit: maximum number of messages to read (0 = unlimited) :param Queue | NoneType outqueue: queue for data from datastream (None) :param Queue | NoneType inqueue: queue for data to datastream (None) @@ -171,6 +176,7 @@ def __init__( else: self._inputhandler = inputhandler self._msgfilter = self._init_msgfilter(msgfilter) + self._parsing = parsing if stopevent is None: self._stopevent = Event() else: @@ -304,6 +310,7 @@ def _read_loop( stream, msgmode=self._msgmode, # protfilter=protfilter, # messages filtered externally + parsing=self._parsing, validate=self._validate, quitonerror=self._quitonerror, parsebitfield=self._parsebitfield, @@ -413,6 +420,8 @@ def _filtered(self, parsed_data: object) -> bool: protocol = SBF_PROTOCOL elif isinstance(parsed_data, QGCMessage): protocol = QGC_PROTOCOL + elif isinstance(parsed_data, GNSSMessage): + protocol = parsed_data.protocol else: return True diff --git a/src/pygnssutils/gnssstreamer_cli.py b/src/pygnssutils/gnssstreamer_cli.py index 327dd8b..89e72ec 100644 --- a/src/pygnssutils/gnssstreamer_cli.py +++ b/src/pygnssutils/gnssstreamer_cli.py @@ -75,7 +75,14 @@ ) from pygnssutils.gnssmqttclient import GNSSMQTTClient from pygnssutils.gnssntripclient import GNSSNTRIPClient -from pygnssutils.gnssreader import ERR_LOG, SETPOLL, GNSSReader +from pygnssutils.gnssreader import ( + ERR_LOG, + PARSE_FULL, + PARSE_META, + PARSE_NONE, + SETPOLL, + GNSSReader, +) from pygnssutils.gnssstreamer import GNSSStreamer from pygnssutils.helpers import parse_url, set_common_args from pygnssutils.socket_server import runserver @@ -480,6 +487,17 @@ def main(): choices=[0, 1, 2], default=1, ) + ap.add_argument( + "--parsing", + required=False, + help=( + f"{PARSE_NONE} - No parsing (raw only), {PARSE_FULL} - Full parsing, " + f"{PARSE_META} - Parse metadata (protocol & identity) only" + ), + type=int, + choices=(PARSE_NONE, PARSE_FULL, PARSE_META), + default=PARSE_FULL, + ) ap.add_argument( "--protfilter", required=False, diff --git a/src/pygnssutils/rawnav.py b/src/pygnssutils/rawnav.py index c4daee2..97ab16d 100644 --- a/src/pygnssutils/rawnav.py +++ b/src/pygnssutils/rawnav.py @@ -7,11 +7,15 @@ Once a RawNav object is instantiated, the `parse` function can be invoked repeatedly to collate data from separate sequential -subframes e.g. for GPS LNAV, subframe 1 contains clock corrections, -subframes 2 & 3 contain ephemerides and subframe 4 page 18 contains -ionospheric corrections. +subframes e.g. for GPS LNAV: -An `subframeacq` bitfield signifies which subframe/page IDs have + - subframe 1 contains clock corrections. + - subframes 2 & 3 contain ephemerides. + - subframe 4 page 56 contains ionospheric corrections. + - subframes 5 pages 1-24 and 4 pages 25-32 contain almanac data. + - etc. + +A `subframeacq` bitfield signifies which subframe/page IDs have been acquired, and hence whether or not the RawNav frame contains sufficient information to be converted to a NAV record e.g. as a precursor to RINEX conversion. @@ -26,13 +30,18 @@ Format of subframe definition dictionary:: - dict[field_name, tuple[offset, length, encoding, scaling] + dict[attribute_name, tuple[length, encoding, scaling] + +where -where offset and length are in bits (see, for example, -`rawnav_subframes_gps.py`). + - length = attribute length in bits + - encoding = U (unsigned integer) or S (two's complement signed integer) + - scaling = scaling factor (resolution) as integer or float (0 = no scaling) + +See, for example, `rawnav_subframes_gps.py`. MSB, ISB (intermediate bits) and LSB field names MUST be suffixed "_msb", -"_isb" and "_lsb" respectively - the `parse` function will automatically +"_is1"/"_isb" and "_lsb" respectively - the `parse` function will automatically combine them. Created on 20 Apr 2026 @@ -44,12 +53,11 @@ # pylint: disable=too-many-arguments, too-many-positional-arguments -import struct from logging import getLogger from typing import Literal from pygnssutils.exceptions import RINEXProcessingError -from pygnssutils.rinex_globals import GLO +from pygnssutils.rinex_globals import GLO, GPS, SBA from pygnssutils.rinex_helpers import get_svcode IS1 = "_is1" @@ -70,9 +78,11 @@ """subframe page id field name""" WN = "wn" """WN (week number) field name - used to establish epoch""" +SVIDALM = "svidalm#" +"""current satellite number (slot) in almanac message""" PREAMBLE = "_preamble" -VALPREAMBLE = "_valid_preamble" +SUBFRAMELENGTH = "_subframelength" D = "D" # IEEE 754 64-bit double float F = "F" # IEEE 754 32-bit float S = "S" # 2's complement signed integer @@ -107,9 +117,9 @@ def __init__( self._svid = svid self._sigcode = sigcode if gnss != GLO: - self.wn = -1 - self.toc = -1 - self.tow = -1 + self.wn = None + self.toc = None + self.tow = None self._subframeacq = 0 self._msb = {} self._isb = {} @@ -119,11 +129,12 @@ def __init__( self._lasttoc = 0 self._firstwn = 999999999 self._lastwn = 0 + self._svidalm = 0 def parse( self, data: int, - subframedef: dict[str, tuple[int, int, str, int]], + subframedef: dict[str, int | tuple[int, int, str, int]], subframeacq: int, sequence: bool = True, ): @@ -131,75 +142,278 @@ def parse( Parse raw subframe data into its constituent attributes. :param int data: raw, unpadded input data - :param dict[str, tuple[int, int, str, int]] subframedef: subframe \ + :param dict[str, int | tuple[int, int, str, int]] subframedef: subframe \ definition dictionary (from GNSS ICD) :param int subframeacq: subframe acquisition bitmask :param bool sequence: process subframe as part of a contiguous sequence (True) :raises: RINEXProcessingError """ + errstr = "" try: - # get exemplary preamble value if one is available - valpre = subframedef.pop(VALPREAMBLE, 0) - - # get total subframe length in bits - offset, bitlen, _, _ = list(subframedef.values())[-1] - sfrlen = offset + bitlen - - # parse each attribute in subframe, combining MSB, ISB and - # LSB fields where appropriate - for att, (offset, length, encoding, scaling) in subframedef.items(): - - if att[0:1] == "_": # ignore non-data attributes - continue - bits = data >> (sfrlen - offset - length) & (2**length - 1) - - # validate preamble if an exemplary value is available - if valpre and att == PREAMBLE and bits != valpre: - raise RINEXProcessingError( - f"Invalid preamble - expected 0b{valpre:b}, got 0b{bits:b}" - ) - - # recombine MSB, IS1, ISB and LSB bits - if att[-4:].lower() == MSB: # most significant bits - self._msb[att] = (bits, length, encoding, scaling) - continue - if att[-4:].lower() == IS1: # intermediate bits 1 (BDS) - self._is1[att] = (bits, length, encoding, scaling) - continue - if att[-4:].lower() == ISB: # intermediate bits (BDS) - self._isb[att] = (bits, length, encoding, scaling) - continue - if att[-4:].lower() == LSB: # least significant bits - attns = att[:-4] - msbbits, msblen, _, _ = self._msb.pop(f"{attns}{MSB}", (0, 0, 0, 0)) - is1bits, is1len, _, _ = self._is1.pop(f"{attns}{IS1}", (0, 0, 0, 0)) - isbbits, isblen, _, _ = self._isb.pop(f"{attns}{ISB}", (0, 0, 0, 0)) - bits = ( - (msbbits << (is1len + isblen + length)) - + (is1bits << (isblen + length)) - + (isbbits << length) - + bits - ) - if msblen: # if combining with msb... - att = attns # strip "_lsb" suffix - length += msblen + is1len + isblen + subframelen = subframedef[SUBFRAMELENGTH] + if not isinstance(subframelen, int): + errstr = f". Subframe length must be integer, not {subframelen}" + raise TypeError("Invalid subframe length") - val = self._bits2val(bits, length, encoding, scaling) - setattr(self, att, val) + offset = 0 # payload offset in bits + index = [] # array of (nested) group indices + for anam in subframedef: # process each attribute in subframe definition + errstr = f". Error processing `{anam}`" + offset, index = self._set_attribute( + data, subframedef, subframelen, subframeacq, anam, offset, index + ) - if att in (SID, SPID): # update subframe acquisition status - self._subframeacq |= subframeacq + # check final offset is same as defined subframe length + if offset != subframelen: + errstr = f". Final offset {offset} does not match subframe length {subframelen}" + raise ValueError("Invalid offset") if not sequence: self._store_orphaned_msb() except (ValueError, TypeError, KeyError) as err: raise RINEXProcessingError( - "Invalid subframe definition dictionary." + f"Invalid subframe definition for {self.identity}{errstr}." ) from err + def _set_attribute( + self, + data: int, + subframedef: dict, + subframelen: int, + subframeacq: int, + anam: str, + offset: int, + index: list, + ) -> tuple: + """ + Recursive routine to set individual, alternate or grouped subframe attributes. + + :param int data: raw, unpadded input data + :param dict[str, int | tuple[int, int, str, int]] subframedef: subframe \ + definition dictionary (from GNSS ICD) + :param int subframelen: subframe length in bits + :param int subframeacq: subframe acquisition bitmask + :param str anam: attribute name + :param int offset: payload offset in bits + :param list index: repeating group index array + :return: (offset, index[]) + :rtype: tuple + + """ + + adef = subframedef[anam] # get attribute definition + if isinstance(adef, int): # non-parsable reference attribute + return offset, index + if "_grp" in anam: # repeating attribute group + offset, index = self._set_attribute_group( + data, subframelen, subframeacq, adef, offset, index + ) + elif "_alt" in anam: # alternate attribute group + offset, index = self._set_attribute_alternate( + data, subframelen, subframeacq, adef, offset, index + ) + else: # single attribute + offset = self._set_attribute_single( + data, + subframelen, + subframeacq, + offset, + index, + anam, + adef, + ) + + return offset, index + + def _set_attribute_alternate( + self, + data: int, + subframelen: int, + subframeacq: int, + adef: tuple[tuple[str, int], dict], + offset: int, + index: list[int], + ) -> tuple: + """ + Process alternate group of attributes - group is present if attribute value + = specific value, otherwise absent. + + :param int data: raw, unpadded input data + :param int subframelen: subframe length in bits + :param int subframeacq: subframe acquisition bitmask + :param tuple[tuple[str, int], dict] adef: attribute definition + :param int offset: payload offset in bits + :param list[int] index: repeating group index array + :return: (offset, index[]) + :rtype: tuple + """ + + (anam, con), gdict = adef # (attribute name, condition), group dictionary + if getattr(self, anam) == con: # if condition is met... + # recursively process each group attribute, + # incrementing the payload offset as we go + for anamg in gdict: + offset, index = self._set_attribute( + data, gdict, subframelen, subframeacq, anamg, offset, index + ) + + return offset, index + + def _set_attribute_group( + self, + data: int, + subframelen: int, + subframeacq: int, + adef: tuple[str, dict], + offset: int, + index: list[int], + ) -> tuple: + """ + Process (nested) group of attributes. + + :param int data: raw, unpadded input data + :param int subframelen: subframe length in bits + :param int subframeacq: subframe acquisition bitmask + :param tuple[str, dict] adef: attribute definition + :param int offset: payload offset in bits + :param list[int] index: repeating group index array + :return: (offset, index[]) + :rtype: tuple + + """ + + anam, gdict = adef # attribute signifying group size, group dictionary + # derive or retrieve number of items in group + if isinstance(anam, int): # fixed number of repeats + gsiz = anam + else: # number of repeats is defined in named attribute + gsiz = getattr(self, anam) + + index.append(0) # add a (nested) group index level + ic = self._get_index_continuation() + # recursively process each group attribute, + # incrementing the payload offset and index as we go + for i in range(gsiz): + index[-1] = i + ic + 1 + for anamg in gdict: + offset, index = self._set_attribute( + data, gdict, subframelen, subframeacq, anamg, offset, index + ) + + index.pop() # remove this (nested) group index + + return offset, index + + def _set_attribute_single( + self, + data: int, + subframelen: int, + subframeacq: int, + offset: int, + index: list[int], + anam: str, + adef: tuple[int, str, int | float], + ) -> int: + """ + Parse individual attribute. + + :param int data: raw, unpadded input data + :param int subframelen: total subframe length + :param int subframeacq: subframe acquisition bitmask + :param int offset: subframe bit offset + :param list[int] index: repeating group index array + :param str anam: attribute name + :param tuple[int, str, int | float] adef: attribute definition + :return: new offset + :rtype: int + """ + + length, encoding, scaling = adef + clength = length # combined length of MSB, ISB, LSB + bits = data >> (subframelen - offset - length) & ((1 << length) - 1) + + # recombine MSB, IS1, ISB and LSB bits + if anam[-4:].lower() == MSB: # most significant bits + self._msb[anam] = (bits, length, encoding, scaling) + return offset + length + if anam[-4:].lower() == IS1: # intermediate bits 1 (BDS) + self._is1[anam] = (bits, length, encoding, scaling) + return offset + length + if anam[-4:].lower() == ISB: # intermediate bits (BDS) + self._isb[anam] = (bits, length, encoding, scaling) + return offset + length + if anam[-4:].lower() == LSB: # least significant bits + attns = anam[:-4] + msbbits, msblen, _, _ = self._msb.pop(f"{attns}{MSB}", (0, 0, 0, 0)) + is1bits, is1len, _, _ = self._is1.pop(f"{attns}{IS1}", (0, 0, 0, 0)) + isbbits, isblen, _, _ = self._isb.pop(f"{attns}{ISB}", (0, 0, 0, 0)) + bits = ( + (msbbits << (is1len + isblen + length)) + + (is1bits << (isblen + length)) + + (isbbits << length) + + bits + ) + if msblen: # if combining with msb... + anam = attns # strip "_lsb" suffix + clength += msblen + is1len + isblen + + # get value of attribute + val = self._bits2val(bits, clength, encoding, scaling) + + # if attribute represents current almanac svid, store value + if anam == SVIDALM: + self._svidalm = val + + # if attribute is part of a (nested) repeating group, append name with index + anami = anam + for i in index: # one index for each nested level + if i > 0: + anami += f"_{i:02d}" + + # if almanac attribute, append name with stored almanac svid + # (GLO needs fudge as cna# & mna# precede svidalm#) + svidalm = ( + self._svidalm + 1 + if self.gnss == GLO and anam in ("cna#", "mna#") + else self._svidalm + ) + anami = anami.replace("#", f"_{svidalm:03d}") + + # add attribute to RawNav instance + if getattr(self, anami, None) is None: + setattr(self, anami, val) + + # update subframe acquisition status + acq = ( + (SID, SPID, SVIDALM) + if (self.gnss == GPS and self.sigcode == "1C") + else (SID, SPID) + ) + if anam in acq: + self._subframeacq |= subframeacq + + return offset + length + + def _get_index_continuation(self) -> int: + """ + Special processing for messages with alternate repeating + groups (e.g. SBAS MT25) to retain consistent index numbering. + + :return: index continuation + :rtype: int + """ + + ic = 0 + if self.gnss == SBA and getattr(self, "velcode2", None) is not None: + if getattr(self, "velcode1", None) == 0: + ic = 2 + elif getattr(self, "velcode1", None) == 1: + ic = 1 + return ic + def _store_orphaned_msb(self): """ If not processing sequentially, store any 'orphaned' MSB/IS1 now rather @@ -213,7 +427,7 @@ def _store_orphaned_msb(self): msb = {} def _bits2val( - self, vali: int, length: int, encoding: str, scaling: int + self, vali: int, length: int, encoding: str, scaling: int | float ) -> int | float: """ Convert encoded bits to value. @@ -221,7 +435,7 @@ def _bits2val( :param int vali: value as raw integer :param int length: length in bits :param str encoding: bit encoding e.g. U, S, N, F - :param int scaling: scaling factor (0 = no scaling) + :param int | float scaling: scaling factor (0 = no scaling) :return: decoded value :rtype: int | float :raises: RINEXProcessingError @@ -233,14 +447,14 @@ def _bits2val( elif encoding == "S": # 2's complement signed integer if vali >= (1 << (length - 1)): val = vali - (1 << length) - elif encoding == "F": # IEEE 754 32 bit floating point - valb = int.to_bytes(vali, 4, "little") - val = struct.unpack(" str: stg = ( f"> 238) & 0b11 subframepageid = subframe >> 232 & 0b111111 - elif sigcode in ("2L", "2S", "5I", "5Q"): # GPS CNAV + elif sigcode in ("2X", "5X"): # GPS CNAV for i in range(numw): wrd = getattr(data, f"dwrd_{i+1:02d}") subframe += wrd << (32 * (numw - 1 - i)) @@ -174,7 +174,7 @@ def _process_rxm_sfrbx_gal( subframeid = 0 subframepageid = 0 - if sigcode == "5I": # GAL FNAV + if sigcode == "5X": # GAL FNAV for i in range(numw): wrd = getattr(data, f"dwrd_{i+1:02d}") subframe += wrd << (32 * (numw - 1 - i)) @@ -183,7 +183,7 @@ def _process_rxm_sfrbx_gal( ) & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF # (2**244 - 1) subframeid = (subframe >> 238) & 0b111111 - elif sigcode in ("1B", "7I"): # GAL INAV + elif sigcode in ("1X", "7X"): # GAL INAV supsubframe = 0 for i in range(numw): wrd = getattr(data, f"dwrd_{i+1:02d}") @@ -261,7 +261,8 @@ def _process_rxm_sfrbx_glo( Reassemble GLONASS subframe from individual UBX RXM-SFRBX dwrds. - GLO subframe = 85 bits; 3 * 32-bit dwrds with 11 bits padding at end, - plus a receiver-generated 4th dwrd containing superframe and frame ids + plus (for some receiver versions) a receiver-generated 4th dwrd + containing superframe and frame ids :param str gnss: RINEX gnss code e.g. "R" :param int svid: SV @@ -272,23 +273,36 @@ def _process_rxm_sfrbx_glo( :raises: RINEXProcessingError """ + superframeid = 0 # see caveat in UBX Integration Manual + frameid = 0 # see caveat in UBX Integration Manual subframe = 0 subframeid = 0 subframepageid = 0 - freqid = data.freqId + freqid = data.freqId - 7 if sigcode in ("1C",): # GLO L1OF for i in range(numw): wrd = getattr(data, f"dwrd_{i+1:02d}") subframe += wrd << (32 * (numw - 1 - i)) - # strip padding & 4th dwrd, leaving 85 bit subframe - subframe = (subframe >> 43) & 0x1FFFFFFFFFFFFFFFFFFFFF # 2**85-1 + if numw == 4: # strip padding & 4th dwrd, leaving 85 bit subframe + superframeid = (subframe >> 16) & 0xFFFF + frameid = subframe & 0xFF + subframe = (subframe >> 43) & 0x1FFFFFFFFFFFFFFFFFFFFF # 2**85-1 + else: # numw = 3, strip padding, leaving 85 bit subframe + subframe = (subframe >> 11) & 0x1FFFFFFFFFFFFFFFFFFFFF # 2**85-1 subframeid = (subframe >> 80) & 0b01111 + # frame 5 has different definitions for subframes 14 & 15 + # so we add 100 to distinguish between them + if frameid == 5 and subframeid in (14, 15): + subframeid += 100 + return { "gnss": gnss, "svid": svid, "sigcode": sigcode, + "superframeid": superframeid, + "frameid": frameid, "subframeid": subframeid, "subframepageid": subframepageid, "subframe": subframe, @@ -373,9 +387,8 @@ def _process_rxm_sfrbx_qzs( subframepageid = (subframe >> 232) & 0b111111 elif sigcode in ( - "2L", - "2S", - "5I", + "2X", + "5X", ): # QZSS L2C, L5I - CNAV for i in range(numw): wrd = getattr(data, f"dwrd_{i+1:02d}") diff --git a/src/pygnssutils/rawnav_subframes_bds.py b/src/pygnssutils/rawnav_subframes_bds.py index f05b36b..eaa9ac7 100644 --- a/src/pygnssutils/rawnav_subframes_bds.py +++ b/src/pygnssutils/rawnav_subframes_bds.py @@ -16,11 +16,22 @@ the payloads of raw NAV subframe messages, via the associated `pygnssutils.RawNav` class defined in `rawnav.py`. +Format of subframe definition dictionary:: + + dict[attribute_name, tuple[length, encoding, scaling] + +where + + - length = attribute length in bits + - encoding = U (unsigned integer) or S (two's complement signed integer) + - scaling = scaling factor (resolution) as integer or float (0 = no scaling) + NB: - MSB, intermediate bit and LSB fields MUST be suffixed '_msb', '_isb' and '_lsb' respectively. - Non-data bits (reserved, parity, non) MUST be prefixed '_'. -- Avoid the following reserved field names: gnss, svid, sigid, subframeacq, epoch +- '#' character in almanac attributes will be replaced with relevant almanac svid. +- Avoid the following reserved field names: gnss, svid, sigid, subframeacq, epoch. Created on 6 Oct 2025 @@ -31,7 +42,7 @@ # pylint: disable=fixme, unused-import -from pygnssutils.rawnav import PREAMBLE, SID, SPID, VALPREAMBLE, WN, S, U +from pygnssutils.rawnav import PREAMBLE, SID, SPID, SUBFRAMELENGTH, WN, S, U from pygnssutils.rinex_globals import ( CNV1, CNV2, @@ -64,360 +75,360 @@ # subframe 4,5 Almanac & time corrections # ********************************************************************** -# attribute_name: (bit offset, bit length, bit encoding, scaling) BDS_D1_TLM = { - # VALPREAMBLE: 0b11100010010, # optional, used to validate preamble value - PREAMBLE: (0, 11, U, 0), - "rev": (11, 4, U, 0), - SID: (15, 3, U, 0), # subframe id - "tow_msb": (18, 8, U, 0), # used to derive epoch - "_parity1": (26, 4, U, 0), - "tow_lsb": (30, 12, U, 0), + SUBFRAMELENGTH: 300, + PREAMBLE: (11, U, 0), + "rev": (4, U, 0), + SID: (3, U, 0), # subframe id + "tow_msb": (8, U, 0), # used to derive epoch + "_parity1": (4, U, 0), + "tow_lsb": (12, U, 0), } BDS_D1_SUBFRAME_1 = { **BDS_D1_TLM, - "sath1": (42, 1, U, 0), - "aodc": (43, 5, U, 0), - "urai": (48, 4, U, 1), - "_parity2": (52, 8, U, 0), - WN: (60, 13, U, 1), # used to derive epoch - "toc_msb": (73, 9, U, P2_P3), # must be named "toc" - "_parity3": (82, 8, U, 0), - "toc_lsb": (90, 8, U, P2_P3), # must be named "toc" - "tgd1": (98, 10, S, P1_D1), - "tgd2_msb": (108, 4, S, P1_D1), - "_parity4": (112, 8, U, 0), - "tgd2_lsb": (120, 6, S, P1_D1), - "alpha0": (126, 8, S, P2_N30), - "alpha1": (134, 8, S, P2_N27), - "_parity5": (142, 8, U, 0), - "alpha2": (150, 8, S, P2_N24), - "alpha3": (158, 8, S, P2_N24), - "beta0_msb": (166, 6, S, P2_P11), - "_parity6": (172, 8, U, 0), - "beta0_lsb": (180, 2, S, P2_P11), - "beta1": (182, 8, S, P2_P14), - "beta2": (190, 8, S, P2_P16), - "beta3_msb": (198, 4, S, P2_P16), - "_parity7": (202, 8, U, 0), - "beta3_lsb": (210, 4, S, P2_P16), - "af2": (214, 11, S, P2_N66), - "af0_msb": (225, 7, S, P2_N33), - "_parity8": (232, 8, U, 0), - "af0_lsb": (240, 17, S, P2_N33), - "af1_msb": (257, 5, S, P2_N50), - "_parity9": (262, 8, U, 0), - "af1_lsb": (270, 17, S, P2_N50), - "aode": (287, 5, U, 0), - "_parity10": (292, 8, U, 0), + "sath1": (1, U, 0), + "aodc": (5, U, 0), + "urai": (4, U, 1), + "_parity2": (8, U, 0), + WN: (13, U, 1), # used to derive epoch + "toc_msb": (9, U, P2_P3), # must be named "toc" + "_parity3": (8, U, 0), + "toc_lsb": (8, U, P2_P3), # must be named "toc" + "tgd1": (10, S, P1_D1), + "tgd2_msb": (4, S, P1_D1), + "_parity4": (8, U, 0), + "tgd2_lsb": (6, S, P1_D1), + "alpha0": (8, S, P2_N30), + "alpha1": (8, S, P2_N27), + "_parity5": (8, U, 0), + "alpha2": (8, S, P2_N24), + "alpha3": (8, S, P2_N24), + "beta0_msb": (6, S, P2_P11), + "_parity6": (8, U, 0), + "beta0_lsb": (2, S, P2_P11), + "beta1": (8, S, P2_P14), + "beta2": (8, S, P2_P16), + "beta3_msb": (4, S, P2_P16), + "_parity7": (8, U, 0), + "beta3_lsb": (4, S, P2_P16), + "af2": (11, S, P2_N66), + "af0_msb": (7, S, P2_N33), + "_parity8": (8, U, 0), + "af0_lsb": (17, S, P2_N33), + "af1_msb": (5, S, P2_N50), + "_parity9": (8, U, 0), + "af1_lsb": (17, S, P2_N50), + "aode": (5, U, 0), + "_parity10": (8, U, 0), } BDS_D1_SUBFRAME_2 = { **BDS_D1_TLM, - "deltan_msb": (42, 10, S, P2_N43), - "_parity2": (52, 8, U, 0), - "deltan_lsb": (60, 6, S, P2_N43), - "cuc_msb": (66, 16, S, P2_N31), - "_parity3": (82, 8, U, 0), - "cuc_lsb": (90, 2, S, P2_N31), - "m0_msb": (92, 20, S, P2_N31), - "_parity4": (112, 8, U, 0), - "m0_lsb": (120, 12, S, P2_N31), - "e_msb": (132, 10, U, P2_N33), - "_parity5": (142, 8, U, 0), - "e_lsb": (150, 22, U, P2_N33), - "_parity6": (172, 8, U, 0), - "cus": (180, 18, S, P2_N31), - "crc_msb": (198, 4, S, P2_N6), - "_parity7": (202, 8, U, 0), - "crc_lsb": (210, 14, S, P2_N6), - "crs_msb": (224, 8, S, P2_N6), - "_parity8": (232, 8, U, 0), - "crs_lsb": (240, 10, S, P2_N6), - "sqrta_msb": (250, 12, U, P2_N19), - "_parity9": (262, 8, U, 0), - "sqrta_lsb": (270, 20, U, P2_N19), - "toe_msb": (290, 2, U, P2_P3), # split between subframes 2 and 3 - "_parity10": (292, 8, U, 0), + "deltan_msb": (10, S, P2_N43), + "_parity2": (8, U, 0), + "deltan_lsb": (6, S, P2_N43), + "cuc_msb": (16, S, P2_N31), + "_parity3": (8, U, 0), + "cuc_lsb": (2, S, P2_N31), + "m0_msb": (20, S, P2_N31), + "_parity4": (8, U, 0), + "m0_lsb": (12, S, P2_N31), + "e_msb": (10, U, P2_N33), + "_parity5": (8, U, 0), + "e_lsb": (22, U, P2_N33), + "_parity6": (8, U, 0), + "cus": (18, S, P2_N31), + "crc_msb": (4, S, P2_N6), + "_parity7": (8, U, 0), + "crc_lsb": (14, S, P2_N6), + "crs_msb": (8, S, P2_N6), + "_parity8": (8, U, 0), + "crs_lsb": (10, S, P2_N6), + "sqrta_msb": (12, U, P2_N19), + "_parity9": (8, U, 0), + "sqrta_lsb": (20, U, P2_N19), + "toe_msb": (2, U, P2_P3), # split between subframes 2 and 3 + "_parity10": (8, U, 0), } BDS_D1_SUBFRAME_3 = { **BDS_D1_TLM, - "toe_isb": (42, 10, U, P2_P3), # between MSB & LSB - "_parity2": (52, 8, U, 0), - "toe_lsb": (60, 5, U, P2_P3), # split between subframes 2 and 3 - "i0_msb": (65, 17, S, P2_N31), - "_parity3": (82, 8, U, 0), - "i0_lsb": (90, 15, S, P2_N31), - "cic_msb": (105, 7, S, P2_N31), - "_parity4": (112, 8, U, 0), - "cic_lsb": (120, 11, S, P2_N31), - "omegadot_msb": (131, 11, S, P2_N43), - "_parity5": (142, 8, U, 0), - "omegadot_lsb": (150, 13, S, P2_N43), - "cis_msb": (163, 9, S, P2_N31), - "_parity6": (172, 8, U, 0), - "cis_lsb": (180, 9, S, P2_N31), - "idot_msb": (189, 13, S, P2_N43), - "_parity7": (202, 8, U, 0), - "idot_lsb": (210, 1, S, P2_N43), - "omega0_msb": (211, 21, S, P2_N31), - "_parity8": (232, 8, U, 0), - "omega0_lsb": (240, 11, S, P2_N31), - "omega_msb": (251, 11, S, P2_N31), - "_parity9": (262, 8, U, 0), - "omega_lsb": (270, 21, S, P2_N31), - "rev1": (291, 1, U, 0), - "_parity10": (292, 8, U, 0), + "toe_isb": (10, U, P2_P3), # between MSB & LSB + "_parity2": (8, U, 0), + "toe_lsb": (5, U, P2_P3), # split between subframes 2 and 3 + "i0_msb": (17, S, P2_N31), + "_parity3": (8, U, 0), + "i0_lsb": (15, S, P2_N31), + "cic_msb": (7, S, P2_N31), + "_parity4": (8, U, 0), + "cic_lsb": (11, S, P2_N31), + "omegadot_msb": (11, S, P2_N43), + "_parity5": (8, U, 0), + "omegadot_lsb": (13, S, P2_N43), + "cis_msb": (9, S, P2_N31), + "_parity6": (8, U, 0), + "cis_lsb": (9, S, P2_N31), + "idot_msb": (13, S, P2_N43), + "_parity7": (8, U, 0), + "idot_lsb": (1, S, P2_N43), + "omega0_msb": (21, S, P2_N31), + "_parity8": (8, U, 0), + "omega0_lsb": (11, S, P2_N31), + "omega_msb": (11, S, P2_N31), + "_parity9": (8, U, 0), + "omega_lsb": (21, S, P2_N31), + "rev1": (1, U, 0), + "_parity10": (8, U, 0), } BDS_D1_SUBFRAME_4_P01_24 = { **BDS_D1_TLM, - SPID: (42, 7, U, 0), - "_remainder": (49, 251, U, 0), # TODO not needed for RINEX NAV + SPID: (7, U, 0), + "_remainder": (251, U, 0), # TODO not needed for RINEX NAV } BDS_D1_SUBFRAME_5_P01_6 = BDS_D1_SUBFRAME_4_P01_24 BDS_D1_SUBFRAME_5_P07 = { **BDS_D1_TLM, - SPID: (42, 7, U, 0), - "_remainder": (49, 251, U, 0), # TODO not needed for RINEX NAV + SPID: (7, U, 0), + "_remainder": (251, U, 0), # TODO not needed for RINEX NAV } BDS_D1_SUBFRAME_5_P08 = { **BDS_D1_TLM, - SPID: (42, 7, U, 0), - "_remainder": (49, 251, U, 0), # TODO not needed for RINEX NAV + SPID: (7, U, 0), + "_remainder": (251, U, 0), # TODO not needed for RINEX NAV } BDS_D1_SUBFRAME_5_P09 = { **BDS_D1_TLM, - "_rev1": (42, 1, U, 0), - SPID: (43, 7, U, 0), - "_rev2": (50, 2, U, 0), - "_parity2": (52, 8, U, 0), - "_rev3": (60, 22, U, 0), - "_parity3": (82, 8, U, 0), - "_rev4": (90, 6, U, 0), - "a0gps": (96, 14, U, 0.1), - "a1gps_msb": (110, 2, U, 0.1), - "_parity4": (112, 8, U, 0), - "a1gps_lsb": (120, 14, U, 0.1), - "a0gal_msb": (134, 8, U, 0.1), - "_parity5": (142, 8, U, 0), - "a0gal_lsb": (150, 6, U, 0.1), - "a1gal": (156, 16, U, 0.1), - "_parity6": (172, 8, U, 0), - "a0glo": (180, 14, U, 0.1), - "a1glo_msb": (194, 8, U, 0.1), - "_parity7": (202, 8, U, 0), - "a1glo_lsb": (210, 8, U, 0.1), - "_rev5": (218, 58, U, 0), - "_parity10": (276, 24, U, 0), + "_rev1": (1, U, 0), + SPID: (7, U, 0), + "_rev2": (2, U, 0), + "_parity2": (8, U, 0), + "_rev3": (22, U, 0), + "_parity3": (8, U, 0), + "_rev4": (6, U, 0), + "a0gps": (14, U, 0.1), + "a1gps_msb": (2, U, 0.1), + "_parity4": (8, U, 0), + "a1gps_lsb": (14, U, 0.1), + "a0gal_msb": (8, U, 0.1), + "_parity5": (8, U, 0), + "a0gal_lsb": (6, U, 0.1), + "a1gal": (16, U, 0.1), + "_parity6": (8, U, 0), + "a0glo": (14, U, 0.1), + "a1glo_msb": (8, U, 0.1), + "_parity7": (8, U, 0), + "a1glo_lsb": (8, U, 0.1), + "_rev5": (58, U, 0), + "_parity10": (24, U, 0), } BDS_D1_SUBFRAME_5_P10 = { **BDS_D1_TLM, - "_rev1": (42, 1, U, 0), - SPID: (43, 7, U, 0), - "deltatls_msb": (50, 2, U, 0), - "_parity2": (52, 8, U, 0), - "deltatls_lsb": (60, 6, U, 1), - "deltatlsf": (66, 8, U, 1), - "wnlsf": (74, 8, U, 1), - "_parity3": (82, 8, U, 0), - "a0_msb": (90, 22, U, P2_N30), # utc - "_parity4": (112, 8, U, 0), - "a0_lsb": (120, 10, U, P2_N30), # utc - "a1_msb": (130, 12, U, P2_N50), # utc - "_parity5": (142, 8, U, 0), - "a1_lsb": (150, 12, U, P2_N50), # utc - "dn": (162, 8, U, 1), - "_rev2": (170, 90, U, 0), - "_parity6": (260, 40, U, 0), + "_rev1": (1, U, 0), + SPID: (7, U, 0), + "deltatls_msb": (2, U, 0), + "_parity2": (8, U, 0), + "deltatls_lsb": (6, U, 1), + "deltatlsf": (8, U, 1), + "wnlsf": (8, U, 1), + "_parity3": (8, U, 0), + "a0_msb": (22, U, P2_N30), # utc + "_parity4": (8, U, 0), + "a0_lsb": (10, U, P2_N30), # utc + "a1_msb": (12, U, P2_N50), # utc + "_parity5": (8, U, 0), + "a1_lsb": (12, U, P2_N50), # utc + "dn": (8, U, 1), + "_rev2": (90, U, 0), + "_parity6": (40, U, 0), } BDS_D1_SUBFRAME_5_P11_23 = { **BDS_D1_TLM, - SPID: (42, 7, U, 0), - "_remainder": (49, 251, U, 0), # TODO not needed for RINEX NAV + SPID: (7, U, 0), + "_remainder": (251, U, 0), # TODO not needed for RINEX NAV } BDS_D1_SUBFRAME_5_P24 = { **BDS_D1_TLM, - SPID: (42, 7, U, 0), - "_remainder": (49, 251, U, 0), # TODO not needed for RINEX NAV + SPID: (7, U, 0), + "_remainder": (251, U, 0), # TODO not needed for RINEX NAV } # ********************************************************************** # D2 - "2I", "6I", "7I" (B1I, B3I, B2I - D2 GEO) # # each subframe/page 300 bits -# subframes 1,2,3, 4 CEI +# subframes 1,2,4 CEI # subframe 5 Almanac, Ionosphere, Time Offsets # ********************************************************************** BDS_D2_TLM = { - # VALPREAMBLE: 0b11100010010, # optional, used to validate preamble value - PREAMBLE: (0, 11, U, 0), - "rev": (11, 4, U, 0), - SID: (15, 3, U, 0), # subframe id - "tow_msb": (18, 8, U, 0), # used to derive epoch - "_parity1": (26, 4, U, 0), - "tow_lsb": (30, 12, U, 0), - SPID: (42, 4, U, 0), + SUBFRAMELENGTH: 300, + PREAMBLE: (11, U, 0), + "rev": (4, U, 0), + SID: (3, U, 0), # subframe id + "tow_msb": (8, U, 0), # used to derive epoch + "_parity1": (4, U, 0), + "tow_lsb": (12, U, 0), + SPID: (4, U, 0), } BDS_D2_SUBFRAME_1_END = { - "_parity5": (142, 8, U, 0), - "_reserved1": (150, 150, U, 0), + "_parity5": (8, U, 0), + "_reserved1": (150, U, 0), } BDS_D2_SUBFRAME_1_P01 = { **BDS_D2_TLM, - "sath1": (46, 1, U, 0), - "aodc": (47, 5, U, 0), - "_parity2": (52, 8, U, 0), - "urai": (60, 4, U, 1), - WN: (64, 13, U, 1), # used to derive epoch - "toc_msb": (77, 5, U, P2_P3), # must be named "toc" - "_parity3": (82, 8, U, 0), - "toc_lsb": (90, 12, U, P2_P3), # must be named "toc" - "tgd1": (102, 10, S, P1_D1), - "_parity4": (112, 8, U, 0), - "tgd2": (120, 10, S, P1_D1), - "_rev1": (130, 12, U, 0), + "sath1": (1, U, 0), + "aodc": (5, U, 0), + "_parity2": (8, U, 0), + "urai": (4, U, 1), + WN: (13, U, 1), # used to derive epoch + "toc_msb": (5, U, P2_P3), # must be named "toc" + "_parity3": (8, U, 0), + "toc_lsb": (12, U, P2_P3), # must be named "toc" + "tgd1": (10, S, P1_D1), + "_parity4": (8, U, 0), + "tgd2": (10, S, P1_D1), + "_rev1": (12, U, 0), **BDS_D2_SUBFRAME_1_END, } BDS_D2_SUBFRAME_1_P02 = { **BDS_D2_TLM, - "alpha0_msb": (46, 6, S, P2_N30), - "_parity2": (52, 8, U, 0), - "alpha0_lsb": (60, 2, S, P2_N30), - "alpha1": (62, 8, S, P2_N27), - "alpha2": (70, 8, S, P2_N24), - "alpha3_msb": (78, 4, S, P2_N24), - "_parity3": (82, 8, U, 0), - "alpha3_lsb": (90, 4, S, P2_N24), - "beta0": (94, 8, S, P2_P11), - "beta1": (102, 8, S, P2_P14), - "beta2_msb": (110, 2, S, P2_P16), - "_parity4": (112, 8, U, 0), - "beta2_lsb": (120, 6, S, P2_P16), - "beta3": (126, 8, S, P2_P16), - "_rev1": (134, 8, U, 0), + "alpha0_msb": (6, S, P2_N30), + "_parity2": (8, U, 0), + "alpha0_lsb": (2, S, P2_N30), + "alpha1": (8, S, P2_N27), + "alpha2": (8, S, P2_N24), + "alpha3_msb": (4, S, P2_N24), + "_parity3": (8, U, 0), + "alpha3_lsb": (4, S, P2_N24), + "beta0": (8, S, P2_P11), + "beta1": (8, S, P2_P14), + "beta2_msb": (2, S, P2_P16), + "_parity4": (8, U, 0), + "beta2_lsb": (6, S, P2_P16), + "beta3": (8, S, P2_P16), + "_rev1": (8, U, 0), **BDS_D2_SUBFRAME_1_END, } BDS_D2_SUBFRAME_1_P03 = { **BDS_D2_TLM, - "_rev1": (46, 6, U, 0), - "_parity2": (52, 8, U, 0), - "_rev2": (60, 22, U, 0), - "_parity3": (82, 8, U, 0), - "_rev3": (90, 10, U, 0), - "af0_msb": (100, 12, S, P2_N33), - "_parity4": (112, 8, U, 0), - "af0_lsb": (120, 12, S, P2_N33), - "af1_msb": (132, 4, S, P2_N50), - "_rev4": (136, 6, U, 0), + "_rev1": (6, U, 0), + "_parity2": (8, U, 0), + "_rev2": (22, U, 0), + "_parity3": (8, U, 0), + "_rev3": (10, U, 0), + "af0_msb": (12, S, P2_N33), + "_parity4": (8, U, 0), + "af0_lsb": (12, S, P2_N33), + "af1_msb": (4, S, P2_N50), + "_rev4": (6, U, 0), **BDS_D2_SUBFRAME_1_END, } BDS_D2_SUBFRAME_1_P04 = { **BDS_D2_TLM, - "af1_isb": (46, 6, S, P2_N50), - "_parity2": (52, 8, U, 0), - "af1_lsb": (60, 12, S, P2_N50), - "af2_msb": (72, 10, S, P2_N66), - "_parity3": (82, 8, U, 0), - "af2_lsb": (90, 1, S, P2_N66), - "aode": (91, 5, U, 0), - "deltan": (96, 16, S, P2_N43), - "_parity4": (112, 8, U, 0), - "cuc_msb": (120, 14, S, P2_N31), - "_rev2": (134, 8, U, 0), + "af1_isb": (6, S, P2_N50), + "_parity2": (8, U, 0), + "af1_lsb": (12, S, P2_N50), + "af2_msb": (10, S, P2_N66), + "_parity3": (8, U, 0), + "af2_lsb": (1, S, P2_N66), + "aode": (5, U, 0), + "deltan": (16, S, P2_N43), + "_parity4": (8, U, 0), + "cuc_msb": (14, S, P2_N31), + "_rev2": (8, U, 0), **BDS_D2_SUBFRAME_1_END, } BDS_D2_SUBFRAME_1_P05 = { **BDS_D2_TLM, - "cuc": (46, 4, S, P2_N31), - "m0_msb": (50, 2, S, P2_N31), - "_parity2": (52, 8, U, 0), - "m0_isb": (60, 22, S, P2_N31), - "_parity3": (82, 8, U, 0), - "m0_lsb": (90, 8, S, P2_N31), - "cus_msb": (98, 14, S, P2_N31), - "_parity4": (112, 8, U, 0), - "cus_lsb": (120, 4, S, P2_N31), - "e_msb": (124, 10, U, P2_N33), - "_rev2": (134, 8, U, 0), + "cuc": (4, S, P2_N31), + "m0_msb": (2, S, P2_N31), + "_parity2": (8, U, 0), + "m0_isb": (22, S, P2_N31), + "_parity3": (8, U, 0), + "m0_lsb": (8, S, P2_N31), + "cus_msb": (14, S, P2_N31), + "_parity4": (8, U, 0), + "cus_lsb": (4, S, P2_N31), + "e_msb": (10, U, P2_N33), + "_rev2": (8, U, 0), **BDS_D2_SUBFRAME_1_END, } BDS_D2_SUBFRAME_1_P06 = { **BDS_D2_TLM, - "e_isb": (46, 6, U, P2_N33), - "_parity2": (52, 8, U, 0), - "e_lsb": (60, 16, U, P2_N33), - "sqrta_msb": (76, 6, U, P2_N19), - "_parity3": (82, 8, U, 0), - "sqrta_isb": (90, 22, U, P2_N19), - "_parity4": (112, 8, U, 0), - "sqrta_lsb": (120, 4, U, P2_N19), - "cic_msb": (124, 10, S, P2_N31), - "_rev2": (134, 8, U, 0), + "e_isb": (6, U, P2_N33), + "_parity2": (8, U, 0), + "e_lsb": (16, U, P2_N33), + "sqrta_msb": (6, U, P2_N19), + "_parity3": (8, U, 0), + "sqrta_isb": (22, U, P2_N19), + "_parity4": (8, U, 0), + "sqrta_lsb": (4, U, P2_N19), + "cic_msb": (10, S, P2_N31), + "_rev2": (8, U, 0), **BDS_D2_SUBFRAME_1_END, } BDS_D2_SUBFRAME_1_P07 = { **BDS_D2_TLM, - "cic_isb": (46, 6, S, P2_N31), - "_parity2": (52, 8, U, 0), - "cic_lsb": (60, 2, S, P2_N31), - "cis_msb": (62, 18, S, P2_N31), - "toe_msb": (80, 2, U, P2_P3), - "_parity3": (82, 8, U, 0), - "toe_lsb": (90, 15, U, P2_P3), - "i0_msb": (105, 7, S, P2_N31), - "_parity4": (112, 8, U, 0), - "i0_is1": (120, 14, S, P2_N31), - "_rev2": (134, 8, U, 0), + "cic_isb": (6, S, P2_N31), + "_parity2": (8, U, 0), + "cic_lsb": (2, S, P2_N31), + "cis_msb": (18, S, P2_N31), + "toe_msb": (2, U, P2_P3), + "_parity3": (8, U, 0), + "toe_lsb": (15, U, P2_P3), + "i0_msb": (7, S, P2_N31), + "_parity4": (8, U, 0), + "i0_is1": (14, S, P2_N31), + "_rev2": (8, U, 0), **BDS_D2_SUBFRAME_1_END, } BDS_D2_SUBFRAME_1_P08 = { **BDS_D2_TLM, - "i0_isb": (46, 6, S, P2_N31), - "_parity2": (52, 8, U, 0), - "i0_lsb": (60, 5, S, P2_N31), - "crc_msb": (65, 17, S, P2_N6), - "_parity3": (82, 8, U, 0), - "crc_lsb": (90, 1, S, P2_N6), - "crs_msb": (91, 18, S, P2_N6), - "omegadot_msb": (109, 3, S, P2_N43), - "_parity4": (112, 8, U, 0), - "omegadot_isb": (120, 16, S, P2_N43), - "_rev2": (136, 6, U, 0), + "i0_isb": (6, S, P2_N31), + "_parity2": (8, U, 0), + "i0_lsb": (5, S, P2_N31), + "crc_msb": (17, S, P2_N6), + "_parity3": (8, U, 0), + "crc_lsb": (1, S, P2_N6), + "crs_msb": (18, S, P2_N6), + "omegadot_msb": (3, S, P2_N43), + "_parity4": (8, U, 0), + "omegadot_isb": (16, S, P2_N43), + "_rev2": (6, U, 0), **BDS_D2_SUBFRAME_1_END, } BDS_D2_SUBFRAME_1_P09 = { **BDS_D2_TLM, - "omegadot_lsb": (46, 5, S, P2_N43), - "omega0_msb": (51, 1, S, P2_N31), - "_parity2": (52, 8, U, 0), - "omega0_isb": (60, 22, S, P2_N31), - "_parity3": (82, 8, U, 0), - "omega0_lsb": (90, 9, S, P2_N31), - "omega_msb": (99, 13, S, P2_N31), - "_parity4": (112, 8, U, 0), - "omega_isb": (120, 14, S, P2_N31), - "_rev2": (134, 8, U, 0), + "omegadot_lsb": (5, S, P2_N43), + "omega0_msb": (1, S, P2_N31), + "_parity2": (8, U, 0), + "omega0_isb": (22, S, P2_N31), + "_parity3": (8, U, 0), + "omega0_lsb": (9, S, P2_N31), + "omega_msb": (13, S, P2_N31), + "_parity4": (8, U, 0), + "omega_isb": (14, S, P2_N31), + "_rev2": (8, U, 0), **BDS_D2_SUBFRAME_1_END, } BDS_D2_SUBFRAME_1_P10 = { **BDS_D2_TLM, - "omega_isb": (46, 5, S, P2_N31), - "idot_msb": (51, 1, S, P2_N43), - "_parity2": (52, 8, U, 0), - "idot_lsb": (60, 13, S, P2_N43), - "rev1": (73, 9, U, 0), - "_parity3": (82, 8, U, 0), - "rev2": (90, 22, U, 0), - "_parity4": (112, 8, U, 0), - "rev3": (120, 22, U, 0), + "omega_isb": (5, S, P2_N31), + "idot_msb": (1, S, P2_N43), + "_parity2": (8, U, 0), + "idot_lsb": (13, S, P2_N43), + "rev1": (9, U, 0), + "_parity3": (8, U, 0), + "rev2": (22, U, 0), + "_parity4": (8, U, 0), + "rev3": (22, U, 0), **BDS_D2_SUBFRAME_1_END, } +# you want them? you add them ;-) ... BDS_D2_SUBFRAME_2 = {} BDS_D2_SUBFRAME_3 = {} BDS_D2_SUBFRAME_4 = {} diff --git a/src/pygnssutils/rawnav_subframes_gal.py b/src/pygnssutils/rawnav_subframes_gal.py index 1a8be74..7c0e310 100644 --- a/src/pygnssutils/rawnav_subframes_gal.py +++ b/src/pygnssutils/rawnav_subframes_gal.py @@ -9,11 +9,22 @@ the payloads of raw NAV subframe messages, via the associated `pygnssutils.RawNav` class defined in `rawnav.py`. +Format of subframe definition dictionary:: + + dict[attribute_name, tuple[length, encoding, scaling] + +where + + - length = attribute length in bits + - encoding = U (unsigned integer) or S (two's complement signed integer) + - scaling = scaling factor (resolution) as integer or float (0 = no scaling) + NB: - MSB and LSB fields MUST be suffixed '_msb' and '_lsb' respectively. - Non-data bits (reserved, parity, non) MUST be prefixed '_'. -- Avoid the following reserved field names: gnss, svid, sigid, subframeacq, epoch +- '#' character in almanac attributes will be replaced with relevant almanac svid. +- Avoid the following reserved field names: gnss, svid, sigid, subframeacq, epoch. Created on 6 Oct 2025 @@ -24,7 +35,7 @@ # pylint: disable=fixme -from pygnssutils.rawnav import SID, TOC, TOW, WN, S, U +from pygnssutils.rawnav import SID, SUBFRAMELENGTH, TOC, TOW, WN, S, U from pygnssutils.rinex_globals import ( FNAV, INAV, @@ -63,22 +74,23 @@ # preamble = 0b101101110000 GAL_FNAV_SUBFRAME = { - "word": (0, 244, U, 0), - "padding": (244, 12, U, 0), + "word": (244, U, 0), + "padding": (12, U, 0), } # 8 * 32-bit dwrds GAL_FNAV_SUBFRAME_TLM = { - SID: (0, 6, U, 0), # subframe id + SUBFRAMELENGTH: 244, + SID: (6, U, 0), # subframe id } GAL_FNAV_CRC = { - "_parity": (214, 24, U, 0), - "_tail": (238, 6, U, 0), + "_parity": (24, U, 0), + "_tail": (6, U, 0), } GAL_FNAV_SUBFRAME_GENERIC = { **GAL_FNAV_SUBFRAME_TLM, - "payload": (6, 208, U, 0), + "payload": (208, U, 0), **GAL_FNAV_CRC, } @@ -86,134 +98,134 @@ # BGD, GST, Signal health and Data validity status GAL_FNAV_SUBFRAME_1 = { **GAL_FNAV_SUBFRAME_TLM, - "sv": (6, 6, U, 0), - "iodn": (12, 10, U, 0), - TOC: (22, 14, U, 60), - "af0": (36, 31, S, P2_N34), - "af1": (67, 21, S, P2_N46), - "af2": (88, 6, S, P2_N59), - "sisa": (94, 8, U, 0), - "ai0": (102, 11, U, P2_N2), - "ai1": (113, 11, S, P2_N8), - "ai2": (124, 14, S, P2_N15), - "idf1": (138, 1, U, 0), - "idf2": (139, 1, U, 0), - "idf3": (140, 1, U, 0), - "idf4": (141, 1, U, 0), - "idf5": (142, 1, U, 0), - "bgde5a": (143, 10, S, P2_N32), - "e5ahs": (153, 2, U, 0), - WN: (155, 12, U, 1), # used to derive epoch - TOW: (167, 20, U, 1), # used to derive epoch - "e5advs": (187, 1, U, 0), - "_spare": (188, 26, U, 0), + "sv": (6, U, 0), + "iodn": (10, U, 0), + TOC: (14, U, 60), + "af0": (31, S, P2_N34), + "af1": (21, S, P2_N46), + "af2": (6, S, P2_N59), + "sisa": (8, U, 0), + "ai0": (11, U, P2_N2), + "ai1": (11, S, P2_N8), + "ai2": (14, S, P2_N15), + "idf1": (1, U, 0), + "idf2": (1, U, 0), + "idf3": (1, U, 0), + "idf4": (1, U, 0), + "idf5": (1, U, 0), + "bgde5a": (10, S, P2_N32), + "e5ahs": (2, U, 0), + WN: (12, U, 1), # used to derive epoch + TOW: (20, U, 1), # used to derive epoch + "e5advs": (1, U, 0), + "_spare": (26, U, 0), **GAL_FNAV_CRC, } # Ephemeris (1/3) and GST GAL_FNAV_SUBFRAME_2 = { **GAL_FNAV_SUBFRAME_TLM, - "iodn": (6, 10, U, 0), - "m0": (16, 32, S, P2_N31), - "omegadot": (48, 24, S, P2_N43), - "e": (72, 32, U, P2_N33), - "sqrta": (104, 32, U, P2_N19), - "omega0": (136, 32, S, P2_N31), - "idot": (168, 14, S, P2_N43), - WN: (182, 12, U, 1), - TOW: (194, 20, U, 1), + "iodn": (10, U, 0), + "m0": (32, S, P2_N31), + "omegadot": (24, S, P2_N43), + "e": (32, U, P2_N33), + "sqrta": (32, U, P2_N19), + "omega0": (32, S, P2_N31), + "idot": (14, S, P2_N43), + WN: (12, U, 1), + TOW: (20, U, 1), **GAL_FNAV_CRC, } # Ephemeris (2/3) and GST GAL_FNAV_SUBFRAME_3 = { **GAL_FNAV_SUBFRAME_TLM, - "iodn": (6, 10, U, 0), - "i0": (16, 32, S, P2_N31), - "omega": (48, 32, S, P2_N31), - "deltan": (80, 16, S, P2_N43), - "cuc": (96, 16, S, P2_N29), - "cus": (112, 16, S, P2_N29), - "crc": (128, 16, S, P2_N5), - "crs": (144, 16, S, P2_N5), - "toe": (160, 14, U, 60), - WN: (174, 12, U, 1), - TOW: (186, 20, U, 1), - "_spare": (206, 8, U, 0), + "iodn": (10, U, 0), + "i0": (32, S, P2_N31), + "omega": (32, S, P2_N31), + "deltan": (16, S, P2_N43), + "cuc": (16, S, P2_N29), + "cus": (16, S, P2_N29), + "crc": (16, S, P2_N5), + "crs": (16, S, P2_N5), + "toe": (14, U, 60), + WN: (12, U, 1), + TOW: (20, U, 1), + "_spare": (8, U, 0), **GAL_FNAV_CRC, } # Ephemeris (3/3), GST-UTC conversion, GST-GPS conversion and TOW. GAL_FNAV_SUBFRAME_4 = { **GAL_FNAV_SUBFRAME_TLM, - "iodn": (6, 10, U, 0), - "cic": (16, 16, S, P2_N29), - "cis": (32, 16, S, P2_N29), - "a0": (48, 32, S, P2_N30), - "a1": (80, 24, S, P2_N50), - "deltatls": (104, 8, S, 1), - "tot": (112, 8, U, 3600), - "wn0t": (120, 8, U, 1), - "wnlsf": (128, 8, U, 1), - "dn": (136, 3, U, 1), - "deltatlsf": (139, 8, S, 1), - "t0g": (147, 8, U, 3600), - "a0g": (155, 16, S, P2_N35), - "a1g": (171, 12, S, P2_N51), - "wn0g": (183, 6, U, 1), - TOW: (189, 20, U, 0), - "_spare": (209, 5, U, 0), + "iodn": (10, U, 0), + "cic": (16, S, P2_N29), + "cis": (16, S, P2_N29), + "a0": (32, S, P2_N30), + "a1": (24, S, P2_N50), + "deltatls": (8, S, 1), + "tot": (8, U, 3600), + "wn0t": (8, U, 1), + "wnlsf": (8, U, 1), + "dn": (3, U, 1), + "deltatlsf": (8, S, 1), + "t0g": (8, U, 3600), + "a0g": (16, S, P2_N35), + "a1g": (12, S, P2_N51), + "wn0g": (6, U, 1), + TOW: (20, U, 0), + "_spare": (5, U, 0), **GAL_FNAV_CRC, } # Almanac (SVID1 and SVID2(1/2)), Week Number and almanac reference time GAL_FNAV_SUBFRAME_5 = { **GAL_FNAV_SUBFRAME_TLM, - "ioda": (6, 4, U, 0), - "wna": (10, 2, U, 1), - "t0a": (12, 10, U, 600), - "sv1": (22, 6, U, 0), - "sv1_deltasqrta": (28, 13, S, P2_N9), - "sv1_e": (41, 11, U, P2_N16), - "sv1_omega": (52, 16, S, P2_N15), - "sv1_deltai": (68, 11, S, P2_N14), - "sv1_omega0": (79, 16, S, P2_N15), - "sv1_omegadot": (95, 11, S, P2_N33), - "sv1_m0": (106, 16, S, P2_N15), - "sv1_af0": (122, 16, S, P2_N19), - "sv1_af1": (138, 13, S, P2_N38), - "sv1_e5ahs": (151, 2, U, 0), - "sv2": (153, 6, U, 0), - "sv2_deltasqrta": (159, 13, S, P2_N9), - "sv2_e": (172, 11, U, P2_N16), - "sv2_omega": (183, 16, S, P2_N15), - "sv2_deltai": (199, 11, S, P2_N14), - "sv2_omega0_msb": (210, 4, S, P2_N15), + "ioda": (4, U, 0), + "wna": (2, U, 1), + "t0a": (10, U, 600), + "sv1": (6, U, 0), + "sv1_deltasqrta": (13, S, P2_N9), + "sv1_e": (11, U, P2_N16), + "sv1_omega": (16, S, P2_N15), + "sv1_deltai": (11, S, P2_N14), + "sv1_omega0": (16, S, P2_N15), + "sv1_omegadot": (11, S, P2_N33), + "sv1_m0": (16, S, P2_N15), + "sv1_af0": (16, S, P2_N19), + "sv1_af1": (13, S, P2_N38), + "sv1_e5ahs": (2, U, 0), + "sv2": (6, U, 0), + "sv2_deltasqrta": (13, S, P2_N9), + "sv2_e": (11, U, P2_N16), + "sv2_omega": (16, S, P2_N15), + "sv2_deltai": (11, S, P2_N14), + "sv2_omega0_msb": (4, S, P2_N15), **GAL_FNAV_CRC, } # Almanac (SVID2(2/2) and SVID3) GAL_FNAV_SUBFRAME_6 = { **GAL_FNAV_SUBFRAME_TLM, - "ioda": (6, 4, U, 0), - "sv2_omega0_lsb": (10, 12, S, P2_N15), - "sv2_omegadot": (22, 11, S, P2_N33), - "sv2_m0": (33, 16, S, P2_N15), - "sv2_af0": (49, 16, S, P2_N19), - "sv2_af1": (65, 13, S, P2_N38), - "sv2_e5ahs": (78, 2, U, 0), - "sv3": (80, 6, U, 0), - "sv3_deltasqrta": (86, 13, S, P2_N9), - "sv3_e": (99, 11, U, P2_N16), - "sv3_omega": (110, 16, S, P2_N15), - "sv3_deltai": (126, 11, S, P2_N14), - "sv3_omega0": (137, 16, S, P2_N15), - "sv3_omegadot": (153, 11, S, P2_N33), - "sv3_m0": (164, 16, S, P2_N15), - "sv3_af0": (180, 16, S, P2_N19), - "sv3_af1": (196, 13, S, P2_N38), - "sv3_e5ahs": (209, 2, U, 0), - "_spare": (211, 3, U, 0), + "ioda": (4, U, 0), + "sv2_omega0_lsb": (12, S, P2_N15), + "sv2_omegadot": (11, S, P2_N33), + "sv2_m0": (16, S, P2_N15), + "sv2_af0": (16, S, P2_N19), + "sv2_af1": (13, S, P2_N38), + "sv2_e5ahs": (2, U, 0), + "sv3": (6, U, 0), + "sv3_deltasqrta": (13, S, P2_N9), + "sv3_e": (11, U, P2_N16), + "sv3_omega": (16, S, P2_N15), + "sv3_deltai": (11, S, P2_N14), + "sv3_omega0": (16, S, P2_N15), + "sv3_omegadot": (11, S, P2_N33), + "sv3_m0": (16, S, P2_N15), + "sv3_af0": (16, S, P2_N19), + "sv3_af1": (13, S, P2_N38), + "sv3_e5ahs": (2, U, 0), + "_spare": (3, U, 0), **GAL_FNAV_CRC, } @@ -224,199 +236,212 @@ # preamble = 0b0101100000 GAL_INAV_SUBFRAME = { - "eo1": (0, 1, U, 0), - "pagetype1": (1, 1, U, 0), # 0 = nominal, 1 = alert - "word_msb": (2, 112, U, 0), # 6 msb is word number - "tail1": (114, 6, U, 0), - "padding1": (120, 8, U, 0), - "eo2": (128, 1, U, 0), - "pagetype2": (129, 1, U, 0), - "word_lsb": (130, 16, U, 0), - "reserved1": (146, 40, U, 0), - "sar": (186, 22, U, 0), # E-1B only, else reserved - "spare": (208, 2, U, 0), - "crc": (210, 24, U, 0), - "ssp": (234, 8, U, 0), - "tail2": (242, 6, U, 0), - "padding2": (248, 8, U, 0), + SUBFRAMELENGTH: 256, + "eo1": (1, U, 0), + "pagetype1": (1, U, 0), # 0 = nominal, 1 = alert + "word_msb": (112, U, 0), # 6 msb is word number + "tail1": (6, U, 0), + "padding1": (8, U, 0), + "eo2": (1, U, 0), + "pagetype2": (1, U, 0), + "word_lsb": (16, U, 0), + "reserved1": (40, U, 0), + "sar": (22, U, 0), # E-1B only, else reserved + "spare": (2, U, 0), + "crc": (24, U, 0), + "ssp": (8, U, 0), + "tail2": (6, U, 0), + "padding2": (8, U, 0), } # 8 * 32-bit dwrds # Ephemeris (1/4) GAL_INAV_WORD_1 = { - SID: (0, 6, U, 0), - "iodn": (6, 10, U, 0), - "toe": (16, 14, U, 60), - "m0": (30, 32, S, P2_N31), - "e": (62, 32, U, P2_N33), - "sqrta": (94, 32, U, P2_N19), - "_spare": (126, 2, U, 0), + SUBFRAMELENGTH: 128, + SID: (6, U, 0), + "iodn": (10, U, 0), + "toe": (14, U, 60), + "m0": (32, S, P2_N31), + "e": (32, U, P2_N33), + "sqrta": (32, U, P2_N19), + "_spare": (2, U, 0), } # Ephemeris (2/4) GAL_INAV_WORD_2 = { - SID: (0, 6, U, 0), - "iodn": (6, 10, U, 0), - "omega0": (16, 32, S, P2_N31), - "i0": (48, 32, S, P2_N31), - "omega": (80, 32, S, P2_N31), - "idot": (112, 14, S, P2_N43), - "_spare": (126, 2, U, 0), + SUBFRAMELENGTH: 128, + SID: (6, U, 0), + "iodn": (10, U, 0), + "omega0": (32, S, P2_N31), + "i0": (32, S, P2_N31), + "omega": (32, S, P2_N31), + "idot": (14, S, P2_N43), + "_spare": (2, U, 0), } # Ephemeris (3/4) and SISA GAL_INAV_WORD_3 = { - SID: (0, 6, U, 0), - "iodn": (6, 10, U, 0), - "omegadot": (16, 24, S, P2_N43), - "deltan": (40, 16, S, P2_N43), - "cuc": (56, 16, S, P2_N29), - "cus": (72, 16, S, P2_N29), - "crc": (88, 16, S, P2_N5), - "crs": (104, 16, S, P2_N5), - "sisa": (120, 8, U, 0), + SUBFRAMELENGTH: 128, + SID: (6, U, 0), + "iodn": (10, U, 0), + "omegadot": (24, S, P2_N43), + "deltan": (16, S, P2_N43), + "cuc": (16, S, P2_N29), + "cus": (16, S, P2_N29), + "crc": (16, S, P2_N5), + "crs": (16, S, P2_N5), + "sisa": (8, U, 0), } # SVID, Ephemeris (4/4), and Clock correction parameters GAL_INAV_WORD_4 = { - SID: (0, 6, U, 0), - "iodn": (6, 10, U, 0), - "sv": (16, 6, U, 0), - "cic": (22, 16, S, P2_N29), - "cis": (38, 16, S, P2_N29), - TOC: (54, 14, U, 60), - "af0": (68, 31, S, P2_N34), - "af1": (99, 21, S, P2_N46), - "af2": (120, 6, S, P2_N59), - "_spare": (126, 2, U, 0), + SUBFRAMELENGTH: 128, + SID: (6, U, 0), + "iodn": (10, U, 0), + "sv": (6, U, 0), + "cic": (16, S, P2_N29), + "cis": (16, S, P2_N29), + TOC: (14, U, 60), + "af0": (31, S, P2_N34), + "af1": (21, S, P2_N46), + "af2": (6, S, P2_N59), + "_spare": (2, U, 0), } # Ionospheric correction, BGD, signal health and data validity status and GST GAL_INAV_WORD_5 = { - SID: (0, 6, U, 0), - "ai0": (6, 11, U, P2_N2), - "ai1": (17, 11, S, P2_N8), - "ai2": (28, 14, S, P2_N15), - "idf1": (42, 1, U, 0), - "idf2": (43, 1, U, 0), - "idf3": (44, 1, U, 0), - "idf4": (45, 1, U, 0), - "idf5": (46, 1, U, 0), - "bgde5a": (47, 10, S, P2_N32), - "bgde5b": (57, 10, S, P2_N32), - "e5ahs": (67, 2, U, 0), - "e5bhs": (69, 2, U, 0), - "e5advs": (71, 1, U, 0), - "e5bdvs": (72, 1, U, 0), - WN: (73, 12, U, 1), # used to derive epoch - TOW: (85, 20, U, 1), # used to derive epoch - "_spare": (105, 23, U, 0), + SUBFRAMELENGTH: 128, + SID: (6, U, 0), + "ai0": (11, U, P2_N2), + "ai1": (11, S, P2_N8), + "ai2": (14, S, P2_N15), + "idf1": (1, U, 0), + "idf2": (1, U, 0), + "idf3": (1, U, 0), + "idf4": (1, U, 0), + "idf5": (1, U, 0), + "bgde5a": (10, S, P2_N32), + "bgde5b": (10, S, P2_N32), + "e5ahs": (2, U, 0), + "e5bhs": (2, U, 0), + "e5advs": (1, U, 0), + "e5bdvs": (1, U, 0), + WN: (12, U, 1), # used to derive epoch + TOW: (20, U, 1), # used to derive epoch + "_spare": (23, U, 0), } # GST-UTC conversion parameters GAL_INAV_WORD_6 = { - SID: (0, 6, U, 0), - "a0": (6, 32, S, P2_N30), - "a1": (38, 24, S, P2_N50), - "deltatls": (62, 8, S, 1), - "tot": (70, 8, U, 3600), - "wn0t": (78, 8, U, 1), - "wnlsf": (86, 8, U, 1), - "dn": (94, 3, U, 1), - "deltatlsf": (97, 8, S, 1), - TOW: (105, 20, U, 1), - "_spare": (125, 3, U, 0), + SUBFRAMELENGTH: 128, + SID: (6, U, 0), + "a0": (32, S, P2_N30), + "a1": (24, S, P2_N50), + "deltatls": (8, S, 1), + "tot": (8, U, 3600), + "wn0t": (8, U, 1), + "wnlsf": (8, U, 1), + "dn": (3, U, 1), + "deltatlsf": (8, S, 1), + TOW: (20, U, 1), + "_spare": (3, U, 0), } # Almanac for SVID1 (1/2), almanac reference time and almanac reference week number GAL_INAV_WORD_7 = { - SID: (0, 6, U, 0), - "ioda": (6, 4, U, 0), - "wna": (10, 2, U, 1), - "t0a": (12, 10, U, 600), - "sv1": (22, 6, U, 0), - "sv1_deltasqrta": (28, 13, S, P2_N9), - "sv1_e": (41, 11, U, P2_N16), - "sv1_omega": (52, 16, S, P2_N15), - "sv1_deltai": (68, 11, S, P2_N14), - "sv1_omega0": (79, 16, S, P2_N15), - "sv1_omegadot": (95, 11, S, P2_N33), - "sv1_m0": (106, 16, S, P2_N15), - "_reserved": (122, 6, U, 0), + SUBFRAMELENGTH: 128, + SID: (6, U, 0), + "ioda": (4, U, 0), + "wna": (2, U, 1), + "t0a": (10, U, 600), + "sv1": (6, U, 0), + "sv1_deltasqrta": (13, S, P2_N9), + "sv1_e": (11, U, P2_N16), + "sv1_omega": (16, S, P2_N15), + "sv1_deltai": (11, S, P2_N14), + "sv1_omega0": (16, S, P2_N15), + "sv1_omegadot": (11, S, P2_N33), + "sv1_m0": (16, S, P2_N15), + "_reserved": (6, U, 0), } # Almanac for SVID1 (2/2) and SVID2 (1/2)) GAL_INAV_WORD_8 = { - SID: (0, 6, U, 0), - "ioda": (6, 4, U, 0), - "sv1_af0": (10, 16, S, P2_N19), - "sv1_af1": (26, 13, S, P2_N38), - "sv1_e5ahs": (39, 2, U, 0), - "sv1_e5bhs": (41, 2, U, 0), - "sv2": (43, 6, U, 0), - "sv2_deltasqrta": (49, 13, S, P2_N9), - "sv2_e": (62, 11, U, P2_N16), - "sv2_omega": (73, 16, S, P2_N15), - "sv2_deltai": (89, 11, S, P2_N14), - "sv2_omega0": (100, 16, S, P2_N15), - "sv2_omegadot": (116, 11, S, P2_N33), - "_spare": (127, 1, U, 0), + SUBFRAMELENGTH: 128, + SID: (6, U, 0), + "ioda": (4, U, 0), + "sv1_af0": (16, S, P2_N19), + "sv1_af1": (13, S, P2_N38), + "sv1_e5ahs": (2, U, 0), + "sv1_e5bhs": (2, U, 0), + "sv2": (6, U, 0), + "sv2_deltasqrta": (13, S, P2_N9), + "sv2_e": (11, U, P2_N16), + "sv2_omega": (16, S, P2_N15), + "sv2_deltai": (11, S, P2_N14), + "sv2_omega0": (16, S, P2_N15), + "sv2_omegadot": (11, S, P2_N33), + "_spare": (1, U, 0), } # Almanac for SVID2 (2/2) and SVID3 (1/2)) GAL_INAV_WORD_9 = { - SID: (0, 6, U, 0), - "ioda": (6, 4, U, 0), - "wna": (10, 2, U, 1), - "t0a": (12, 10, U, 600), - "sv2_m0": (22, 16, S, P2_N15), - "sv2_af0": (38, 16, S, P2_N19), - "sv2_af1": (54, 13, S, P2_N38), - "sv2_e5ahs": (67, 2, U, 0), - "sv2_e5bhs": (69, 2, U, 0), - "sv3": (71, 6, U, 0), - "sv3_deltasqrta": (77, 13, S, P2_N9), - "sv3_e": (90, 11, U, P2_N16), - "sv3_omega": (101, 16, S, P2_N15), - "sv3_deltai": (117, 11, S, P2_N14), + SUBFRAMELENGTH: 128, + SID: (6, U, 0), + "ioda": (4, U, 0), + "wna": (2, U, 1), + "t0a": (10, U, 600), + "sv2_m0": (16, S, P2_N15), + "sv2_af0": (16, S, P2_N19), + "sv2_af1": (13, S, P2_N38), + "sv2_e5ahs": (2, U, 0), + "sv2_e5bhs": (2, U, 0), + "sv3": (6, U, 0), + "sv3_deltasqrta": (13, S, P2_N9), + "sv3_e": (11, U, P2_N16), + "sv3_omega": (16, S, P2_N15), + "sv3_deltai": (11, S, P2_N14), } # Almanac for SVID3 (2/2) and GST-GPS conversion parameters GAL_INAV_WORD_10 = { - "wordid": (0, 6, U, 0), - "ioda": (6, 4, U, 0), - "sv3_omega0": (10, 16, S, P2_N15), - "sv3_omegadot": (26, 11, S, P2_N33), - "sv3_m0": (37, 16, S, P2_N15), - "sv3_af0": (53, 16, S, P2_N19), - "sv3_af1": (69, 13, S, P2_N38), - "sv3_e5ahs": (82, 2, U, 0), - "sv3_e5bhs": (84, 2, U, 0), - "a0g": (86, 16, S, P2_N35), - "a1g": (102, 12, S, P2_N51), - "t0g": (114, 8, U, 3600), - "wn0g": (122, 6, U, 1), + SUBFRAMELENGTH: 128, + "wordid": (6, U, 0), + "ioda": (4, U, 0), + "sv3_omega0": (16, S, P2_N15), + "sv3_omegadot": (11, S, P2_N33), + "sv3_m0": (16, S, P2_N15), + "sv3_af0": (16, S, P2_N19), + "sv3_af1": (13, S, P2_N38), + "sv3_e5ahs": (2, U, 0), + "sv3_e5bhs": (2, U, 0), + "a0g": (16, S, P2_N35), + "a1g": (12, S, P2_N51), + "t0g": (8, U, 3600), + "wn0g": (6, U, 1), } # Reduced Clock and Ephemeris Data (CED) parameters GAL_INAV_WORD_16 = { - SID: (0, 6, U, 0), - "deltaared": (6, 5, S, P2_P8), - "exred": (11, 13, S, P2_N22), - "eyred": (24, 13, S, P2_N22), - "deltaiored": (37, 17, S, P2_N22), - "omega0red": (54, 23, S, P2_N22), - "lambda0red": (77, 23, S, P2_N22), - "af0red": (100, 22, S, P2_N26), - "af1red": (122, 6, S, P2_N35), + SUBFRAMELENGTH: 128, + SID: (6, U, 0), + "deltaared": (5, S, P2_P8), + "exred": (13, S, P2_N22), + "eyred": (13, S, P2_N22), + "deltaiored": (17, S, P2_N22), + "omega0red": (23, S, P2_N22), + "lambda0red": (23, S, P2_N22), + "af0red": (22, S, P2_N26), + "af1red": (6, S, P2_N35), } # dummy word GAL_INAV_WORD_0 = { - SID: (0, 6, U, 0), - "time": (6, 2, U, 0), - "_spare": (8, 88, U, 0), - WN: (96, 12, U, 1), - TOW: (108, 20, U, 1), + SUBFRAMELENGTH: 128, + SID: (6, U, 0), + "time": (2, U, 0), + "_spare": (88, U, 0), + WN: (12, U, 1), + TOW: (20, U, 1), } # mapping for (subframe, page) acquisition mask subframeacq @@ -430,8 +455,8 @@ (2, 0): (GAL_FNAV_SUBFRAME_2, 2), (3, 0): (GAL_FNAV_SUBFRAME_3, 4), (4, 0): (GAL_FNAV_SUBFRAME_4, 8), - (5, 0): (GAL_FNAV_SUBFRAME_5, 16), - (6, 0): (GAL_FNAV_SUBFRAME_6, 32), + # (5, 0): (GAL_FNAV_SUBFRAME_5, 16), + # (6, 0): (GAL_FNAV_SUBFRAME_6, 32), }, INAV: { TARGET: 0b111111, # subframes 1,2,3,4,5,6 @@ -442,10 +467,10 @@ (4, 0): (GAL_INAV_WORD_4, 8), (5, 0): (GAL_INAV_WORD_5, 16), (6, 0): (GAL_INAV_WORD_6, 32), - (7, 0): (GAL_INAV_WORD_7, 64), - (8, 0): (GAL_INAV_WORD_8, 128), - (9, 0): (GAL_INAV_WORD_9, 256), - (10, 0): (GAL_INAV_WORD_10, 512), - (16, 0): (GAL_INAV_WORD_16, 1024), + # (7, 0): (GAL_INAV_WORD_7, 64), + # (8, 0): (GAL_INAV_WORD_8, 128), + # (9, 0): (GAL_INAV_WORD_9, 256), + # (10, 0): (GAL_INAV_WORD_10, 512), + # (16, 0): (GAL_INAV_WORD_16, 1024), }, } diff --git a/src/pygnssutils/rawnav_subframes_glo.py b/src/pygnssutils/rawnav_subframes_glo.py index e04df26..b97946b 100644 --- a/src/pygnssutils/rawnav_subframes_glo.py +++ b/src/pygnssutils/rawnav_subframes_glo.py @@ -10,11 +10,22 @@ the payloads of raw NAV subframe messages, via the associated `pygnssutils.RawNav` class defined in `rawnav.py`. +Format of subframe definition dictionary:: + + dict[attribute_name, tuple[length, encoding, scaling] + +where + + - length = attribute length in bits + - encoding = U (unsigned integer) or S (two's complement signed integer) + - scaling = scaling factor (resolution) as integer or float (0 = no scaling) + NB: - MSB and LSB fields MUST be suffixed '_msb' and '_lsb' respectively. - Non-data bits (reserved, parity, non) MUST be prefixed '_'. -- Avoid the following reserved field names: gnss, svid, sigid, subframeacq, epoch +- '#' character in almanac attributes will be replaced with relevant almanac svid. +- Avoid the following reserved field names: gnss, svid, sigid, subframeacq, epoch. Created on 6 Oct 2025 @@ -23,10 +34,17 @@ :license: BSD 3-Clause """ -from pygnssutils.rawnav import SID, S, U +from pygnssutils.rawnav import SID, SUBFRAMELENGTH, SVIDALM, S, U from pygnssutils.rinex_globals import ( L1OF, + P2_N5, + P2_N9, + P2_N10, P2_N11, + P2_N14, + P2_N15, + P2_N16, + P2_N18, P2_N20, P2_N30, P2_N31, @@ -43,90 +61,142 @@ # (ICD just refers to MSB being sign bit, without explicitly using the term # two's complement) -# GLONASS ICD refers to subframes as 'strings' +# GLONASS ICD refers to subframes as 'strings' and individual attributes as 'words' GLO_L1OF_SUBFRAME_TLM = { - "_idle": (0, 1, U, 0), - SID: (1, 4, U, 0), # subframe (string) number + SUBFRAMELENGTH: 85, + "_idle": (1, U, 0), + SID: (4, U, 0), # subframe (string) number } GLO_L1OF_SUBFRAME_END = { - "_hamming": (77, 8, U, 0), + "_hamming": (8, U, 0), } # content of 4 32-bit dwrds in RXM-SFRBX payload: GLO_SUPERFRAME = { **GLO_L1OF_SUBFRAME_TLM, - "data": (5, 72, U, 0), + "data": (72, U, 0), # payloads defined below **GLO_L1OF_SUBFRAME_END, - "_padding1": (85, 11, U, 0), - "superframeid": (96, 16, U, 0), - "_padding2": (112, 8, U, 0), - "frameid": (120, 8, U, 0), + "_padding1": (11, U, 0), + "superframeid": (16, U, 0), + "_padding2": (8, U, 0), + "frameid": (8, U, 0), } -# attribute_name: (bit offset, bit length, bit encoding, scaling) GLO_L1OF_SUBFRAME_1 = { **GLO_L1OF_SUBFRAME_TLM, - "_reserved1": (5, 2, U, 0), - "p1": (7, 2, U, 1), - "tk": (9, 12, U, 0), - # "tk_hours": (9, 5, U, 0), - # "tk_mins": (14, 6, U, 0), - # "tk_secs": (20, 1, U, 30), - "xntbdot": (21, 24, S, P2_N20), - "xntbdot2": (45, 5, S, P2_N30), - "xntb": (50, 27, S, P2_N11), + "_reserved1": (2, U, 0), + "p1": (2, U, 1), # data update interval + "tk": (12, U, 0), # time + # "tk_hours": (5, U, 0), + # "tk_mins": (6, U, 0), + # "tk_secs": (1, U, 30), + "xntbdot": (24, S, P2_N20), # x velocity + "xntbdot2": (5, S, P2_N30), # x acceleration + "xntb": (27, S, P2_N11), # x position **GLO_L1OF_SUBFRAME_END, } + GLO_L1OF_SUBFRAME_2 = { **GLO_L1OF_SUBFRAME_TLM, - "bn": (5, 3, U, 0), - "p2": (8, 1, U, 1), - "tb": (9, 7, U, 15), - "_reserved1": (16, 5, U, 0), - "yntbdot": (21, 24, S, P2_N20), - "yntbdot2": (45, 5, S, P2_N30), - "yntb": (50, 27, S, P2_N11), + "bn": (3, U, 0), # health flag + "p2": (1, U, 1), # flag for odd/even tb + "tb": (7, U, 15), # index time interval + "_reserved1": (5, U, 0), + "yntbdot": (24, S, P2_N20), # y velocity + "yntbdot2": (5, S, P2_N30), # y acceleration + "yntb": (27, S, P2_N11), # y position **GLO_L1OF_SUBFRAME_END, } + GLO_L1OF_SUBFRAME_3 = { **GLO_L1OF_SUBFRAME_TLM, - "p3": (5, 1, U, 1), - "gammantb": (6, 11, S, P2_N40), - "_reserved1": (17, 1, U, 0), - "p": (18, 2, U, 1), - "ln": (20, 1, U, 1), - "zntbdot": (21, 24, S, P2_N20), - "zntbdot2": (45, 5, S, P2_N30), - "zntb": (50, 27, S, P2_N11), + "p3": (1, U, 1), # num satellites in frame (4/5) + "gammantb": (11, S, P2_N40), + "_reserved1": (1, U, 0), + "p": (2, U, 1), + "ln": (1, U, 1), # ephemeris health flag + "zntbdot": (24, S, P2_N20), # z velocity + "zntbdot2": (5, S, P2_N30), # z acceleration + "zntb": (27, S, P2_N11), # z position **GLO_L1OF_SUBFRAME_END, } + GLO_L1OF_SUBFRAME_4 = { **GLO_L1OF_SUBFRAME_TLM, - "tauntb": (5, 22, S, P2_N30), - "deltataun": (27, 5, S, P2_N30), - "en": (32, 5, U, 1), - "_reserved1": (37, 14, U, 0), - "p4": (51, 1, U, 1), - "ft": (52, 4, U, 0), - "_reserved2": (56, 3, U, 0), - "nt": (59, 11, U, 0), - "n": (70, 5, U, 0), - "m": (75, 2, U, 0), # 0 = GLONASS, 1 = GLONASS-M + "tauntb": (22, S, P2_N30), + "deltataun": (5, S, P2_N30), + "en": (5, U, 1), + "_reserved1": (14, U, 0), + "p4": (1, U, 1), # ephemeris present flag + "ft": (4, U, 0), # satellite URA + "_reserved2": (3, U, 0), + "nt": (11, U, 0), # current day/date + "n": (5, U, 0), # slot number + "m": (2, U, 0), # 0 = GLONASS, 1 = GLONASS-M **GLO_L1OF_SUBFRAME_END, } + GLO_L1OF_SUBFRAME_5 = { **GLO_L1OF_SUBFRAME_TLM, - "na": (5, 11, S, P2_N30), - "tauc": (16, 32, S, P2_N31), - "_reserved1": (48, 1, U, 0), - "n4": (49, 5, U, 1), - "taugps": (54, 22, S, P2_N30), - "ln": (76, 1, U, 0), + "na": (11, S, P2_N30), # calendar day within 4-year period + "tauc": (32, S, P2_N31), # time correction GLO -> UTC + "_reserved1": (1, U, 0), + "n4": (5, U, 1), # 4-year interval from 1996 + "taugps": (22, S, P2_N30), # time correction GPS -> GLO + "lna": (1, U, 0), # almanac health flag + **GLO_L1OF_SUBFRAME_END, +} + +GLO_L1OF_SUBFRAME_6 = { + **GLO_L1OF_SUBFRAME_TLM, + "cna#": (1, U, 1), # health flag + "mna#": (2, U, 1), # 0 = GLONASS, 1 = GLONASS-M + SVIDALM: (5, U, 1), # almanac svid (slot) number (will replace '#' below) + "tauna#": (10, S, P2_N18), # time correction to GLO time + "lambdana#": (21, S, P2_N20), # longitude of ascending node + "deltaina#": (18, S, P2_N20), # rate of change of inclination + "epsilonna#": (15, S, P2_N20), # eccentricity **GLO_L1OF_SUBFRAME_END, } +GLO_L1OF_SUBFRAME_7 = { + **GLO_L1OF_SUBFRAME_TLM, + "omegana#": (16, S, P2_N15), # argument of perigee + "taulambdana#": (21, S, P2_N5), # time of first ascending node + "deltatna#": (22, S, P2_N9), # correction to Draconian period + "deltatdotna#": (7, S, P2_N14), # rate of change of Draconian period + "hna#": (5, U, 1), # carrier frequency number + "lna#": (1, U, 0), # almanac health flag + **GLO_L1OF_SUBFRAME_END, +} + +GLO_L1OF_SUBFRAME_8 = GLO_L1OF_SUBFRAME_6 +GLO_L1OF_SUBFRAME_9 = GLO_L1OF_SUBFRAME_7 +GLO_L1OF_SUBFRAME_10 = GLO_L1OF_SUBFRAME_6 +GLO_L1OF_SUBFRAME_11 = GLO_L1OF_SUBFRAME_7 +GLO_L1OF_SUBFRAME_12 = GLO_L1OF_SUBFRAME_6 +GLO_L1OF_SUBFRAME_13 = GLO_L1OF_SUBFRAME_7 +GLO_L1OF_SUBFRAME_14 = GLO_L1OF_SUBFRAME_6 # frames 1-4 +GLO_L1OF_SUBFRAME_15 = GLO_L1OF_SUBFRAME_7 # frames 1-4 + +GLO_L1OF_SUBFRAME_14_F5 = { + **GLO_L1OF_SUBFRAME_TLM, + "b1": (11, S, P2_N10), # time coefficient UT1 UTC(SU) + "b2": (10, S, P2_N16), # time coefficient delta UT1 + "kp": (2, U, 1), # notification of leapsecond + "_reserved": (49, U, 0), + **GLO_L1OF_SUBFRAME_END, +} # frame 5 + +GLO_L1OF_SUBFRAME_15_F5 = { + **GLO_L1OF_SUBFRAME_TLM, + "_reserved": (71, U, 0), + "lna#": (1, U, 0), # almanac health flag + **GLO_L1OF_SUBFRAME_END, +} # frame 5 + # mapping for (subframe, page) acquisition mask subframeacq # NB subframes containing only almanac data are not generally # required for RINEX conversion purposes @@ -139,5 +209,17 @@ (3, 0): (GLO_L1OF_SUBFRAME_3, 4), (4, 0): (GLO_L1OF_SUBFRAME_4, 8), (5, 0): (GLO_L1OF_SUBFRAME_5, 16), + # (6, 0): (GLO_L1OF_SUBFRAME_6, 32), + # (7, 0): (GLO_L1OF_SUBFRAME_7, 64), + # (8, 0): (GLO_L1OF_SUBFRAME_8, 128), + # (9, 0): (GLO_L1OF_SUBFRAME_9, 256), + # (10, 0): (GLO_L1OF_SUBFRAME_10, 512), + # (11, 0): (GLO_L1OF_SUBFRAME_11, 1024), + # (12, 0): (GLO_L1OF_SUBFRAME_12, 2048), + # (13, 0): (GLO_L1OF_SUBFRAME_13, 4096), + # (14, 0): (GLO_L1OF_SUBFRAME_14, 8192), + # (15, 0): (GLO_L1OF_SUBFRAME_15, 16384), + # (114, 0): (GLO_L1OF_SUBFRAME_14_F5, 32768), + # (115, 0): (GLO_L1OF_SUBFRAME_15_F5, 65536), }, } diff --git a/src/pygnssutils/rawnav_subframes_gps.py b/src/pygnssutils/rawnav_subframes_gps.py index 6c43d22..2f1e922 100644 --- a/src/pygnssutils/rawnav_subframes_gps.py +++ b/src/pygnssutils/rawnav_subframes_gps.py @@ -10,11 +10,22 @@ the payloads of raw NAV subframe messages, via the associated `pygnssutils.RawNav` class defined in `rawnav.py`. +Format of subframe definition dictionary:: + + dict[attribute_name, tuple[length, encoding, scaling] + +where + + - length = attribute length in bits + - encoding = U (unsigned integer) or S (two's complement signed integer) + - scaling = scaling factor (resolution) as integer or float (0 = no scaling) + NB: - MSB and LSB fields MUST be suffixed '_msb' and '_lsb' respectively. - Non-data bits (reserved, parity, non) MUST be prefixed '_'. -- Avoid the following reserved field names: gnss, svid, sigid, subframeacq, epoch +- '#' character in almanac attributes will be replaced with relevant almanac svid. +- Avoid the following reserved field names: gnss, svid, sigid, subframeacq, epoch. Created on 6 Oct 2025 @@ -23,7 +34,18 @@ :license: BSD 3-Clause """ -from pygnssutils.rawnav import PREAMBLE, SID, SPID, TOC, TOW, VALPREAMBLE, WN, S, U +from pygnssutils.rawnav import ( + PREAMBLE, + SID, + SPID, + SUBFRAMELENGTH, + SVIDALM, + TOC, + TOW, + WN, + S, + U, +) from pygnssutils.rinex_globals import ( CNAV, LNAV, @@ -32,6 +54,7 @@ P2_N6, P2_N8, P2_N9, + P2_N11, P2_N14, P2_N15, P2_N16, @@ -74,23 +97,22 @@ # LNAV - L1 C/A # ********************************************************************** -# attribute_name: (bit offset, bit length, bit encoding, scaling) GPS_LNAV_TLM = { - VALPREAMBLE: 0b10001011, # optional, used to validate preamble value - PREAMBLE: (0, 8, U, 0), - "tlm": (8, 14, U, 0), - "integrity": (22, 1, U, 0), - "_reserved1": (23, 1, U, 0), - "_parity1": (24, 6, U, 0), + SUBFRAMELENGTH: 300, + PREAMBLE: (8, U, 0), + "tlm": (14, U, 0), + "integrity": (1, U, 0), + "_reserved1": (1, U, 0), + "_parity1": (6, U, 0), } GPS_LNAV_HOW = { - TOW: (30, 17, U, 6), # used to derive epoch, check TOW * 6 = seconds - "alert": (47, 1, U, 0), - "antispoof": (48, 1, U, 0), - SID: (49, 3, U, 0), # subframe id - "_non1": (52, 2, U, 0), - "_parity2": (54, 6, U, 0), + TOW: (17, U, 6), # TOW * 6 = seconds + "alert": (1, U, 0), + "antispoof": (1, U, 0), + SID: (3, U, 0), # subframe id + "_non1": (2, U, 0), + "_parity2": (6, U, 0), } GPS_LNAV_GENERIC = { @@ -99,7 +121,7 @@ # word2 **GPS_LNAV_HOW, # words 3 - 10 - "_word3_10": (60, 240, U, 0), + "_word3_10": (240, U, 0), } GPS_LNAV_SUBFRAME_1 = { @@ -108,39 +130,39 @@ # word2 **GPS_LNAV_HOW, # word3 - WN: (60, 10, U, 1), # used to derive epoch - "l2codes": (70, 2, U, 1), - "ura": (72, 4, U, 1), - "svhealth": (76, 6, U, 1), - "iodc_msb": (82, 2, U, 0), - "_parity3": (84, 6, U, 0), + WN: (10, U, 1), # used to derive epoch + "l2codes": (2, U, 1), + "ura": (4, U, 1), + "svhealth": (6, U, 1), + "iodc_msb": (2, U, 0), + "_parity3": (6, U, 0), # word4 - "l2pdata": (90, 1, U, 1), - "_reserved2": (91, 23, U, 0), - "_parity4": (114, 6, U, 0), + "l2pdata": (1, U, 1), + "_reserved2": (23, U, 0), + "_parity4": (6, U, 0), # word5 - "_reserved3": (120, 24, U, 0), - "_parity5": (144, 6, U, 0), + "_reserved3": (24, U, 0), + "_parity5": (6, U, 0), # word6 - "_reserved4": (150, 24, U, 0), - "_parity6": (174, 6, U, 0), + "_reserved4": (24, U, 0), + "_parity6": (6, U, 0), # word7 - "_reserved5": (180, 16, U, 0), - "tgd": (196, 8, S, P2_N31), - "_parity7": (204, 6, U, 0), + "_reserved5": (16, U, 0), + "tgd": (8, S, P2_N31), + "_parity7": (6, U, 0), # word8 - "iodc_lsb": (210, 8, U, 0), - TOC: (218, 16, U, P2_P4), # must be named "toc" - "_parity8": (234, 6, U, 0), + "iodc_lsb": (8, U, 0), + TOC: (16, U, P2_P4), # used to derive epoch + "_parity8": (6, U, 0), # word9 - "af2": (240, 8, S, P2_N55), - "af1": (248, 16, S, P2_N43), - "_parity9": (264, 6, U, 0), + "af2": (8, S, P2_N55), + "af1": (16, S, P2_N43), + "_parity9": (6, U, 0), # word10 - "af0": (270, 22, S, P2_N31), - "_non2": (292, 2, U, 0), - "_parity10": (294, 6, U, 0), -} + "af0": (22, S, P2_N31), + "_non2": (2, U, 0), + "_parity10": (6, U, 0), +} # clock corrections GPS_LNAV_SUBFRAME_2 = { # word1 @@ -148,37 +170,37 @@ # word2 **GPS_LNAV_HOW, # word3 - "iode": (60, 8, U, 0), - "crs": (68, 16, S, P2_N5), - "_parity3": (84, 6, U, 0), + "iode": (8, U, 0), + "crs": (16, S, P2_N5), + "_parity3": (6, U, 0), # word4 - "deltan": (90, 16, S, P2_N43), - "m0_msb": (106, 8, S, P2_N31), - "_parity4": (114, 6, U, 0), + "deltan": (16, S, P2_N43), + "m0_msb": (8, S, P2_N31), + "_parity4": (6, U, 0), # word5 - "m0_lsb": (120, 24, S, P2_N31), - "_parity5": (144, 6, U, 0), + "m0_lsb": (24, S, P2_N31), + "_parity5": (6, U, 0), # word6 - "cuc": (150, 16, S, P2_N29), - "e_msb": (166, 8, U, 0), - "_parity6": (174, 6, U, 0), + "cuc": (16, S, P2_N29), + "e_msb": (8, U, 0), + "_parity6": (6, U, 0), # word7 - "e_lsb": (180, 24, U, P2_N33), - "_parity7": (204, 6, U, 0), + "e_lsb": (24, U, P2_N33), + "_parity7": (6, U, 0), # word8 - "cus": (210, 16, S, P2_N29), - "sqrta_msb": (226, 8, U, P2_N19), - "_parity8": (234, 6, U, 0), + "cus": (16, S, P2_N29), + "sqrta_msb": (8, U, P2_N19), + "_parity8": (6, U, 0), # word9 - "sqrta_lsb": (240, 24, U, P2_N19), - "_parity9": (264, 6, U, 0), + "sqrta_lsb": (24, U, P2_N19), + "_parity9": (6, U, 0), # word10 - "toe": (270, 16, U, P2_P4), - "fit": (286, 1, U, 0), - "aodo": (287, 5, U, 900), - "_non2": (292, 2, U, 0), - "_parity10": (294, 6, U, 0), -} + "toe": (16, U, P2_P4), + "fit": (1, U, 0), + "aodo": (5, U, 900), + "_non2": (2, U, 0), + "_parity10": (6, U, 0), +} # emphemeredes 1 of 2 GPS_LNAV_SUBFRAME_3 = { # word1 @@ -186,35 +208,35 @@ # word2 **GPS_LNAV_HOW, # word3 - "cic": (60, 16, S, P2_N29), - "omega0_msb": (76, 8, S, P2_N31), - "_parity3": (84, 6, U, 0), + "cic": (16, S, P2_N29), + "omega0_msb": (8, S, P2_N31), + "_parity3": (6, U, 0), # word4 - "omega0_lsb": (90, 24, S, P2_N31), - "_parity4": (114, 6, U, 0), + "omega0_lsb": (24, S, P2_N31), + "_parity4": (6, U, 0), # word5 - "cis": (120, 16, S, P2_N29), - "i0_msb": (136, 8, S, P2_N31), - "_parity5": (144, 6, U, 0), + "cis": (16, S, P2_N29), + "i0_msb": (8, S, P2_N31), + "_parity5": (6, U, 0), # word6 - "i0_lsb": (150, 24, S, P2_N31), - "_parity6": (174, 6, U, 0), + "i0_lsb": (24, S, P2_N31), + "_parity6": (6, U, 0), # word7 - "crc": (180, 16, S, P2_N5), - "omega_msb": (196, 8, S, P2_N31), - "_parity7": (204, 6, U, 0), + "crc": (16, S, P2_N5), + "omega_msb": (8, S, P2_N31), + "_parity7": (6, U, 0), # word8 - "omega_lsb": (210, 24, S, P2_N31), - "_parity8": (234, 6, U, 0), + "omega_lsb": (24, S, P2_N31), + "_parity8": (6, U, 0), # word9 - "omegadot": (240, 24, S, P2_N43), - "_parity9": (264, 6, U, 0), + "omegadot": (24, S, P2_N43), + "_parity9": (6, U, 0), # word10 - "iode": (270, 8, U, 0), - "idot": (278, 14, S, P2_N43), - "_non2": (292, 2, U, 0), - "_parity10": (294, 6, U, 0), -} + "iode": (8, U, 0), + "idot": (14, S, P2_N43), + "_non2": (2, U, 0), + "_parity10": (6, U, 0), +} # emphemeredes 2 of 2 GPS_LNAV_SUBFRAME_45_GENERIC = { # word1 @@ -222,9 +244,9 @@ # word2 **GPS_LNAV_HOW, # word3 - "dataid": (60, 2, U, 0), - SPID: (62, 6, U, 0), - "_word3_10": (68, 232, U, 0), + "dataid": (2, U, 0), + SPID: (6, U, 0), + "_word3_10": (232, U, 0), } GPS_LNAV_SUBFRAME_5_P01 = { @@ -233,37 +255,37 @@ # word2 **GPS_LNAV_HOW, # word3 - "dataid": (60, 2, U, 0), - SPID: (62, 6, U, 0), # subframe page id - "e": (68, 16, U, P2_N21), - "_parity3": (84, 6, U, 0), + "dataid": (2, U, 0), + SVIDALM: (6, U, 0), # = subframe page id (will replace '#' below) + "e#": (16, U, P2_N21), + "_parity3": (6, U, 0), # word4 - "toa": (90, 8, U, P2_P12), - "deltai": (98, 16, S, P2_N19), - "_parity4": (114, 6, U, 0), + "toa#": (8, U, P2_P12), + "deltai#": (16, S, P2_N19), + "_parity4": (6, U, 0), # word5 - "omegadot": (120, 16, S, P2_N38), - "svhealth": (136, 8, U, 0), - "_parity5": (144, 6, U, 0), + "omegadot#": (16, S, P2_N38), + "svhealth#": (8, U, 0), + "_parity5": (6, U, 0), # word6 - "sqrta": (150, 24, U, P2_N21), - "_parity6": (174, 6, U, 0), + "sqrta#": (24, U, P2_N11), + "_parity6": (6, U, 0), # word7 - "omega0": (180, 24, S, P2_N23), - "_parity7": (204, 6, U, 0), + "omega0#": (24, S, P2_N23), + "_parity7": (6, U, 0), # word8 - "omega": (210, 24, S, P2_N23), - "_parity8": (234, 6, U, 0), + "omega#": (24, S, P2_N23), + "_parity8": (6, U, 0), # word9 - "m0": (240, 24, S, P2_N23), - "_parity9": (264, 6, U, 0), + "m0#": (24, S, P2_N23), + "_parity9": (6, U, 0), # word10 - "af0_msb": (270, 8, S, P2_N20), - "af1": (278, 11, S, P2_N38), - "af0_lsb": (289, 3, U, P2_N20), - "_non2": (292, 2, U, 0), - "_parity10": (294, 6, U, 0), -} + "af0#_msb": (8, S, P2_N20), + "af1#": (11, S, P2_N38), + "af0#_lsb": (3, U, P2_N20), + "_non2": (2, U, 0), + "_parity10": (6, U, 0), +} # almanac GPS_LNAV_SUBFRAME_5_P25 = { # word1 @@ -271,53 +293,53 @@ # word2 **GPS_LNAV_HOW, # word3 - "dataid": (60, 2, U, 0), - SPID: (62, 6, U, 0), - "toa": (68, 8, U, 0), - "wna": (76, 8, U, 0), - "_parity3": (84, 6, U, 0), + "dataid": (2, U, 0), + SPID: (6, U, 0), + "toa": (8, U, P2_P12), + "wna": (8, U, 1), + "_parity3": (6, U, 0), # word4 - "sv65health": (90, 6, U, 0), - "sv66health": (96, 6, U, 0), - "sv67health": (102, 6, U, 0), - "sv68health": (108, 6, U, 0), - "_parity4": (114, 6, U, 0), + "sv01health": (6, U, 0), + "sv02health": (6, U, 0), + "sv03health": (6, U, 0), + "sv04health": (6, U, 0), + "_parity4": (6, U, 0), # word5 - "sv69health": (120, 6, U, 0), - "sv70health": (126, 6, U, 0), - "sv71health": (132, 6, U, 0), - "sv72health": (138, 6, U, 0), - "_parity5": (144, 6, U, 0), + "sv05health": (6, U, 0), + "sv06health": (6, U, 0), + "sv07health": (6, U, 0), + "sv08health": (6, U, 0), + "_parity5": (6, U, 0), # word6 - "sv73health": (150, 6, U, 0), - "sv74health": (156, 6, U, 0), - "sv75health": (162, 6, U, 0), - "sv76health": (168, 6, U, 0), - "_parity6": (174, 6, U, 0), + "sv09health": (6, U, 0), + "sv10health": (6, U, 0), + "sv11health": (6, U, 0), + "sv12health": (6, U, 0), + "_parity6": (6, U, 0), # word7 - "sv77health": (180, 6, U, 0), - "sv78health": (186, 6, U, 0), - "sv79health": (192, 6, U, 0), - "sv80health": (198, 6, U, 0), - "_parity7": (204, 6, U, 0), + "sv13health": (6, U, 0), + "sv14health": (6, U, 0), + "sv15health": (6, U, 0), + "sv16health": (6, U, 0), + "_parity7": (6, U, 0), # word8 - "sv81health": (210, 6, U, 0), - "sv82health": (216, 6, U, 0), - "sv83health": (222, 6, U, 0), - "sv84health": (228, 6, U, 0), - "_parity8": (234, 6, U, 0), + "sv17health": (6, U, 0), + "sv18health": (6, U, 0), + "sv19health": (6, U, 0), + "sv20health": (6, U, 0), + "_parity8": (6, U, 0), # word9 - "sv85health": (240, 6, U, 0), - "sv86health": (246, 6, U, 0), - "sv87health": (252, 6, U, 0), - "sv88health": (258, 6, U, 0), - "_parity9": (264, 6, U, 0), + "sv21health": (6, U, 0), + "sv22health": (6, U, 0), + "sv23health": (6, U, 0), + "sv24health": (6, U, 0), + "_parity9": (6, U, 0), # word10 - "_reserved2": (270, 6, U, 0), - "_reserved3": (276, 16, U, 0), - "_non2": (292, 2, U, 0), - "_parity10": (294, 6, U, 0), -} + "_reserved2": (6, U, 0), + "_reserved3": (16, U, 0), + "_non2": (2, U, 0), + "_parity10": (6, U, 0), +} # SV health GPS_LNAV_SUBFRAME_4_P01 = { # 1,6,11,16,21 @@ -326,34 +348,34 @@ # word2 **GPS_LNAV_HOW, # word3 - "dataid": (60, 2, U, 0), - SPID: (62, 6, U, 0), - "_reserved2": (68, 16, U, 0), - "_parity3": (84, 6, U, 0), + "dataid": (2, U, 0), + SPID: (6, U, 0), + "_reserved2": (16, U, 0), + "_parity3": (6, U, 0), # word4 - "_reserved3": (90, 24, U, 0), - "_parity4": (114, 6, U, 0), + "_reserved3": (24, U, 0), + "_parity4": (6, U, 0), # word5 - "_reserved4": (120, 24, U, 0), - "_parity5": (144, 6, U, 0), + "_reserved4": (24, U, 0), + "_parity5": (6, U, 0), # word6 - "_reserved5": (150, 24, U, 0), - "_parity6": (174, 6, U, 0), + "_reserved5": (24, U, 0), + "_parity6": (6, U, 0), # word7 - "_reserved6": (180, 24, U, 0), - "_parity7": (204, 6, U, 0), + "_reserved6": (24, U, 0), + "_parity7": (6, U, 0), # word8 - "_reserved7": (210, 24, U, 0), - "_parity8": (234, 6, U, 0), + "_reserved7": (24, U, 0), + "_parity8": (6, U, 0), # word9 - "_reserved8": (240, 8, U, 0), - "_reserved9": (248, 16, U, 0), - "_parity9": (264, 6, U, 0), + "_reserved8": (8, U, 0), + "_reserved9": (16, U, 0), + "_parity9": (6, U, 0), # word10 - "af0": (270, 22, U, 0), - "_non2": (292, 2, U, 0), - "_parity10": (294, 6, U, 0), -} + "af0": (22, U, 0), + "_non2": (2, U, 0), + "_parity10": (6, U, 0), +} # reserved GPS_LNAV_SUBFRAME_4_P12 = { # 12,19,20,22,23,24 @@ -362,34 +384,34 @@ # word2 **GPS_LNAV_HOW, # word3 - "dataid": (60, 2, U, 0), - SPID: (62, 6, U, 0), - "_reserved2": (68, 16, U, 0), - "_parity3": (84, 6, U, 0), + "dataid": (2, U, 0), + SPID: (6, U, 0), + "_reserved2": (16, U, 0), + "_parity3": (6, U, 0), # word4 - "_reserved3": (90, 24, U, 0), - "_parity4": (114, 6, U, 0), + "_reserved3": (24, U, 0), + "_parity4": (6, U, 0), # word5 - "_reserved4": (120, 24, U, 0), - "_parity5": (144, 6, U, 0), + "_reserved4": (24, U, 0), + "_parity5": (6, U, 0), # word6 - "_reserved5": (150, 24, U, 0), - "_parity6": (174, 6, U, 0), + "_reserved5": (24, U, 0), + "_parity6": (6, U, 0), # word7 - "_reserved6": (180, 24, U, 0), - "_parity7": (204, 6, U, 0), + "_reserved6": (24, U, 0), + "_parity7": (6, U, 0), # word8 - "_reserved7": (210, 24, U, 0), - "_parity8": (234, 6, U, 0), + "_reserved7": (24, U, 0), + "_parity8": (6, U, 0), # word9 - "_reserved8": (240, 8, U, 0), - "_reserved9": (248, 16, U, 0), - "_parity9": (264, 6, U, 0), + "_reserved8": (8, U, 0), + "_reserved9": (16, U, 0), + "_parity9": (6, U, 0), # word10 - "_reserved10": (270, 22, U, 0), - "_non2": (292, 2, U, 0), - "_parity10": (294, 6, U, 0), -} + "_reserved10": (22, U, 0), + "_non2": (2, U, 0), + "_parity10": (6, U, 0), +} # reserved GPS_LNAV_SUBFRAME_4_P18 = { # word1 @@ -397,43 +419,43 @@ # word2 **GPS_LNAV_HOW, # word3 - "dataid": (60, 2, U, 0), - SPID: (62, 6, U, 0), - "alpha0": (68, 8, S, P2_N30), - "alpha1": (76, 8, S, P2_N27), - "_parity3": (84, 6, U, 0), + "dataid": (2, U, 0), + SPID: (6, U, 0), + "alpha0": (8, S, P2_N30), + "alpha1": (8, S, P2_N27), + "_parity3": (6, U, 0), # word4 - "alpha2": (90, 8, S, P2_N24), - "alpha3": (98, 8, S, P2_N24), - "beta0": (106, 8, S, P2_P11), - "_parity4": (114, 6, U, 0), + "alpha2": (8, S, P2_N24), + "alpha3": (8, S, P2_N24), + "beta0": (8, S, P2_P11), + "_parity4": (6, U, 0), # word5 - "beta1": (120, 8, S, P2_P14), - "beta2": (128, 8, S, P2_P16), - "beta3": (136, 8, S, P2_P16), - "_parity5": (144, 6, U, 0), + "beta1": (8, S, P2_P14), + "beta2": (8, S, P2_P16), + "beta3": (8, S, P2_P16), + "_parity5": (6, U, 0), # word6 - "a1": (150, 24, S, P2_N30), - "_parity6": (174, 6, U, 0), + "a1": (24, S, P2_N30), + "_parity6": (6, U, 0), # word7 - "a0_msb": (180, 24, S, P2_N50), - "_parity7": (204, 6, U, 0), + "a0_msb": (24, S, P2_N50), + "_parity7": (6, U, 0), # word8 - "a0_lsb": (210, 8, S, P2_N50), - "tot": (218, 8, U, P2_P12), - "wnt": (226, 8, U, 0), - "_parity8": (234, 6, U, 0), + "a0_lsb": (8, S, P2_N50), + "tot": (8, U, P2_P12), + "wnt": (8, U, 0), + "_parity8": (6, U, 0), # word9 - "deltatls": (240, 8, S, 1), - "wnlsf": (248, 8, U, 0), - "dn": (256, 8, U, 0), - "_parity9": (264, 6, U, 0), + "deltatls": (8, S, 1), + "wnlsf": (8, U, 0), + "dn": (8, U, 0), + "_parity9": (6, U, 0), # word10 - "deltatlsf": (270, 8, S, 1), - "_reserved2": (278, 14, U, 0), - "_non2": (292, 2, U, 0), - "_parity10": (294, 6, U, 0), -} + "deltatlsf": (8, S, 1), + "_reserved2": (14, U, 0), + "_non2": (2, U, 0), + "_parity10": (6, U, 0), +} # ionospheric corrections GPS_LNAV_SUBFRAME_4_P25 = { # word1 @@ -441,66 +463,67 @@ # word2 **GPS_LNAV_HOW, # word3 - "dataid": (60, 2, U, 0), - SPID: (62, 6, U, 0), - "sv65asc": (68, 4, U, 0), - "sv66asc": (72, 4, U, 0), - "sv67asc": (76, 4, U, 0), - "sv68asc": (80, 4, U, 0), - "_parity3": (84, 6, U, 0), + "dataid": (2, U, 0), + SPID: (6, U, 0), + "sv01asc": (4, U, 0), + "sv02asc": (4, U, 0), + "sv03asc": (4, U, 0), + "sv04asc": (4, U, 0), + "_parity3": (6, U, 0), # word4 - "sv69asc": (90, 4, U, 0), - "sv70asc": (94, 4, U, 0), - "sv71asc": (98, 4, U, 0), - "sv72asc": (102, 4, U, 0), - "sv73asc": (106, 4, U, 0), - "sv74asc": (110, 4, U, 0), - "_parity4": (114, 6, U, 0), + "sv05asc": (4, U, 0), + "sv06asc": (4, U, 0), + "sv07asc": (4, U, 0), + "sv08asc": (4, U, 0), + "sv09asc": (4, U, 0), + "sv10asc": (4, U, 0), + "_parity4": (6, U, 0), # word5 - "sv75asc": (120, 4, U, 0), - "sv76asc": (124, 4, U, 0), - "sv77asc": (128, 4, U, 0), - "sv78asc": (132, 4, U, 0), - "sv79asc": (136, 4, U, 0), - "sv80asc": (140, 4, U, 0), - "_parity5": (144, 6, U, 0), + "sv11asc": (4, U, 0), + "sv12asc": (4, U, 0), + "sv13asc": (4, U, 0), + "sv14asc": (4, U, 0), + "sv15asc": (4, U, 0), + "sv16asc": (4, U, 0), + "_parity5": (6, U, 0), # word6 - "sv81asc": (150, 4, U, 0), - "sv82asc": (154, 4, U, 0), - "sv83asc": (158, 4, U, 0), - "sv84asc": (162, 4, U, 0), - "sv85asc": (166, 4, U, 0), - "sv86asc": (170, 4, U, 0), - "_parity6": (174, 6, U, 0), + "sv17asc": (4, U, 0), + "sv18asc": (4, U, 0), + "sv19asc": (4, U, 0), + "sv20asc": (4, U, 0), + "sv21asc": (4, U, 0), + "sv22asc": (4, U, 0), + "_parity6": (6, U, 0), # word7 - "sv87asc": (180, 4, U, 0), - "sv88asc": (184, 4, U, 0), - "sv89asc": (188, 4, U, 0), - "sv90asc": (192, 4, U, 0), - "sv91asc": (196, 4, U, 0), - "sv92asc": (200, 4, U, 0), - "_parity7": (204, 6, U, 0), + "sv23asc": (4, U, 0), + "sv24asc": (4, U, 0), + "sv25asc": (4, U, 0), + "sv26asc": (4, U, 0), + "sv27asc": (4, U, 0), + "sv28asc": (4, U, 0), + "_parity7": (6, U, 0), # word8 - "sv93asc": (210, 4, U, 0), - "sv94asc": (214, 4, U, 0), - "sv95asc": (218, 4, U, 0), - "_reserved2": (222, 4, U, 0), - "_reserved3": (226, 2, U, 0), - "sv89health": (228, 6, U, 0), - "_parity8": (234, 6, U, 0), + "sv29asc": (4, U, 0), + "sv30asc": (4, U, 0), + "sv31asc": (4, U, 0), + "sv32asc": (4, U, 0), + "_reserved2": (2, U, 0), + "sv25health": (6, U, 0), + "_parity8": (6, U, 0), # word9 - "sv90health": (240, 6, U, 0), - "sv91health": (246, 6, U, 0), - "sv92health": (252, 6, U, 0), - "sv93health": (258, 6, U, 0), - "_parity9": (264, 6, U, 0), + "sv26health": (6, U, 0), + "sv27health": (6, U, 0), + "sv28health": (6, U, 0), + "sv29health": (6, U, 0), + "_parity9": (6, U, 0), # word10 - "sv94health": (270, 6, U, 0), - "sv95health": (276, 6, U, 0), - "_reserved4": (282, 10, U, 0), - "_non2": (292, 2, U, 0), - "_parity10": (294, 6, U, 0), -} + "sv30health": (6, U, 0), + "sv31health": (6, U, 0), + "sv32health": (6, U, 0), + "_reserved3": (4, U, 0), + "_non2": (2, U, 0), + "_parity10": (6, U, 0), +} # SV anti-spoof GPS_LNAV_SUBFRAME_4_P13 = { # word1 @@ -508,63 +531,63 @@ # word2 **GPS_LNAV_HOW, # word3 - "dataid": (60, 2, U, 0), - SPID: (62, 6, U, 0), - "avail": (68, 2, U, 0), - "erd1": (70, 6, U, 0), - "erd2": (76, 6, U, 0), - "erd3_msb": (82, 2, U, 0), - "_parity3": (84, 6, U, 0), + "dataid": (2, U, 0), + SPID: (6, U, 0), + "avail": (2, U, 0), + "erd1": (6, U, 0), + "erd2": (6, U, 0), + "erd3_msb": (2, U, 0), + "_parity3": (6, U, 0), # word4 - "erd3_lsb": (90, 4, U, 0), - "erd4": (94, 6, U, 0), - "erd5": (100, 6, U, 0), - "erd6": (106, 6, U, 0), - "erd7_msb": (112, 2, U, 0), - "_parity4": (114, 6, U, 0), + "erd3_lsb": (4, U, 0), + "erd4": (6, U, 0), + "erd5": (6, U, 0), + "erd6": (6, U, 0), + "erd7_msb": (2, U, 0), + "_parity4": (6, U, 0), # word5 - "erd7_lsb": (120, 4, U, 0), - "erd8": (124, 6, U, 0), - "erd9": (130, 6, U, 0), - "erd10": (136, 6, U, 0), - "erd11_msb": (142, 2, U, 0), - "_parity5": (144, 6, U, 0), + "erd7_lsb": (4, U, 0), + "erd8": (6, U, 0), + "erd9": (6, U, 0), + "erd10": (6, U, 0), + "erd11_msb": (2, U, 0), + "_parity5": (6, U, 0), # word6 - "erd11_lsb": (150, 4, U, 0), - "erd12": (154, 6, U, 0), - "erd13": (160, 6, U, 0), - "erd14": (166, 6, U, 0), - "erd15_msb": (172, 2, U, 0), - "_parity6": (174, 6, U, 0), + "erd11_lsb": (4, U, 0), + "erd12": (6, U, 0), + "erd13": (6, U, 0), + "erd14": (6, U, 0), + "erd15_msb": (2, U, 0), + "_parity6": (6, U, 0), # word7 - "erd15_lsb": (180, 4, U, 0), - "erd16": (184, 6, U, 0), - "erd17": (190, 6, U, 0), - "erd18": (196, 6, U, 0), - "erd19_msb": (202, 2, U, 0), - "_parity7": (204, 6, U, 0), + "erd15_lsb": (4, U, 0), + "erd16": (6, U, 0), + "erd17": (6, U, 0), + "erd18": (6, U, 0), + "erd19_msb": (2, U, 0), + "_parity7": (6, U, 0), # word8 - "erd19_lsb": (210, 4, U, 0), - "erd20": (214, 6, U, 0), - "erd21": (220, 6, U, 0), - "erd22": (226, 6, U, 0), - "erd23_msb": (232, 2, U, 0), - "_parity8": (234, 6, U, 0), + "erd19_lsb": (4, U, 0), + "erd20": (6, U, 0), + "erd21": (6, U, 0), + "erd22": (6, U, 0), + "erd23_msb": (2, U, 0), + "_parity8": (6, U, 0), # word9 - "erd23_lsb": (240, 4, U, 0), - "erd24": (244, 6, U, 0), - "erd25": (250, 6, U, 0), - "erd26": (256, 6, U, 0), - "erd27_msb": (262, 2, U, 0), - "_parity9": (264, 6, U, 0), + "erd23_lsb": (4, U, 0), + "erd24": (6, U, 0), + "erd25": (6, U, 0), + "erd26": (6, U, 0), + "erd27_msb": (2, U, 0), + "_parity9": (6, U, 0), # word10 - "erd27_lsb": (270, 4, U, 0), - "erd28": (274, 6, U, 0), - "erd29": (280, 6, U, 0), - "erd30": (286, 6, U, 0), - "_non2": (292, 2, U, 0), - "_parity10": (294, 6, U, 0), -} + "erd27_lsb": (4, U, 0), + "erd28": (6, U, 0), + "erd29": (6, U, 0), + "erd30": (6, U, 0), + "_non2": (2, U, 0), + "_parity10": (6, U, 0), +} # Navigation Message Correction Table (NMCT) estimated range deviation GPS_LNAV_SUBFRAME_4_P14 = { # 14,15,17 @@ -573,33 +596,33 @@ # word2 **GPS_LNAV_HOW, # word3 - "dataid": (60, 2, U, 0), - SPID: (62, 6, U, 0), - "_reserved2": (68, 16, U, 0), - "_parity3": (84, 6, U, 0), + "dataid": (2, U, 0), + SPID: (6, U, 0), + "_reserved2": (16, U, 0), + "_parity3": (6, U, 0), # word4 - "_reserved3": (90, 24, U, 0), - "_parity4": (114, 6, U, 0), + "_reserved3": (24, U, 0), + "_parity4": (6, U, 0), # word5 - "_reserved4": (120, 24, U, 0), - "_parity5": (144, 6, U, 0), + "_reserved4": (24, U, 0), + "_parity5": (6, U, 0), # word6 - "_reserved5": (150, 24, U, 0), - "_parity6": (174, 6, U, 0), + "_reserved5": (24, U, 0), + "_parity6": (6, U, 0), # word7 - "_reserved6": (180, 24, U, 0), - "_parity7": (204, 6, U, 0), + "_reserved6": (24, U, 0), + "_parity7": (6, U, 0), # word8 - "_reserved7": (210, 24, U, 0), - "_parity8": (234, 6, U, 0), + "_reserved7": (24, U, 0), + "_parity8": (6, U, 0), # word9 - "_reserved8": (240, 24, U, 0), - "_parity9": (264, 6, U, 0), + "_reserved8": (24, U, 0), + "_parity9": (6, U, 0), # word10 - "_reserved9": (270, 22, U, 0), - "_non2": (292, 2, U, 0), - "_parity10": (294, 6, U, 0), -} + "_reserved9": (22, U, 0), + "_non2": (2, U, 0), + "_parity10": (6, U, 0), +} # reserved GPS_LNAV_SUBFRAME_4_P02 = GPS_LNAV_SUBFRAME_5_P01 GPS_LNAV_SUBFRAME_4_P03 = GPS_LNAV_SUBFRAME_5_P01 @@ -649,307 +672,298 @@ # ********************************************************************** GPS_CNAV_SUBFRAME = { - "word": (0, 300, U, 0), - "padding": (300, 20, U, 0), + "word": (300, U, 0), + "padding": (20, U, 0), } # 10 * 32-bit dwrds GPS_CNAV_TLM = { - # VALPREAMBLE: 0b10001011, # optional, used to validate preamble value - PREAMBLE: (0, 8, U, 0), - "prn": (8, 6, U, 0), - SID: (14, 6, U, 0), - TOW: (20, 17, U, 6), # used to derive epoch, check TOW * 6 = seconds - "alert": (37, 1, U, 0), + SUBFRAMELENGTH: 300, + PREAMBLE: (8, U, 0), + "prn": (6, U, 0), + SID: (6, U, 0), + TOW: (17, U, 6), # TOW * 6 = seconds + "alert": (1, U, 0), } GPS_CNAV_CLOCK = { - "top": (38, 11, U, 300), - "uraned0": (49, 5, S, 0), - "uraned1": (54, 3, U, 0), - "uraned2": (57, 3, U, 0), - TOC: (60, 11, U, 300), - "af0n": (71, 26, S, P2_N35), - "af1n_msb": (97, 3, S, P2_N48), - "af1n_lsb": (100, 17, S, P2_N48), - "af2n": (117, 10, S, P2_N60), + "top": (11, U, 300), + "uraned0": (5, S, 0), + "uraned1": (3, U, 0), + "uraned2": (3, U, 0), + TOC: (11, U, 300), # used to derive epoch + "af0n": (26, S, P2_N35), + "af1n_msb": (3, S, P2_N48), + "af1n_lsb": (17, S, P2_N48), + "af2n": (10, S, P2_N60), } GPS_CNAV_PARITY = { - "_parity": (276, 24, U, 0), + "_parity": (24, U, 0), } GPS_CNAV_RAP = { - "prn": (0, 6, U, 0), - "deltaa": (6, 8, S, P2_P9), # Relative to Aref = 26,559,710 meters, meters - "omega0": (14, 7, S, P2_N6), # semi-circles - "phi0": (21, 7, S, P2_N6), # M0 + omega, semi-circles - "l1health": (28, 1, U, 0), - "l2health": (29, 1, U, 0), - "l5health": (30, 1, U, 0), + SVIDALM: (6, U, 0), # will replace '#' below + "deltaa#": (8, S, P2_P9), # Relative to Aref = 26,559,710 meters, meters + "omega0#": (7, S, P2_N6), # semi-circles + "phi0#": (7, S, P2_N6), # M0 + omega, semi-circles + "l1health#": (1, U, 0), + "l2health#": (1, U, 0), + "l5health#": (1, U, 0), } # 31 bit reduced almanac packet GPS_CNAV_CDC = { - "prn": (0, 8, U, 0), - "deltaaf0": (8, 13, U, P2_N35), - "deltaaf1": (21, 8, U, P2_N51), - "udra": (29, 5, S, 0), + "prn": (8, U, 0), + "deltaaf0": (13, U, P2_N35), + "deltaaf1": (8, U, P2_N51), + "udra": (5, S, 0), } # 34 bit clock differential correction GPS_CNAV_EDC = { - "prn": (0, 8, U, 0), - "deltaalpha": (8, 14, S, P2_N34), - "deltabeta": (22, 14, S, P2_N34), - "deltalambda": (36, 15, S, P2_N32), - "deltai": (51, 12, S, P2_N31), - "deltaomega": (63, 12, S, P2_N32), - "deltaa": (75, 12, S, P2_N9), - "udradot": (87, 5, S, 0), + "prn": (8, U, 0), + "deltaalpha": (14, S, P2_N34), + "deltabeta": (14, S, P2_N34), + "deltalambda": (15, S, P2_N32), + "deltai": (12, S, P2_N31), + "deltaomega": (12, S, P2_N32), + "deltaa": (12, S, P2_N9), + "udradot": (5, S, 0), } # 92 bit ephemeris differential correction GPS_CNAV_SUBFRAME_10 = { **GPS_CNAV_TLM, - WN: (38, 13, U, 0), # used to derive epoch - "l1health": (51, 1, U, 0), - "l2health": (52, 1, U, 0), - "l5health": (53, 1, U, 0), - "top": (54, 11, U, 300), - "uraed": (65, 5, U, 0), - "toe": (70, 11, U, 300), - "deltaa_msb": (81, 19, S, P2_N9), - "deltaa_lsb": (100, 7, S, P2_N9), - "adot": (107, 25, S, P2_N21), - "deltan0": (132, 17, S, P2_N44), - "deltan0dot": (149, 23, S, P2_N57), - "m0_msb": (172, 28, S, P2_N32), - "m0_lsb": (200, 5, S, P2_N32), - "e": (205, 33, U, P2_N34), - "omega": (238, 33, S, P2_N32), - "integrity": (271, 1, U, 0), - "l2phase": (272, 1, U, 0), - "_reserved1": (273, 3, U, 0), + WN: (13, U, 0), # used to derive epoch + "l1health": (1, U, 0), + "l2health": (1, U, 0), + "l5health": (1, U, 0), + "top": (11, U, 300), + "uraed": (5, U, 0), + "toe": (11, U, 300), + "deltaa_msb": (19, S, P2_N9), + "deltaa_lsb": (7, S, P2_N9), + "adot": (25, S, P2_N21), + "deltan0": (17, S, P2_N44), + "deltan0dot": (23, S, P2_N57), + "m0_msb": (28, S, P2_N32), + "m0_lsb": (5, S, P2_N32), + "e": (33, U, P2_N34), + "omega": (33, S, P2_N32), + "integrity": (1, U, 0), + "l2phase": (1, U, 0), + "_reserved1": (3, U, 0), **GPS_CNAV_PARITY, } # Ephemeris 1 GPS_CNAV_SUBFRAME_11 = { **GPS_CNAV_TLM, - "toe": (38, 11, U, 300), - "omega0": (49, 33, S, P2_N32), - "i0_msb": (82, 18, S, P2_N32), - "i0_lsb": (100, 15, S, P2_N32), - "deltaomegadot": (115, 17, S, P2_N44), - "idot": (132, 15, S, P2_N44), - "cis": (147, 16, S, P2_N30), - "cic": (163, 16, S, P2_N30), - "crs_msb": (179, 21, S, P2_N8), - "crs_lsb": (200, 3, S, P2_N8), - "crc": (203, 24, S, P2_N8), - "cus": (227, 21, S, P2_N30), - "cuc": (248, 21, S, P2_N30), - "_reserved1": (269, 7, U, 0), + "toe": (11, U, 300), + "omega0": (33, S, P2_N32), + "i0_msb": (18, S, P2_N32), + "i0_lsb": (15, S, P2_N32), + "deltaomegadot": (17, S, P2_N44), + "idot": (15, S, P2_N44), + "cis": (16, S, P2_N30), + "cic": (16, S, P2_N30), + "crs_msb": (21, S, P2_N8), + "crs_lsb": (3, S, P2_N8), + "crc": (24, S, P2_N8), + "cus": (21, S, P2_N30), + "cuc": (21, S, P2_N30), + "_reserved1": (7, U, 0), **GPS_CNAV_PARITY, } # Ephemeris 2 GPS_CNAV_SUBFRAME_12 = { **GPS_CNAV_TLM, - "wna": (38, 13, U, 0), - "toa": (51, 8, U, P2_P12), - "rap1": (59, 31, U, 0), # GPS_CNAV_RAP - "rap2_msb": (90, 10, U, 0), - "rap2_lsb": (100, 21, U, 0), - "rap3": (121, 31, U, 0), - "rap4": (152, 31, U, 0), - "rap5_msb": (183, 17, U, 0), - "rap5_lsb": (200, 14, U, 0), - "rap6": (214, 31, U, 0), - "rap7": (245, 31, U, 0), + "wna": (13, U, 0), + "toa": (8, U, P2_P12), + "rap_grp": ( + 7, + { + **GPS_CNAV_RAP, + }, + ), **GPS_CNAV_PARITY, } # Reduced Almanac GPS_CNAV_SUBFRAME_13 = { **GPS_CNAV_TLM, - "topd": (38, 11, U, 300), - "tod": (49, 11, U, 300), - "dctype1": (60, 1, U, 0), - "cdc1": (61, 34, U, 0), # GPS_CNAV_CDC - "dctype2": (95, 1, U, 0), - "cdc2_msb": (96, 4, U, 0), - "cdc2_lsb": (100, 30, U, 0), - "dctype3": (130, 1, U, 0), - "cdc3": (131, 34, U, 0), - "dctype4": (165, 1, U, 0), - "cdc4": (166, 34, U, 0), - "dctype5": (200, 1, U, 0), - "cdc5": (201, 34, U, 0), - "dctype6": (235, 1, U, 0), - "cdc6": (236, 34, U, 0), - "_reserved1": (270, 6, U, 0), + "topd": (11, U, 300), + "tod": (11, U, 300), + "cdc_grp": ( + 6, + { + "dctype": (1, U, 0), + **GPS_CNAV_CDC, + }, + ), + "_reserved1": (6, U, 0), **GPS_CNAV_PARITY, } # Clock Differential Correction GPS_CNAV_SUBFRAME_14 = { **GPS_CNAV_TLM, - "topd": (38, 11, U, 300), - "tod": (49, 11, U, 300), - "dctype1": (60, 1, U, 0), - "edc1_msb": (61, 39, U, 0), # GPS_CNAV_EDC - "edc1_lsb": (100, 53, U, 0), - "dctype2": (153, 1, U, 0), - "edc2_msb": (154, 46, U, 0), - "edc2_lsb": (200, 46, U, 0), - "_reserved1": (246, 30, U, 0), + "topd": (11, U, 300), + "tod": (11, U, 300), + "edc_grp": ( + 2, + { + **GPS_CNAV_EDC, + }, + ), + "_reserved1": (30, U, 0), **GPS_CNAV_PARITY, } # Ephemeris Differential Correction GPS_CNAV_SUBFRAME_15 = { **GPS_CNAV_TLM, - "text": (38, 232, U, 0), - "textpage": (270, 4, U, 0), - "_reserved1": (274, 2, U, 0), + "text": (232, U, 0), + "textpage": (4, U, 0), + "_reserved1": (2, U, 0), **GPS_CNAV_PARITY, } # Text GPS_CNAV_SUBFRAME_30 = { **GPS_CNAV_TLM, **GPS_CNAV_CLOCK, - "tgd": (127, 13, S, P2_N35), - "iscl1ca": (140, 13, S, P2_N35), - "iscl2c": (153, 13, S, P2_N35), - "iscl5i5": (166, 13, S, P2_N35), - "iscl5q5": (179, 13, S, P2_N35), - "alpha0": (192, 8, U, P2_N30), # where is scaling defined for CNAV? - "alpha1": (200, 8, U, P2_N27), # have assumed same as LNAV - "alpha2": (208, 8, U, P2_N24), - "alpha3": (216, 8, U, P2_N24), - "beta0": (224, 8, U, P2_P11), - "beta1": (232, 8, U, P2_P14), - "beta2": (240, 8, U, P2_P16), - "beta3": (248, 8, U, P2_P16), - "wno": (256, 8, U, 0), - "_reserved1": (264, 12, U, 0), + "tgd": (13, S, P2_N35), + "iscl1ca": (13, S, P2_N35), + "iscl2c": (13, S, P2_N35), + "iscl5i5": (13, S, P2_N35), + "iscl5q5": (13, S, P2_N35), + "alpha0": (8, U, P2_N30), # where is scaling defined for CNAV? + "alpha1": (8, U, P2_N27), # have assumed same as LNAV + "alpha2": (8, U, P2_N24), + "alpha3": (8, U, P2_N24), + "beta0": (8, U, P2_P11), + "beta1": (8, U, P2_P14), + "beta2": (8, U, P2_P16), + "beta3": (8, U, P2_P16), + "wno": (8, U, 0), + "_reserved1": (12, U, 0), **GPS_CNAV_PARITY, } # Clock, IONO & Group Delay GPS_CNAV_SUBFRAME_31 = { **GPS_CNAV_TLM, **GPS_CNAV_CLOCK, - "wna": (127, 13, U, 0), - "toa": (140, 8, U, P2_P12), - "rap1": (148, 31, U, 0), # GPS_CNAV_RAP - "rap2_msb": (179, 21, U, 0), - "rap2_lsb": (200, 10, U, 0), - "rap3": (210, 31, U, 0), - "rap4": (241, 31, U, 0), - "_reserved1": (272, 4, U, 0), + "wna": (13, U, 0), + "toa": (8, U, P2_P12), + "rap_grp": ( + 4, + { + **GPS_CNAV_RAP, # GPS_CNAV_RAP + }, + ), + "_reserved1": (4, U, 0), **GPS_CNAV_PARITY, } # Clock & Reduced Almanac GPS_CNAV_SUBFRAME_32 = { **GPS_CNAV_TLM, **GPS_CNAV_CLOCK, - "teop": (127, 16, U, P2_P4), - "pmx": (143, 21, S, P2_N20), - "pmxdot": (164, 15, S, P2_N21), - "pmy": (179, 21, S, P2_N20), - "pmydot": (200, 15, S, P2_N21), - "deltautgps": (215, 31, S, P2_N23), - "deltautgpsdot": (246, 19, S, P2_N25), - "_reserved1": (265, 11, U, 0), + "teop": (16, U, P2_P4), + "pmx": (21, S, P2_N20), + "pmxdot": (15, S, P2_N21), + "pmy": (21, S, P2_N20), + "pmydot": (15, S, P2_N21), + "deltautgps": (31, S, P2_N23), + "deltautgpsdot": (19, S, P2_N25), + "_reserved1": (11, U, 0), **GPS_CNAV_PARITY, -} # Clock & EOP +} # Clock & Earth Orientation Parameters GPS_CNAV_SUBFRAME_33 = { **GPS_CNAV_TLM, **GPS_CNAV_CLOCK, - "a0": (127, 16, S, P2_N35), - "a1": (143, 13, S, P2_N51), - "a2": (156, 7, S, P2_N68), - "deltatls": (163, 8, S, 1), - "tot": (171, 16, U, P2_P4), - "wnot": (187, 13, U, 1), - "wnlsf": (200, 13, U, 1), - "dn": (213, 4, U, 1), - "deltatlsf": (217, 8, S, 1), - "_reserved1": (225, 51, U, 0), + "a0": (16, S, P2_N35), + "a1": (13, S, P2_N51), + "a2": (7, S, P2_N68), + "deltatls": (8, S, 1), + "tot": (16, U, P2_P4), + "wnot": (13, U, 1), + "wnlsf": (13, U, 1), + "dn": (4, U, 1), + "deltatlsf": (8, S, 1), + "_reserved1": (51, U, 0), **GPS_CNAV_PARITY, } # Clock & UTC GPS_CNAV_SUBFRAME_34 = { **GPS_CNAV_TLM, **GPS_CNAV_CLOCK, - "topd": (127, 11, U, 300), - "tod": (138, 11, U, 300), - "dctype": (149, 1, U, 0), - "cdc": (150, 34, U, 0), # GPS_CNAV_CDC - "edc_msb": (184, 16, U, 0), # GPS_CNAV_EDC - "edc_lsb": (200, 76, U, 0), - # "_reserved1": (276, 51, U, 0), + "topd": (11, U, 300), + "tod": (11, U, 300), + "dctype": (1, U, 0), + "cdc": (34, U, 0), # GPS_CNAV_CDC + "edc_msb": (16, U, 0), # GPS_CNAV_EDC + "edc_lsb": (76, U, 0), + # "_reserved1": (51, U, 0), **GPS_CNAV_PARITY, } # Clock & Differential Correction GPS_CNAV_SUBFRAME_35 = { **GPS_CNAV_TLM, **GPS_CNAV_CLOCK, - "tggto": (127, 16, U, P2_P4), - "wnggto": (143, 13, U, 1), - "gnssid": (156, 3, U, 0), - "a0ggto": (159, 16, U, P2_N35), - "a1ggto": (175, 13, U, P2_N51), - "a2ggto": (188, 7, U, P2_N68), - "_reserved1": (195, 5, U, 0), - "_reserved2": (200, 76, U, 0), + "tggto": (16, U, P2_P4), + "wnggto": (13, U, 1), + "gnssid": (3, U, 0), + "a0ggto": (16, U, P2_N35), + "a1ggto": (13, U, P2_N51), + "a2ggto": (7, U, P2_N68), + "_reserved1": (5, U, 0), + "_reserved2": (76, U, 0), **GPS_CNAV_PARITY, } # Clock & GGTO (GPS/GNSS Time Offset) GPS_CNAV_SUBFRAME_36 = { **GPS_CNAV_TLM, **GPS_CNAV_CLOCK, - "text_msb": (127, 73, U, 0), - "text_lsb": (200, 71, U, 0), - "textpage": (271, 4, U, 0), - "_reserved1": (275, 1, U, 0), + "text_msb": (73, U, 0), + "text_lsb": (71, U, 0), + "textpage": (4, U, 0), + "_reserved1": (1, U, 0), **GPS_CNAV_PARITY, } # Clock & Text GPS_CNAV_SUBFRAME_37 = { **GPS_CNAV_TLM, **GPS_CNAV_CLOCK, - "wna": (127, 13, U, 0), - "toa": (140, 8, U, P2_P12), - "prna": (148, 6, U, 0), - "l1health": (154, 1, U, 0), - "l2health": (155, 1, U, 0), - "l5health": (156, 1, U, 0), - "e": (157, 11, U, P2_N16), - "omegai": (168, 11, S, P2_N14), - "omegadot": (179, 11, S, P2_N33), - "sqrta_msb": (190, 10, U, P2_N4), - "sqrta_lsb": (200, 7, U, P2_N4), - "omega0": (207, 16, S, P2_N15), - "omega": (223, 16, S, P2_N15), - "m0": (239, 16, S, P2_N15), - "af0": (255, 11, S, P2_N20), - "af1": (266, 10, S, P2_N37), + "wna": (13, U, 0), + "toa": (8, U, P2_P12), + SVIDALM: (6, U, 0), + "l1health#": (1, U, 0), + "l2health#": (1, U, 0), + "l5health#": (1, U, 0), + "e#": (11, U, P2_N16), + "omegai#": (11, S, P2_N14), + "omegadot#": (11, S, P2_N33), + "sqrta#": (17, U, P2_N4), + "omega0#": (16, S, P2_N15), + "omega#": (16, S, P2_N15), + "m0#": (16, S, P2_N15), + "af0#": (11, S, P2_N20), + "af1#": (10, S, P2_N37), **GPS_CNAV_PARITY, } # Clock & Midi Almanac GPS_CNAV_SUBFRAME_40 = { **GPS_CNAV_TLM, - "gnssid": (38, 4, U, 0), - "wnism": (42, 13, U, 1), - "towism": (55, 6, U, 4), - "tcorrel": (61, 4, U, 0), - "bnom": (65, 4, U, 0), - "lambdanom": (69, 4, U, 0), - "rsat": (73, 4, U, 0), - "pconst": (77, 4, U, 0), - "mfd": (81, 4, U, 0), - "servicelevel": (85, 3, U, 0), - "mask": (88, 63, U, 0), - "filler": (151, 93, U, 0), - "_ism_crc": (244, 32, U, 0), + "gnssid": (4, U, 0), + "wnism": (13, U, 1), + "towism": (6, U, 4), + "tcorrel": (4, U, 0), + "bnom": (4, U, 0), + "lambdanom": (4, U, 0), + "rsat": (4, U, 0), + "pconst": (4, U, 0), + "mfd": (4, U, 0), + "servicelevel": (3, U, 0), + "mask": (63, U, 0), + "filler": (93, U, 0), + "_ism_crc": (32, U, 0), **GPS_CNAV_PARITY, } # Integrity Support Message @@ -958,20 +972,74 @@ # required for RINEX conversion purposes GPS_SUBFRAMEACQ_MAP = { LNAV: { - TARGET: 0b1111, # subframes 1,2,3,4p18 + TARGET: 0b1111, # subframes 1,2,3,4p56 START: 1, (1, 0): (GPS_LNAV_SUBFRAME_1, 1), (2, 0): (GPS_LNAV_SUBFRAME_2, 2), (3, 0): (GPS_LNAV_SUBFRAME_3, 4), (4, 56): (GPS_LNAV_SUBFRAME_4_P18, 8), + # (5, 1): (GPS_LNAV_SUBFRAME_5_P01, 16), + # (5, 2): (GPS_LNAV_SUBFRAME_5_P01, 32), + # (5, 3): (GPS_LNAV_SUBFRAME_5_P01, 64), + # (5, 4): (GPS_LNAV_SUBFRAME_5_P01, 128), + # (5, 5): (GPS_LNAV_SUBFRAME_5_P01, 256), + # (5, 6): (GPS_LNAV_SUBFRAME_5_P01, 512), + # (5, 7): (GPS_LNAV_SUBFRAME_5_P01, 1024), + # (5, 8): (GPS_LNAV_SUBFRAME_5_P01, 2048), + # (5, 9): (GPS_LNAV_SUBFRAME_5_P01, 4096), + # (5, 10): (GPS_LNAV_SUBFRAME_5_P01, 8192), + # (5, 11): (GPS_LNAV_SUBFRAME_5_P01, 16384), + # (5, 12): (GPS_LNAV_SUBFRAME_5_P01, 2**15), + # (5, 13): (GPS_LNAV_SUBFRAME_5_P01, 2**16), + # (5, 14): (GPS_LNAV_SUBFRAME_5_P01, 2**17), + # (5, 15): (GPS_LNAV_SUBFRAME_5_P01, 2**18), + # (5, 16): (GPS_LNAV_SUBFRAME_5_P01, 2**19), + # (5, 17): (GPS_LNAV_SUBFRAME_5_P01, 2**20), + # (5, 18): (GPS_LNAV_SUBFRAME_5_P01, 2**21), + # (5, 19): (GPS_LNAV_SUBFRAME_5_P01, 2**22), + # (5, 20): (GPS_LNAV_SUBFRAME_5_P01, 2**23), + # (5, 21): (GPS_LNAV_SUBFRAME_5_P01, 2**24), + # (5, 22): (GPS_LNAV_SUBFRAME_5_P01, 2**25), + # (5, 23): (GPS_LNAV_SUBFRAME_5_P01, 2**26), + # (5, 24): (GPS_LNAV_SUBFRAME_5_P01, 2**27), + # (4, 25): (GPS_LNAV_SUBFRAME_5_P01, 2**28), + # (4, 26): (GPS_LNAV_SUBFRAME_5_P01, 2**29), + # (4, 27): (GPS_LNAV_SUBFRAME_5_P01, 2**30), + # (4, 28): (GPS_LNAV_SUBFRAME_5_P01, 2**31), + # (4, 29): (GPS_LNAV_SUBFRAME_5_P01, 2**32), + # (4, 30): (GPS_LNAV_SUBFRAME_5_P01, 2**33), + # (4, 31): (GPS_LNAV_SUBFRAME_5_P01, 2**34), + # (4, 32): (GPS_LNAV_SUBFRAME_5_P01, 2**35), + # (4, 52): (GPS_LNAV_SUBFRAME_4_P13, 2**36), + # (4, 53): (GPS_LNAV_SUBFRAME_4_P14, 2**37), + # (4, 54): (GPS_LNAV_SUBFRAME_4_P14, 2**38), + # (4, 55): (GPS_LNAV_SUBFRAME_4_P14, 2**39), + # (4, 57): (GPS_LNAV_SUBFRAME_4_P01, 2**40), + # (4, 58): (GPS_LNAV_SUBFRAME_4_P12, 2**41), + # (4, 59): (GPS_LNAV_SUBFRAME_4_P12, 2**42), + # (4, 60): (GPS_LNAV_SUBFRAME_4_P12, 2**43), + # (4, 61): (GPS_LNAV_SUBFRAME_4_P12, 2**44), + # (4, 62): (GPS_LNAV_SUBFRAME_4_P12, 2**45), + # (4, 63): (GPS_LNAV_SUBFRAME_4_P25, 2**46), + # (5, 51): (GPS_LNAV_SUBFRAME_5_P25, 2**47), }, CNAV: { - TARGET: 0b1111, # subframes 10,11,30,33 (32 EOP optional) + TARGET: 0b11111, # subframes 10,11,30,32,33 START: 10, (10, 0): (GPS_CNAV_SUBFRAME_10, 1), (11, 0): (GPS_CNAV_SUBFRAME_11, 2), (30, 0): (GPS_CNAV_SUBFRAME_30, 4), - (33, 0): (GPS_CNAV_SUBFRAME_33, 8), - (32, 0): (GPS_CNAV_SUBFRAME_32, 16), + (32, 0): (GPS_CNAV_SUBFRAME_32, 8), # EOP + (33, 0): (GPS_CNAV_SUBFRAME_33, 16), + # (12, 0): (GPS_CNAV_SUBFRAME_12, 32), + # (13, 0): (GPS_CNAV_SUBFRAME_13, 64), + # (14, 0): (GPS_CNAV_SUBFRAME_14, 128), + # (15, 0): (GPS_CNAV_SUBFRAME_15, 256), + # (31, 0): (GPS_CNAV_SUBFRAME_31, 512), + # (34, 0): (GPS_CNAV_SUBFRAME_34, 1024), + # (35, 0): (GPS_CNAV_SUBFRAME_35, 2048), + # (36, 0): (GPS_CNAV_SUBFRAME_36, 4096), + # (37, 0): (GPS_CNAV_SUBFRAME_37, 8192), + # (40, 0): (GPS_CNAV_SUBFRAME_40, 16384), }, } diff --git a/src/pygnssutils/rawnav_subframes_irn.py b/src/pygnssutils/rawnav_subframes_irn.py index 9ca4998..d03357c 100644 --- a/src/pygnssutils/rawnav_subframes_irn.py +++ b/src/pygnssutils/rawnav_subframes_irn.py @@ -10,11 +10,22 @@ the payloads of raw NAV subframe messages, via the associated `pygnssutils.RawNav` class defined in `rawnav.py`. +Format of subframe definition dictionary:: + + dict[attribute_name, tuple[length, encoding, scaling] + +where + + - length = attribute length in bits + - encoding = U (unsigned integer) or S (two's complement signed integer) + - scaling = scaling factor (resolution) as integer or float (0 = no scaling) + NB: - MSB and LSB fields MUST be suffixed '_msb' and '_lsb' respectively. - Non-data bits (reserved, parity, non) MUST be prefixed '_'. -- Avoid the following reserved field names: gnss, svid, sigid, subframeacq, epoch +- '#' character in almanac attributes will be replaced with relevant almanac svid. +- Avoid the following reserved field names: gnss, svid, sigid, subframeacq, epoch. Created on 6 Oct 2025 @@ -23,7 +34,7 @@ :license: BSD 3-Clause """ -from pygnssutils.rawnav import SID, SPID, TOW, WN, S, U +from pygnssutils.rawnav import SID, SPID, SUBFRAMELENGTH, TOC, TOW, WN, S, U from pygnssutils.rinex_globals import ( L1CA, P2_N4, @@ -56,98 +67,99 @@ # ********************************************************************** IRN_LNAV_SUBFRAME_TLM = { - "_tlm": (0, 8, U, 0), - TOW: (8, 17, U, 12), - "alert": (25, 1, U, 0), - "autonav": (26, 1, U, 0), - SID: (27, 2, U, 0), - "_spare": (29, 1, U, 0), + SUBFRAMELENGTH: 292, + "_tlm": (8, U, 0), + TOW: (17, U, 12), + "alert": (1, U, 0), + "autonav": (1, U, 0), + SID: (2, U, 0), + "_spare": (1, U, 0), } -IRN_LNAV_SUBFRAME_END = {"_parity": (262, 24, U, 0), "_tail": (286, 6, U, 0)} +IRN_LNAV_SUBFRAME_END = {"_parity": (24, U, 0), "_tail": (6, U, 0)} IRN_LNAV_SUBFRAME_1 = { **IRN_LNAV_SUBFRAME_TLM, - WN: (30, 10, U, 0), - "af0": (40, 22, S, P2_N31), - "af1": (62, 16, S, P2_N43), - "af2": (78, 8, S, P2_N55), - "ura": (86, 4, U, 0), - "toc": (90, 16, U, 16), - "tgd": (106, 8, S, P2_N31), - "deltan": (114, 22, S, P2_N41), - "iodec": (136, 8, U, 1), - "_reserved1": (144, 10, U, 0), - "l5flag": (154, 1, U, 0), - "sflag": (155, 1, U, 0), - "cuc": (156, 15, S, P2_N28), - "cus": (171, 15, S, P2_N28), - "cic": (186, 15, S, P2_N28), - "cis": (201, 15, S, P2_N28), - "crc": (216, 15, S, P2_N4), - "crs": (231, 15, S, P2_N4), - "idot": (246, 14, S, P2_N43), - "_reserved2": (260, 2, U, 0), + WN: (10, U, 0), + "af0": (22, S, P2_N31), + "af1": (16, S, P2_N43), + "af2": (8, S, P2_N55), + "ura": (4, U, 0), + TOC: (16, U, 16), + "tgd": (8, S, P2_N31), + "deltan": (22, S, P2_N41), + "iodec": (8, U, 1), + "_reserved1": (10, U, 0), + "l5flag": (1, U, 0), + "sflag": (1, U, 0), + "cuc": (15, S, P2_N28), + "cus": (15, S, P2_N28), + "cic": (15, S, P2_N28), + "cis": (15, S, P2_N28), + "crc": (15, S, P2_N4), + "crs": (15, S, P2_N4), + "idot": (14, S, P2_N43), + "_reserved2": (2, U, 0), **IRN_LNAV_SUBFRAME_END, } IRN_LNAV_SUBFRAME_2 = { **IRN_LNAV_SUBFRAME_TLM, - "m0": (30, 32, S, P2_N31), - "toe": (62, 16, U, 16), - "e": (78, 32, U, P2_N33), - "sqrta": (110, 32, U, P2_N19), - "omega0": (142, 32, S, P2_N31), - "omega": (174, 32, S, P2_N31), - "omegadot": (206, 22, S, P2_N41), - "i0": (228, 32, S, P2_N31), - "_reserved1": (260, 2, U, 0), + "m0": (32, S, P2_N31), + "toe": (16, U, 16), + "e": (32, U, P2_N33), + "sqrta": (32, U, P2_N19), + "omega0": (32, S, P2_N31), + "omega": (32, S, P2_N31), + "omegadot": (22, S, P2_N41), + "i0": (32, S, P2_N31), + "_reserved1": (2, U, 0), **IRN_LNAV_SUBFRAME_END, } IRN_LNAV_SUBFRAME_3_P9 = { **IRN_LNAV_SUBFRAME_TLM, - SPID: (30, 6, U, 0), - "a0utc": (36, 16, S, P2_N35), - "a1utc": (52, 13, S, P2_N51), - "a2utc": (65, 7, S, P2_N68), - "deltatls": (72, 8, S, 1), - "toutc": (80, 16, U, P2_P4), - "wnoutc": (96, 10, U, 1), - "wnlsf": (106, 10, U, 1), - "dn": (116, 4, U, 1), - "deltatlsf": (120, 8, S, 1), - "a0": (128, 16, S, P2_N35), - "a1": (144, 13, S, P2_N51), - "a2": (157, 7, S, P2_N68), - "tot": (164, 16, U, P2_P4), - "wnot": (180, 10, U, 1), - "gnssid": (190, 3, U, 0), - "_reserved1": (193, 63, U, 0), - "prn": (256, 6, U, 0), + SPID: (6, U, 0), + "a0utc": (16, S, P2_N35), + "a1utc": (13, S, P2_N51), + "a2utc": (7, S, P2_N68), + "deltatls": (8, S, 1), + "toutc": (16, U, P2_P4), + "wnoutc": (10, U, 1), + "wnlsf": (10, U, 1), + "dn": (4, U, 1), + "deltatlsf": (8, S, 1), + "a0": (16, S, P2_N35), + "a1": (13, S, P2_N51), + "a2": (7, S, P2_N68), + "tot": (16, U, P2_P4), + "wnot": (10, U, 1), + "gnssid": (3, U, 0), + "_reserved1": (63, U, 0), + "prn": (6, U, 0), **IRN_LNAV_SUBFRAME_END, } IRN_LNAV_SUBFRAME_3_P11 = { **IRN_LNAV_SUBFRAME_TLM, - SPID: (30, 6, U, 0), - "teop": (36, 16, S, P2_P4), - "pmx": (52, 21, S, P2_N20), - "pmxdot": (73, 15, S, P2_N21), - "pmy": (88, 21, S, P2_N20), - "pmydot": (109, 15, S, P2_N21), - "deltaut1": (124, 31, S, P2_N24), - "deltautdot1": (155, 19, S, P2_N25), - "alpha0": (174, 8, S, P2_N30), - "alpha1": (182, 8, S, P2_N27), - "alpha2": (190, 8, S, P2_N24), - "alpha3": (198, 8, S, P2_N24), - "beta0": (206, 8, S, P2_P11), - "beta1": (214, 8, S, P2_P14), - "beta2": (222, 8, S, P2_P16), - "beta3": (230, 8, S, P2_P16), - "_reserved1": (238, 18, U, 0), - "prn": (256, 6, U, 0), + SPID: (6, U, 0), + "teop": (16, S, P2_P4), + "pmx": (21, S, P2_N20), + "pmxdot": (15, S, P2_N21), + "pmy": (21, S, P2_N20), + "pmydot": (15, S, P2_N21), + "deltaut1": (31, S, P2_N24), + "deltautdot1": (19, S, P2_N25), + "alpha0": (8, S, P2_N30), + "alpha1": (8, S, P2_N27), + "alpha2": (8, S, P2_N24), + "alpha3": (8, S, P2_N24), + "beta0": (8, S, P2_P11), + "beta1": (8, S, P2_P14), + "beta2": (8, S, P2_P16), + "beta3": (8, S, P2_P16), + "_reserved1": (18, U, 0), + "prn": (6, U, 0), **IRN_LNAV_SUBFRAME_END, } diff --git a/src/pygnssutils/rawnav_subframes_qzs.py b/src/pygnssutils/rawnav_subframes_qzs.py index a7a2a76..fe63542 100644 --- a/src/pygnssutils/rawnav_subframes_qzs.py +++ b/src/pygnssutils/rawnav_subframes_qzs.py @@ -11,10 +11,21 @@ the payloads of raw NAV subframe messages, via the associated `pygnssutils.RawNav` class defined in `rawnav.py`. +Format of subframe definition dictionary:: + + dict[attribute_name, tuple[length, encoding, scaling] + +where + + - length = attribute length in bits + - encoding = U (unsigned integer) or S (two's complement signed integer) + - scaling = scaling factor (resolution) as integer or float (0 = no scaling) + NB: - MSB and LSB fields MUST be suffixed '_msb' and '_lsb' respectively. - Non-data bits (reserved, parity, non) MUST be prefixed '_'. +- '#' character in almanac attributes will be replaced with relevant almanac svid. - Avoid the following reserved field names: gnss, svid, sigid, subframeacq, epoch Created on 6 Oct 2025 @@ -24,7 +35,7 @@ :license: BSD 3-Clause """ -from pygnssutils.rawnav import PREAMBLE, SID, SPID, TOC, TOW, WN, S, U +from pygnssutils.rawnav import PREAMBLE, SID, SPID, SUBFRAMELENGTH, TOC, TOW, WN, S, U from pygnssutils.rinex_globals import ( CNAV, CNV2, @@ -68,22 +79,22 @@ # LNAV - "1C" (L1C/A, L1C/B) - SAME AS GPS LNAV # ********************************************************************** -# attribute_name: (bit offset, bit length, bit encoding, scaling) QZS_LNAV_TLM = { - PREAMBLE: (0, 8, U, 0), - "tlm": (8, 14, U, 0), - "integrity": (22, 1, U, 0), - "_reserved1": (23, 1, U, 0), - "_parity1": (24, 6, U, 0), + SUBFRAMELENGTH: 300, + PREAMBLE: (8, U, 0), + "tlm": (14, U, 0), + "integrity": (1, U, 0), + "_reserved1": (1, U, 0), + "_parity1": (6, U, 0), } QZS_LNAV_HOW = { - TOW: (30, 17, U, 6), # used to derive epoch, check TOW * 6 = seconds - "alert": (47, 1, U, 0), - "antispoof": (48, 1, U, 0), - SID: (49, 3, U, 0), # subframe id - "_non1": (52, 2, U, 0), - "_parity2": (54, 6, U, 0), + TOW: (17, U, 6), # used to derive epoch, check TOW * 6 = seconds + "alert": (1, U, 0), + "antispoof": (1, U, 0), + SID: (3, U, 0), # subframe id + "_non1": (2, U, 0), + "_parity2": (6, U, 0), } QZS_LNAV_GENERIC = { @@ -92,7 +103,7 @@ # word2 **QZS_LNAV_HOW, # words 3 - 10 - "_word3_10": (60, 240, U, 0), + "_word3_10": (240, U, 0), } QZS_LNAV_SUBFRAME_1 = { @@ -101,44 +112,44 @@ # word2 **QZS_LNAV_HOW, # word3 - WN: (60, 10, U, 1), # used to derive epoch - "l2codes": (70, 2, U, 1), - "ura": (72, 4, U, 1), - "svhealth": (76, 6, U, 1), + WN: (10, U, 1), # used to derive epoch + "l2codes": (2, U, 1), + "ura": (4, U, 1), + "svhealth": (6, U, 1), # "l1health": (76,1,U,0), # "l1cahealth": (77,1,U,0), # "l2health": (78,1,U,0), # "l5health": (79,1,U,0), # "l1chealth": (80,1,U,0), # "l1cbhealth": (81,1,U,0), - "iodc_msb": (82, 2, U, 0), - "_parity3": (84, 6, U, 0), + "iodc_msb": (2, U, 0), + "_parity3": (6, U, 0), # word4 - "l2pdata": (90, 1, U, 1), - "_reserved2": (91, 23, U, 0), - "_parity4": (114, 6, U, 0), + "l2pdata": (1, U, 1), + "_reserved2": (23, U, 0), + "_parity4": (6, U, 0), # word5 - "_reserved3": (120, 24, U, 0), - "_parity5": (144, 6, U, 0), + "_reserved3": (24, U, 0), + "_parity5": (6, U, 0), # word6 - "_reserved4": (150, 24, U, 0), - "_parity6": (174, 6, U, 0), + "_reserved4": (24, U, 0), + "_parity6": (6, U, 0), # word7 - "_reserved5": (180, 16, U, 0), - "tgd": (196, 8, S, P2_N31), - "_parity7": (204, 6, U, 0), + "_reserved5": (16, U, 0), + "tgd": (8, S, P2_N31), + "_parity7": (6, U, 0), # word8 - "iodc_lsb": (210, 8, U, 0), - TOC: (218, 16, U, P2_P4), # must be named "toc" - "_parity8": (234, 6, U, 0), + "iodc_lsb": (8, U, 0), + TOC: (16, U, P2_P4), # must be named "toc" + "_parity8": (6, U, 0), # word9 - "af2": (240, 8, S, P2_N55), - "af1": (248, 16, S, P2_N43), - "_parity9": (264, 6, U, 0), + "af2": (8, S, P2_N55), + "af1": (16, S, P2_N43), + "_parity9": (6, U, 0), # word10 - "af0": (270, 22, S, P2_N31), - "_non2": (292, 2, U, 0), - "_parity10": (294, 6, U, 0), + "af0": (22, S, P2_N31), + "_non2": (2, U, 0), + "_parity10": (6, U, 0), } QZS_LNAV_SUBFRAME_2 = { @@ -147,36 +158,36 @@ # word2 **QZS_LNAV_HOW, # word3 - "iode": (60, 8, U, 0), - "crs": (68, 16, S, P2_N5), - "_parity3": (84, 6, U, 0), + "iode": (8, U, 0), + "crs": (16, S, P2_N5), + "_parity3": (6, U, 0), # word4 - "deltan": (90, 16, S, P2_N43), - "m0_msb": (106, 8, S, P2_N31), - "_parity4": (114, 6, U, 0), + "deltan": (16, S, P2_N43), + "m0_msb": (8, S, P2_N31), + "_parity4": (6, U, 0), # word5 - "m0_lsb": (120, 24, S, P2_N31), - "_parity5": (144, 6, U, 0), + "m0_lsb": (24, S, P2_N31), + "_parity5": (6, U, 0), # word6 - "cuc": (150, 16, S, P2_N29), - "e_msb": (166, 8, U, 0), - "_parity6": (174, 6, U, 0), + "cuc": (16, S, P2_N29), + "e_msb": (8, U, 0), + "_parity6": (6, U, 0), # word7 - "e_lsb": (180, 24, U, P2_N33), - "_parity7": (204, 6, U, 0), + "e_lsb": (24, U, P2_N33), + "_parity7": (6, U, 0), # word8 - "cus": (210, 16, S, P2_N29), - "sqrta_msb": (226, 8, U, P2_N19), - "_parity8": (234, 6, U, 0), + "cus": (16, S, P2_N29), + "sqrta_msb": (8, U, P2_N19), + "_parity8": (6, U, 0), # word9 - "sqrta_lsb": (240, 24, U, P2_N19), - "_parity9": (264, 6, U, 0), + "sqrta_lsb": (24, U, P2_N19), + "_parity9": (6, U, 0), # word10 - "toe": (270, 16, U, P2_P4), - "fit": (286, 1, U, 0), - "aodo": (287, 5, U, 900), - "_non2": (292, 2, U, 0), - "_parity10": (294, 6, U, 0), + "toe": (16, U, P2_P4), + "fit": (1, U, 0), + "aodo": (5, U, 900), + "_non2": (2, U, 0), + "_parity10": (6, U, 0), } QZS_LNAV_SUBFRAME_3 = { @@ -185,34 +196,34 @@ # word2 **QZS_LNAV_HOW, # word3 - "cic": (60, 16, S, P2_N29), - "omega0_msb": (76, 8, S, P2_N31), - "_parity3": (84, 6, U, 0), + "cic": (16, S, P2_N29), + "omega0_msb": (8, S, P2_N31), + "_parity3": (6, U, 0), # word4 - "omega0_lsb": (90, 24, S, P2_N31), - "_parity4": (114, 6, U, 0), + "omega0_lsb": (24, S, P2_N31), + "_parity4": (6, U, 0), # word5 - "cis": (120, 16, S, P2_N29), - "i0_msb": (136, 8, S, P2_N31), - "_parity5": (144, 6, U, 0), + "cis": (16, S, P2_N29), + "i0_msb": (8, S, P2_N31), + "_parity5": (6, U, 0), # word6 - "i0_lsb": (150, 24, S, P2_N31), - "_parity6": (174, 6, U, 0), + "i0_lsb": (24, S, P2_N31), + "_parity6": (6, U, 0), # word7 - "crc": (180, 16, S, P2_N5), - "omega_msb": (196, 8, S, P2_N31), - "_parity7": (204, 6, U, 0), + "crc": (16, S, P2_N5), + "omega_msb": (8, S, P2_N31), + "_parity7": (6, U, 0), # word8 - "omega_lsb": (210, 24, S, P2_N31), - "_parity8": (234, 6, U, 0), + "omega_lsb": (24, S, P2_N31), + "_parity8": (6, U, 0), # word9 - "omegadot": (240, 24, S, P2_N43), - "_parity9": (264, 6, U, 0), + "omegadot": (24, S, P2_N43), + "_parity9": (6, U, 0), # word10 - "iode": (270, 8, U, 0), - "idot": (278, 14, S, P2_N43), - "_non2": (292, 2, U, 0), - "_parity10": (294, 6, U, 0), + "iode": (8, U, 0), + "idot": (14, S, P2_N43), + "_non2": (2, U, 0), + "_parity10": (6, U, 0), } QZS_LNAV_SUBFRAME_4_P56 = { @@ -221,42 +232,42 @@ # word2 **QZS_LNAV_HOW, # word3 - "dataid": (60, 2, U, 0), # = 3 - SPID: (62, 6, U, 0), # = 56 - "alpha0": (68, 8, S, P2_N30), - "alpha1": (76, 8, S, P2_N27), - "_parity3": (84, 6, U, 0), + "dataid": (2, U, 0), # = 3 + SPID: (6, U, 0), # = 56 + "alpha0": (8, S, P2_N30), + "alpha1": (8, S, P2_N27), + "_parity3": (6, U, 0), # word4 - "alpha2": (90, 8, S, P2_N24), - "alpha3": (98, 8, S, P2_N24), - "beta0": (106, 8, S, P2_P11), - "_parity4": (114, 6, U, 0), + "alpha2": (8, S, P2_N24), + "alpha3": (8, S, P2_N24), + "beta0": (8, S, P2_P11), + "_parity4": (6, U, 0), # word5 - "beta1": (120, 8, S, P2_P14), - "beta2": (128, 8, S, P2_P16), - "beta3": (136, 8, S, P2_P16), - "_parity5": (144, 6, U, 0), + "beta1": (8, S, P2_P14), + "beta2": (8, S, P2_P16), + "beta3": (8, S, P2_P16), + "_parity5": (6, U, 0), # word6 - "a1": (150, 24, S, P2_N30), - "_parity6": (174, 6, U, 0), + "a1": (24, S, P2_N30), + "_parity6": (6, U, 0), # word7 - "a0_msb": (180, 24, S, P2_N50), - "_parity7": (204, 6, U, 0), + "a0_msb": (24, S, P2_N50), + "_parity7": (6, U, 0), # word8 - "a0_lsb": (210, 8, S, P2_N50), - "tot": (218, 8, U, P2_P12), - "wnt": (226, 8, U, 0), - "_parity8": (234, 6, U, 0), + "a0_lsb": (8, S, P2_N50), + "tot": (8, U, P2_P12), + "wnt": (8, U, 0), + "_parity8": (6, U, 0), # word9 - "deltatls": (240, 8, S, 1), - "wnlsf": (248, 8, U, 0), - "dn": (256, 8, U, 0), - "_parity9": (264, 6, U, 0), + "deltatls": (8, S, 1), + "wnlsf": (8, U, 0), + "dn": (8, U, 0), + "_parity9": (6, U, 0), # word10 - "deltatlsf": (270, 8, S, 1), - "_reserved2": (278, 14, U, 0), - "_non2": (292, 2, U, 0), - "_parity10": (294, 6, U, 0), + "deltatlsf": (8, S, 1), + "_reserved2": (14, U, 0), + "_non2": (2, U, 0), + "_parity10": (6, U, 0), } QZS_LNAV_SUBFRAME_5_P56 = QZS_LNAV_SUBFRAME_4_P56 @@ -266,121 +277,121 @@ # ********************************************************************** QZS_CNAV_TLM = { - # VALPREAMBLE: 0b10001011, # optional, used to validate preamble value - PREAMBLE: (0, 8, U, 0), - "prn": (8, 6, U, 0), - SID: (14, 6, U, 0), - TOW: (20, 17, U, 6), # used to derive epoch, check TOW * 6 = seconds - "alert": (37, 1, U, 0), + SUBFRAMELENGTH: 300, + PREAMBLE: (8, U, 0), + "prn": (6, U, 0), + SID: (6, U, 0), + TOW: (17, U, 6), # used to derive epoch, check TOW * 6 = seconds + "alert": (1, U, 0), } QZS_CNAV_CLOCK = { - "top": (38, 11, U, 300), - "uraned0": (49, 5, S, 0), - "uraned1": (54, 3, U, 0), - "uraned2": (57, 3, U, 0), - TOC: (60, 11, U, 300), - "af0n": (71, 26, S, P2_N35), - "af1n_msb": (97, 3, S, P2_N48), - "af1n_lsb": (100, 17, S, P2_N48), - "af2n": (117, 10, S, P2_N60), + "top": (11, U, 300), + "uraned0": (5, S, 0), + "uraned1": (3, U, 0), + "uraned2": (3, U, 0), + TOC: (11, U, 300), + "af0n": (26, S, P2_N35), + "af1n_msb": (3, S, P2_N48), + "af1n_lsb": (17, S, P2_N48), + "af2n": (10, S, P2_N60), } QZS_CNAV_PARITY = { - "_parity": (276, 24, U, 0), + "_parity": (24, U, 0), } QZS_CNAV_SUBFRAME_10 = { **QZS_CNAV_TLM, - WN: (38, 13, U, 0), # used to derive epoch - "l1health": (51, 1, U, 0), - "l2health": (52, 1, U, 0), - "l5health": (53, 1, U, 0), - "top": (54, 11, U, 300), - "uraed": (65, 5, U, 0), - "toe": (70, 11, U, 300), - "deltaa_msb": (81, 19, S, P2_N9), - "deltaa_lsb": (100, 7, S, P2_N9), - "adot": (107, 25, S, P2_N21), - "deltan0": (132, 17, S, P2_N44), - "deltan0dot": (149, 23, S, P2_N57), - "m0_msb": (172, 28, S, P2_N32), - "m0_lsb": (200, 5, S, P2_N32), - "e": (205, 33, U, P2_N34), - "omega": (238, 33, S, P2_N32), - "integrity": (271, 1, U, 0), - "l2phase": (272, 1, U, 0), - "_reserved1": (273, 3, U, 0), + WN: (13, U, 0), # used to derive epoch + "l1health": (1, U, 0), + "l2health": (1, U, 0), + "l5health": (1, U, 0), + "top": (11, U, 300), + "uraed": (5, U, 0), + "toe": (11, U, 300), + "deltaa_msb": (19, S, P2_N9), + "deltaa_lsb": (7, S, P2_N9), + "adot": (25, S, P2_N21), + "deltan0": (17, S, P2_N44), + "deltan0dot": (23, S, P2_N57), + "m0_msb": (28, S, P2_N32), + "m0_lsb": (5, S, P2_N32), + "e": (33, U, P2_N34), + "omega": (33, S, P2_N32), + "integrity": (1, U, 0), + "l2phase": (1, U, 0), + "_reserved1": (3, U, 0), **QZS_CNAV_PARITY, } # Ephemeris 1 QZS_CNAV_SUBFRAME_11 = { **QZS_CNAV_TLM, - "toe": (38, 11, U, 300), - "omega0": (49, 33, S, P2_N32), - "i0_msb": (82, 18, S, P2_N32), - "i0_lsb": (100, 15, S, P2_N32), - "deltaomegadot": (115, 17, S, P2_N44), - "idot": (132, 15, S, P2_N44), - "cis": (147, 16, S, P2_N30), - "cic": (163, 16, S, P2_N30), - "crs_msb": (179, 21, S, P2_N8), - "crs_lsb": (200, 3, S, P2_N8), - "crc": (203, 24, S, P2_N8), - "cus": (227, 21, S, P2_N30), - "cuc": (248, 21, S, P2_N30), - "_reserved1": (269, 7, U, 0), + "toe": (11, U, 300), + "omega0": (33, S, P2_N32), + "i0_msb": (18, S, P2_N32), + "i0_lsb": (15, S, P2_N32), + "deltaomegadot": (17, S, P2_N44), + "idot": (15, S, P2_N44), + "cis": (16, S, P2_N30), + "cic": (16, S, P2_N30), + "crs_msb": (21, S, P2_N8), + "crs_lsb": (3, S, P2_N8), + "crc": (24, S, P2_N8), + "cus": (21, S, P2_N30), + "cuc": (21, S, P2_N30), + "_reserved1": (7, U, 0), **QZS_CNAV_PARITY, } # Ephemeris 2 QZS_CNAV_SUBFRAME_30 = { **QZS_CNAV_TLM, **QZS_CNAV_CLOCK, - "tgd": (127, 13, S, P2_N35), - "iscl1ca": (140, 13, S, P2_N35), - "iscl2c": (153, 13, S, P2_N35), - "iscl5i5": (166, 13, S, P2_N35), - "iscl5q5": (179, 13, S, P2_N35), - "alpha0": (192, 8, U, P2_N30), # where is scaling defined for CNAV? - "alpha1": (200, 8, U, P2_N27), # have assumed same as LNAV - "alpha2": (208, 8, U, P2_N24), - "alpha3": (216, 8, U, P2_N24), - "beta0": (224, 8, U, P2_P11), - "beta1": (232, 8, U, P2_P14), - "beta2": (240, 8, U, P2_P16), - "beta3": (248, 8, U, P2_P16), - "wno": (256, 8, U, 0), - "_reserved1": (264, 12, U, 0), + "tgd": (13, S, P2_N35), + "iscl1ca": (13, S, P2_N35), + "iscl2c": (13, S, P2_N35), + "iscl5i5": (13, S, P2_N35), + "iscl5q5": (13, S, P2_N35), + "alpha0": (8, U, P2_N30), # where is scaling defined for CNAV? + "alpha1": (8, U, P2_N27), # have assumed same as LNAV + "alpha2": (8, U, P2_N24), + "alpha3": (8, U, P2_N24), + "beta0": (8, U, P2_P11), + "beta1": (8, U, P2_P14), + "beta2": (8, U, P2_P16), + "beta3": (8, U, P2_P16), + "wno": (8, U, 0), + "_reserved1": (12, U, 0), **QZS_CNAV_PARITY, } # Clock, IONO & Group Delay QZS_CNAV_SUBFRAME_32 = { **QZS_CNAV_TLM, **QZS_CNAV_CLOCK, - "teop": (127, 16, U, P2_P4), - "pmx": (143, 21, S, P2_N20), - "pmxdot": (164, 15, S, P2_N21), - "pmy": (179, 21, S, P2_N20), - "pmydot": (200, 15, S, P2_N21), - "deltautgps": (215, 31, S, P2_N23), - "deltautgpsdot": (246, 19, S, P2_N25), - "_reserved1": (265, 11, U, 0), + "teop": (16, U, P2_P4), + "pmx": (21, S, P2_N20), + "pmxdot": (15, S, P2_N21), + "pmy": (21, S, P2_N20), + "pmydot": (15, S, P2_N21), + "deltautgps": (31, S, P2_N23), + "deltautgpsdot": (19, S, P2_N25), + "_reserved1": (11, U, 0), **QZS_CNAV_PARITY, } # Clock & EOP QZS_CNAV_SUBFRAME_33 = { **QZS_CNAV_TLM, **QZS_CNAV_CLOCK, - "a0": (127, 16, S, P2_N35), - "a1": (143, 13, S, P2_N51), - "a2": (156, 7, S, P2_N68), - "deltatls": (163, 8, S, 1), - "tot": (171, 16, U, P2_P4), - "wnot": (187, 13, U, 1), - "wnlsf": (200, 13, U, 1), - "dn": (213, 4, U, 1), - "deltatlsf": (217, 8, S, 1), - "_reserved1": (225, 51, U, 0), + "a0": (16, S, P2_N35), + "a1": (13, S, P2_N51), + "a2": (7, S, P2_N68), + "deltatls": (8, S, 1), + "tot": (16, U, P2_P4), + "wnot": (13, U, 1), + "wnlsf": (13, U, 1), + "dn": (4, U, 1), + "deltatlsf": (8, S, 1), + "_reserved1": (51, U, 0), **QZS_CNAV_PARITY, } # Clock & UTC @@ -388,98 +399,103 @@ # CNV2 - "1S" (L1S) # ********************************************************************** -QZS_CNV2_SUBFRAME_1 = {"toi": (0, 9, U, 18)} +QZS_CNV2_SUBFRAME_1 = {"toi": (9, U, 18)} QZS_CNV2_SUBFRAME_2 = { - WN: (0, 13, U, 1), - "itow": (13, 8, U, 1), - "top": (21, 11, U, 300), - "l1chealth": (32, 1, U, 1), - "uraed": (33, 5, S, 1), - "toe": (38, 11, U, 300), - "deltaa": (49, 26, S, P2_N9), - "adot": (75, 25, S, P2_N21), - "deltan0": (100, 17, S, P2_N44), - "deltan0dot": (117, 23, S, P2_N57), - "m0_msb": (140, 10, S, P2_N32), - "m0_lsb": (150, 23, S, P2_N32), - "e": (173, 33, S, P2_N34), - "omega": (206, 33, S, P2_N32), - "omega0": (239, 33, S, P2_N32), - "i0_msb": (272, 28, S, P2_N32), - "i0_lsb": (300, 5, S, P2_N32), - "deltanomegadot": (305, 17, S, P2_N44), - "idot": (322, 15, S, P2_N44), - "cis": (337, 16, S, P2_N30), - "cic": (353, 16, S, P2_N30), - "crs": (369, 24, S, P2_N8), - "crc": (393, 24, S, P2_N8), - "cus": (417, 21, S, P2_N30), - "cuc_msb": (438, 12, S, P2_N30), - "cuc_lsb": (450, 9, S, P2_N30), - "uraned0": (459, 5, S, 1), - "uraned1": (464, 3, U, 1), - "uraned2": (467, 3, U, 1), - "af0": (470, 26, S, P2_N35), - "af1": (496, 20, S, P2_N48), - "af2": (516, 10, S, P2_N60), - "tgd": (526, 13, S, P2_N35), - "iscl1gp": (539, 13, S, P2_N35), - "iscl1cd": (552, 13, S, P2_N35), - "isf": (565, 1, S, 0), - "wnop": (566, 8, S, 1), - "_reserved": (574, 2, U, 0), - "_crc": (576, 24, U, 0), + SUBFRAMELENGTH: 600, + WN: (13, U, 1), + "itow": (8, U, 1), + "top": (11, U, 300), + "l1chealth": (1, U, 1), + "uraed": (5, S, 1), + "toe": (11, U, 300), + "deltaa": (26, S, P2_N9), + "adot": (25, S, P2_N21), + "deltan0": (17, S, P2_N44), + "deltan0dot": (23, S, P2_N57), + "m0_msb": (10, S, P2_N32), + "m0_lsb": (23, S, P2_N32), + "e": (33, S, P2_N34), + "omega": (33, S, P2_N32), + "omega0": (33, S, P2_N32), + "i0_msb": (28, S, P2_N32), + "i0_lsb": (5, S, P2_N32), + "deltanomegadot": (17, S, P2_N44), + "idot": (15, S, P2_N44), + "cis": (16, S, P2_N30), + "cic": (16, S, P2_N30), + "crs": (24, S, P2_N8), + "crc": (24, S, P2_N8), + "cus": (21, S, P2_N30), + "cuc_msb": (12, S, P2_N30), + "cuc_lsb": (9, S, P2_N30), + "uraned0": (5, S, 1), + "uraned1": (3, U, 1), + "uraned2": (3, U, 1), + "af0": (26, S, P2_N35), + "af1": (20, S, P2_N48), + "af2": (10, S, P2_N60), + "tgd": (13, S, P2_N35), + "iscl1gp": (13, S, P2_N35), + "iscl1cd": (13, S, P2_N35), + "isf": (1, S, 0), + "wnop": (8, S, 1), + "_reserved": (2, U, 0), + "_crc": (24, U, 0), } # emphemeris +QZS_CNV2_SUBFRAME_3_TLM = { + SUBFRAMELENGTH: 274, + "prn": (8, U, 0), + SPID: (6, U, 0), +} + QZS_CNV2_SUBFRAME_3_P1 = { - "prn": (0, 8, U, 0), - SPID: (8, 6, U, 0), # = 1 - "a0": (14, 16, S, P2_N35), - "a1": (30, 13, S, P2_N51), - "a2": (43, 7, S, P2_N68), - "deltatls": (50, 8, S, 1), - "tot": (58, 16, U, P2_P4), - "wnot": (74, 13, U, 1), - "wnlsf": (87, 13, U, 1), - "dn": (100, 4, U, 1), - "deltatlsf": (104, 8, S, 1), - "alpha0": (112, 8, S, P2_N30), - "alpha1": (120, 8, S, P2_N27), - "alpha2": (128, 8, S, P2_N24), - "alpha3": (136, 8, S, P2_N24), - "beta0_msb": (144, 6, S, P2_P11), - "beta0_lsb": (150, 2, S, P2_P11), - "beta1": (152, 8, S, P2_P14), - "beta2": (160, 8, S, P2_P16), - "beta3": (168, 8, S, P2_P16), - "iscl1": (176, 13, S, P2_N35), - "lscl2": (189, 13, S, P2_N35), - "iscl5i5": (202, 13, S, P2_N35), - "iscl5q5": (215, 13, S, P2_N35), - "_reserved1": (228, 22, U, 0), - "_crc": (250, 24, U, 0), + **QZS_CNV2_SUBFRAME_3_TLM, + "a0": (16, S, P2_N35), + "a1": (13, S, P2_N51), + "a2": (7, S, P2_N68), + "deltatls": (8, S, 1), + "tot": (16, U, P2_P4), + "wnot": (13, U, 1), + "wnlsf": (13, U, 1), + "dn": (4, U, 1), + "deltatlsf": (8, S, 1), + "alpha0": (8, S, P2_N30), + "alpha1": (8, S, P2_N27), + "alpha2": (8, S, P2_N24), + "alpha3": (8, S, P2_N24), + "beta0_msb": (6, S, P2_P11), + "beta0_lsb": (2, S, P2_P11), + "beta1": (8, S, P2_P14), + "beta2": (8, S, P2_P16), + "beta3": (8, S, P2_P16), + "iscl1": (13, S, P2_N35), + "lscl2": (13, S, P2_N35), + "iscl5i5": (13, S, P2_N35), + "iscl5q5": (13, S, P2_N35), + "_reserved1": (22, U, 0), + "_crc": (24, U, 0), } # UTC and iono parameters (wide area) QZS_CNV2_SUBFRAME_3_P2 = { - "prn": (0, 8, U, 0), - SPID: (8, 6, U, 0), # = 2 - "gnssid": (14, 3, U, 0), - "tggto": (17, 16, U, P2_P4), - "wnggto": (33, 13, U, 1), - "a0ggto": (46, 16, S, P2_N35), - "a1ggto": (62, 13, S, P2_N51), - "a2ggto": (75, 7, S, P2_N68), - "teop": (82, 16, U, P2_P4), - "pmx": (98, 21, S, P2_N20), - "pmxdot": (119, 15, S, P2_N21), - "pmy_msb": (134, 16, S, P2_N20), - "pmy_lsb": (150, 5, S, P2_N20), - "pmydot": (155, 15, S, P2_N21), - "deltaut1": (170, 31, S, P2_N24), - "deltaut1dot": (201, 19, S, P2_N25), - "_reserved": (220, 30, U, 0), - "_crc": (250, 24, U, 0), + **QZS_CNV2_SUBFRAME_3_TLM, + "gnssid": (3, U, 0), + "tggto": (16, U, P2_P4), + "wnggto": (13, U, 1), + "a0ggto": (16, S, P2_N35), + "a1ggto": (13, S, P2_N51), + "a2ggto": (7, S, P2_N68), + "teop": (16, U, P2_P4), + "pmx": (21, S, P2_N20), + "pmxdot": (15, S, P2_N21), + "pmy_msb": (16, S, P2_N20), + "pmy_lsb": (5, S, P2_N20), + "pmydot": (15, S, P2_N21), + "deltaut1": (31, S, P2_N24), + "deltaut1dot": (19, S, P2_N25), + "_reserved": (30, U, 0), + "_crc": (24, U, 0), } # GGTO and EOP QZS_CNV2_SUBFRAME_3_P61 = QZS_CNV2_SUBFRAME_3_P1 # UTC and IONO parameters (Japan area) diff --git a/src/pygnssutils/rawnav_subframes_sba.py b/src/pygnssutils/rawnav_subframes_sba.py index e8a1e64..3526f33 100644 --- a/src/pygnssutils/rawnav_subframes_sba.py +++ b/src/pygnssutils/rawnav_subframes_sba.py @@ -14,11 +14,22 @@ the payloads of raw NAV subframe messages, via the associated `pygnssutils.RawNav` class defined in `rawnav.py`. +Format of subframe definition dictionary:: + + dict[attribute_name, tuple[length, encoding, scaling] + +where + + - length = attribute length in bits + - encoding = U (unsigned integer) or S (two's complement signed integer) + - scaling = scaling factor (resolution) as integer or float (0 = no scaling) + NB: - MSB and LSB fields MUST be suffixed '_msb' and '_lsb' respectively. - Non-data bits (reserved, parity, non) MUST be prefixed '_'. -- Avoid the following reserved field names: gnss, svid, sigid, subframeacq, epoch +- '#' character in almanac attributes will be replaced with relevant almanac svid. +- Avoid the following reserved field names: gnss, svid, sigid, subframeacq, epoch. Created on 6 Oct 2025 @@ -27,11 +38,14 @@ :license: BSD 3-Clause """ -from pygnssutils.rawnav import PREAMBLE, SID, TOW, WN, S, U +from pygnssutils.rawnav import PREAMBLE, SID, SUBFRAMELENGTH, TOC, TOW, WN, S, U from pygnssutils.rinex_globals import ( L1CA, + P2_N3, + P2_N11, P2_N30, P2_N31, + P2_N39, P2_N40, P2_N50, P2_P12, @@ -46,99 +60,337 @@ # SBAS ICD (RTCA-DO-229D) refers to subframes as 'Message Types' (MT) SBA_L1CA_MT_TLM = { - PREAMBLE: (0, 8, U, 0), - SID: (8, 6, U, 0), # MT record number + SUBFRAMELENGTH: 250, + PREAMBLE: (8, U, 0), + SID: (6, U, 0), # MT record number } SBA_L1CA_MT_END = { - "_parity": (226, 24, U, 0), + "_parity": (24, U, 0), } -# attribute_name: (bit offset, bit length, bit encoding, scaling) +SBA_L1CA_MT_1 = { + **SBA_L1CA_MT_TLM, + "prnmask": (210, U, 0), + "iodp": (2, U, 0), + **SBA_L1CA_MT_END, +} # PRN mask + +SBA_L1CA_MT_2 = { + **SBA_L1CA_MT_TLM, + "iodf": (2, U, 1), + "iodp": (2, U, 1), + "prc_grp": ( # repeating group x 13 + 13, + { + "prc": (12, S, P2_N3), + }, + ), + "udrei_grp": ( # repeating group x 13 + 13, + { + "udrei": (4, U, 1), + }, + ), + **SBA_L1CA_MT_END, +} # fast corrections +SBA_L1CA_MT_0 = SBA_L1CA_MT_2 # while testing, otherwise all zeros +SBA_L1CA_MT_3 = SBA_L1CA_MT_2 +SBA_L1CA_MT_4 = SBA_L1CA_MT_2 +SBA_L1CA_MT_5 = SBA_L1CA_MT_2 + +SBA_L1CA_MT_6 = { + **SBA_L1CA_MT_TLM, + "iodf2": (2, U, 1), + "iodf3": (2, U, 1), + "iodf4": (2, U, 1), + "iodf5": (2, U, 1), + "udrei_grp": ( # repeating group x 51 + 51, + { + "udrei": (4, U, 1), + }, + ), + **SBA_L1CA_MT_END, +} # integrity + +SBA_L1CA_MT_7 = { + **SBA_L1CA_MT_TLM, + "tl": (4, U, 1), + "iodp": (2, U, 1), + "_spare": (2, U, 0), + "udre_grp": ( # repeating group x 51 + 51, + { + "udre": (4, U, 1), + }, + ), + **SBA_L1CA_MT_END, +} # fast correction degradation factors + SBA_L1CA_MT_9 = { **SBA_L1CA_MT_TLM, - "iodn": (14, 8, U, 0), # marked as reserved - "t0": (22, 13, U, 16), - "ura": (35, 4, U, 0), # 15 = do not use for ranging - "xpos": (39, 30, S, 0.08), - "ypos": (69, 30, S, 0.08), - "zpos": (99, 25, S, 0.4), - "xdot": (124, 17, S, 0.000625), - "ydot": (141, 17, S, 0.000625), - "zdot": (158, 18, S, 0.004), - "xdot2": (176, 10, S, 0.0000125), - "ydot2": (186, 10, S, 0.0000125), - "zdot2": (196, 10, S, 0.0000625), - "agf0": (206, 12, S, P2_N31), - "agf1": (218, 8, S, P2_N40), + "iodn": (8, U, 0), # marked as reserved + "t0": (13, U, 16), + "ura": (4, U, 0), # 15 = do not use for ranging + "xpos": (30, S, 0.08), + "ypos": (30, S, 0.08), + "zpos": (25, S, 0.4), + "xdot": (17, S, 0.000625), + "ydot": (17, S, 0.000625), + "zdot": (18, S, 0.004), + "xdot2": (10, S, 0.0000125), + "ydot2": (10, S, 0.0000125), + "zdot2": (10, S, 0.0000625), + "agf0": (12, S, P2_N31), + "agf1": (8, S, P2_N40), **SBA_L1CA_MT_END, -} +} # GEO navigation + +SBA_L1CA_MT_10 = { + **SBA_L1CA_MT_TLM, + "brrc": (10, U, 0.002), + "cltc_lsb": (10, U, 0.002), + "cltc_v1": (10, U, 0.00005), + "iltc_v1": (9, U, 1), + "cltc_v0": (10, U, 0.002), + "iltc_v0": (9, U, 1), + "cgeo_lsb": (10, U, 0.0005), + "cgeo_v": (10, U, 0.00005), + "igeo": (9, U, 1), + "cer": (6, U, 0.5), + "ciono_step": (10, U, 0.001), + "iiono": (9, U, 1), + "ciono_ramp": (10, U, 0.000005), + "rssudre": (1, U, 0), + "rssiono": (1, U, 0), + "ccovariance": (7, U, 0.1), + "_spare": (81, U, 0), + **SBA_L1CA_MT_END, +} # degradation factors SBA_L1CA_MT_12 = { **SBA_L1CA_MT_TLM, - "a1": (14, 24, S, P2_N50), - "a0": (38, 32, S, P2_N30), - "toc": (70, 8, U, P2_P12), # tot - "wnt": (78, 8, U, 1), - "deltatls": (86, 8, S, 1), - "wnlsf": (94, 8, U, 1), - "dn": (102, 8, U, 1), - "deltatlsf": (110, 8, S, 1), - "utcid": (118, 3, U, 1), # see UTCID lookup - TOW: (121, 20, U, 1), - WN: (141, 10, U, 1), - "gloind": (151, 1, U, 0), - "deltatglo": (152, 74, U, 0), + "a1": (24, S, P2_N50), + "a0": (32, S, P2_N30), + TOC: (8, U, P2_P12), # tot + "wnt": (8, U, 1), + "deltatls": (8, S, 1), + "wnlsf": (8, U, 1), + "dn": (8, U, 1), + "deltatlsf": (8, S, 1), + "utcid": (3, U, 1), # see UTCID lookup + TOW: (20, U, 1), + WN: (10, U, 1), + "gloind": (1, U, 0), + "deltatglo": (74, U, 0), **SBA_L1CA_MT_END, -} +} # Network time offset SBA_L1CA_MT_17 = { **SBA_L1CA_MT_TLM, - "dataid_01": (14, 2, U, 0), - "prn_01": (16, 8, U, 0), - "svhealth_01": (24, 8, U, 0), - # Bit 0 (LSB) Ranging On (0), Off (1); 1 = do not use for ranging - # Bit 1 Corrections On (0), Off (1); 1 = do not use for corrections - # Bit 2 Broadcast Integrity On (0), Off (1) - # Bits 3 Reserved - # Bits 4-7 Service Provider ID - "xg_01": (32, 15, S, 2600), - "yg_01": (47, 15, S, 2600), - "zg_01": (62, 9, S, 26000), - "xgdot_01": (71, 3, S, 10), - "ygdot_01": (74, 3, S, 10), - "zgdot_01": (77, 4, S, 60), - "dataid_02": (81, 2, U, 0), - "prn_02": (83, 8, U, 0), - "svhealth_02": (91, 8, U, 0), - "xg_02": (99, 15, S, 2600), - "yg_02": (114, 15, S, 2600), - "zg_02": (129, 9, S, 26000), - "xgdot_02": (138, 3, S, 10), - "ygdot_02": (141, 3, S, 10), - "zgdot_02": (144, 4, S, 60), - "dataid_03": (148, 2, U, 0), - "prn_03": (150, 8, U, 0), - "svhealth_03": (158, 8, U, 0), - "xg_03": (166, 15, S, 2600), - "yg_03": (181, 15, S, 2600), - "zg_03": (196, 9, S, 26000), - "xgdot_03": (205, 3, S, 10), - "ygdot_03": (208, 3, S, 10), - "zgdot_03": (211, 4, S, 60), - "t0": (215, 11, S, 64), + "prn_grp": ( # repeating group x 3 + 3, + { + "dataid": (2, U, 0), + "prn": (8, U, 0), + "svhealth": (8, U, 0), + # Bit 0 (LSB) Ranging On (0), Off (1); 1 = do not use for ranging + # Bit 1 Corrections On (0), Off (1); 1 = do not use for corrections + # Bit 2 Broadcast Integrity On (0), Off (1) + # Bits 3 Reserved + # Bits 4-7 Service Provider ID + "xg": (15, S, 2600), + "yg": (15, S, 2600), + "zg": (9, S, 26000), + "xgdot": (3, S, 10), + "ygdot": (3, S, 10), + "zgdot": (4, S, 60), + }, + ), + "t0": (11, S, 64), **SBA_L1CA_MT_END, -} +} # GEO almanac + +SBA_L1CA_MT_18 = { + **SBA_L1CA_MT_TLM, + "numbands": (4, U, 1), + "bandno": (4, U, 1), + "iodi": (2, U, 1), + "igpmask": (201, U, 1), + "_spare": (1, U, 0), + **SBA_L1CA_MT_END, +} # ionospheric grid mask + +SBA_L1CA_MT_25_V0 = { + "prn_grp": ( # repeating group x 2 + 2, + { + "prnmask": (6, U, 1), + "iod": (8, S, 1), + "deltax": (9, S, P2_N3), + "deltay": (9, S, P2_N3), + "deltaz": (9, S, P2_N3), + "deltaaf0": (10, S, P2_N31), + }, + ), + "iodpv0": (2, U, 1), + "_spare2": (1, U, 0), +} # 105 bits - Position and Clock Offset Corrections Only + +SBA_L1CA_MT_25_V1 = { + "prn_grp": ( # nominal repeating group x 1 + 1, + { + "prnmask": (6, U, 1), + "iod": (8, S, 1), + "deltax": (11, S, P2_N3), + "deltay": (11, S, P2_N3), + "deltaz": (11, S, P2_N3), + "deltaaf0": (11, S, P2_N31), + "deltaxdot": (8, S, P2_N11), + "deltaydot": (8, S, P2_N11), + "deltazdot": (8, S, P2_N11), + "deltaaf1": (8, S, P2_N39), + }, + ), + "t0": (13, U, 16), + "iodpv1": (2, U, 0), +} # 105 bits - Velocity and Clock Drift Corrections Included + +SBA_L1CA_MT_24 = { + **SBA_L1CA_MT_TLM, + "prn_grp": ( # repeating group x 6 + 6, + { + "prc": (12, S, P2_N3), + }, + ), + "udrei_grp": ( # repeating group x 6 + 6, + { + "udrei": (4, U, 1), + }, + ), + "iodpfc": (2, U, 0), + "blockid": (2, U, 0), + "iodffc": (2, U, 0), + "_spare1": (4, U, 0), + "velcode": (1, U, 1), # velocity code = 0 or 1 + "v0_alt": ( # if velcode = 0 + ("velcode", 0), + {**SBA_L1CA_MT_25_V0}, + ), + "v1_alt": ( # if velcode = 1 + ("velcode", 1), + {**SBA_L1CA_MT_25_V1}, + ), + **SBA_L1CA_MT_END, +} # mixed fast correction / long term satellite error + +SBA_L1CA_MT_25 = { + **SBA_L1CA_MT_TLM, + "velcode1": (1, U, 1), # velocity code = 0 or 1 + "v10_alt": ( # if velcode1 = 0 + ("velcode1", 0), + {**SBA_L1CA_MT_25_V0}, + ), + "v11_alt": ( # if velcode1 = 1 + ("velcode1", 1), + {**SBA_L1CA_MT_25_V1}, + ), + "velcode2": (1, U, 1), # velocity code = 0 or 1 + "v20_alt": ( # if velcode2 = 0 + ("velcode2", 0), + {**SBA_L1CA_MT_25_V0}, + ), + "v21_alt": ( # if velcode2 = 1 + ("velcode2", 1), + {**SBA_L1CA_MT_25_V1}, + ), + **SBA_L1CA_MT_END, +} # long term satellite error correction + +SBA_L1CA_MT_26 = { + **SBA_L1CA_MT_TLM, + "bandno": (4, U, 1), + "blockid": (4, U, 1), + "igp_grp": ( # repeating group x 15 + 15, + { + "igpvde": (9, U, P2_N3), + "givei": (4, U, 1), + }, + ), + "iodi": (2, U, 1), + "_spare": (7, U, 0), + **SBA_L1CA_MT_END, +} # ionospheric delay model parameters + +SBA_L1CA_MT_27 = { + **SBA_L1CA_MT_TLM, + "iods": (3, U, 1), + "nummsg": (3, U, 1), + "msgno": (3, U, 1), + "numregions": (3, U, 1), + "ptycode": (2, U, 1), + "deltaudre_in": (4, U, 1), + "deltaudre_out": (4, U, 1), + "shape_grp": ( # repeating group x 5 + 5, + { + "lat1": (8, S, 1), + "lon1": (9, S, 1), + "lat2": (8, S, 1), + "lon2": (9, S, 1), + "shape": (1, U, 1), + }, + ), + "_spare": (15, U, 1), + **SBA_L1CA_MT_END, +} # service message parameters + +SBA_L1CA_MT_28 = { + **SBA_L1CA_MT_TLM, + "iodp": (2, U, 1), + "prn_grp": ( # repeating group x 2 + 2, + { + "prnmaskno": (6, U, 1), + "scaleexp": (3, U, 1), + "e11": (9, U, 1), + "e22": (9, U, 1), + "e33": (9, U, 1), + "e44": (9, U, 1), + "e12": (10, S, 1), + "e13": (10, S, 1), + "e14": (10, S, 1), + "e23": (10, S, 1), + "e24": (10, S, 1), + "e34": (10, S, 1), + }, + ), + **SBA_L1CA_MT_END, +} # clock emphemeris covariance # mapping for (subframe, page) acquisition mask subframeacq # NB subframes containing only almanac data are not generally # required for RINEX conversion purposes SBA_SUBFRAMEACQ_MAP = { L1CA: { - TARGET: 0b111, # Message Type (MT) codes 9,12 (17 optional) + TARGET: 0b111, # Message Type (MT) codes 9,12,17 START: 9, (9, 0): (SBA_L1CA_MT_9, 1), (12, 0): (SBA_L1CA_MT_12, 2), (17, 0): (SBA_L1CA_MT_17, 4), + # (6, 0): (SBA_L1CA_MT_6, 8), + # (7, 0): (SBA_L1CA_MT_7, 16), + # (10, 0): (SBA_L1CA_MT_10, 32), + # (18, 0): (SBA_L1CA_MT_18, 64), + # (24, 0): (SBA_L1CA_MT_24, 128), + # (25, 0): (SBA_L1CA_MT_25, 256), + # (26, 0): (SBA_L1CA_MT_26, 512), + # (27, 0): (SBA_L1CA_MT_27, 1024), + # (28, 0): (SBA_L1CA_MT_28, 2048), }, } diff --git a/src/pygnssutils/rinex_conv.py b/src/pygnssutils/rinex_conv.py index d61788a..6f2b68d 100644 --- a/src/pygnssutils/rinex_conv.py +++ b/src/pygnssutils/rinex_conv.py @@ -24,29 +24,26 @@ from typing import Any, Literal from pygnssutils.exceptions import RINEXProcessingError -from pygnssutils.gnssreader import ( - NMEA_PROTOCOL, - RTCM3_PROTOCOL, - UBX_PROTOCOL, - GNSSReader, -) +from pygnssutils.gnssreader import GNSSReader from pygnssutils.rinex_conv_met import RinexConverterMeteorology from pygnssutils.rinex_conv_nav import RinexConverterNavigation from pygnssutils.rinex_conv_obs import RinexConverterObservation from pygnssutils.rinex_globals import ( - ALLGNSS, - ALLOBS, EPOCHMAX, EPOCHMIN, MET, NAV, + NMEA, OBS, RINEX4, RINEX_CANCELLED, RINEX_ERROR, RINEX_NORECS, RINEX_OK, + RINEXDATASOURCE, RINEXVER_DEFAULT, + RTCM3, + UBLOX, ) from pygnssutils.rinex_helpers import ( format_comments, @@ -56,6 +53,7 @@ format_runby, format_stationinfo, format_version, + set_filters, ) RINEXHANDLERS = { @@ -84,22 +82,24 @@ def __init__( self, app, rinex_version: str, - rinex_types: list[str], - gnssfilter: list[str], - obsfilter: list[str], + rinex_types: tuple[str], + gnssfilter: tuple[str], + obsfilter: tuple[str], timecorr: bool, ionocorr: bool, eopcorr: bool, - datasource: list[str], starttime: datetime | str, minobs: int, - marker: list[str], - antenna: list[str], - antennahed: list[float | str], - receiver: list[str], + marker: tuple[str], + antenna: tuple[str], + antennahed: tuple[float | str], + receiver: tuple[str], observer: str, - comments: list[str], - protfilter: int = NMEA_PROTOCOL | UBX_PROTOCOL | RTCM3_PROTOCOL, + comments: tuple[str], + obssource: str = UBLOX, + navsource: str = UBLOX, + metsource: str = NMEA, + svfilter: tuple[str] = ("",), verbosity: Literal[-1, 0, 1, 2, 3] = VERBOSITY_MEDIUM, logtofile: str = "", **kwargs, @@ -109,28 +109,29 @@ def __init__( :param object app: application from which this class is invoked (None) :param str rinex_version: RINEX protocol version (3.05) - :param list[str] rinex_type: RINEX output type(s) e.g. ["O","N"] - :param list[str] gnssfilter: List of GNSS codes to process \ - (or None for all) e.g. [GPS,GAL] - :param list[str] obsfilter: List of observation codes to process \ - (or None for all) e.g. ["1C","2B"] + :param tuple[str] rinex_type: RINEX output type(s) e.g. ("O","N") + :param tuple[str] gnssfilter: List of GNSS codes to process \ + (or None for all) e.g. (GPS,GAL) + :param tuple[str] obsfilter: List of observation codes to process \ + (or None for all) e.g. ()"1C","2B") + :param tuple[str] svfilter: List of SV to process + (or blank for ALL) e.g. ("G01","E21") :param bool timecorr: Include time (clock) corrections :param bool ionocorr: Include ionospheric corrections :param bool eopcorr: Include earth orientation corrections - :param list[str] datasource: List of datasources for each rinex \ - type e.g. ["R","R","R"] :param datetime | str starttime: Approximate start time of RTCM3 (e.g. NTRIP) data \ in format "YYYYMMDDHHMMSS±ZZZZ e.g. "20260508090123+0100" \ (defaults to current UTC datetime) :param int minobs: Minimum observations per observation type (0) - :param list[str] marker: marker details (name, number, type) - :param list[str] antenna: antenna details (number, type) - :param list[float | str] antennahed: antenna delta H,E,D - :param list[str] | receiver: receiver details (number, type, version) + :param tuple[str] marker: marker details (name, number, type) + :param tuple[str] antenna: antenna details (number, type) + :param tuple[float | str] antennahed: antenna delta H,E,D + :param tuple[str] | receiver: receiver details (number, type, version) :param str observer: observer details - :param list[str] comments: user comments - :param int protfilter: input message protocol mask NMEA=1, UBX=2, \ - RTCM3=4. Can be OR'd (7) + :param tuple[str] comments: user comments + :param str obssource: data source of observation data (u-blox) + :param str navsource: data source of navigation data (u-blox) + :param str metsource: data source of meterology data (nmea) :param Literal[-1,0,1,2,3] verbosity: log message verbosity -1 = critical, 0 = error, \ 1 = warning, 2 = info, 3 = debug (1) :param str logtofile: fully qualified path to logfile ("" = no logfile) @@ -138,17 +139,26 @@ def __init__( """ self.logger = getLogger(__name__) + self.logger.debug( + ( + f"{rinex_version=}, {rinex_types=}, {gnssfilter=}, {obsfilter=}, {timecorr=}, " + f"{ionocorr=}, {eopcorr=}, {starttime=}, {minobs=}, {marker=}, {antenna=}, " + f"{antennahed=}, {receiver=}, {observer=}, {comments=},{obssource=}, " + f"{navsource=}, {metsource=}, {svfilter=}, {verbosity=}, {logtofile=}" + ) + ) self.__app = app # pylint: disable=unused-private-member self._rinex_version = RINEXVER_DEFAULT if rinex_version == "" else rinex_version - self._rinex_types = ALLOBS if rinex_types == [""] else rinex_types - self._gnssfilter = ALLGNSS if gnssfilter == [""] else gnssfilter + self._rinex_types = (OBS, NAV, MET) if rinex_types == ("",) else rinex_types + self._gnssfilter = gnssfilter self._obsfilter = obsfilter + self._svfilter = svfilter self._timecorrflag = timecorr self._ionocorrflag = ionocorr self._eopcorrflag = eopcorr - while len(datasource) < 3: # OBS, NAV, MET - datasource.append("R") - self._datasource = ["R", "R", "R"] if datasource == [""] else datasource + self._obssource = obssource + self._navsource = navsource + self._metsource = metsource if isinstance(starttime, datetime): self.starttime = starttime elif starttime == "": @@ -162,7 +172,12 @@ def __init__( self._receiver = receiver self._observer = observer self.user_comments = comments - self._protfilter = protfilter + + # filter unneeded message protocols/types to speed conversion + self._protfilter, self._msgfilter = set_filters( + rinex_types, obssource, navsource, metsource + ) + self._epochdata = { OBS: {MAX: EPOCHMIN, MIN: EPOCHMAX, CUR: EPOCHMIN, FRQ: 0}, NAV: {MAX: EPOCHMIN, MIN: EPOCHMAX, CUR: EPOCHMIN, FRQ: 0}, @@ -176,24 +191,29 @@ def __init__( self._logfile = "" self._outputs = {} - self._tot = 0 self._recs = {} self._msgcount = 0 - self._progress = 0 self._prev_progress = 0 # set up conversion handlers for OBS, NAV, MET as required for rt in self._rinex_types: + if rt == NAV: + datasource = self._navsource + elif rt == MET: + datasource = self._metsource + else: # OBS + datasource = self._obssource self._outputs[rt] = {} self._outputs[rt][HANDLER] = RINEXHANDLERS[rt]( self, rinex_version=self._rinex_version, gnssfilter=self._gnssfilter, obsfilter=self._obsfilter, + svfilter=self._svfilter, timecorr=self._timecorrflag, ionocorr=self._ionocorrflag, eopcorr=self._eopcorrflag, - datasource=self._datasource[{OBS: 0, NAV: 1, MET: 2}[rt]], + datasource=datasource, minobs=self._minobs, marker=self._marker, antenna=self._antenna, @@ -231,7 +251,11 @@ def process_input( # check total number of raw messages in file self._msgcount = 0 with open(infile, "rb") as inputstream: - gnr = GNSSReader(inputstream, parsing=False, protfilter=self._protfilter) + gnr = GNSSReader( + inputstream, + parsing=False, + protfilter=self._protfilter, + ) for raw, _ in gnr: if raw is not None: self._msgcount += 1 @@ -251,7 +275,7 @@ def process_input( def process_input_data( self, - rinextypes: list[str], + rinextypes: tuple[str], instream: BufferedReader, outputpath: Path, stopevent: Event | NoneType = None, @@ -261,7 +285,7 @@ def process_input_data( """ Process binary data stream. - :param list[str] rinextypes: RINEX conversion type (O, N, M) + :param tuple[str] rinextypes: RINEX conversion type (O, N, M) :param BufferedReader instream: input binary file stream :param TextIOWrapper outstream: output text file stream :param Event | NoneType stopevent: stop event for remote cancellation (None) @@ -270,37 +294,43 @@ def process_input_data( :return: return code (0 = success, >0 = error) :rtype: int """ - # pylint: disable=consider-using-with - self.logger.debug( - ( - f"{self._rinex_version=} {rinextypes=} {self._gnssfilter=} " - f"{self._obsfilter=} {self._protfilter=} {self._minobs=} " - f"{instream=} {outputpath=} {kwargs=}" - ) - ) - res = RINEX_ERROR try: + i = 0 # parse incoming data stream until complete or cancelled - gnr = GNSSReader(instream, parsing=True, protfilter=self._protfilter) + gnr = GNSSReader( + instream, + parsing=True, + protfilter=self._protfilter, + msgfilter=self._msgfilter, + ) for raw, parsed in gnr: if stopevent is not None: if stopevent.is_set(): raise KeyboardInterrupt("Terminated by user") - if raw is not None and parsed is not None: - self._tot += 1 - self._progress = int(round(100 * self._tot / self._msgcount, 0)) - self._do_progress_update(progcallback, self._progress) - self._recs[parsed.identity] = self._recs.get(parsed.identity, 0) + 1 - for rt in rinextypes: - rc = self._outputs[rt][HANDLER].process_input_data(parsed) - self._outputs[rt][PROC] += rc + if raw is not None: + i += 1 + progress = int(round(100 * i / self._msgcount, 0)) + self._do_progress_update(progcallback, progress) + if parsed is not None: + self._recs[parsed.identity] = ( + self._recs.get(parsed.identity, 0) + 1 + ) + for rt in rinextypes: + rc = self._outputs[rt][HANDLER].process_input_data(parsed) + self._outputs[rt][PROC] += rc # setup output file streams for OBS, NAV, MET as required for rt in rinextypes: + if rt == NAV: + datasource = self._navsource + elif rt == MET: + datasource = self._metsource + else: # OBS + datasource = self._obssource fnm = format_filename( rt, self._gnssfilter, @@ -308,7 +338,7 @@ def process_input_data( self._epochdata[rt]["max"], self._epochdata[rt]["frq"], outputpath, - self._datasource[{OBS: 0, NAV: 1, MET: 2}[rt]], + datasource, ) self._outputs[rt][FILENAME] = fnm self._outputs[rt][STREAM] = open(fnm, "w", encoding="utf-8") @@ -340,12 +370,12 @@ def process_input_data( return res - def process_output_data(self, rinextypes: list[str]): + def process_output_data(self, rinextypes: tuple[str]): """ Process any accumulated data for each RINEX category (OBS, NAV, MET). - :param list[str] rinextypes: rinex type(s) + :param tuple[str] rinextypes: rinex type(s) """ for rt in rinextypes: @@ -353,23 +383,34 @@ def process_output_data(self, rinextypes: list[str]): if op[PROC] > 0: # only process if at least 1 valid observation op[HANDLER].process_output_file() - def format_header_common(self, rinextype: Literal["O", "N", "M"]) -> str: + def format_header_common( + self, rinextype: Literal["O", "N", "M"], datasource: str = "R" + ) -> str: """ Format common header lines. :param Literal["O","N","M"] rinextype: rinextype + :param str datasource: datasource :return: formatted string :rtype: str """ - datasource = self._datasource[{"O": 0, "N": 1, "M": 2}[rinextype]] hdr = ( format_version(self._rinex_version, rinextype, self._gnssfilter) + format_runby() + format_comments(f"log: {self._logfile}") - + format_comments(f"format: {datasource}") - + format_comments(self.user_comments) + + format_comments(f"format: {RINEXDATASOURCE.get(datasource, datasource)}") ) + if self._gnssfilter != ("",): + fil = ",".join(self._gnssfilter) + hdr += format_comments(f"gnss filter: {fil}") + if self._obsfilter != ("",): + fil = ",".join(self._obsfilter) + hdr += format_comments(f"obs filter: {fil}") + if self._svfilter != ("",): + fil = ",".join(self._svfilter) + hdr += format_comments(f"sv filter: {fil}") + hdr += format_comments(self.user_comments) if self._rinex_version >= RINEX4: hdr += ( format_doi(self._doi) diff --git a/src/pygnssutils/rinex_conv_cli.py b/src/pygnssutils/rinex_conv_cli.py index 639eb70..194b0ee 100644 --- a/src/pygnssutils/rinex_conv_cli.py +++ b/src/pygnssutils/rinex_conv_cli.py @@ -13,29 +13,26 @@ from argparse import ArgumentDefaultsHelpFormatter, ArgumentParser from pygnssutils import ( - NMEA_PROTOCOL, - RTCM3_PROTOCOL, - UBX_PROTOCOL, prog_callback, set_common_args, ) from pygnssutils.globals import CLIAPP, EPILOG from pygnssutils.rinex_conv import RinexConverter from pygnssutils.rinex_globals import ( - ALLGNSS, - ALLOBS, MET, MINOBS, NAV, + NMEA, OBS, PYRINEXCONV_VERSION, RINEX_NORECS, RINEX_OK, RINEXTYPE, RINEXVER_DEFAULT, - RINEXVERSIONS, + RTCM3, + UBLOX, ) -from pygnssutils.rinex_helpers import listify +from pygnssutils.rinex_helpers import tuplefy def main(): @@ -64,7 +61,7 @@ def main(): required=False, help="RINEX Version", type=str, - choices=RINEXVERSIONS, + choices=("3.05", "4.02"), default=RINEXVER_DEFAULT, ) ap.add_argument( @@ -74,12 +71,13 @@ def main(): help=( f"Comma-separated list of RINEX output type(s), or blank for all: " f"{OBS} {RINEXTYPE[OBS]}, {NAV} {RINEXTYPE[NAV]}," - f" {MET} {RINEXTYPE[MET]} e.g. '{OBS},{MET}'. " + f" {MET} {RINEXTYPE[MET]} e.g. '{OBS},{NAV}'. " ), type=str, - default="", + default=f"{OBS},{NAV},{MET}", ) ap.add_argument( + "-G", "--gnssfilter", required=False, help=( @@ -90,21 +88,47 @@ def main(): default="", ) ap.add_argument( + "-O", "--obsfilter", required=False, - help="Comma-separated list of observation codes to process, or blank for all e.g '1C,2S'", + help="Comma-separated list of observation codes to process, or blank for all e.g. '1C,2S'", type=str, default="", ) ap.add_argument( - "--datasource", + "-S", + "--svfilter", required=False, - help=( - "Comma-separated list of data sources for each of (OBS, NAV, MET): " - "R Receiver, S Stream, N NTRIP (RTCM3), U Unknown, UBLOX, RTCM3, NMEA e.g 'R,S,R'" - ), + help="Comma-separated list of satellite codes to process, or blank for all e.g. 'G01,E22'", + type=str, + default="", + ) + ap.add_argument( + "-os", + "--obssource", + required=False, + help=("Source of observation data"), + type=str, + choices=(UBLOX,), + default=UBLOX, + ) + ap.add_argument( + "-ns", + "--navsource", + required=False, + help=("Source of navigation data"), type=str, - default="R,R,R", + choices=(UBLOX, RTCM3), + default=UBLOX, + ) + ap.add_argument( + "-ms", + "--metsource", + required=False, + help=("Source of meteorology data"), + type=str, + choices=(NMEA,), + default=NMEA, ) ap.add_argument( "--starttime", @@ -210,16 +234,6 @@ def main(): help="Comma-separated list of header user comment(s)", type=str, ) - ap.add_argument( - "--protfilter", - required=False, - help=( - f"Input protocol mask - NMEA={NMEA_PROTOCOL}, UBX={UBX_PROTOCOL}, " - f"RTCM3={RTCM3_PROTOCOL}. Can be OR'd" - ), - type=int, - default=NMEA_PROTOCOL | UBX_PROTOCOL | RTCM3_PROTOCOL, - ) kwargs = set_common_args("rinexconvertor", ap) infile = kwargs.pop("infile") @@ -228,27 +242,27 @@ def main(): rc = RinexConverter( CLIAPP, rinex_version=kwargs.pop("rinexver", RINEXVER_DEFAULT), - rinex_types=listify(kwargs.pop("rinextype", ALLOBS)), - gnssfilter=listify(kwargs.pop("gnssfilter", ALLGNSS)), - obsfilter=listify(kwargs.pop("obsfilter", [""])), - datasource=listify(kwargs.pop("datasource", ["R", "R", "R"])), + rinex_types=tuplefy(kwargs.pop("rinextype", f"{OBS},{NAV},{MET}").upper()), + gnssfilter=tuplefy(kwargs.pop("gnssfilter", "")), + obsfilter=tuplefy(kwargs.pop("obsfilter", "")), + svfilter=tuplefy(kwargs.pop("svfilter", "")), + obssource=kwargs.pop("obssource", UBLOX), + navsource=kwargs.pop("navsource", UBLOX), + metsource=kwargs.pop("metsource", NMEA), starttime=kwargs.pop("starttime", ""), minobs=int(kwargs.pop("minobs", MINOBS)), - marker=listify(kwargs.pop("marker", [""])), - antenna=listify(kwargs.pop("antenna", [""])), - antennahed=listify(kwargs.pop("antennahed", "0.0,0.0,0.0")), - receiver=listify(kwargs.pop("receiver", [""])), + marker=tuplefy(kwargs.pop("marker", "")), + antenna=tuplefy(kwargs.pop("antenna", "")), + antennahed=tuplefy(kwargs.pop("antennahed", "0.0,0.0,0.0")), + receiver=tuplefy(kwargs.pop("receiver", "")), observer=kwargs.pop("observer", ""), - comments=listify(kwargs.pop("comments", [""])), - protfilter=int( - kwargs.pop("protfilter", NMEA_PROTOCOL | UBX_PROTOCOL | RTCM3_PROTOCOL) - ), + comments=tuplefy(kwargs.pop("comments", "")), doi=kwargs.pop("doi", ""), license=kwargs.pop("license", ""), station=kwargs.pop("station", ""), - timecorr=int(kwargs.pop("timecorr", 1)), - ionocorr=int(kwargs.pop("ionocorr", 1)), - eopcorr=int(kwargs.pop("eopcorr", 1)), + timecorr=bool(kwargs.pop("timecorr", 1)), + ionocorr=bool(kwargs.pop("ionocorr", 1)), + eopcorr=bool(kwargs.pop("eopcorr", 1)), **kwargs, ) res = rc.process_input( diff --git a/src/pygnssutils/rinex_conv_met.py b/src/pygnssutils/rinex_conv_met.py index a662007..0937ace 100644 --- a/src/pygnssutils/rinex_conv_met.py +++ b/src/pygnssutils/rinex_conv_met.py @@ -25,7 +25,7 @@ from pygnssutils.exceptions import RINEXProcessingError from pygnssutils.globals import VERBOSITY_MEDIUM -from pygnssutils.rinex_globals import BDS, COLWIDTH, EPOCHMIN, MET +from pygnssutils.rinex_globals import BDS, COLWIDTH, EPOCHMIN, MET, NMEA from pygnssutils.rinex_helpers import ( FRNX, format_fileend, @@ -47,11 +47,12 @@ def __init__( self, app: Any, rinex_version: str, - gnssfilter: list[str], - obsfilter: list[str], + gnssfilter: tuple[str], + obsfilter: tuple[str], + svfilter: tuple[str], datasource: str, minobs: int, - marker: list[str], + marker: tuple[str], verbosity: Literal[-1, 0, 1, 2, 3] = VERBOSITY_MEDIUM, logtofile: str = "", **kwargs, @@ -61,13 +62,15 @@ def __init__( :param Any app: application from which this class is invoked :param str rinex_version: RINEX protocol version (3.05) - :param list[str] gnssfilter: List of GNSS codes to process + :param tuple[str] gnssfilter: List of GNSS codes to process (or blank for ALL) e.g. [GPS,GAL] - :param list[str] obsfilter: List of observation codes to process + :param tuple[str] obsfilter: List of observation codes to process (or blank for ALL) e.g. ["1C","2B"] + :param tuple[str] svfilter: List of SV to process + (or blank for ALL) e.g. ["G01","E21"] :param str source: data source (R) :param int minobs: Minimum observations per observation type (10) - :param list[str] | str marker: marker details (name, number, type) + :param tuple[str] | str marker: marker details (name, number, type) :param Literal[-1,0,1,2,3] verbosity: log message verbosity -1 = critical, 0 = error, 1 = warning, 2 = info, 3 = debug (1) :param str logtofile: fully qualified path to logfile ("" = no logfile) @@ -75,8 +78,9 @@ def __init__( """ self.__app = app # pylint: disable=unused-private-member - self._gnss_filter = gnssfilter - self._obscode_filter = obsfilter + self._gnssfilter = gnssfilter + self._obsfilter = obsfilter + self._svfilter = svfilter self._datasource = "R" if datasource == "" else datasource self._minobs = minobs self.verbosity = int(verbosity) @@ -106,6 +110,7 @@ def process_input_data( ret = 0 if isinstance(parsed, NMEAMessage): + self._datasource = NMEA if parsed.identity[2:] in ("RMC"): self.get_nmea_epoch(parsed) if parsed.identity[2:] in ("MWD"): # NMEA Wind Speed and Direction @@ -145,7 +150,7 @@ def _format_met_data(self, metdata: dict[datetime, dict] | str = ""): :param dict[datetime, dict] | str metdata: met sensor data dictionary """ - if metdata == "": + if isinstance(metdata, str): metdata = {} # 1X,I4.4, 5(1X,I2), mF7.1 4X,10F7.1 @@ -180,7 +185,7 @@ def _format_header(self): """ hdr = ( - self.__app.format_header_common(MET) + self.__app.format_header_common(MET, self._datasource) + format_marker(self._marker_name, self._marker_num, self._marker_type) + format_met_obstypes(self._sensortypes) + format_met_sensortype(self._sensortypes) diff --git a/src/pygnssutils/rinex_conv_nav.py b/src/pygnssutils/rinex_conv_nav.py index aa709cc..5343d24 100644 --- a/src/pygnssutils/rinex_conv_nav.py +++ b/src/pygnssutils/rinex_conv_nav.py @@ -94,6 +94,7 @@ CLKRATE = "clkrate" EPH = "EPH" EPOCH = "epoch" +SVCODE = "svcode" RECTYPE = "rectype" @@ -106,8 +107,9 @@ def __init__( self, app: Any, rinex_version: str, - gnssfilter: list[str], - obsfilter: list[str], + gnssfilter: tuple[str], + obsfilter: tuple[str], + svfilter: tuple[str], timecorr: bool, ionocorr: bool, eopcorr: bool, @@ -122,10 +124,12 @@ def __init__( :param Any app: application from which this class is invoked :param str rinex_version: RINEX protocol version (3.05) - :param list[str] gnssfilter: List of GNSS codes to process + :param tuple[str] gnssfilter: List of GNSS codes to process (or blank for ALL) e.g. [GPS,GAL] - :param list[str] obsfilter: List of observation codes to process + :param tuple[str] obsfilter: List of observation codes to process (or blank for ALL) e.g. ["1C","2B"] + :param tuple[str] svfilter: List of SV to process + (or blank for ALL) e.g. ["G01","E21"] :param bool timecorr: Include time (clock) corrections :param bool ionocorr: Include ionospheric corrections :param bool eopcorr: Include earth orientation corrections @@ -142,8 +146,9 @@ def __init__( self.__app = app # pylint: disable=unused-private-member self._rinex_version = rinex_version - self._gnss_filter = gnssfilter - self._obscode_filter = obsfilter + self._gnssfilter = gnssfilter + self._obsfilter = obsfilter + self._svfilter = svfilter self._datasource = "R" if datasource == "" else datasource self._minobs = minobs self.verbosity = int(verbosity) @@ -176,45 +181,47 @@ def process_input_data( res = 0 if isinstance(parsed, UBXMessage): + self._datasource = "u-blox" if parsed.identity == "RXM-SFRBX": self._collate_rxmsfrbx(parsed) res += 1 - elif isinstance(parsed, RTCMMessage) and self._datasource in ("N"): + elif isinstance(parsed, RTCMMessage) and self._datasource in ("N", "RTCM3"): + self._datasource = "RTCM3" if parsed.identity in ("1005", "1006"): # station ID self._format_rtcm1005(parsed) res += 1 - elif ( - parsed.identity == "1019" and GPS in self._gnss_filter + elif parsed.identity == "1019" and ( + self._gnssfilter == ("",) or GPS in self._gnssfilter ): # GPS Ephemerides self._format_rtcm1019(parsed) res += 1 - elif ( - parsed.identity == "1020" and GLO in self._gnss_filter + elif parsed.identity == "1020" and ( + self._gnssfilter == ("",) or GLO in self._gnssfilter ): # GLONASS Ephemerides self._format_rtcm1020(parsed) res += 1 - elif ( - parsed.identity == "1041" and IRN in self._gnss_filter + elif parsed.identity == "1041" and ( + self._gnssfilter == ("",) or IRN in self._gnssfilter ): # NavIC/IRNSS Ephemerides self._format_rtcm1041(parsed) res += 1 - elif ( - parsed.identity == "1042" and BDS in self._gnss_filter + elif parsed.identity == "1042" and ( + self._gnssfilter == ("",) or BDS in self._gnssfilter ): # Beidou Ephemerides self._format_rtcm1042(parsed) res += 1 - elif ( - parsed.identity == "1044" and QZS in self._gnss_filter + elif parsed.identity == "1044" and ( + self._gnssfilter == ("",) or QZS in self._gnssfilter ): # QZSS Ephemerides self._format_rtcm1044(parsed) res += 1 - elif ( - parsed.identity == "1045" and GAL in self._gnss_filter + elif parsed.identity == "1045" and ( + self._gnssfilter == ("",) or GAL in self._gnssfilter ): # Galileo F/NAV Ephemerides self._format_rtcm1045(parsed) res += 1 - elif ( - parsed.identity == "1046" and GAL in self._gnss_filter + elif parsed.identity == "1046" and ( + self._gnssfilter == ("",) or GAL in self._gnssfilter ): # Galileo I/NAV Ephemerides self._format_rtcm1046(parsed) res += 1 @@ -243,12 +250,15 @@ def _collate_rxmsfrbx(self, data: UBXMessage): formatter = None # MethodType kwargs = {} gnss = sfrdata.get("gnss", "") + svid = sfrdata.get("svid", "") + svcode = get_svcode(gnss, svid, leadzero=True) sigcode = sfrdata.get("sigcode", "") - # filter out any unwanted gnss or signal codes - filtobs = self._obscode_filter != [""] and len(self._obscode_filter) > 0 - if gnss not in self._gnss_filter or ( - filtobs and sigcode not in self._obscode_filter + # filter out any unwanted gnss, signal or satellite codes + if ( + (self._gnssfilter != ("",) and gnss not in self._gnssfilter) + or (self._obsfilter != ("",) and sigcode not in self._obsfilter) + or (self._svfilter != ("",) and svcode not in self._svfilter) ): return @@ -256,14 +266,14 @@ def _collate_rxmsfrbx(self, data: UBXMessage): if sigcode == "1C": sfrmap = GPS_SUBFRAMEACQ_MAP[LNAV] formatter = self._format_rawnav_gps_lnav - elif sigcode in ("2L", "2S", "5I", "5Q"): - sfrmap = GPS_SUBFRAMEACQ_MAP[CNAV] - formatter = self._format_rawnav_gps_cnav + # elif sigcode in ("2X", "5X"): + # sfrmap = GPS_SUBFRAMEACQ_MAP[CNAV] + # formatter = self._format_rawnav_gps_cnav elif gnss == GAL: - if sigcode == "5I": + if sigcode == "5X": sfrmap = GAL_SUBFRAMEACQ_MAP[FNAV] formatter = self._format_rawnav_gal_fnav - elif sigcode in ("1B", "7I"): + elif sigcode in ("1X", "7X"): sfrmap = GAL_SUBFRAMEACQ_MAP[INAV] formatter = self._format_rawnav_gal_fnav elif gnss == BDS: @@ -288,12 +298,12 @@ def _collate_rxmsfrbx(self, data: UBXMessage): if sigcode in ("1C",): sfrmap = QZS_SUBFRAMEACQ_MAP[LNAV] formatter = self._format_rawnav_qzs_lnav - elif sigcode in ("2S", "2L", "5S", "5Q"): + elif sigcode in ("2X", "5X"): sfrmap = QZS_SUBFRAMEACQ_MAP[CNAV] formatter = self._format_rawnav_qzs_cnav - elif sigcode in ("1Z",): - sfrmap = QZS_SUBFRAMEACQ_MAP[CNV2] - formatter = self._format_rawnav_qzs_cnv2 + # elif sigcode in ("1Z",): + # sfrmap = QZS_SUBFRAMEACQ_MAP[CNV2] + # formatter = self._format_rawnav_qzs_cnv2 elif gnss == IRN: if sigcode in ("5A",): sfrmap = IRN_SUBFRAMEACQ_MAP[LNAV] @@ -325,7 +335,7 @@ def _format_nav_data( Format of navdata dict: navdata = { - (svcode (str), iodc (int)): { + (svcode (str), toc (int)): { "epoch": epoch (datetime), "rectype: rectype (str), "clkbias": clkbias (float), @@ -346,11 +356,13 @@ def _format_nav_data( :param dict[tuple[str,int], dict[str, Any]] | str navdata: observation data dictionary """ - if navdata == "": + if isinstance(navdata, str): navdata = {} - # sort NAV records by epoch - for (svcode, _), data in sorted(navdata.items(), key=lambda it: it[1][EPOCH]): + # sort NAV records by epoch and svcode + for (svcode, _), data in sorted( + navdata.items(), key=lambda it: (it[1][EPOCH], it[1][SVCODE]) + ): if self._rinex_version >= RINEX4: rectyp = format_nav_typesvmssg(EPH, svcode, data[RECTYPE]) else: @@ -380,7 +392,7 @@ def _format_header(self): Format navigation header lines. """ - hdr = self.__app.format_header_common(NAV) + hdr = self.__app.format_header_common(NAV, self._datasource) if self._rinex_version < RINEX4: for _, ion in self._ionocorr.items(): # RINEX 3 ionospheric corrections hdr += ion @@ -388,7 +400,7 @@ def _format_header(self): hdr += sto hdr += format_leapseconds( self.__app.get_start_epoch(NAV), # TODO check this is correct date - self._gnss_filter, + self._gnssfilter, ) # debug only vvv # hdr += format_timefirstlast(self.__app.get_start_epoch(NAV), "FIRST") @@ -407,9 +419,9 @@ def _format_rtcm1005(self, data: RTCMMessage): if not self._station_set: sid = data.DF003 x, y, z = data.DF025, data.DF026, data.DF027 - self.__app.user_comments.append(f"Format: RTCM3, Station ID: {sid}") - self.__app.user_comments.append( - f"Station ECEF: {DRNX(x,10,8)} {DRNX(y,10,8)} {DRNX(z,10,8)}" + self.__app.user_comments += (f"station id: {sid}",) + self.__app.user_comments += ( + f"station xyz: {DRNX(x,10,8)} {DRNX(y,10,8)} {DRNX(z,10,8)}", ) self._station_set = True @@ -433,6 +445,7 @@ def _format_rtcm1019(self, data: RTCMMessage): nvd = self._navdata[(svcode, iodc)] nvd[EPOCH] = epoch + nvd[SVCODE] = svcode nvd[RECTYPE] = EPHNAVTYPES[GPS, "L1 C/A"] nvd[CLKBIAS] = data.DF084 # clock bias nvd[CLKDRIFT] = data.DF083 # clock drift @@ -499,6 +512,7 @@ def _format_rtcm1020(self, data: RTCMMessage): self._navdata[(svcode, iodc)] = {} nvd = self._navdata[(svcode, iodc)] nvd[EPOCH] = epoch + nvd[SVCODE] = svcode nvd[RECTYPE] = EPHNAVTYPES[GLO, "L1 C/A"] nvd[CLKBIAS] = ( data.DF124 @@ -550,6 +564,7 @@ def _format_rtcm1045(self, data: RTCMMessage): self._navdata[(svcode, iodc)] = {} nvd = self._navdata[(svcode, iodc)] nvd[EPOCH] = epoch + nvd[SVCODE] = svcode nvd[RECTYPE] = EPHNAVTYPES[GAL, "E5a"] nvd[CLKBIAS] = data.DF296 # clock bias nvd[CLKDRIFT] = data.DF295 # clock drift @@ -614,6 +629,7 @@ def _format_rtcm1046(self, data: RTCMMessage): self._navdata[(svcode, iodc)] = {} nvd = self._navdata[(svcode, iodc)] nvd[EPOCH] = epoch + nvd[SVCODE] = svcode nvd[RECTYPE] = EPHNAVTYPES[GAL, "E5b"] nvd[CLKBIAS] = data.DF296 # clock bias nvd[CLKDRIFT] = data.DF295 # clock drift @@ -679,6 +695,7 @@ def _format_rtcm1042(self, data: RTCMMessage): nvd = self._navdata[(svcode, iodc)] nvd[EPOCH] = epoch + nvd[SVCODE] = svcode nvd[RECTYPE] = EPHNAVTYPES[BDS, "B1C"] nvd[CLKBIAS] = data.DF496 # clock bias nvd[CLKDRIFT] = data.DF495 # clock drift @@ -743,6 +760,7 @@ def _format_rtcm1044(self, data: RTCMMessage): nvd = self._navdata[(svcode, iodc)] nvd[EPOCH] = epoch + nvd[SVCODE] = svcode nvd[RECTYPE] = EPHNAVTYPES[QZS, "L1 C/A"] nvd[CLKBIAS] = data.DF433 # clock bias nvd[CLKDRIFT] = data.DF432 # clock drift @@ -807,6 +825,7 @@ def _format_rtcm1041(self, data: RTCMMessage): nvd = self._navdata[(svcode, iodc)] nvd[EPOCH] = epoch + nvd[SVCODE] = svcode nvd[RECTYPE] = EPHNAVTYPES[IRN, "L1"] nvd[CLKBIAS] = data.DF518 # clock bias nvd[CLKDRIFT] = data.DF519 # clock drift @@ -903,12 +922,13 @@ def _format_rawnav_gps_lnav(self, data: RawNav, **kwargs): raw NAV subframe sources. """ - self._navdata[(data.svcode, data.iode)] = {} - nvd = self._navdata[(data.svcode, data.iode)] + self._navdata[(data.svcode, data.toc)] = {} + nvd = self._navdata[(data.svcode, data.toc)] - epoch, wn_cont = get_epoch(wno=data.wn, tow=data.tow, gnss=data.gnss) + epoch, wn_cont = get_epoch(wno=data.wn, tow=data.toc, gnss=data.gnss) self.__app.set_current_epoch(epoch, NAV) nvd[EPOCH] = epoch + nvd[SVCODE] = data.svcode nvd[RECTYPE] = "LNAV" nvd[CLKBIAS] = data.af0 # clock bias nvd[CLKDRIFT] = data.af1 # clock drift @@ -929,7 +949,7 @@ def _format_rawnav_gps_lnav(self, data: RawNav, **kwargs): nvb[1][2] = data.cus # - Cus (radians) nvb[1][3] = data.sqrta # - sqrt(a) (sqrt(m)) # BROADCAST ORBIT - 3 - nvb[2][0] = data.toe # - Toe Time of Ephemeris (sec of NAVIC week) + nvb[2][0] = data.toe # - Toe Time of Ephemeris (sec of GPS week) nvb[2][1] = data.cic # - Cic (radians) nvb[2][2] = data.omega0 * pi # - OMEGA0 (radians) nvb[2][3] = data.cis # - Cis (radians) @@ -982,12 +1002,13 @@ def _format_rawnav_gps_cnav(self, data: RawNav, **kwargs): raw NAV subframe sources. """ - self._navdata[(data.svcode, data.top)] = {} # have assumed top => iode - nvd = self._navdata[(data.svcode, data.top)] + self._navdata[(data.svcode, data.toc)] = {} # have assumed top => iode + nvd = self._navdata[(data.svcode, data.toc)] - epoch, wn_cont = get_epoch(wno=data.wn, tow=data.tow, gnss=data.gnss) + epoch, wn_cont = get_epoch(wno=data.wn, tow=data.toc, gnss=data.gnss) self.__app.set_current_epoch(epoch, NAV) nvd[EPOCH] = epoch + nvd[SVCODE] = data.svcode nvd[RECTYPE] = "CNAV" nvd[CLKBIAS] = data.af0n # clock bias nvd[CLKDRIFT] = data.af1n # clock drift @@ -1067,7 +1088,7 @@ def _format_rawnav_gps_cnav(self, data: RawNav, **kwargs): svcode=data.svcode, msgtype="CNAV", msgsubtype="", - epoch=data.epoch, + epoch=epoch, tom=data.teop, xp=data.pmx, dxpdt=data.pmxdot, @@ -1080,7 +1101,7 @@ def _format_rawnav_gps_cnav(self, data: RawNav, **kwargs): d2deltaut1dt2=0, ) except AttributeError: - pass + pass # EOP data not available, ignore def _format_rawnav_gal_fnav(self, data: RawNav, **kwargs): """ @@ -1091,12 +1112,13 @@ def _format_rawnav_gal_fnav(self, data: RawNav, **kwargs): raw NAV subframe sources. """ - self._navdata[(data.svcode, data.iodn)] = {} - nvd = self._navdata[(data.svcode, data.iodn)] + self._navdata[(data.svcode, data.toc)] = {} + nvd = self._navdata[(data.svcode, data.toc)] - epoch, wn_cont = get_epoch(wno=data.wn, tow=data.tow, gnss=data.gnss) + epoch, wn_cont = get_epoch(wno=data.wn, tow=data.toc, gnss=data.gnss) self.__app.set_current_epoch(epoch, NAV) nvd[EPOCH] = epoch + nvd[SVCODE] = data.svcode nvd[RECTYPE] = "FNAV" if data.sigcode == "5I" else "INAV" nvd[CLKBIAS] = data.af0 # clock bias nvd[CLKDRIFT] = data.af1 # clock drift @@ -1186,12 +1208,13 @@ def _format_rawnav_bds_d1d2(self, data: RawNav, **kwargs): """ d1d2 = kwargs.get("d1d2", 0) - self._navdata[(data.svcode, data.aode)] = {} - nvd = self._navdata[(data.svcode, data.aode)] + self._navdata[(data.svcode, data.toc)] = {} + nvd = self._navdata[(data.svcode, data.toc)] - epoch, wn_cont = get_epoch(wno=data.wn, tow=data.tow, gnss=data.gnss) + epoch, wn_cont = get_epoch(wno=data.wn, tow=data.toc, gnss=data.gnss) self.__app.set_current_epoch(epoch, NAV) nvd[EPOCH] = epoch + nvd[SVCODE] = data.svcode nvd[RECTYPE] = "D2" if d1d2 == 2 else "D1" nvd[CLKBIAS] = data.af0 # clock bias nvd[CLKDRIFT] = data.af1 # clock drift @@ -1276,6 +1299,7 @@ def _format_rawnav_glo_l1of(self, data: RawNav, **kwargs): nd = (epoch.weekday() + 1) % 7 # GPS week Sunday = 0 self.__app.set_current_epoch(epoch, NAV) nvd[EPOCH] = epoch + nvd[SVCODE] = data.svcode nvd[RECTYPE] = "FDMA" nvd[CLKBIAS] = -data.tauntb # clock bias (sec) nvd[CLKDRIFT] = data.gammantb # clock relative freq bias (sec) @@ -1299,6 +1323,7 @@ def _format_rawnav_glo_l1of(self, data: RawNav, **kwargs): nvb[2][1] = data.zntbdot # - vel (km/s) nvb[2][2] = data.zntbdot2 # - acc (km/s2) nvb[2][3] = data.en # - age of operation info (days) + # BROADCAST ORBIT - 4 health = ( (data.m << 6) + (data.p4 << 5) @@ -1337,7 +1362,7 @@ def _format_rawnav_sba_l1ca(self, data: RawNav, **kwargs): self._navdata[(data.svcode, data.toc)] = {} nvd = self._navdata[(data.svcode, data.toc)] - epoch, wn_cont = get_epoch(wno=data.wn, tow=data.tow, gnss=data.gnss) + epoch, wn_cont = get_epoch(wno=data.wn, tow=data.toc, gnss=data.gnss) self.__app.set_current_epoch(epoch, NAV) # derive health flag from MT17 record, if available @@ -1352,6 +1377,7 @@ def _format_rawnav_sba_l1ca(self, data: RawNav, **kwargs): health |= int(ura == 15) << 5 # do not use for ranging nvd[EPOCH] = epoch + nvd[SVCODE] = data.svcode nvd[RECTYPE] = "SBAS" nvd[CLKBIAS] = data.agf0 # clock bias (sec) nvd[CLKDRIFT] = data.agf1 # relative frequency bias (sec2) @@ -1399,12 +1425,13 @@ def _format_rawnav_qzs_lnav(self, data: RawNav, **kwargs): raw NAV subframe sources. """ - self._navdata[(data.svcode, data.iode)] = {} - nvd = self._navdata[(data.svcode, data.iode)] + self._navdata[(data.svcode, data.toc)] = {} + nvd = self._navdata[(data.svcode, data.toc)] - epoch, wn_cont = get_epoch(wno=data.wn, tow=data.tow, gnss=data.gnss) + epoch, wn_cont = get_epoch(wno=data.wn, tow=data.toc, gnss=data.gnss) self.__app.set_current_epoch(epoch, NAV) nvd[EPOCH] = epoch + nvd[SVCODE] = data.svcode nvd[RECTYPE] = "LNAV" nvd[CLKBIAS] = data.af0 # clock bias nvd[CLKDRIFT] = data.af1 # clock drift @@ -1478,12 +1505,13 @@ def _format_rawnav_qzs_cnav(self, data: RawNav, **kwargs): raw NAV subframe sources. """ - self._navdata[(data.svcode, data.top)] = {} # have assumed top => iode - nvd = self._navdata[(data.svcode, data.top)] + self._navdata[(data.svcode, data.toc)] = {} # have assumed top => iode + nvd = self._navdata[(data.svcode, data.toc)] - epoch, wn_cont = get_epoch(wno=data.wn, tow=data.tow, gnss=data.gnss) + epoch, wn_cont = get_epoch(wno=data.wn, tow=data.toc, gnss=data.gnss) self.__app.set_current_epoch(epoch, NAV) nvd[EPOCH] = epoch + nvd[SVCODE] = data.svcode nvd[RECTYPE] = "CNAV" nvd[CLKBIAS] = data.af0n # clock bias nvd[CLKDRIFT] = data.af1n # clock drift @@ -1563,7 +1591,7 @@ def _format_rawnav_qzs_cnav(self, data: RawNav, **kwargs): svcode=data.svcode, msgtype="CNVX", msgsubtype="", - epoch=data.epoch, + epoch=epoch, tom=data.teop, xp=data.pmx, dxpdt=data.pmxdot, @@ -1598,12 +1626,13 @@ def _format_rawnav_irn_lnav(self, data: RawNav, **kwargs): raw NAV subframe sources. """ - self._navdata[(data.svcode, data.iodec)] = {} - nvd = self._navdata[(data.svcode, data.iodec)] + self._navdata[(data.svcode, data.toc)] = {} + nvd = self._navdata[(data.svcode, data.toc)] - epoch, wn_cont = get_epoch(wno=data.wn, tow=data.tow, gnss=data.gnss) + epoch, wn_cont = get_epoch(wno=data.wn, tow=data.toc, gnss=data.gnss) self.__app.set_current_epoch(epoch, NAV) nvd[EPOCH] = epoch + nvd[SVCODE] = data.svcode nvd[RECTYPE] = "LNAV" nvd[CLKBIAS] = data.af0 # clock bias nvd[CLKDRIFT] = data.af1 # clock drift @@ -1673,7 +1702,7 @@ def _format_rawnav_irn_lnav(self, data: RawNav, **kwargs): svcode=data.svcode, msgtype="LNAV", msgsubtype="", - epoch=data.epoch, + epoch=epoch, tom=data.teop, xp=data.pmx, dxpdt=data.pmxdot, @@ -1702,12 +1731,12 @@ def _format_timecorr_3(self, data: RawNav, source: int = 0): a0 = -data.tauc a1 = 0 timeref = 0 - weekno = 0 + wncont = 0 else: a0 = data.a0 a1 = data.a1 - timeref = data.toc - weekno = data.wn + timeref = data.tow + _, wncont = get_epoch(wno=data.wn, tow=data.tow, gnss=data.gnss) if data.gnss == SBA: svcode = SBASPRN.get(data.svid, "N/A") else: @@ -1718,7 +1747,7 @@ def _format_timecorr_3(self, data: RawNav, source: int = 0): svcode=svcode, source=source, timeref=timeref, - weekno=weekno, + weekno=wncont, a0=a0, a1=a1, ) diff --git a/src/pygnssutils/rinex_conv_obs.py b/src/pygnssutils/rinex_conv_obs.py index b3bc27c..c61b60b 100644 --- a/src/pygnssutils/rinex_conv_obs.py +++ b/src/pygnssutils/rinex_conv_obs.py @@ -56,6 +56,7 @@ format_leapseconds, format_marker, format_numsats, + format_observation_epoch, format_observer, format_obstypes, format_rcvrtype, @@ -80,14 +81,15 @@ def __init__( self, app: Any, rinex_version: str, - gnssfilter: list[str], - obsfilter: list[str], + gnssfilter: tuple[str], + obsfilter: tuple[str], + svfilter: tuple[str], minobs: int, datasource: str, - marker: list[str], - antenna: list[str], - antennahed: list[float | str], - receiver: list[str], + marker: tuple[str], + antenna: tuple[str], + antennahed: tuple[float | str], + receiver: tuple[str], observer: str = "", verbosity: Literal[-1, 0, 1, 2, 3] = VERBOSITY_MEDIUM, logtofile: str = "", @@ -98,16 +100,18 @@ def __init__( :param Any app: application from which this class is invoked :param str rinex_version: RINEX protocol version (3.05) - :param list[str] gnssfilter: List of GNSS codes to process + :param tuple[str] gnssfilter: List of GNSS codes to process (or blank for ALL) e.g. [GPS,GAL] - :param list[str] obsfilter: List of observation codes to process + :param tuple[str] obsfilter: List of observation codes to process (or blank for ALL) e.g. ["1C","2B"] + :param tuple[str] svfilter: List of SV to process + (or blank for ALL) e.g. ["G01","E21"] :param str datasource: data source (R) :param int minobs: Minimum observations per observation type (10) - :param list[str] marker: marker details (name, number, type) - :param list[str] antenna: antenna details (number, type) - :param list(float | str) antennahed: antenna delta H,E,D - :param list[str] receiver: receiver details (number, type, version) + :param tuple[str] marker: marker details (name, number, type) + :param tuple[str] antenna: antenna details (number, type) + :param tuple(float | str) antennahed: antenna delta H,E,D + :param tuple[str] receiver: receiver details (number, type, version) :param str observer: observer details :param Literal[-1,0,1,2,3] verbosity: log message verbosity -1 = critical, 0 = error, 1 = warning, 2 = info, 3 = debug (1) @@ -119,8 +123,9 @@ def __init__( self.logger = getLogger(__name__) self._rinex_version = rinex_version - self._gnss_filter = gnssfilter - self._obscode_filter = obsfilter + self._gnssfilter = gnssfilter + self._obsfilter = obsfilter + self._svfilter = svfilter self._datasource = "R" if datasource == "" else datasource self._minobs = minobs self._marker_name = marker[0] if len(marker) > 0 else "" @@ -129,7 +134,7 @@ def __init__( self._antenna_num = antenna[0] if len(antenna) > 0 else "" self._antenna_type = antenna[1] if len(antenna) > 1 else "" while len(antennahed) < 3: # H,E,D - antennahed.append(0.0) + antennahed += (0.0,) self._ant_deltaheight = float(antennahed[0]) self._ant_deltaen = [float(antennahed[1]), float(antennahed[2])] self._rcvr_num = receiver[0] if len(receiver) > 0 else "" @@ -176,6 +181,7 @@ def process_input_data( ret = 0 if isinstance(parsed, UBXMessage): + self._datasource = "u-blox" if parsed.identity in ("RXM-RAWX", "RXM-RAW"): self.convert_ubx_rxmrawx(parsed) ret += 1 @@ -208,50 +214,13 @@ def _trim_obstypes(self): """ for gnssr, obstypes in self._obstypes.items(): - for obstype, numobs in list(obstypes.items()): + for obstype, numobs in obstypes.items(): if numobs < self._minobs: self.logger.debug( f"{gnssr} {obstype} omitted - {numobs} < {self._minobs}" ) obstypes.pop(obstype) - def _format_observation_epoch( - self, - epoch: datetime | str, - numobs: int | str, - epochflag: int | str = "0", - clkoffset: float | str = "", - picosecond: int | str = "", - ) -> str: - """ - Format observation epoch. - - :param datetime | str epoch: observation epoch or blank if event - :param int | str numobs: number of observations in this epoch - :param int | str epochflag: epoch flag - :param float | str clkoffset: clock offset - :param int | str picosets: picoseconds (RINEX 4 only) - :return: formatted string - :rtype: str - """ - - nummeas = f"{str(numobs):<2}" - if clkoffset == 0.0: - clkoffset = "" - if epoch == "": # event - return f">{'':>39}{epochflag:>3}{numobs:>3}{'':>21}\n" # A1 ... 2X,I1 I3 - # epoch - if isinstance(picosecond, int): - pico = f"{picosecond:05d}" - else: - pico = f"{picosecond:>5}" - return ( - f">{epoch.year:>5}{epoch.month:>3}{epoch.day:>3}" - f"{epoch.hour:>3}{epoch.minute:>3}" - f"{FRNX(epoch.second + epoch.microsecond/1000000,11,7)}" - f"{epochflag:>3}{nummeas:>3}{'':>6}{FRNX(clkoffset,15,12)} {pico}\n" - ) # A1 1X,I4 4(1X,I2.2) F11.7 2X,I1 I3 6X F15.12 (1X,I5.5) - def _format_obs_data(self, obsdata: dict[datetime, dict] | str = ""): """ Format observation data for each epoch and prn from obsdata dict. @@ -276,21 +245,23 @@ def _format_obs_data(self, obsdata: dict[datetime, dict] | str = ""): :param dict[datetime,dict] | str obsdata: observation data dictionary """ - if obsdata == "": + if isinstance(obsdata, str): obsdata = {} for epoch, data in obsdata.items(): epoch_flag = data.get("epochflag", 0) epoch_clkoffset = data.get("clkoffset", 0.0) epoch_obs = data.get("obs", {}) + if epoch_obs == {}: + continue if epoch_flag in (0, 1, 6): # observation or (6) cycle slip self.__app.output( - self._format_observation_epoch( + format_observation_epoch( epoch, len(epoch_obs), epoch_flag, epoch_clkoffset ), OBS, ) - for svcode, observations in epoch_obs.items(): + for svcode, observations in sorted(epoch_obs.items()): gnssr = svcode[0:1] prn = svcode[1:3] obs = f"{gnssr}{prn:<2}" # A1 + I2.2 @@ -308,7 +279,7 @@ def _format_obs_data(self, obsdata: dict[datetime, dict] | str = ""): elif epoch_flag in (2, 3, 4, 5): # antenna, site move & external events numevents = 0 self.__app.output( - self._format_observation_epoch("", "", epoch_flag, numevents), OBS + format_observation_epoch("", "", epoch_flag, numevents), OBS ) # epoch line for i in range(numevents): self.__app.output( @@ -323,14 +294,12 @@ def _format_header(self): Format observation header lines. """ - self.logger.debug(f"{self._obstypes=}") - # redact obstypes with less than the required number of observations if self._minobs > 0: self._trim_obstypes() hdr = ( - self.__app.format_header_common(OBS) + self.__app.format_header_common(OBS, self._datasource) + format_marker(self._marker_name, self._marker_num, self._marker_type) + format_observer(self._observer) + format_rcvrtype(self._rcvr_num, self._rcvr_type, self._rcvr_ver) @@ -357,7 +326,7 @@ def _format_header(self): + format_glonassphasebias(self._glonass_pb) + format_leapseconds( self.__app.get_start_epoch(OBS), # TODO check this is correct date - self._gnss_filter, + self._gnssfilter, ) + format_numsats(len(self._numsats)) + format_headerend() @@ -434,9 +403,10 @@ def convert_ubx_rxmrawx(self, data: UBXMessage): def geta(att: str, i: int): return getattr(data, f"{att}_{i+1:02d}") + newepoch = False epoch, _ = get_epoch(data.week, data.rcvTow, GPS) if epoch != self.__app.get_current_epoch(OBS): - self.__app.set_current_epoch(epoch, OBS) + newepoch = True self._obsdata[epoch] = {} self._obsdata[epoch]["epochflag"] = 0 self._obsdata[epoch]["clkoffset"] = 0.0 @@ -457,15 +427,13 @@ def geta(att: str, i: int): svcode = get_svcode(gnssr, svid) obscode = get_obscode_ubx(gnss, sigid) - # ignore any filtered out gnss - if self._gnss_filter != [""]: - if gnssr not in self._gnss_filter: - continue - - # ignore any unwanted observation codes - if self._obscode_filter != [""]: - if obscode not in self._obscode_filter: - continue + # filter out any unwanted gnss, signal or satellite codes + if ( + (self._gnssfilter != ("",) and gnssr not in self._gnssfilter) + or (self._obsfilter != ("",) and obscode not in self._obsfilter) + or (self._svfilter != ("",) and svcode not in self._svfilter) + ): + continue obs[svcode] = obs.get(svcode, {}) # ssi = get_ssi(cno) # deprecated in 3.05 @@ -476,7 +444,6 @@ def geta(att: str, i: int): lli = geta("lli", i) elif data.identity == "RXM-RAWX": freqid = geta("freqId", i) - 7 - # TODO check lli derivation...? lli = int(not geta("cpValid", i)) if gnssr == GLO: # GLONASS only @@ -501,3 +468,6 @@ def geta(att: str, i: int): ) self._numsats[svcode] = self._numsats.get(svcode, 0) + 1 + + if newepoch and obs != {}: + self.__app.set_current_epoch(epoch, OBS) diff --git a/src/pygnssutils/rinex_globals.py b/src/pygnssutils/rinex_globals.py index 7d1b031..298eb79 100644 --- a/src/pygnssutils/rinex_globals.py +++ b/src/pygnssutils/rinex_globals.py @@ -28,10 +28,6 @@ D2 = "D2" DATAWIDTH = 60 EOP = "EOP" -EPOCH0_BEIDOU = datetime(2006, 1, 1, 0, 0, 0, tzinfo=timezone.utc) -EPOCH0_GAL = datetime(1999, 8, 22, 0, 0, 0, tzinfo=timezone.utc) -EPOCH0_GPS = datetime(1980, 1, 6, 0, 0, 0, tzinfo=timezone.utc) -EPOCH0_IRN = datetime(1999, 8, 22, 0, 0, 0, tzinfo=timezone.utc) EPOCHMAX = datetime(9999, 12, 31, tzinfo=timezone.utc) EPOCHMIN = datetime(1900, 1, 1, tzinfo=timezone.utc) FDMA = "FDMA" @@ -53,17 +49,18 @@ MIX = "M" NAV = "N" NEQUICK = "NEQUICK" +NMEA = "nmea" OBS = "O" OMEGADOTREF = -2.6e-9 -PYRINEXCONV_VERSION = "0.1.5 Alpha" +PYRINEXCONV_VERSION = "0.1.7 Alpha" QZS = "J" RINEX_CANCELLED = 2 RINEX_ERROR = 99 RINEX_NORECS = 1 RINEX_OK = 0 RINEX4 = "4.00" -RINEXVERSIONS = ["3.05", "4.02"] -RINEXVER_DEFAULT = RINEXVERSIONS[0] +RINEXVER_DEFAULT = "3.05" +RTCM3 = "rtcm3" SBA = "S" START = "STA" STO = "STO" @@ -71,26 +68,26 @@ TIME_BEIDOU = "BDT" TIME_GPS = "GPS" TIME_UNDEFINED = "00U" +UBLOX = "u-blox" -ALLGNSS = [GPS, GLO, GAL, BDS, SBA, QZS, IRN] -"""All Available GNSS Codes.""" -ALLOBS = [OBS, NAV, MET] -"""All Available Observation Codes.""" RINEXTYPE = {OBS: "observation", NAV: "navigation", MET: "meteorology"} """RINEX File Types.""" # scaling factors P1_D1 = 0.1 P2_N2 = 0.25 # 2**-2 +P2_N3 = 0.125 # 2**-3 P2_N4 = 0.0625 # 2**-4 P2_N5 = 0.03125 # 2**-5 P2_N6 = 0.015625 # 2**-6 P2_N8 = 0.00390625 # 2**-8 P2_N9 = 0.001953125 # 2**-9 +P2_N10 = 0.0009765625 # 2**-10 P2_N11 = 0.00048828125 # 2**-11 P2_N14 = 6.103515625e-05 # 2**-14 P2_N15 = 3.0517578125e-05 # 2**-15 P2_N16 = 1.52587890625e-05 # 2**-16 +P2_N18 = 3.814697265625e-06 # 2**-18 P2_N19 = 1.9073486328125e-06 # 2**-19 P2_N20 = 9.5367431640625e-07 # 2**-20 P2_N21 = 4.76837158203125e-07 # 2**-21 @@ -110,6 +107,7 @@ P2_N35 = 2.9103830456733704e-11 # 2**-35 P2_N37 = 7.275957614183426e-12 # 2**-37 P2_N38 = 3.637978807091713e-12 # 2**-38 +P2_N39 = 1.8189894035458565e-12 # 2**-39 P2_N40 = 9.094947017729282e-13 # 2**-40 P2_N41 = 4.547473508864641e-13 # 2**-41 P2_N43 = 1.1368683772161603e-13 # 2**-43 @@ -133,6 +131,13 @@ P2_P14 = 16384 # 2**14 P2_P16 = 65536 # 2**16 +RINEXDATASOURCE = { + "R": "Receiver", + "S": "Stream", + "U": "Unknown", +} +"""RINEX Datasources.""" + RINEXGNSSR = { GPS: "GPS", SBA: "SBS", @@ -219,19 +224,19 @@ (0, 0): "1C", # GPS L1 C/A Legacy LNAV # (0, 1): "1S", # GPS L1C D Data code CNV2 (not yet implemented by u-blox) * # (0, 2): "1L", # GPS L1C P Pilot code CNV2 (not yet implemented by u-blox) * - (0, 3): "2L", # GPS L2C L Civil Long-length code CNAV - (0, 4): "2S", # GPS L2C M Civil Moderate code CNAV - (0, 6): "5I", # GPS L5 I In-phase code CNAV - (0, 7): "5Q", # GPS L5 Q Quadrature code CNAV * + (0, 3): "2X", # GPS L2C L Civil Long-length code CNAV + (0, 4): "2X", # GPS L2C M Civil Moderate code CNAV + (0, 6): "5X", # GPS L5 I In-phase code CNAV + (0, 7): "5X", # GPS L5 Q Quadrature code CNAV * (1, 0): "1C", # SBA L1 C/A - (2, 0): "1C", # GAL E1_C * - (2, 1): "1B", # GAL E1 (E1_B) INAV - (2, 3): "5I", # GAL E5a (E5_aI) FNAV - (2, 4): "5Q", # GAL E5_aQ * - (2, 5): "7I", # GAL E5_bI INAV * - (2, 6): "7Q", # GAL E5_bQ * - (2, 8): "6B", # GAL E6 (E6_B) CNAV - (2, 9): "6C", # GAL E6_C * + (2, 0): "1X", # GAL E1_C * + (2, 1): "1X", # GAL E1 (E1_B) INAV + (2, 3): "5X", # GAL E5a (E5_aI) FNAV + (2, 4): "5X", # GAL E5_aQ * + (2, 5): "7X", # GAL E5_bI INAV * + (2, 6): "7X", # GAL E5_bQ * + (2, 8): "6X", # GAL E6 (E6_B) CNAV + (2, 9): "6X", # GAL E6_C * (3, 0): "2I", # BDS B1I_D1 D1 (3, 1): "2I", # BDS B1I_D2 D2 (3, 2): "7I", # BDS B2I_D1 D1 * @@ -244,10 +249,10 @@ (3, 8): "5D", # BDS B2A (B2_ad) CNV2 * (5, 0): "1C", # QZS L1_C/A LNAV (5, 1): "1Z", # QZS L1_S - (5, 4): "2S", # QZS L2_CM CNAV - (5, 5): "2L", # QZS L2_CL - (5, 8): "5I", # QZS L5_I CNAV - (5, 9): "5Q", # QZS L5_Q * + (5, 4): "2X", # QZS L2_CM CNAV + (5, 5): "2X", # QZS L2_CL + (5, 8): "5X", # QZS L5_I CNAV + (5, 9): "5X", # QZS L5_Q * (5, 12): "1B", # QZS L1_CB CNV2 * (6, 0): "1C", # GLO L1_OF (6, 2): "2C", # GLO L2_OF diff --git a/src/pygnssutils/rinex_helpers.py b/src/pygnssutils/rinex_helpers.py index a95579e..c14cde6 100644 --- a/src/pygnssutils/rinex_helpers.py +++ b/src/pygnssutils/rinex_helpers.py @@ -20,29 +20,33 @@ from types import NoneType from typing import Literal -from pynmeagps import leapsecond, utc2wnotow, wnotow2utc +from pynmeagps import EPOCH0_BEIDOU, EPOCH0_GPS, leapsecond, utc2wnotow, wnotow2utc +from pygnssutils.gnssreader import NMEA_PROTOCOL, RTCM3_PROTOCOL, UBX_PROTOCOL from pygnssutils.rinex_globals import ( BDS, DATAWIDTH, - EPOCH0_BEIDOU, - EPOCH0_GPS, EPOCHMAX, EPOCHMIN, GLO, GPS, KLOB, + MET, MIX, NAV, NEQUICK, + NMEA, + OBS, PYRINEXCONV_VERSION, QZS, RINEXGNSSR, RINEXTYPE, + RTCM3, SBA, TIME_BEIDOU, TIME_GPS, TIME_UNDEFINED, + UBLOX, UBXRINEXOBSCODE, ) @@ -255,9 +259,46 @@ def get_ssi(cno: float) -> int: return min(max(int(cno / 6), 1), 9) +def format_observation_epoch( + epoch: datetime | str, + numobs: int | str, + epochflag: int | str = "0", + clkoffset: float | str = "", + picosecond: int | str = "", +) -> str: + """ + Format observation epoch. + + :param datetime | str epoch: observation epoch or blank if event + :param int | str numobs: number of observations in this epoch + :param int | str epochflag: epoch flag + :param float | str clkoffset: clock offset + :param int | str picosets: picoseconds (RINEX 4 only) + :return: formatted string + :rtype: str + """ + + nummeas = f"{str(numobs):<2}" + if clkoffset == 0.0: + clkoffset = "" + if epoch == "": # event + return f">{'':>39}{epochflag:>3}{numobs:>3}{'':>21}\n" # A1 ... 2X,I1 I3 + # epoch + if isinstance(picosecond, int): + pico = f"{picosecond:05d}" + else: + pico = f"{picosecond:>5}" + return ( + f">{epoch.year:>5}{epoch.month:>3}{epoch.day:>3}" + f"{epoch.hour:>3}{epoch.minute:>3}" + f"{FRNX(epoch.second + epoch.microsecond/1000000,11,7)}" + f"{epochflag:>3}{nummeas:>3}{'':>6}{FRNX(clkoffset,15,12)} {pico}\n" + ) # A1 1X,I4 4(1X,I2.2) F11.7 2X,I1 I3 6X F15.12 (1X,I5.5) + + def format_filename( rinextype: Literal["O", "N", "M"], - gnssfilter: list[str], + gnssfilter: tuple[str], startepoch: datetime, endepoch: datetime, interval: int | float, @@ -275,7 +316,7 @@ def format_filename( e.g. pygpsdata_R_202604101416_30M_01S_MO.rnx :param Literal["O","N","M"] rinextype: RINEX type - :param list[str] gnssfilter: list of GNSS systems included (G, E, etc.) + :param tuple[str] gnssfilter: tuple of GNSS systems included (G, E, etc.) :param datetime startepoch: first observation epoch :param datetime endepoch: last observation epoch :param int | float interval: observation interval in seconds @@ -313,33 +354,33 @@ def format_filename( start = startepoch.strftime("%Y%m%d%H%M") gnu = gnssr.upper() rtu = rinextype.upper() - if source.upper() in ("UBLOX", "NMEA"): + if source.lower() in (UBLOX, NMEA): src = "R" - elif source.upper() in ("RTCM3", "NTRIP", "N"): + elif source.lower() in (RTCM3,): src = "S" else: - src = source.upper() + src = "U" return Path.joinpath( outputpath, f"{station}_{src}_{start}_{period}_{frequency}{gnu}{rtu}.rnx" ) -def format_comments(comments: list | str = "") -> str: +def format_comments(comments: tuple[str] | str = "") -> str: """ Format comments. - :param list | str comments: comments + :param tuple[str] | str comments: comments :return: formatted string :rtype: str """ if comments == "": - comments = [] + comments = () elif isinstance(comments, str): - comments = [comments] + comments = (comments,) out = "" - for comment in list(comments): + for comment in comments: while len(comment) > 0: comm = comment[0:DATAWIDTH] out += f"{comm:<{DATAWIDTH}}COMMENT\n" # A60 @@ -348,7 +389,7 @@ def format_comments(comments: list | str = "") -> str: def format_version( - rinexver: str, rinextype: Literal["O", "N", "M"], gnssr: list[str] | str + rinexver: str, rinextype: Literal["O", "N", "M"], gnssr: tuple[str] | str ) -> str: """ Format RINEX version. @@ -363,8 +404,10 @@ def format_version( # F9.2, 11X A1,19X A1,19X filedesc = RINEXTYPE[rinextype].upper() filetype = f"{rinextype}: {filedesc}" - gnsstypes = [gnssr] if isinstance(gnssr, str) else gnssr - gnsstypes = MIX if (len(gnsstypes) > 1 or gnssr in ([""], ["M"])) else gnsstypes[0] + gnsstypes = (gnssr,) if isinstance(gnssr, str) else gnssr + gnsstypes = ( + MIX if (len(gnsstypes) > 1 or gnssr in (("",), ("M",))) else gnsstypes[0] + ) gnssdesc = RINEXGNSSR[gnsstypes].upper() gnsstype = f"{gnsstypes}: {gnssdesc}" return ( @@ -930,7 +973,7 @@ def format_glonassphasebias(corrs: dict | str = "") -> str: :rtype: str """ - if corrs == "": + if isinstance(corrs, str): corrs = {} # 4(X1,A3,X1,F8.3) @@ -942,7 +985,7 @@ def format_glonassphasebias(corrs: dict | str = "") -> str: def format_leapseconds( epoch: datetime | str = "", - gnssfilter: list[str] | str = "", + gnssfilter: tuple[str] | str = "", ) -> str: """ Format Current Number of leap seconds (optional). @@ -951,12 +994,12 @@ def format_leapseconds( this refers to; have assumed first observation date :param datetime | str epoch: epoch to which leapsecond refers - :param list[str] | str gnssfilter: list of gnss included + :param tuple[str] | str gnssfilter: tuple of gnss included :return: formatted string :rtype: str """ - if epoch == "" or gnssfilter in (GLO, [GLO]): # omit if only GLONASS + if epoch == "" or gnssfilter in (GLO, (GLO,)): # omit if only GLONASS return "" timesource, e0 = ( @@ -1385,17 +1428,63 @@ def format_fileend(): return format_comments("END OF FILE") -def listify(arg: list[str] | str | NoneType) -> list[str]: - """ - Convert comma-separated CLI argument str to list type. +def set_filters( + rinex_types: tuple[str], + obssource: str, + navsource: str, + metsource: str, +) -> tuple[int, tuple]: + """ + Filter unneeded message protocols/types to speed conversion. + + :param tuple[str] rinex_type: RINEX output type(s) e.g. ("O","N") + :param str obssource: Source of observation data + :param str navsource: Source of navigation data + :param str metsource: Source of meteorology data + :return: tuple of protfilter, msgfilter + :rtype: tuple[int, tuple] + """ + + protfilter = 0 + msgfilter = () + if OBS in rinex_types: + if obssource == UBLOX: + protfilter |= UBX_PROTOCOL + msgfilter += (533, 263) # RXM-RAWX, NAV-PVT + if NAV in rinex_types: + if navsource.lower() == UBLOX: + protfilter |= UBX_PROTOCOL + msgfilter += (531,) # RXM-SFRBX + elif navsource.lower() == RTCM3: + protfilter |= RTCM3_PROTOCOL + msgfilter += ( + 1005, + 1006, + 1019, + 1020, + 1041, + 1042, + 1044, + 1045, + 1046, + ) # ephemerides + if MET in rinex_types: + if metsource == NMEA: + protfilter |= NMEA_PROTOCOL + return protfilter, msgfilter + + +def tuplefy(arg: list[str] | str | NoneType) -> tuple[str]: + """ + Convert comma-separated CLI argument str to tuple type. :parm list[str] | str | NoneType arg: argument :return: argument as list - :rtype: list[str] + :rtype: tuple[str] """ if arg is None: - return [""] - if isinstance(arg, (list, tuple)): - return list(arg) - return [i.strip() for i in arg.split(",")] + return ("",) + if isinstance(arg, tuple): + return arg + return tuple([i.strip() for i in arg.split(",")]) diff --git a/tests/pygpsdata-NTRIP-USCL00CHL0.log b/tests/pygpsdata-NTRIP-USCL00CHL0.log new file mode 100644 index 0000000..77e5418 Binary files /dev/null and b/tests/pygpsdata-NTRIP-USCL00CHL0.log differ diff --git a/tests/test_rawnav.py b/tests/test_rawnav.py index c25c672..cfdbb3e 100644 --- a/tests/test_rawnav.py +++ b/tests/test_rawnav.py @@ -12,7 +12,7 @@ from datetime import datetime, timezone from pygnssutils.exceptions import RINEXProcessingError -from pygnssutils.rawnav import RawNav, PREAMBLE, VALPREAMBLE, U, S +from pygnssutils.rawnav import RawNav, PREAMBLE, U, S from pygnssutils.rawnav_subframes_gps import ( GPS_LNAV_SUBFRAME_1, GPS_LNAV_SUBFRAME_3, @@ -21,7 +21,6 @@ ) SUBFRAME1 = { - VALPREAMBLE: 0b10001011, # optional, used to validate preamble value PREAMBLE: (0, 8, U, 0), "test": (8, 8, U, 0), "tlm_msb": (8, 6, U, 0), @@ -30,7 +29,6 @@ "_parity1": (24, 6, U, 0), } SUBFRAME2 = { - VALPREAMBLE: 0b10001011, # optional, used to validate preamble value PREAMBLE: (0, 8, U, 0), "tlm_lsb": (8, 14, U, 0), "antispoof": (22, 1, U, 0), @@ -38,7 +36,6 @@ "_parity1": (24, 6, U, 0), } SUBFRAME3 = { - VALPREAMBLE: 0b10001011, # optional, used to validate preamble value PREAMBLE: (0, 8, U, 0), "omegadot": (8, 7, S, 2e-23), "m0": (22, 8, S, 2e-8), diff --git a/tests/test_rinex.py b/tests/test_rinex.py index adf948f..5091b03 100644 --- a/tests/test_rinex.py +++ b/tests/test_rinex.py @@ -27,7 +27,20 @@ BDS_D1_SUBFRAME_3, ) from pygnssutils.rinex_conv import RinexConverter -from pygnssutils.rinex_globals import BDS, EPOCH0_GPS, EPOCHMAX, EPOCHMIN, GAL, GPS, IRN +from pygnssutils.rinex_globals import ( + BDS, + EPOCHMAX, + EPOCHMIN, + GAL, + GPS, + IRN, + OBS, + NAV, + MET, + NMEA, + UBLOX, + RTCM3, +) from pygnssutils.rinex_helpers import ( DRNX, FRNX, @@ -77,7 +90,8 @@ get_svcode, gpsura2m, glotk2sec, - listify, + tuplefy, + set_filters, ) SENSORTYPES = { @@ -369,6 +383,16 @@ def testformat_version(self): res, " 3.05 O: OBSERVATION M: MIXED RINEX VERSION / TYPE\n", ) + res = format_version("4.02", "O", "G") + self.assertEqual( + res, + " 4.02 O: OBSERVATION G: GPS RINEX VERSION / TYPE\n", + ) + res = format_version("4.02", "N", ("G", "E")) + self.assertEqual( + res, + " 4.02 N: NAVIGATION M: MIXED RINEX VERSION / TYPE\n", + ) def testformat_iono_corr(self): EXPECTED_RESULT = ( @@ -530,19 +554,14 @@ def testadjust_time_units(self): self.assertEqual(adjust_time_units("asdfa"), (0, "U")) self.assertEqual(adjust_time_units(1.23e20), (0, "U")) - def testlistify(self): - self.assertEqual(listify("first,second,third"), ["first", "second", "third"]) - self.assertEqual(listify("first, second, third "), ["first", "second", "third"]) - self.assertEqual( - listify(["first", "second", "third"]), ["first", "second", "third"] - ) - self.assertEqual(listify("test"), ["test"]) - self.assertEqual(listify(""), [""]) - self.assertEqual(listify(None), [""]) - self.assertEqual(listify([""]), [""]) + def testtuplefy(self): + self.assertEqual(tuplefy("first,second,third"), ("first", "second", "third")) + self.assertEqual(tuplefy("first, second, third "), ("first", "second", "third")) + self.assertEqual(tuplefy("test"), ("test",)) + self.assertEqual(tuplefy(""), ("",)) + self.assertEqual(tuplefy(None), ("",)) def testgpsura2m(self): - self.assertEqual(gpsura2m(1), 2.8) self.assertEqual(gpsura2m(2), 4.0) self.assertEqual(gpsura2m(3), 5.7) @@ -552,3 +571,18 @@ def testgpsura2m(self): self.assertEqual(gpsura2m(15), 0) self.assertEqual(gpsura2m(-16), 0) self.assertEqual(gpsura2m(-8), 0.1) + + def testsetfilters(self): + self.assertEqual( + set_filters((OBS, NAV, MET), UBLOX, UBLOX, NMEA), (3, (533, 263, 531)) + ) + self.assertEqual( + set_filters((OBS, NAV, MET), UBLOX, RTCM3, NMEA), + (7, (533, 263, 1005, 1006, 1019, 1020, 1041, 1042, 1044, 1045, 1046)), + ) + self.assertEqual( + set_filters((OBS, NAV), UBLOX, UBLOX, NMEA), (2, (533, 263, 531)) + ) + self.assertEqual(set_filters((OBS), UBLOX, UBLOX, NMEA), (2, (533, 263))) + self.assertEqual(set_filters((NAV), UBLOX, UBLOX, NMEA), (2, (531,))) + self.assertEqual(set_filters((MET), UBLOX, UBLOX, NMEA), (1, ())) diff --git a/tests/test_rinex_defs.py b/tests/test_rinex_defs.py index b85d67b..bc3845a 100644 --- a/tests/test_rinex_defs.py +++ b/tests/test_rinex_defs.py @@ -1,7 +1,7 @@ """ Sanity check ICD payload definitions for RINEX conversion. -Check that individual bit offsets and lengths and total payload +Check that individual bit lengths and total payload length are consistent with ICD definition. Created on 26 May 2022 @@ -18,7 +18,7 @@ import pygnssutils.rawnav_subframes_sba as sba import pygnssutils.rawnav_subframes_qzs as qzs import pygnssutils.rawnav_subframes_irn as irn -from pygnssutils.rawnav import VALPREAMBLE +from pygnssutils.rawnav import SUBFRAMELENGTH class StaticTest(unittest.TestCase): @@ -28,16 +28,18 @@ def setUp(self): def tearDown(self): pass - def scandefs(self, sfrdefs, sfrlen): + def scandefs(self, sfrdefs): for dic in sfrdefs: offset = 0 totlen = 0 + sfrlen = 0 for key, vals in dic.items(): - if key == VALPREAMBLE: + if key == SUBFRAMELENGTH: + sfrlen = vals continue - off, len, typ, sca = vals - self.assertEqual(off, offset) - # print(f'"{key}": ({offset},{len},{typ},{sca}),') + self.assertGreater(sfrlen,0) + len, typ, sca = vals + # print(f'"{key}": (len},{typ},{sca}),') offset += len totlen += len self.assertEqual(totlen, sfrlen) @@ -50,19 +52,18 @@ def testGPSLNAV(self): gps.GPS_LNAV_SUBFRAME_3, gps.GPS_LNAV_SUBFRAME_4_P18, ) - sfrlen = 300 - self.scandefs(sfrdefs, sfrlen) + self.scandefs(sfrdefs) def testGPSCNAV(self): sfrdefs = ( gps.GPS_CNAV_SUBFRAME_10, gps.GPS_CNAV_SUBFRAME_11, - gps.GPS_CNAV_SUBFRAME_12, - gps.GPS_CNAV_SUBFRAME_13, - gps.GPS_CNAV_SUBFRAME_14, + #gps.GPS_CNAV_SUBFRAME_12, + #gps.GPS_CNAV_SUBFRAME_13, + #gps.GPS_CNAV_SUBFRAME_14, gps.GPS_CNAV_SUBFRAME_15, gps.GPS_CNAV_SUBFRAME_30, - gps.GPS_CNAV_SUBFRAME_31, + #gps.GPS_CNAV_SUBFRAME_31, gps.GPS_CNAV_SUBFRAME_32, gps.GPS_CNAV_SUBFRAME_33, gps.GPS_CNAV_SUBFRAME_34, @@ -71,8 +72,7 @@ def testGPSCNAV(self): gps.GPS_CNAV_SUBFRAME_37, gps.GPS_CNAV_SUBFRAME_40, ) - sfrlen = 300 - self.scandefs(sfrdefs, sfrlen) + self.scandefs(sfrdefs) def testGALFNAV(self): @@ -84,14 +84,12 @@ def testGALFNAV(self): gal.GAL_FNAV_SUBFRAME_5, gal.GAL_FNAV_SUBFRAME_6, ) - sfrlen = 244 - self.scandefs(sfrdefs, sfrlen) + self.scandefs(sfrdefs) def testGALINAV(self): sfrdefs = (gal.GAL_INAV_SUBFRAME,) - sfrlen = 256 - self.scandefs(sfrdefs, sfrlen) + self.scandefs(sfrdefs) sfrdefs = ( gal.GAL_INAV_WORD_1, @@ -106,8 +104,7 @@ def testGALINAV(self): gal.GAL_INAV_WORD_10, gal.GAL_INAV_WORD_16, ) - sfrlen = 128 - self.scandefs(sfrdefs, sfrlen) + self.scandefs(sfrdefs) def testBDSD1(self): @@ -118,8 +115,7 @@ def testBDSD1(self): bds.BDS_D1_SUBFRAME_5_P09, bds.BDS_D1_SUBFRAME_5_P10, ) - sfrlen = 300 - self.scandefs(sfrdefs, sfrlen) + self.scandefs(sfrdefs) def testBDSD2(self): @@ -136,7 +132,7 @@ def testBDSD2(self): bds.BDS_D2_SUBFRAME_1_P10, ) sfrlen = 300 - self.scandefs(sfrdefs, sfrlen) + self.scandefs(sfrdefs) def testGLOL1OF(self): @@ -146,19 +142,44 @@ def testGLOL1OF(self): glo.GLO_L1OF_SUBFRAME_3, glo.GLO_L1OF_SUBFRAME_4, glo.GLO_L1OF_SUBFRAME_5, + glo.GLO_L1OF_SUBFRAME_6, + glo.GLO_L1OF_SUBFRAME_7, + glo.GLO_L1OF_SUBFRAME_8, + glo.GLO_L1OF_SUBFRAME_9, + glo.GLO_L1OF_SUBFRAME_10, + glo.GLO_L1OF_SUBFRAME_11, + glo.GLO_L1OF_SUBFRAME_12, + glo.GLO_L1OF_SUBFRAME_13, + glo.GLO_L1OF_SUBFRAME_14, + glo.GLO_L1OF_SUBFRAME_15, + glo.GLO_L1OF_SUBFRAME_14_F5, + glo.GLO_L1OF_SUBFRAME_15_F5, ) - sfrlen = 85 - self.scandefs(sfrdefs, sfrlen) + self.scandefs(sfrdefs) def testSBAL1CA(self): sfrdefs = ( + # sba.SBA_L1CA_MT_0, + sba.SBA_L1CA_MT_1, + # sba.SBA_L1CA_MT_2, + # sba.SBA_L1CA_MT_3, + # sba.SBA_L1CA_MT_4, + # sba.SBA_L1CA_MT_5, + # sba.SBA_L1CA_MT_6, + # sba.SBA_L1CA_MT_7, sba.SBA_L1CA_MT_9, + sba.SBA_L1CA_MT_10, sba.SBA_L1CA_MT_12, - sba.SBA_L1CA_MT_17, + # sba.SBA_L1CA_MT_17, + sba.SBA_L1CA_MT_18, + # sba.SBA_L1CA_MT_24, + # sba.SBA_L1CA_MT_25, + # sba.SBA_L1CA_MT_26, + # sba.SBA_L1CA_MT_27, + # sba.SBA_L1CA_MT_28, ) - sfrlen = 250 - self.scandefs(sfrdefs, sfrlen) + self.scandefs(sfrdefs) def testQZSLNAV(self): @@ -169,26 +190,24 @@ def testQZSLNAV(self): qzs.QZS_LNAV_SUBFRAME_4_P56, qzs.QZS_LNAV_SUBFRAME_5_P56, ) - sfrlen = 300 - self.scandefs(sfrdefs, sfrlen) + self.scandefs(sfrdefs) def testQZSCNAV(self): sfrdefs = ( + qzs.QZS_CNAV_SUBFRAME_10, qzs.QZS_CNAV_SUBFRAME_10, qzs.QZS_CNAV_SUBFRAME_11, qzs.QZS_CNAV_SUBFRAME_30, qzs.QZS_CNAV_SUBFRAME_32, qzs.QZS_CNAV_SUBFRAME_33, ) - sfrlen = 300 - self.scandefs(sfrdefs, sfrlen) + self.scandefs(sfrdefs) def testQZSCNV2_2(self): sfrdefs = ( qzs.QZS_CNV2_SUBFRAME_2, ) - sfrlen = 600 - self.scandefs(sfrdefs, sfrlen) + self.scandefs(sfrdefs) def testQZSCNV2_3(self): sfrdefs = ( @@ -196,8 +215,7 @@ def testQZSCNV2_3(self): qzs.QZS_CNV2_SUBFRAME_3_P2, qzs.QZS_CNV2_SUBFRAME_3_P61, ) - sfrlen = 274 - self.scandefs(sfrdefs, sfrlen) + self.scandefs(sfrdefs) def testIRNLNAV(self): @@ -208,5 +226,4 @@ def testIRNLNAV(self): irn.IRN_LNAV_SUBFRAME_3_P9, irn.IRN_LNAV_SUBFRAME_3_P11, ) - sfrlen = 292 - self.scandefs(sfrdefs, sfrlen) + self.scandefs(sfrdefs) diff --git a/tests/test_rinex_parse.py b/tests/test_rinex_parse.py index e49b505..fcf9a5d 100644 --- a/tests/test_rinex_parse.py +++ b/tests/test_rinex_parse.py @@ -12,7 +12,16 @@ import unittest -from pygnssutils.rawnav import RawNav +from pygnssutils.rawnav import ( + RawNav, + RINEXProcessingError, + SUBFRAMELENGTH, + WN, + TOW, + SID, + U, + S, +) from pygnssutils.rawnav_subframes_glo import ( GLO_L1OF_SUBFRAME_1, GLO_L1OF_SUBFRAME_2, @@ -27,8 +36,8 @@ SBA_L1CA_MT_9, SBA_L1CA_MT_12, SBA_L1CA_MT_17, + SBA_L1CA_MT_25, ) -from pygnssutils.rinex_helpers import adjust_time_units,gpsura2m,listify # only run RINEX file tests locally RINEXFILETEST = False # system() == "Darwin" @@ -41,85 +50,52 @@ def setUp(self): def tearDown(self): pass - def testadjust_time_units(self): - self.assertEqual(adjust_time_units(34), (34, "S")) - self.assertEqual(adjust_time_units(345), (6.0, "M")) - self.assertEqual(adjust_time_units(4800), (80, "M")) - self.assertEqual(adjust_time_units(12345), (3.0, "H")) - self.assertEqual(adjust_time_units(1958400), (23.0, "D")) - self.assertEqual(adjust_time_units(1958400000), (62.0, "Y")) - self.assertEqual(adjust_time_units("asdfa"), (0, "U")) - self.assertEqual(adjust_time_units(1.23e20), (0, "U")) - - def testlistify(self): - self.assertEqual(listify("first,second,third"), ["first", "second", "third"]) - self.assertEqual(listify("first, second, third "), ["first", "second", "third"]) - self.assertEqual( - listify(["first", "second", "third"]), ["first", "second", "third"] - ) - self.assertEqual(listify("test"), ["test"]) - self.assertEqual(listify(""), [""]) - self.assertEqual(listify(None), [""]) - self.assertEqual(listify([""]), [""]) - - def testgpsura2m(self): - - self.assertEqual(gpsura2m(1), 2.8) - self.assertEqual(gpsura2m(2), 4.0) - self.assertEqual(gpsura2m(3), 5.7) - self.assertEqual(gpsura2m(5), 11.3) - self.assertEqual(gpsura2m(8), 64) - self.assertEqual(gpsura2m(14), 4096) - self.assertEqual(gpsura2m(15), 0) - self.assertEqual(gpsura2m(-16), 0) - self.assertEqual(gpsura2m(-8), 0.1) - def testrawnavparserGLO( self, ): # test parser produces correct scaled values from subframe bits - EXPECTED_RESULT = "" + EXPECTED_RESULT = "" BITS = ( ( ( - "0", # idle - "0001", # sid - "00", # reserved - "01", # p1 - "000110001110", # tk - "000000000000000000000111", # xntbdot - "11001", # xntbdot2 - "000000000000000000000001111", # xntb - "00000000", # hamming + "0", # idle + "0001", # sid + "00", # reserved + "01", # p1 + "000110001110", # tk + "000000000000000000000111", # xntbdot + "11001", # xntbdot2 + "000000000000000000000001111", # xntb + "00000000", # hamming ), GLO_L1OF_SUBFRAME_1, ), ( ( - "0", # idle + "0", # idle "0010", # sid - "001", # bn - "1", # p2 - "0000111", # tb - "00000", # reserved - "000000000000000000001111", # yntbdot - "10001", # yntbdot2 - "000000000000000000000011111", # yntb + "001", # bn + "1", # p2 + "0000111", # tb + "00000", # reserved + "000000000000000000001111", # yntbdot + "10001", # yntbdot2 + "000000000000000000000011111", # yntb "00000000", # hamming ), GLO_L1OF_SUBFRAME_2, ), ( ( - "0", # idle + "0", # idle "0011", # sid - "1", # p3 - "00000000111", # gammantb - "0", # reserved - "01", # p - "1", # ln - "000000000000000000011111", # zntbdot - "11101", # zntbdot2 - "000000000000000000000111111", # zntb + "1", # p3 + "00000000111", # gammantb + "0", # reserved + "01", # p + "1", # ln + "000000000000000000011111", # zntbdot + "11101", # zntbdot2 + "000000000000000000000111111", # zntb "00000000", # hamming ), GLO_L1OF_SUBFRAME_3, @@ -135,7 +111,7 @@ def testrawnavparserGLO( self.assertEqual(raw.sigcode, "1C") self.assertEqual(raw.svcode, "R 2") self.assertEqual(raw.identity, "R021C") - self.assertEqual(raw.sid, 3) + self.assertEqual(raw.sid, 1) self.assertEqual(raw.subframeacq, 7) self.assertEqual(raw.tk, 398) self.assertEqual(raw.tb, 7 * 15) @@ -154,123 +130,123 @@ def testrawnavparserGLO( def testrawnavparserBDS( self, ): # test parser produces correct scaled values from subframe bits - EXPECTED_RESULT = "" + EXPECTED_RESULT = "" BITS = ( ( ( - "11100010010", # preamble - "0000", # rev - "001", # sid - "00000111", # tow_msb - "0000", # _parity1 - "000000000111", # tow_lsb - "0", # sath1 - "00000", # aodc - "0000", # urai - "00000000", # _parity2 - "0000000000000", # wn - "000000000", # toc_msb - "00000000", # _parity3 - "00000000", # toc_lsb - "0000000000", # tgd1 - "0000", # tgd2_msb - "00000000", # _parity4 - "000000", # tgd2_lsb - "00000000", # alpha0 - "00000000", # alpha1 - "00000000", # _parity5 - "00000000", # alpha2 - "00000000", # alpha3 - "000000", # beta0_msb - "00000000", # _parity6 - "00", # beta0_lsb - "00000000", # beta1 - "00000000", # beta2 - "0000", # beta3_msb - "00000000", # _parity7 - "0000", # beta3_lsb - "00000000000", # af2 - "0000000", # af0_msb - "00000000", # _parity8 - "00000000000000000", # af0_lsb - "00000", # af1_msb - "00000000", # _parity9 - "00000000000000000", # af1_lsb - "00000", # aode - "00000000", # _parity10 + "11100010010", # preamble + "0000", # rev + "001", # sid + "00000111", # tow_msb + "0000", # _parity1 + "000000000111", # tow_lsb + "0", # sath1 + "00000", # aodc + "0000", # urai + "00000000", # _parity2 + "0000000000000", # wn + "000000000", # toc_msb + "00000000", # _parity3 + "00000000", # toc_lsb + "0000000000", # tgd1 + "0000", # tgd2_msb + "00000000", # _parity4 + "000000", # tgd2_lsb + "00000000", # alpha0 + "00000000", # alpha1 + "00000000", # _parity5 + "00000000", # alpha2 + "00000000", # alpha3 + "000000", # beta0_msb + "00000000", # _parity6 + "00", # beta0_lsb + "00000000", # beta1 + "00000000", # beta2 + "0000", # beta3_msb + "00000000", # _parity7 + "0000", # beta3_lsb + "00000000000", # af2 + "0000000", # af0_msb + "00000000", # _parity8 + "00000000000000000", # af0_lsb + "00000", # af1_msb + "00000000", # _parity9 + "00000000000000000", # af1_lsb + "00000", # aode + "00000000", # _parity10 ), - BDS_D1_SUBFRAME_1, + BDS_D1_SUBFRAME_1, ), ( ( - "11100010010", # preamble - "0000", # rev - "010", # sid - "00000111", # tow_msb - "0000", # _parity1 - "000000000111", # tow_lsb - "0000000000", # deltan_msb - "00000000", # _parity2 - "000000", # deltan_lsb - "0000000000000000", # cuc_msb - "00000000", # _parity3 - "00", # cuc_lsb - "00000000000000000000", # m0_msb - "00000000", # _parity4 - "000000000000", # m0_lsb - "0000000000", # e_msb - "00000000", # _parity5 - "0000000000000000000000", # e_lsb - "00000000", # _parity6 - "000000000000000000", # cus - "0000", # crc_msb - "00000000", # _parity7 - "00000000000000", # crc_lsb - "00000000", # crs_msb - "00000000", # _parity8 - "0000000000", # crs_lsb - "101001010001", # sqrta_msb - "00000000", # _parity9 - "01010001110110010001", # sqrta_lsb - "01", # toe_msb - "00000000", # _parity10 + "11100010010", # preamble + "0000", # rev + "010", # sid + "00000111", # tow_msb + "0000", # _parity1 + "000000000111", # tow_lsb + "0000000000", # deltan_msb + "00000000", # _parity2 + "000000", # deltan_lsb + "0000000000000000", # cuc_msb + "00000000", # _parity3 + "00", # cuc_lsb + "00000000000000000000", # m0_msb + "00000000", # _parity4 + "000000000000", # m0_lsb + "0000000000", # e_msb + "00000000", # _parity5 + "0000000000000000000000", # e_lsb + "00000000", # _parity6 + "000000000000000000", # cus + "0000", # crc_msb + "00000000", # _parity7 + "00000000000000", # crc_lsb + "00000000", # crs_msb + "00000000", # _parity8 + "0000000000", # crs_lsb + "101001010001", # sqrta_msb + "00000000", # _parity9 + "01010001110110010001", # sqrta_lsb + "01", # toe_msb + "00000000", # _parity10 ), BDS_D1_SUBFRAME_2, ), ( ( - "11100010010", # preamble - "0000", # rev - "011", # sid - "00000111", # tow_msb - "0000", # _parity1 - "000000000111", # tow_lsb - "0000000111", # toe_isb - "00000000", # _parity2 - "00111", # toe_lsb - "00000000000000000", # i0_msb - "00000000", # _parity3 - "000000000000000", # i0_lsb - "0000000", # cic_msb - "00000000", # _parity4 - "00000000000", # cic_lsb - "00000000111", # omegadot_msb - "00000000", # _parity5 - "0000000000111", # omegadot_lsb - "000000000", # cis_msb - "00000000", # _parity6 - "000000000", # cis_lsb - "0000000000000", # idot_msb - "00000000", # _parity7 - "0", # idot_lsb - "000000000000000000000", # omega0_msb - "00000000", # _parity8 - "00000000000", # omega0_lsb - "00000000000", # omega_msb - "00000000", # _parity9 - "000000000000000000000", # omega_lsb - "0", # rev1 - "00000000", # _parity10 + "11100010010", # preamble + "0000", # rev + "011", # sid + "00000111", # tow_msb + "0000", # _parity1 + "000000000111", # tow_lsb + "0000000111", # toe_isb + "00000000", # _parity2 + "00111", # toe_lsb + "00000000000000000", # i0_msb + "00000000", # _parity3 + "000000000000000", # i0_lsb + "0000000", # cic_msb + "00000000", # _parity4 + "00000000000", # cic_lsb + "00000000111", # omegadot_msb + "00000000", # _parity5 + "0000000000111", # omegadot_lsb + "000000000", # cis_msb + "00000000", # _parity6 + "000000000", # cis_lsb + "0000000000000", # idot_msb + "00000000", # _parity7 + "0", # idot_lsb + "000000000000000000000", # omega0_msb + "00000000", # _parity8 + "00000000000", # omega0_lsb + "00000000000", # omega_msb + "00000000", # _parity9 + "000000000000000000000", # omega_lsb + "0", # rev1 + "00000000", # _parity10 ), BDS_D1_SUBFRAME_3, ), @@ -285,7 +261,7 @@ def testrawnavparserBDS( self.assertEqual(raw.sigcode, "2I") self.assertEqual(raw.svcode, "C12") self.assertEqual(raw.identity, "C122I") - self.assertEqual(raw.sid, 3) + self.assertEqual(raw.sid, 1) self.assertEqual(raw.subframeacq, 7) self.assertEqual(raw.tow, 0b00000111000000000111) self.assertEqual(raw.toe, 0b01000000011100111 * 2**3) @@ -297,84 +273,84 @@ def testrawnavparserBDS( def testrawnavparserSBA( self, ): # test parser produces correct scaled values from subframe bits - EXPECTED_RESULT = "" + EXPECTED_RESULT = "" BITS = ( ( ( - "00000000", # _preamble - "001001", # sid - "00000000", # iodn - "0000000000000", # t0 - "0000", # ura - "000000000000000000000000000111", # xpos - "000000000000000000000000001111", # ypos - "0000000000000000000011111", # zpos - "00000000000000111", # xdot - "00000000000001111", # ydot - "000000000000011111", # zdot - "0000000111", # xdot2 - "0000001111", # ydot2 - "0000011111", # zdot2 - "000000000111", # agf0 - "00001111", # agf1 - "000000000000000000000000", # _parity + "00000000", # _preamble + "001001", # sid + "00000000", # iodn + "0000000000000", # t0 + "0000", # ura + "000000000000000000000000000111", # xpos + "000000000000000000000000001111", # ypos + "0000000000000000000011111", # zpos + "00000000000000111", # xdot + "00000000000001111", # ydot + "000000000000011111", # zdot + "0000000111", # xdot2 + "0000001111", # ydot2 + "0000011111", # zdot2 + "000000000111", # agf0 + "00001111", # agf1 + "000000000000000000000000", # _parity ), SBA_L1CA_MT_9, ), ( ( - "00000000", # _preamble - "001100", # sid - "000000000000000000000111", # a1 - "00000000000000000000000000001111", # a0 - "00000111", # toc - "00000111", # wnt - "00000111", # deltatls - "00000111", # wnlsf - "00000111", # dn - "00000111", # deltatlsf - "011", # utcid - "00000000000000000111", # tow - "0000000111", # wn - "0", # gloind - "00000000000000000000000000000000000000000000000000000000000000000000000111", # deltatglo - "000000000000000000000000", # _parity + "00000000", # _preamble + "001100", # sid + "000000000000000000000111", # a1 + "00000000000000000000000000001111", # a0 + "00000111", # toc + "00000111", # wnt + "00000111", # deltatls + "00000111", # wnlsf + "00000111", # dn + "00000111", # deltatlsf + "011", # utcid + "00000000000000000111", # tow + "0000000111", # wn + "0", # gloind + "00000000000000000000000000000000000000000000000000000000000000000000000111", # deltatglo + "000000000000000000000000", # _parity ), SBA_L1CA_MT_12, ), ( ( - "00000000", # _preamble - "010001", # sid - "00", # dataid_01 - "00000000", # prn_01 - "00000000", # svhealth_01 - "000000000000000", # xg_01 - "000000000000000", # yg_01 - "000000000", # zg_01 - "000", # xgdot_01 - "000", # ygdot_01 - "0000", # zgdot_01 - "00", # dataid_02 - "00000000", # prn_02 - "00000000", # svhealth_02 - "000000000000000", # xg_02 - "000000000000000", # yg_02 - "000000000", # zg_02 - "000", # xgdot_02 - "000", # ygdot_02 - "0000", # zgdot_02 - "00", # dataid_03 - "00000000", # prn_03 - "00000000", # svhealth_03 - "000000000000000", # xg_03 - "000000000000000", # yg_03 - "000000000", # zg_03 - "000", # xgdot_03 - "000", # ygdot_03 - "0000", # zgdot_03 - "00000000000", # t0 - "000000000000000000000000", # _parity + "00000000", # _preamble + "010001", # sid + "00", # dataid_01 + "00000000", # prn_01 + "00000000", # svhealth_01 + "000000000000000", # xg_01 + "000000000000000", # yg_01 + "000000000", # zg_01 + "000", # xgdot_01 + "000", # ygdot_01 + "0000", # zgdot_01 + "00", # dataid_02 + "00000000", # prn_02 + "00000000", # svhealth_02 + "000000000000000", # xg_02 + "000000000000000", # yg_02 + "000000000", # zg_02 + "000", # xgdot_02 + "000", # ygdot_02 + "0000", # zgdot_02 + "00", # dataid_03 + "00000000", # prn_03 + "00000000", # svhealth_03 + "000000000000000", # xg_03 + "000000000000000", # yg_03 + "000000000", # zg_03 + "000", # xgdot_03 + "000", # ygdot_03 + "0000", # zgdot_03 + "00000000000", # t0 + "000000000000000000000000", # _parity ), SBA_L1CA_MT_17, ), @@ -389,23 +365,295 @@ def testrawnavparserSBA( self.assertEqual(raw.sigcode, "1C") self.assertEqual(raw.svcode, "S36") self.assertEqual(raw.identity, "S361C") - self.assertEqual(raw.sid, 17) + self.assertEqual(raw.sid, 9) self.assertEqual(raw.subframeacq, 7) - self.assertEqual(raw.xpos, 0b111 * .08) - self.assertEqual(raw.ypos, 0b1111 * .08) - self.assertEqual(raw.zpos, 0b11111 * .4) - self.assertEqual(raw.xdot, 0b111 * .000625) - self.assertEqual(raw.ydot, 0b1111 * .000625) - self.assertEqual(raw.zdot, 0b11111 * .004) - self.assertEqual(raw.xdot2, 0b111 * .0000125) - self.assertEqual(raw.ydot2, 0b1111 * .0000125) - self.assertEqual(raw.zdot2, 0b11111 * .0000625) + self.assertEqual(raw.xpos, 0b111 * 0.08) + self.assertEqual(raw.ypos, 0b1111 * 0.08) + self.assertEqual(raw.zpos, 0b11111 * 0.4) + self.assertEqual(raw.xdot, 0b111 * 0.000625) + self.assertEqual(raw.ydot, 0b1111 * 0.000625) + self.assertEqual(raw.zdot, 0b11111 * 0.004) + self.assertEqual(raw.xdot2, 0b111 * 0.0000125) + self.assertEqual(raw.ydot2, 0b1111 * 0.0000125) + self.assertEqual(raw.zdot2, 0b11111 * 0.0000625) self.assertEqual(raw.agf0, 0b111 * 2**-31) self.assertEqual(raw.agf1, 0b1111 * 2**-40) self.assertEqual(raw.a1, 0b111 * 2**-50) self.assertEqual(raw.a0, 0b1111 * 2**-30) self.assertEqual(str(raw), EXPECTED_RESULT) + def testrawnavparserSBAMT25V0( + self, + ): # test alternate group index numbering for SBA MT25 + EXPECTED_RESULT = "" + BITS = ( + ( + "00000000", # _preamble + "011001", # sid + "0", # velcode1 + "000111", # prnmask + "00000111", # iod + "000000111", # deltax + "000000111", # deltay + "000000111", # deltaz + "0000000111", # deltaaf0 + "001111", # prnmask + "00001111", # iod + "000001111", # deltax + "000001111", # deltay + "000001111", # deltaz + "0000001111", # deltaaf0 + "00", # iodp_v0 + "0", # _spare2 + "0", # velcode2 + "000111", # prnmask + "00000111", # iod + "000000111", # deltax + "000000111", # deltay + "000000111", # deltaz + "0000000111", # deltaaf0 + "001111", # prnmask + "00001111", # iod + "000001111", # deltax + "000001111", # deltay + "000001111", # deltaz + "0000001111", # deltaaf0 + "00", # iodp_v0 + "0", # _spare2 + "000000000000000000000000", # _parity + ), + SBA_L1CA_MT_25, + ) + raw = RawNav("S", 136, "1C") + data, dic = BITS + sfrbits = "0b" + "".join(data) + self.assertEqual(len(sfrbits), 250 + 2) + raw.parse(int(sfrbits, 2), dic, 0b1111) + # print(raw) + self.assertEqual(raw.prnmask_01, 0b111) + self.assertEqual(raw.deltax_01, 0b111 * 2**-3) + self.assertEqual(raw.deltax_02, 0b1111 * 2**-3) + self.assertEqual(raw.deltax_03, 0b111 * 2**-3) + self.assertEqual(raw.deltax_04, 0b1111 * 2**-3) + self.assertEqual(raw.deltaaf0_04, 0b1111 * 2**-31) + self.assertEqual(str(raw), EXPECTED_RESULT) + + def testrawnavparserSBAMT25V1( + self, + ): # test alternate group index numbering for SBA MT25 + EXPECTED_RESULT = "" + BITS = ( + ( + "00000000", # _preamble + "011001", # sid + "1", # velcode1 + "000111", # prnmask + "00000111", # iod + "00000000111", # deltax + "00000000111", # deltay + "00000000111", # deltaz + "00000000111", # deltaaf0 + "00000111", # deltaxdot + "00000111", # deltaydot + "00000111", # deltazdot + "00000111", # deltaaf1 + "0000000000111", # t0 + "01", # iodp_v1 + "1", # velcode2 + "001111", # prnmask + "00001111", # iod + "00000001111", # deltax + "00000001111", # deltay + "00000001111", # deltaz + "00000001111", # deltaaf0 + "00001111", # deltaxdot + "00001111", # deltaydot + "00001111", # deltazdot + "00001111", # deltaaf1 + "0000000001111", # t0 + "10", # iodp_v1 + "000000000000000000000000", # _parity + ), + SBA_L1CA_MT_25, + ) + raw = RawNav("S", 136, "1C") + data, dic = BITS + sfrbits = "0b" + "".join(data) + self.assertEqual(len(sfrbits), 250 + 2) + raw.parse(int(sfrbits, 2), dic, 0b1111) + # print(raw) + self.assertEqual(raw.prnmask_02, 0b1111) + self.assertEqual(raw.deltax_01, 0b111 * 2**-3) + self.assertEqual(raw.deltax_02, 0b1111 * 2**-3) + self.assertEqual(raw.deltazdot_01, 0b111 * 2**-11) + self.assertEqual(raw.deltazdot_02, 0b1111 * 2**-11) + self.assertEqual(raw.deltaaf0_02, 0b1111 * 2**-31) + self.assertEqual(str(raw), EXPECTED_RESULT) + + def testrawnavparserSBAMT25V01( + self, + ): # test alternate group index numbering for SBA MT25 + EXPECTED_RESULT = "" + BITS = ( + ( + "00000000", # _preamble + "011001", # sid + "0", # velcode1 + "000111", # prnmask + "00000111", # iod + "000000111", # deltax + "000000111", # deltay + "000000111", # deltaz + "0000000111", # deltaaf0 + "001111", # prnmask + "00001111", # iod + "000011111", # deltax + "000011111", # deltay + "000011111", # deltaz + "0000001111", # deltaaf0 + "00", # iodp_v0 + "0", # _spare2 + "1", # velcode2 + "001111", # prnmask + "00001111", # iod + "00000001111", # deltax + "00000001111", # deltay + "00000001111", # deltaz + "00000001111", # deltaaf0 + "00001111", # deltaxdot + "00001111", # deltaydot + "00001111", # deltazdot + "00001111", # deltaaf1 + "0000000001111", # t0 + "10", # iodp_v1 + "000000000000000000000000", # _parity + ), + SBA_L1CA_MT_25, + ) + raw = RawNav("S", 136, "1C") + data, dic = BITS + sfrbits = "0b" + "".join(data) + self.assertEqual(len(sfrbits), 250 + 2) + raw.parse(int(sfrbits, 2), dic, 0b1111) + # print(raw) + self.assertEqual(raw.prnmask_02, 0b1111) + self.assertEqual(raw.deltax_01, 0b111 * 2**-3) + self.assertEqual(raw.deltax_02, 0b11111 * 2**-3) + self.assertEqual(raw.deltax_03, 0b1111 * 2**-3) + self.assertEqual(raw.deltazdot_03, 0b1111 * 2**-11) + self.assertEqual(raw.deltaaf0_02, 0b1111 * 2**-31) + self.assertEqual(str(raw), EXPECTED_RESULT) + + def testgroupattribute1(self): # test repeating group based on attribute value + EXPECTED_RESULT = "" + GROUP_DEF = { + SUBFRAMELENGTH: 130, + SID: (6, U, 0), + "numprn": (4, U, 0), + "prn_grp": ( + "numprn", + { + "prn": (6, U, 0), + }, + ), + WN: (10, U, 1), + TOW: (20, U, 1), + } + DATA = 2**130 - 1 + raw = RawNav("S", 136, "1C") + raw.parse(DATA, GROUP_DEF, 0b111) + # print(raw) + self.assertEqual(str(raw), EXPECTED_RESULT) + + def testgroupattribute2(self): # test repeating group based on fixed integer + EXPECTED_RESULT = "" + GROUP_DEF = { + SUBFRAMELENGTH: 130, + SID: (6, U, 0), + "time": (4, U, 0), + "prn_grp": ( + 15, + { + "prn": (6, U, 0), + }, + ), + WN: (10, U, 1), + TOW: (20, U, 1), + } + DATA = 2**130 - 1 + raw = RawNav("S", 136, "1C") + raw.parse(DATA, GROUP_DEF, 0b111) + # print(raw) + self.assertEqual(str(raw), EXPECTED_RESULT) + + def testorphanmsb(self): # test orphaned msb, isb + EXPECTED_RESULT = "" + ORPHAN_DEF = { + SUBFRAMELENGTH: 130, + SID: (6, U, 0), + "time_msb": (2, U, 0), + "_spare": (86, U, 0), + "time_is1": (4, U, 0), + WN: (12, U, 1), + TOW: (20, U, 1), + } + DATA = 2**130 - 1 + raw = RawNav("S", 136, "1C") + raw.parse(DATA, ORPHAN_DEF, 0b111, False) + # print(raw) + self.assertEqual(str(raw), EXPECTED_RESULT) + + def testbadsfrlen(self): # test invalid subframe length in message definition + BAD_DEF = { + SUBFRAMELENGTH: "XXX", + SID: (6, U, 0), + "time": (2, U, 0), + "_spare": (88, "X", 0), + WN: (12, U, 1), + TOW: (20, U, 1), + } + DATA = 2**130 - 1 + raw = RawNav("S", 136, "1C") + with self.assertRaisesRegex( + RINEXProcessingError, + "Invalid subframe definition for S361C. Subframe length must be integer, not XXX.", + ): + raw.parse(DATA, BAD_DEF, 0b111) + + def testbaddef(self): # test incorrect subframe length in message definition + BAD_DEF = { + SUBFRAMELENGTH: 130, + SID: (6, U, 0), + "time": (2, U, 0), + "_spare": (88, U, 0), + WN: (12, U, 1), + TOW: (20, U, 1), + } + DATA = 2**130 - 1 + raw = RawNav("S", 136, "1C") + with self.assertRaisesRegex( + RINEXProcessingError, + "Invalid subframe definition for S361C. Final offset 128 does not match subframe length 130.", + ): + raw.parse(DATA, BAD_DEF, 0b111) + + def testbadtype(self): # test incorrect attribute type in message definition + BAD_DEF = { + SUBFRAMELENGTH: 130, + SID: (6, U, 0), + "time": (2, U, 0), + "_spare": (88, "X", 0), + WN: (12, U, 1), + TOW: (20, U, 1), + } + DATA = 2**130 - 1 + raw = RawNav("S", 136, "1C") + with self.assertRaisesRegex( + RINEXProcessingError, + "Invalid subframe definition for S361C. Error processing `_spare`.", + ): + raw.parse(DATA, BAD_DEF, 0b111) + # def testrinexnav(self): # EXPECTED_RESULT_OBS = [ # r" 3.05 O: OBSERVATION M: MIXED RINEX VERSION / TYPE\n", diff --git a/tests/test_static.py b/tests/test_static.py index fb3f0ba..c61a4da 100644 --- a/tests/test_static.py +++ b/tests/test_static.py @@ -31,6 +31,7 @@ ) from pygnssutils.mqttmessage import MQTTMessage from tests.test_sourcetable import TESTSRT +from pygnssutils.gnssreader import GNSSMessage class StaticTest(unittest.TestCase): @@ -213,6 +214,21 @@ def testparseurl(self): with self.assertRaises(ParameterError): res = parse_url(URL) + def testgnssmessage(self): + msg = GNSSMessage(2,533,b"1234567890") + self.assertEqual(str(msg),"") + # print(repr(msg)) + self.assertEqual(str(eval(repr(msg))),str(msg)) + msg = GNSSMessage(1,"GNGSA",b"abcdefghij") + self.assertEqual(str(msg),"") + # print(repr(msg)) + self.assertEqual(str(eval(repr(msg))),str(msg)) + msg = GNSSMessage(32,5133,b"12345678901") + self.assertEqual(str(msg),"") + # print(repr(msg)) + self.assertEqual(str(eval(repr(msg))),str(msg)) + + if __name__ == "__main__": # import sys;sys.argv = ['', 'Test.testName'] unittest.main()