space-packet-parser 4.0.2__tar.gz → 4.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {space_packet_parser-4.0.2 → space_packet_parser-4.2.0}/CITATION.cff +7 -4
- space_packet_parser-4.2.0/PKG-INFO +54 -0
- space_packet_parser-4.2.0/README.md +30 -0
- {space_packet_parser-4.0.2 → space_packet_parser-4.2.0}/pyproject.toml +16 -14
- {space_packet_parser-4.0.2 → space_packet_parser-4.2.0}/space_packet_parser/parser.py +157 -139
- {space_packet_parser-4.0.2 → space_packet_parser-4.2.0}/space_packet_parser/xtcedef.py +287 -30
- space_packet_parser-4.0.2/PKG-INFO +0 -37
- space_packet_parser-4.0.2/README.md +0 -14
- space_packet_parser-4.0.2/setup.py +0 -30
- {space_packet_parser-4.0.2 → space_packet_parser-4.2.0}/LICENSE.txt +0 -0
- {space_packet_parser-4.0.2 → space_packet_parser-4.2.0}/space_packet_parser/__init__.py +0 -0
- {space_packet_parser-4.0.2 → space_packet_parser-4.2.0}/space_packet_parser/csvdef.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
cff-version: 1.2.0
|
|
2
2
|
title: 'space_packet_parser'
|
|
3
3
|
type: software
|
|
4
|
-
version: '4.0
|
|
4
|
+
version: '4.2.0'
|
|
5
5
|
description: A CCSDS telemetry packet decoding library based on the XTCE packet format description standard.
|
|
6
6
|
license: BSD-3-Clause
|
|
7
7
|
abstract: The space_packet_parser Python library is a generalized, configurable packet decoding library for CCSDS telemetry
|
|
@@ -11,9 +11,9 @@ abstract: The space_packet_parser Python library is a generalized, configurable
|
|
|
11
11
|
generator pattern. The binary string may originate from an in-memory bit stream, a buffered file reader opened in
|
|
12
12
|
binary mode, or a python socket object; in every case, a small buffer is used to read chunks of data to ensure that
|
|
13
13
|
memory usage is minimal. The space_packet_parser library supports robust error handling and is capable of skipping malformed
|
|
14
|
-
packet structures
|
|
14
|
+
packet structures and unidentified packet structures and can dynamically parse mixed APID packet streams. The
|
|
15
15
|
`Parser.generator` object supports keyword arguments for debugging such as the ability to return unraised parsing
|
|
16
|
-
errors, which contain
|
|
16
|
+
errors, which contain partial instances of parsed packet data to aid in debugging.
|
|
17
17
|
authors:
|
|
18
18
|
- email: gavin.medley@lasp.colorado.edu
|
|
19
19
|
name: Gavin Medley
|
|
@@ -21,9 +21,12 @@ authors:
|
|
|
21
21
|
- email: michael.chambliss@lasp.colorado.edu
|
|
22
22
|
name: Michael Chambliss
|
|
23
23
|
orcid: "0009-0003-7493-0542"
|
|
24
|
+
- email: greg.lucas@lasp.colorado.edu
|
|
25
|
+
name: Greg Lucas
|
|
26
|
+
orcid: "0000-0003-1331-1863"
|
|
24
27
|
maintainers:
|
|
25
28
|
- email: gavin.medley@lasp.colorado.edu
|
|
26
29
|
name: Gavin Medley
|
|
27
30
|
orcid: "0000-0002-3520-9715"
|
|
28
31
|
repository-code: "https://github.com/medley56/space_packet_parser"
|
|
29
|
-
url: "
|
|
32
|
+
url: "https://space-packet-parser.readthedocs.io"
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: space_packet_parser
|
|
3
|
+
Version: 4.2.0
|
|
4
|
+
Summary: A CCSDS telemetry packet decoding library based on the XTCE packet format description standard.
|
|
5
|
+
Home-page: https://space-packet-parser.readthedocs.io
|
|
6
|
+
License: BSD-3-Clause
|
|
7
|
+
Keywords: ccsds,xtce,space data systems,space packet protocol,packet parsing,lasp,university of colorado,data processing,data extraction,data manipulation,data transformation,data encoding,data decoding,packet inspection,binary data,python
|
|
8
|
+
Author: Gavin Medley
|
|
9
|
+
Author-email: gavin.medley@lasp.colorado.edu
|
|
10
|
+
Maintainer: Gavin Medley
|
|
11
|
+
Maintainer-email: gavin.medley@lasp.colorado.edu
|
|
12
|
+
Requires-Python: >=3.8
|
|
13
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Requires-Dist: bitstring (>=4.0.1)
|
|
21
|
+
Project-URL: Repository, https://github.com/medley56/space_packet_parser
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# Space Packet Parser
|
|
25
|
+
|
|
26
|
+
----------
|
|
27
|
+
[](https://github.com/medley56/space_packet_parser/actions/workflows/tests.yml)
|
|
28
|
+
[](https://readthedocs.org/projects/space-packet-parser/)
|
|
29
|
+
[](https://doi.org/10.5281/zenodo.7735001)
|
|
30
|
+
|
|
31
|
+
Documentation: [https://space-packet-parser.readthedocs.io/en/latest/](https://space-packet-parser.readthedocs.io/en/latest/)
|
|
32
|
+
|
|
33
|
+
Space Packet Parser is a package for decoding CCSDS telemetry packets according to an XTCE or CSV packet structure definition.
|
|
34
|
+
It is based on the UML model of the XTCE spec and aims to support all but the most esoteric elements of the
|
|
35
|
+
XTCE telemetry packet specification.
|
|
36
|
+
|
|
37
|
+
Resources:
|
|
38
|
+
- [XTCE (Green Book - Informational Report)](https://public.ccsds.org/Pubs/660x2g2.pdf)
|
|
39
|
+
- [XTCE Element Description (Green Book - Informational Report)](https://public.ccsds.org/Pubs/660x1g2.pdf)
|
|
40
|
+
- [XTCE (Blue Book - Recommended Standard)](https://public.ccsds.org/Pubs/660x0b2.pdf)
|
|
41
|
+
|
|
42
|
+
## Installation
|
|
43
|
+
```bash
|
|
44
|
+
pip install space_packet_parser
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Missions using Space Packet Parser
|
|
48
|
+
|
|
49
|
+
[<img src="https://imap.princeton.edu/sites/g/files/toruqf1601/files/imap-mark-hor-multicolor-dark.png" alt="IMAP" height="70"/>](https://imap.princeton.edu/)
|
|
50
|
+
[<img src="https://clarreo-pathfinder.larc.nasa.gov/wp-content/uploads/sites/133/2019/08/clarreo_pathfinder_mission_patch_design_v4_decal_1_24_17.png" alt="CLARREO" height="140"/>](https://clarreo-pathfinder.larc.nasa.gov/)
|
|
51
|
+
[<img src="https://lasp.colorado.edu/libera/files/2021/02/Libera-Logo-HiRes.png" alt="Libera" height="70"/>](https://lasp.colorado.edu/libera/)
|
|
52
|
+
[<img src="https://lasp.colorado.edu/ctim/files/2023/01/CTIM_LOGO_350x100_centered_transparent.png" alt="CTIM-FD" height="70"/>](https://lasp.colorado.edu/ctim/)
|
|
53
|
+
[<img src="https://mms.gsfc.nasa.gov/images/promotional_materials/mms_decal_rgb_4in_trabk_72dpi.png" alt="MMS-FEEPS" height="140"/>](https://lasp.colorado.edu/mms/sdc/public/)
|
|
54
|
+
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Space Packet Parser
|
|
2
|
+
|
|
3
|
+
----------
|
|
4
|
+
[](https://github.com/medley56/space_packet_parser/actions/workflows/tests.yml)
|
|
5
|
+
[](https://readthedocs.org/projects/space-packet-parser/)
|
|
6
|
+
[](https://doi.org/10.5281/zenodo.7735001)
|
|
7
|
+
|
|
8
|
+
Documentation: [https://space-packet-parser.readthedocs.io/en/latest/](https://space-packet-parser.readthedocs.io/en/latest/)
|
|
9
|
+
|
|
10
|
+
Space Packet Parser is a package for decoding CCSDS telemetry packets according to an XTCE or CSV packet structure definition.
|
|
11
|
+
It is based on the UML model of the XTCE spec and aims to support all but the most esoteric elements of the
|
|
12
|
+
XTCE telemetry packet specification.
|
|
13
|
+
|
|
14
|
+
Resources:
|
|
15
|
+
- [XTCE (Green Book - Informational Report)](https://public.ccsds.org/Pubs/660x2g2.pdf)
|
|
16
|
+
- [XTCE Element Description (Green Book - Informational Report)](https://public.ccsds.org/Pubs/660x1g2.pdf)
|
|
17
|
+
- [XTCE (Blue Book - Recommended Standard)](https://public.ccsds.org/Pubs/660x0b2.pdf)
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
```bash
|
|
21
|
+
pip install space_packet_parser
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Missions using Space Packet Parser
|
|
25
|
+
|
|
26
|
+
[<img src="https://imap.princeton.edu/sites/g/files/toruqf1601/files/imap-mark-hor-multicolor-dark.png" alt="IMAP" height="70"/>](https://imap.princeton.edu/)
|
|
27
|
+
[<img src="https://clarreo-pathfinder.larc.nasa.gov/wp-content/uploads/sites/133/2019/08/clarreo_pathfinder_mission_patch_design_v4_decal_1_24_17.png" alt="CLARREO" height="140"/>](https://clarreo-pathfinder.larc.nasa.gov/)
|
|
28
|
+
[<img src="https://lasp.colorado.edu/libera/files/2021/02/Libera-Logo-HiRes.png" alt="Libera" height="70"/>](https://lasp.colorado.edu/libera/)
|
|
29
|
+
[<img src="https://lasp.colorado.edu/ctim/files/2023/01/CTIM_LOGO_350x100_centered_transparent.png" alt="CTIM-FD" height="70"/>](https://lasp.colorado.edu/ctim/)
|
|
30
|
+
[<img src="https://mms.gsfc.nasa.gov/images/promotional_materials/mms_decal_rgb_4in_trabk_72dpi.png" alt="MMS-FEEPS" height="140"/>](https://lasp.colorado.edu/mms/sdc/public/)
|
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "space_packet_parser"
|
|
3
|
-
version = "4.0
|
|
3
|
+
version = "4.2.0"
|
|
4
4
|
description = "A CCSDS telemetry packet decoding library based on the XTCE packet format description standard."
|
|
5
5
|
license = "BSD-3-Clause"
|
|
6
6
|
readme = "README.md"
|
|
7
7
|
include = ["LICENSE.txt", "CITATION.cff"]
|
|
8
8
|
authors = [
|
|
9
9
|
"Gavin Medley <gavin.medley@lasp.colorado.edu>",
|
|
10
|
-
"Michael Chambliss <michael.chambliss@lasp.colorado.edu>"
|
|
10
|
+
"Michael Chambliss <michael.chambliss@lasp.colorado.edu>",
|
|
11
|
+
"Greg Lucas <greg.lucas@lasp.colorado.edu>",
|
|
11
12
|
]
|
|
12
13
|
maintainers = [
|
|
13
14
|
"Gavin Medley <gavin.medley@lasp.colorado.edu>"
|
|
14
15
|
]
|
|
15
16
|
repository = "https://github.com/medley56/space_packet_parser"
|
|
16
|
-
homepage = ""
|
|
17
|
+
homepage = "https://space-packet-parser.readthedocs.io"
|
|
17
18
|
keywords = [
|
|
18
19
|
"ccsds",
|
|
19
20
|
"xtce",
|
|
@@ -34,19 +35,20 @@ keywords = [
|
|
|
34
35
|
]
|
|
35
36
|
|
|
36
37
|
[tool.poetry.dependencies]
|
|
37
|
-
python = ">=3.8
|
|
38
|
-
bitstring = ">=
|
|
38
|
+
python = ">=3.8"
|
|
39
|
+
bitstring = ">=4.0.1"
|
|
39
40
|
|
|
40
41
|
[tool.poetry.group.dev.dependencies]
|
|
41
|
-
pylint = "
|
|
42
|
-
pytest = "
|
|
43
|
-
pytest-randomly = "
|
|
44
|
-
pytest-cov = "
|
|
45
|
-
pyyaml = "
|
|
46
|
-
sphinx = "
|
|
47
|
-
myst-parser = "
|
|
48
|
-
sphinx-autoapi = "
|
|
49
|
-
sphinx-rtd-theme = "
|
|
42
|
+
pylint = "*"
|
|
43
|
+
pytest = "*"
|
|
44
|
+
pytest-randomly = "*"
|
|
45
|
+
pytest-cov = "*"
|
|
46
|
+
pyyaml = "*"
|
|
47
|
+
sphinx = "*"
|
|
48
|
+
myst-parser = "*"
|
|
49
|
+
sphinx-autoapi = "*"
|
|
50
|
+
sphinx-rtd-theme = "*"
|
|
51
|
+
coverage = "*"
|
|
50
52
|
|
|
51
53
|
[tool.poetry.group.examples]
|
|
52
54
|
optional = true
|
|
@@ -6,7 +6,7 @@ import io
|
|
|
6
6
|
import logging
|
|
7
7
|
import socket
|
|
8
8
|
import time
|
|
9
|
-
from typing import BinaryIO, Tuple
|
|
9
|
+
from typing import BinaryIO, Optional, Tuple
|
|
10
10
|
import warnings
|
|
11
11
|
# Installed
|
|
12
12
|
import bitstring
|
|
@@ -15,19 +15,19 @@ from space_packet_parser import xtcedef, csvdef
|
|
|
15
15
|
|
|
16
16
|
logger = logging.getLogger(__name__)
|
|
17
17
|
|
|
18
|
-
CcsdsPacketHeaderElement = namedtuple('CcsdsPacketHeaderElement', ['name', '
|
|
18
|
+
CcsdsPacketHeaderElement = namedtuple('CcsdsPacketHeaderElement', ['name', 'nbits'])
|
|
19
19
|
|
|
20
20
|
CCSDS_HEADER_DEFINITION = [
|
|
21
|
-
CcsdsPacketHeaderElement('VERSION',
|
|
22
|
-
CcsdsPacketHeaderElement('TYPE',
|
|
23
|
-
CcsdsPacketHeaderElement('SEC_HDR_FLG',
|
|
24
|
-
CcsdsPacketHeaderElement('PKT_APID',
|
|
25
|
-
CcsdsPacketHeaderElement('SEQ_FLGS',
|
|
26
|
-
CcsdsPacketHeaderElement('SRC_SEQ_CTR',
|
|
27
|
-
CcsdsPacketHeaderElement('PKT_LEN',
|
|
21
|
+
CcsdsPacketHeaderElement('VERSION', 3),
|
|
22
|
+
CcsdsPacketHeaderElement('TYPE', 1),
|
|
23
|
+
CcsdsPacketHeaderElement('SEC_HDR_FLG', 1),
|
|
24
|
+
CcsdsPacketHeaderElement('PKT_APID', 11),
|
|
25
|
+
CcsdsPacketHeaderElement('SEQ_FLGS', 2),
|
|
26
|
+
CcsdsPacketHeaderElement('SRC_SEQ_CTR', 14),
|
|
27
|
+
CcsdsPacketHeaderElement('PKT_LEN', 16)
|
|
28
28
|
]
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
CCSDS_HEADER_LENGTH_BYTES = 6
|
|
31
31
|
|
|
32
32
|
Packet = namedtuple('Packet', ['header', 'data'])
|
|
33
33
|
|
|
@@ -35,7 +35,8 @@ Packet = namedtuple('Packet', ['header', 'data'])
|
|
|
35
35
|
class ParsedDataItem(xtcedef.AttrComparable):
|
|
36
36
|
"""Representation of a parsed parameter"""
|
|
37
37
|
|
|
38
|
-
def __init__(self, name: str, raw_value: any, unit: str = None, derived_value: float or str = None
|
|
38
|
+
def __init__(self, name: str, raw_value: any, unit: str = None, derived_value: float or str = None,
|
|
39
|
+
short_description: str = None, long_description: str = None):
|
|
39
40
|
"""Constructor
|
|
40
41
|
|
|
41
42
|
Parameters
|
|
@@ -48,6 +49,10 @@ class ParsedDataItem(xtcedef.AttrComparable):
|
|
|
48
49
|
Raw representation of the parsed value. May be lots of different types but most often an integer
|
|
49
50
|
derived_value : float or str
|
|
50
51
|
May be a calibrated value or an enum lookup
|
|
52
|
+
short_description : str
|
|
53
|
+
Parameter short description
|
|
54
|
+
long_description : str
|
|
55
|
+
Parameter long description
|
|
51
56
|
"""
|
|
52
57
|
if name is None or raw_value is None:
|
|
53
58
|
raise ValueError("Invalid ParsedDataItem. Must define name and raw_value.")
|
|
@@ -55,6 +60,8 @@ class ParsedDataItem(xtcedef.AttrComparable):
|
|
|
55
60
|
self.raw_value = raw_value
|
|
56
61
|
self.unit = unit
|
|
57
62
|
self.derived_value = derived_value
|
|
63
|
+
self.short_description = short_description
|
|
64
|
+
self.long_description = long_description
|
|
58
65
|
|
|
59
66
|
def __repr__(self):
|
|
60
67
|
return (f"{self.__class__.__name__}("
|
|
@@ -97,62 +104,30 @@ class PacketParser:
|
|
|
97
104
|
self.word_size = word_size
|
|
98
105
|
|
|
99
106
|
@staticmethod
|
|
100
|
-
def _parse_header(packet_data:
|
|
101
|
-
start_position: int = None,
|
|
102
|
-
reset_cursor: bool = False) -> dict:
|
|
107
|
+
def _parse_header(packet_data: bytes) -> dict:
|
|
103
108
|
"""Parses the CCSDS standard header.
|
|
104
109
|
|
|
105
110
|
Parameters
|
|
106
111
|
----------
|
|
107
|
-
packet_data :
|
|
108
|
-
|
|
109
|
-
start_position : int
|
|
110
|
-
Position from which to start parsing. If not provided, will start whenever the cursor currently is.
|
|
111
|
-
reset_cursor : bool
|
|
112
|
-
If True, upon parsing the header data, reset the cursor to the original position in the stream.
|
|
113
|
-
This still applies even if start_position is specified. start_position will be used only for parsing the
|
|
114
|
-
header and then the cursor will be returned to the location it was at before this function was called.
|
|
112
|
+
packet_data : bytes
|
|
113
|
+
6 bytes of binary data.
|
|
115
114
|
|
|
116
115
|
Returns
|
|
117
116
|
-------
|
|
118
117
|
header : dict
|
|
119
118
|
Dictionary of header items.
|
|
120
119
|
"""
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
for item in CCSDS_HEADER_DEFINITION
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
if reset_cursor:
|
|
132
|
-
packet_data.pos = original_cursor_position
|
|
133
|
-
|
|
120
|
+
header = {}
|
|
121
|
+
current_bit = 0
|
|
122
|
+
for item in CCSDS_HEADER_DEFINITION:
|
|
123
|
+
header[item.name] = ParsedDataItem(name=item.name,
|
|
124
|
+
unit=None,
|
|
125
|
+
raw_value=_extract_bits(packet_data, current_bit, item.nbits))
|
|
126
|
+
current_bit += item.nbits
|
|
134
127
|
return header
|
|
135
128
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
"""Calculate the total length of a CCSDS packet in bits based on the PKT_LEN field in its header.
|
|
139
|
-
|
|
140
|
-
Parameters
|
|
141
|
-
----------
|
|
142
|
-
pkt_len : int
|
|
143
|
-
PKT_LEN value from CCSDS header
|
|
144
|
-
|
|
145
|
-
Returns
|
|
146
|
-
-------
|
|
147
|
-
: int
|
|
148
|
-
Length, in bits of the packet
|
|
149
|
-
|
|
150
|
-
"""
|
|
151
|
-
# 4.1.3.5.3 The length count C shall be expressed as:
|
|
152
|
-
# C = (Total Number of Octets in the Packet Data Field) – 1
|
|
153
|
-
# We also just reparsed the CCSDS header though as well, so that's an additional 6 octets
|
|
154
|
-
return 8 * (pkt_len + 1 + 6)
|
|
155
|
-
|
|
129
|
+
# DEPRECATED! Remove in next major release along with CSV parser
|
|
130
|
+
# pylint: disable=inconsistent-return-statements
|
|
156
131
|
def _determine_packet_by_restrictions(self, parsed_header: dict) -> Tuple[str, list]:
|
|
157
132
|
"""Examines a dictionary representation of a CCSDS header and determines which packet type applies.
|
|
158
133
|
This packet type must be unique. If the header data satisfies the restrictions for more than one packet
|
|
@@ -206,6 +181,7 @@ class PacketParser:
|
|
|
206
181
|
"Unable to choose a packet type to parse. "
|
|
207
182
|
"Note: Restricting container inheritance based on non-header data items is not possible in a "
|
|
208
183
|
"general way and is not supported by this package.", partial_data=parsed_header)
|
|
184
|
+
# pylint: enable=inconsistent-return-statements
|
|
209
185
|
|
|
210
186
|
@staticmethod
|
|
211
187
|
def parse_packet(packet_data: bitstring.ConstBitStream,
|
|
@@ -237,7 +213,9 @@ class PacketParser:
|
|
|
237
213
|
name=p.name,
|
|
238
214
|
unit=p.parameter_type.unit,
|
|
239
215
|
raw_value=parsed_value,
|
|
240
|
-
derived_value=derived_value
|
|
216
|
+
derived_value=derived_value,
|
|
217
|
+
short_description=p.short_description,
|
|
218
|
+
long_description=p.long_description
|
|
241
219
|
)
|
|
242
220
|
|
|
243
221
|
def _parse_sequence_container(sc: xtcedef.SequenceContainer):
|
|
@@ -250,11 +228,7 @@ class PacketParser:
|
|
|
250
228
|
parsed_items = {}
|
|
251
229
|
current_container: xtcedef.SequenceContainer = containers[root_container_name]
|
|
252
230
|
while True:
|
|
253
|
-
|
|
254
|
-
if isinstance(entry, xtcedef.Parameter):
|
|
255
|
-
_parse_parameter(entry)
|
|
256
|
-
elif isinstance(entry, xtcedef.SequenceContainer):
|
|
257
|
-
_parse_sequence_container(entry)
|
|
231
|
+
_parse_sequence_container(current_container)
|
|
258
232
|
|
|
259
233
|
valid_inheritors = []
|
|
260
234
|
for inheritor_name in current_container.inheritors:
|
|
@@ -320,23 +294,25 @@ class PacketParser:
|
|
|
320
294
|
name=parameter.name,
|
|
321
295
|
unit=parameter.parameter_type.unit,
|
|
322
296
|
raw_value=parsed_value,
|
|
323
|
-
derived_value=derived_value
|
|
297
|
+
derived_value=derived_value,
|
|
298
|
+
short_description=parameter.short_description,
|
|
299
|
+
long_description=parameter.long_description
|
|
324
300
|
)
|
|
325
301
|
|
|
326
302
|
return Packet(header=header, data=user_data)
|
|
327
303
|
|
|
328
304
|
@staticmethod
|
|
329
|
-
def print_progress(
|
|
305
|
+
def print_progress(current_bytes: int, total_bytes: int or None,
|
|
330
306
|
start_time_ns: int, current_packets: int,
|
|
331
307
|
end: str = '\r', log: bool = False):
|
|
332
308
|
"""Prints a progress bar, including statistics on parsing rate.
|
|
333
309
|
|
|
334
310
|
Parameters
|
|
335
311
|
----------
|
|
336
|
-
|
|
337
|
-
Number of
|
|
338
|
-
|
|
339
|
-
Number of total
|
|
312
|
+
current_bytes : int
|
|
313
|
+
Number of bytes parsed so far.
|
|
314
|
+
total_bytes : int
|
|
315
|
+
Number of total bytes to parse (if known)
|
|
340
316
|
current_packets : int
|
|
341
317
|
Number of packets parsed so far.
|
|
342
318
|
start_time_ns : int
|
|
@@ -349,26 +325,26 @@ class PacketParser:
|
|
|
349
325
|
progress_char = "="
|
|
350
326
|
bar_length = 20
|
|
351
327
|
|
|
352
|
-
if
|
|
353
|
-
percentage = int((
|
|
354
|
-
progress = int((bar_length *
|
|
328
|
+
if total_bytes is not None: # If we actually have an endpoint (i.e. not using a socket)
|
|
329
|
+
percentage = int((current_bytes / total_bytes) * 100) # Percent Completed Calculation
|
|
330
|
+
progress = int((bar_length * current_bytes) / total_bytes) # Progress Done Calculation
|
|
355
331
|
else:
|
|
356
332
|
percentage = "???"
|
|
357
333
|
progress = 0
|
|
358
334
|
|
|
359
335
|
elapsed_ns = time.time_ns() - start_time_ns
|
|
360
336
|
delta = dt.timedelta(microseconds=elapsed_ns / 1E3)
|
|
361
|
-
kbps = int(
|
|
337
|
+
kbps = int(current_bytes // 8 * 1E6 / elapsed_ns)
|
|
362
338
|
pps = int(current_packets * 1E9 / elapsed_ns)
|
|
363
339
|
info_str = f"[Elapsed: {delta}, " \
|
|
364
|
-
f"Parsed {
|
|
340
|
+
f"Parsed {current_bytes} bytes ({current_packets} packets) " \
|
|
365
341
|
f"at {kbps}kb/s ({pps}pkts/s)]"
|
|
366
342
|
loadbar = f"Progress: [{progress*progress_char:{bar_length}}]{percentage}% {info_str}"
|
|
367
343
|
print(loadbar, end=end)
|
|
368
344
|
if log is True:
|
|
369
345
|
logger.info(loadbar)
|
|
370
346
|
|
|
371
|
-
def generator(self,
|
|
347
|
+
def generator(self, # pylint: disable=too-many-branches,too-many-statements
|
|
372
348
|
binary_data: bitstring.ConstBitStream or BinaryIO or socket.socket,
|
|
373
349
|
parse_bad_pkts: bool = True,
|
|
374
350
|
skip_header_bits: int = 0,
|
|
@@ -376,7 +352,7 @@ class PacketParser:
|
|
|
376
352
|
ccsds_headers_only: bool = False,
|
|
377
353
|
yield_unrecognized_packet_errors: bool = False,
|
|
378
354
|
show_progress: bool = False,
|
|
379
|
-
buffer_read_size_bytes: int =
|
|
355
|
+
buffer_read_size_bytes: Optional[int] = None):
|
|
380
356
|
"""Create and return a Packet generator that reads from a ConstBitStream or a filelike object or a socket.
|
|
381
357
|
|
|
382
358
|
Creating a generator object to return allows the user to create
|
|
@@ -401,6 +377,7 @@ class PacketParser:
|
|
|
401
377
|
ccsds_headers_only : bool, Optional
|
|
402
378
|
If True, only parses the packet headers (does not use the provided packet definition).
|
|
403
379
|
yield_unrecognized_packet_errors : bool, Optional
|
|
380
|
+
Default False.
|
|
404
381
|
If False, UnrecognizedPacketTypeErrors are caught silently and parsing continues to the next packet.
|
|
405
382
|
If True, the generator will yield an UnrecognizedPacketTypeError in the event of an unrecognized
|
|
406
383
|
packet. Note: These exceptions are not raised by default but are instead returned so that the generator
|
|
@@ -411,28 +388,24 @@ class PacketParser:
|
|
|
411
388
|
ends.
|
|
412
389
|
buffer_read_size_bytes : int, Optional
|
|
413
390
|
Number of bytes to read from e.g. a BufferedReader or socket binary data source on each read attempt.
|
|
414
|
-
Default is 4096 bytes.
|
|
391
|
+
Default is 4096 bytes from a socket, -1 (full read) from a file.
|
|
415
392
|
|
|
416
393
|
Yields
|
|
417
394
|
-------
|
|
418
|
-
|
|
395
|
+
Packet or UnrecognizedPacketTypeError
|
|
419
396
|
Generator yields Packet objects containing the parsed packet data for each subsequent packet.
|
|
420
397
|
If yield_unrecognized_packet_errors is True, it will yield an unraised exception object,
|
|
421
398
|
which can be raised or used for debugging purposes.
|
|
422
399
|
"""
|
|
423
400
|
|
|
424
|
-
def
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
"""Read data from a source and add it to an existing buffer (BitStream).
|
|
401
|
+
def read_bytes_from_source(source: bitstring.ConstBitStream or BinaryIO or socket.socket,
|
|
402
|
+
read_size_bytes: int) -> int:
|
|
403
|
+
"""Read data from a source and return the bytes read.
|
|
428
404
|
|
|
429
405
|
Parameters
|
|
430
406
|
----------
|
|
431
407
|
source : bitstring.ConstBitStream or BinaryIO or socket.socket
|
|
432
408
|
Source of data.
|
|
433
|
-
buffer : bitstring.BitStream
|
|
434
|
-
A reference to a rotating buffer to which the new data is appended. Mutating this changes the data
|
|
435
|
-
available to the caller by reference so we don't return it.
|
|
436
409
|
read_size_bytes : int
|
|
437
410
|
Max number of bytes to read from the source per read attempt. For sockets, this should be a small
|
|
438
411
|
power of 2 (e.g. 4096) due to networking and hardware conventions. For a file or ConstBitStream object
|
|
@@ -441,99 +414,120 @@ class PacketParser:
|
|
|
441
414
|
|
|
442
415
|
Returns
|
|
443
416
|
-------
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
data even when this returns zero.
|
|
417
|
+
bytes
|
|
418
|
+
The bytes that were read from the source.
|
|
447
419
|
"""
|
|
448
420
|
if isinstance(source, io.BufferedIOBase):
|
|
449
|
-
|
|
450
|
-
buffer += new_bytes
|
|
451
|
-
return len(new_bytes)*8
|
|
452
|
-
|
|
421
|
+
return source.read(read_size_bytes)
|
|
453
422
|
if isinstance(source, socket.socket):
|
|
454
|
-
|
|
455
|
-
buffer += new_bytes # Append BitStream with newly read bytes
|
|
456
|
-
return len(new_bytes)*8
|
|
457
|
-
|
|
423
|
+
return source.recv(read_size_bytes)
|
|
458
424
|
if isinstance(source, bitstring.ConstBitStream):
|
|
425
|
+
# This either reads read_size_bytes bytes or it just reads to the end of the data
|
|
459
426
|
new_bits = source[source.pos:source.pos + read_size_bytes * 8]
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
raise ValueError(f"Unrecognized data source: {source}")
|
|
427
|
+
source.pos += len(new_bits) # Set the source.pos to exactly where we read to
|
|
428
|
+
return new_bits.tobytes()
|
|
429
|
+
if isinstance(source, io.TextIOWrapper):
|
|
430
|
+
raise IOError("Packet data file opened in TextIO mode. You must open packet data in binary mode.")
|
|
431
|
+
raise IOError(f"Unrecognized data source: {source}")
|
|
466
432
|
|
|
467
433
|
# ========
|
|
468
434
|
# Start of generator
|
|
469
435
|
# ========
|
|
470
436
|
if isinstance(binary_data, bitstring.ConstBitStream):
|
|
471
|
-
|
|
437
|
+
total_length_bytes = len(binary_data) // 8
|
|
438
|
+
if buffer_read_size_bytes is None:
|
|
439
|
+
# Default to a full read of the bitstream
|
|
440
|
+
buffer_read_size_bytes = total_length_bytes
|
|
472
441
|
logger.info(
|
|
473
|
-
f"Creating packet generator from pre-loaded ConstBitStream. Total length is {
|
|
442
|
+
f"Creating packet generator from pre-loaded ConstBitStream. Total length is {total_length_bytes} bytes")
|
|
474
443
|
elif isinstance(binary_data, io.BufferedIOBase):
|
|
475
|
-
|
|
444
|
+
if buffer_read_size_bytes is None:
|
|
445
|
+
# Default to a full read of the file
|
|
446
|
+
buffer_read_size_bytes = -1
|
|
447
|
+
total_length_bytes = binary_data.seek(0, io.SEEK_END) # This is probably preferable to len
|
|
476
448
|
binary_data.seek(0, 0)
|
|
477
449
|
logger.info(f"Creating packet generator from a filelike object, {binary_data}. "
|
|
478
|
-
f"Total length is {
|
|
450
|
+
f"Total length is {total_length_bytes} bytes")
|
|
479
451
|
else: # It's a socket and we don't know how much data we will get
|
|
480
452
|
logger.info("Creating packet generator to read from a socket. Total length to parse is unknown.")
|
|
481
|
-
|
|
453
|
+
total_length_bytes = None # We don't know how long it is
|
|
454
|
+
if buffer_read_size_bytes is None:
|
|
455
|
+
# Default to 4096 bytes from a socket
|
|
456
|
+
buffer_read_size_bytes = 4096
|
|
482
457
|
|
|
483
458
|
# ========
|
|
484
459
|
# Packet loop. Each iteration of this loop yields a ParsedPacket object
|
|
485
460
|
# ========
|
|
486
461
|
start_time = time.time_ns()
|
|
487
|
-
|
|
462
|
+
n_bytes_parsed = 0 # Keep track of how many bytes we have parsed
|
|
488
463
|
n_packets_parsed = 0 # Keep track of how many packets we have parsed
|
|
489
|
-
read_buffer =
|
|
464
|
+
read_buffer = b"" # Empty bytes object to start
|
|
465
|
+
skip_header_bytes = skip_header_bits // 8 # Internally keep track of bytes
|
|
466
|
+
current_pos = 0 # Keep track of where we are in the buffer
|
|
490
467
|
while True:
|
|
491
|
-
if
|
|
468
|
+
if total_length_bytes and n_bytes_parsed == total_length_bytes:
|
|
492
469
|
break # Exit if we know the length and we've reached it
|
|
493
470
|
|
|
494
471
|
if show_progress is True:
|
|
495
|
-
self.print_progress(
|
|
472
|
+
self.print_progress(current_bytes=n_bytes_parsed, total_bytes=total_length_bytes,
|
|
496
473
|
start_time_ns=start_time, current_packets=n_packets_parsed)
|
|
497
474
|
|
|
475
|
+
if current_pos > 20_000_000:
|
|
476
|
+
# Only trim the buffer after 20 MB read to prevent modifying
|
|
477
|
+
# the bitstream and trimming after every packet
|
|
478
|
+
read_buffer = read_buffer[current_pos:]
|
|
479
|
+
current_pos = 0
|
|
480
|
+
|
|
498
481
|
# Fill buffer enough to parse a header
|
|
499
|
-
while len(read_buffer) <
|
|
500
|
-
result =
|
|
501
|
-
read_size_bytes=buffer_read_size_bytes)
|
|
482
|
+
while len(read_buffer) - current_pos < skip_header_bytes + CCSDS_HEADER_LENGTH_BYTES:
|
|
483
|
+
result = read_bytes_from_source(binary_data, read_size_bytes=buffer_read_size_bytes)
|
|
502
484
|
if not result: # If there is verifiably no more data to add, break
|
|
503
485
|
break
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
486
|
+
read_buffer += result
|
|
487
|
+
# Skip the header bytes
|
|
488
|
+
current_pos += skip_header_bytes
|
|
489
|
+
header_bytes = read_buffer[current_pos:current_pos + CCSDS_HEADER_LENGTH_BYTES]
|
|
490
|
+
header = self._parse_header(header_bytes)
|
|
491
|
+
|
|
492
|
+
# per the CCSDS spec
|
|
493
|
+
# 4.1.3.5.3 The length count C shall be expressed as:
|
|
494
|
+
# C = (Total Number of Octets in the Packet Data Field) – 1
|
|
495
|
+
n_bytes_data = header['PKT_LEN'].raw_value + 1
|
|
496
|
+
n_bytes_packet = CCSDS_HEADER_LENGTH_BYTES + n_bytes_data
|
|
497
|
+
|
|
498
|
+
# Consider it a counted packet once we've parsed the header
|
|
499
|
+
# and update the number of bits parsed
|
|
500
|
+
n_packets_parsed += 1
|
|
501
|
+
n_bytes_parsed += skip_header_bytes + n_bytes_packet
|
|
509
502
|
if ccsds_headers_only is True:
|
|
510
|
-
#
|
|
511
|
-
|
|
512
|
-
read_buffer = read_buffer[specified_total_packet_length_bits + skip_header_bits:]
|
|
503
|
+
# update the current position to the end of the packet data
|
|
504
|
+
current_pos += n_bytes_packet
|
|
513
505
|
yield Packet(header=header, data=None)
|
|
514
506
|
continue
|
|
515
507
|
|
|
516
508
|
# Based on PKT_LEN fill buffer enough to read a full packet
|
|
517
|
-
while len(read_buffer)
|
|
518
|
-
result =
|
|
519
|
-
read_size_bytes=buffer_read_size_bytes)
|
|
509
|
+
while len(read_buffer) - current_pos < n_bytes_packet:
|
|
510
|
+
result = read_bytes_from_source(binary_data, read_size_bytes=buffer_read_size_bytes)
|
|
520
511
|
if not result: # If there is verifiably no more data to add, break
|
|
521
512
|
break
|
|
522
|
-
|
|
513
|
+
read_buffer += result
|
|
514
|
+
|
|
515
|
+
# current_pos is still before the header, so we are reading the entire packet here
|
|
516
|
+
packet_bytes = read_buffer[current_pos:current_pos + n_bytes_packet]
|
|
517
|
+
current_pos += n_bytes_packet
|
|
518
|
+
# Send bitstring data to the parser for now
|
|
519
|
+
# TODO: Look into parsing the raw bytes directly
|
|
520
|
+
bitstring_packet = bitstring.ConstBitStream(packet_bytes)
|
|
523
521
|
try:
|
|
524
522
|
if isinstance(self.packet_definition, xtcedef.XtcePacketDefinition):
|
|
525
|
-
packet = self.parse_packet(
|
|
523
|
+
packet = self.parse_packet(bitstring_packet,
|
|
526
524
|
self.packet_definition.named_containers,
|
|
527
525
|
root_container_name=root_container_name,
|
|
528
526
|
word_size=self.word_size)
|
|
529
527
|
else:
|
|
530
528
|
_, parameter_list = self._determine_packet_by_restrictions(header)
|
|
531
|
-
packet = self.legacy_parse_packet(
|
|
529
|
+
packet = self.legacy_parse_packet(bitstring_packet, parameter_list, word_size=self.word_size)
|
|
532
530
|
except UnrecognizedPacketTypeError as e:
|
|
533
|
-
# Regardless of whether we handle the error, we still want to chop the read_buffer in preparation
|
|
534
|
-
# for parsing the next packet
|
|
535
|
-
n_bits_parsed += skip_header_bits + specified_total_packet_length_bits
|
|
536
|
-
read_buffer = read_buffer[skip_header_bits + specified_total_packet_length_bits:]
|
|
537
531
|
logger.debug(f"Unrecognized error on packet with APID {header['PKT_APID'].raw_value}'")
|
|
538
532
|
if yield_unrecognized_packet_errors is True:
|
|
539
533
|
# Yield the caught exception without raising it (raising ends generator)
|
|
@@ -547,22 +541,46 @@ class PacketParser:
|
|
|
547
541
|
f"{packet.header['PKT_LEN'].raw_value}. This might be because the CCSDS header is "
|
|
548
542
|
f"incorrectly represented in your packet definition document.")
|
|
549
543
|
|
|
550
|
-
actual_length_parsed =
|
|
551
|
-
|
|
552
|
-
if actual_length_parsed != specified_total_packet_length_bits:
|
|
544
|
+
actual_length_parsed = bitstring_packet.pos // 8
|
|
545
|
+
if actual_length_parsed != n_bytes_packet:
|
|
553
546
|
logger.warning(f"Parsed packet length "
|
|
554
|
-
f"({actual_length_parsed}
|
|
555
|
-
f"length specified in header ({
|
|
556
|
-
f"Updating
|
|
547
|
+
f"({actual_length_parsed}B) did not match "
|
|
548
|
+
f"length specified in header ({n_bytes_packet}B). "
|
|
549
|
+
f"Updating the position to the correct position "
|
|
557
550
|
"indicated by CCSDS header.")
|
|
558
551
|
if not parse_bad_pkts:
|
|
559
552
|
logger.warning("Skipping (not yielding) bad packet because parse_bad_pkts is falsy.")
|
|
560
553
|
continue
|
|
561
|
-
|
|
562
|
-
read_buffer = read_buffer[specified_total_packet_length_bits + skip_header_bits:]
|
|
554
|
+
|
|
563
555
|
yield packet
|
|
564
556
|
|
|
565
557
|
if show_progress is True:
|
|
566
|
-
self.print_progress(
|
|
558
|
+
self.print_progress(current_bytes=n_bytes_parsed, total_bytes=total_length_bytes,
|
|
567
559
|
start_time_ns=start_time, current_packets=n_packets_parsed,
|
|
568
560
|
end="\n", log=True)
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
def _extract_bits(data: bytes, start_bit: int, nbits: int):
|
|
564
|
+
"""Extract nbits from the data starting from the least significant end.
|
|
565
|
+
|
|
566
|
+
If data = b"abcdefgh", start_bit = 2, nbits = 3, then the bits extracted are
|
|
567
|
+
"cde" and those are turned into a Python integer and returned.
|
|
568
|
+
|
|
569
|
+
Parameters
|
|
570
|
+
----------
|
|
571
|
+
data : bytes
|
|
572
|
+
Data to extract bits from
|
|
573
|
+
start_bit : int
|
|
574
|
+
Starting bit location
|
|
575
|
+
nbits : int
|
|
576
|
+
Number of bits to extract
|
|
577
|
+
|
|
578
|
+
Returns
|
|
579
|
+
-------
|
|
580
|
+
int
|
|
581
|
+
Extracted bits as an integer
|
|
582
|
+
"""
|
|
583
|
+
# Shift the value to the right to get the start bit to the least significant position
|
|
584
|
+
# Then mask out the bits we want to keep
|
|
585
|
+
value = int.from_bytes(data, byteorder="big")
|
|
586
|
+
return (value >> (len(data) * 8 - start_bit - nbits)) & (2 ** nbits - 1)
|
|
@@ -237,7 +237,7 @@ class Comparison(MatchCriteria):
|
|
|
237
237
|
if isinstance(required_value, str):
|
|
238
238
|
parsed_value = f"'{parsed_value}'"
|
|
239
239
|
required_value = f"'{required_value}'"
|
|
240
|
-
return eval(f"{parsed_value} {operator} {required_value}")
|
|
240
|
+
return eval(f"{parsed_value} {operator} {required_value}") # pylint: disable=eval-used
|
|
241
241
|
|
|
242
242
|
|
|
243
243
|
class Condition(MatchCriteria):
|
|
@@ -392,7 +392,7 @@ class Condition(MatchCriteria):
|
|
|
392
392
|
if isinstance(left_value, str):
|
|
393
393
|
left_value = f"'{left_value}'"
|
|
394
394
|
right_value = f"'{right_value}'"
|
|
395
|
-
return eval(f"{left_value} {operator} {right_value}")
|
|
395
|
+
return eval(f"{left_value} {operator} {right_value}") # pylint: disable=eval-used
|
|
396
396
|
|
|
397
397
|
|
|
398
398
|
Anded = namedtuple('Anded', ['conditions', 'ors'])
|
|
@@ -463,13 +463,15 @@ class BooleanExpression(MatchCriteria):
|
|
|
463
463
|
return cls(expression=_parse_ored(element.find('xtce:ORedConditions', ns)))
|
|
464
464
|
raise ValueError(f"Failed to parse {element}")
|
|
465
465
|
|
|
466
|
-
def evaluate(self, parsed_data: dict,
|
|
466
|
+
def evaluate(self, parsed_data: dict, current_parsed_value: int or float = None) -> bool:
|
|
467
467
|
"""Evaluate the criteria in the BooleanExpression down to a single boolean.
|
|
468
468
|
|
|
469
469
|
Parameters
|
|
470
470
|
----------
|
|
471
471
|
parsed_data : dict
|
|
472
472
|
Dictionary of parsed parameter data so far. Used to evaluate truthyness of the match criteria.
|
|
473
|
+
current_parsed_value : int or float, Optional
|
|
474
|
+
Current value being parsed.
|
|
473
475
|
|
|
474
476
|
Returns
|
|
475
477
|
-------
|
|
@@ -862,10 +864,12 @@ class ContextCalibrator(AttrComparable):
|
|
|
862
864
|
if context_match_element.find('xtce:Comparison', ns) is not None:
|
|
863
865
|
return [Comparison.from_match_criteria_xml_element(
|
|
864
866
|
context_match_element.find('xtce:Comparison', ns), ns)]
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
867
|
+
if context_match_element.find('xtce:BooleanExpression', ns) is not None:
|
|
868
|
+
return [BooleanExpression.from_match_criteria_xml_element(
|
|
869
|
+
context_match_element.find('xtce:BooleanExpression', ns), ns)]
|
|
870
|
+
raise NotImplementedError("ContextCalibrator doesn't contain Comparison, ComparisonList, or BooleanExpression. "
|
|
871
|
+
"This probably means the match criteria is an unsupported type "
|
|
872
|
+
"(CustomAlgorithm).")
|
|
869
873
|
|
|
870
874
|
@classmethod
|
|
871
875
|
def from_context_calibrator_xml_element(cls, element: ElementTree.Element, ns: dict):
|
|
@@ -1140,6 +1144,7 @@ class StringDataEncoding(DataEncoding):
|
|
|
1140
1144
|
: int or None
|
|
1141
1145
|
Number of bits to skip after parsing the string
|
|
1142
1146
|
"""
|
|
1147
|
+
# pylint: disable=too-many-branches
|
|
1143
1148
|
skip_bits_after = 0 # Gets modified if we have a termination character
|
|
1144
1149
|
if self.fixed_length:
|
|
1145
1150
|
strlen_bits = self.fixed_length
|
|
@@ -1161,10 +1166,7 @@ class StringDataEncoding(DataEncoding):
|
|
|
1161
1166
|
strlen_bits = parsed_data[self.dynamic_length_reference].raw_value
|
|
1162
1167
|
strlen_bits = int(strlen_bits)
|
|
1163
1168
|
elif self.termination_character is not None:
|
|
1164
|
-
print(f"hex termination character: {self.termination_character}")
|
|
1165
1169
|
termination_char_utf8_bytes = bytes.fromhex(self.termination_character)
|
|
1166
|
-
print(f"bytes termination character (utf-8): {termination_char_utf8_bytes}, "
|
|
1167
|
-
f"len={len(termination_char_utf8_bytes)}")
|
|
1168
1170
|
|
|
1169
1171
|
if self.encoding in ['utf-16-le', 'utf-16-be']:
|
|
1170
1172
|
bytes_per_char = 2
|
|
@@ -1178,12 +1180,9 @@ class StringDataEncoding(DataEncoding):
|
|
|
1178
1180
|
bits_per_byte = 8
|
|
1179
1181
|
look_ahead_n_bytes = 0
|
|
1180
1182
|
while look_ahead_n_bytes <= len(packet_data) - packet_data.pos:
|
|
1181
|
-
print(f"looking ahead {look_ahead_n_bytes} bytes")
|
|
1182
1183
|
look_ahead = packet_data.peek(f'bytes:{look_ahead_n_bytes}') # Outputs UTF-8 encoded byte string
|
|
1183
1184
|
look_ahead = look_ahead.decode('utf-8').encode(self.encoding) # Force specified encoding
|
|
1184
|
-
print(f"string so far: {look_ahead}")
|
|
1185
1185
|
if termination_char_utf8_bytes in look_ahead:
|
|
1186
|
-
print('Found termination character.')
|
|
1187
1186
|
# Implicit assumption of one termination character in specified encoding
|
|
1188
1187
|
tclen_bits = bytes_per_char * bits_per_byte
|
|
1189
1188
|
strlen_bits = (look_ahead_n_bytes * bits_per_byte) - tclen_bits
|
|
@@ -1199,6 +1198,7 @@ class StringDataEncoding(DataEncoding):
|
|
|
1199
1198
|
if self.length_linear_adjuster is not None:
|
|
1200
1199
|
strlen_bits = self.length_linear_adjuster(strlen_bits)
|
|
1201
1200
|
return f"bytes:{strlen_bits // 8}", skip_bits_after
|
|
1201
|
+
# pylint: enable=too-many-branches
|
|
1202
1202
|
|
|
1203
1203
|
def parse_value(self, packet_data: bitstring.ConstBitStream, parsed_data: dict, **kwargs):
|
|
1204
1204
|
"""Parse a value from packet data, possibly using previously parsed data items to inform parsing.
|
|
@@ -1228,7 +1228,8 @@ class StringDataEncoding(DataEncoding):
|
|
|
1228
1228
|
Strings in XTCE can be described in three ways:
|
|
1229
1229
|
|
|
1230
1230
|
1. Using a termination character that marks the end of the string.
|
|
1231
|
-
2. Using a fixed length, which may be derived from referenced parameter either directly or via a discrete
|
|
1231
|
+
2. Using a fixed length, which may be derived from referenced parameter either directly or via a discrete
|
|
1232
|
+
lookup table.
|
|
1232
1233
|
3. Using a leading size field that describes the size of the following string.
|
|
1233
1234
|
|
|
1234
1235
|
Parameters
|
|
@@ -1394,7 +1395,10 @@ class IntegerDataEncoding(NumericDataEncoding):
|
|
|
1394
1395
|
: cls
|
|
1395
1396
|
"""
|
|
1396
1397
|
size_in_bits = int(element.attrib['sizeInBits'])
|
|
1397
|
-
encoding
|
|
1398
|
+
if 'encoding' in element.attrib:
|
|
1399
|
+
encoding = element.attrib['encoding']
|
|
1400
|
+
else:
|
|
1401
|
+
encoding = "unsigned"
|
|
1398
1402
|
calibrator = cls.get_default_calibrator(element, ns)
|
|
1399
1403
|
context_calibrators = cls.get_context_calibrators(element, ns)
|
|
1400
1404
|
return cls(size_in_bits=size_in_bits, encoding=encoding,
|
|
@@ -1431,6 +1435,9 @@ class FloatDataEncoding(NumericDataEncoding):
|
|
|
1431
1435
|
f"Must be one of {self._supported_encodings}.")
|
|
1432
1436
|
if encoding == 'MIL-1750A':
|
|
1433
1437
|
raise NotImplementedError("MIL-1750A encoded floats are not supported by this library yet.")
|
|
1438
|
+
if encoding == 'IEEE-754' and size_in_bits not in (16, 32, 64):
|
|
1439
|
+
raise ValueError(f"Invalid size_in_bits value for IEEE-754 FloatDataEncoding, {size_in_bits}. "
|
|
1440
|
+
"Must be 16, 32, or 64.")
|
|
1434
1441
|
super().__init__(size_in_bits, encoding=encoding,
|
|
1435
1442
|
default_calibrator=default_calibrator, context_calibrators=context_calibrators)
|
|
1436
1443
|
|
|
@@ -1631,7 +1638,7 @@ class ParameterType(AttrComparable, metaclass=ABCMeta):
|
|
|
1631
1638
|
|
|
1632
1639
|
@classmethod
|
|
1633
1640
|
def from_parameter_type_xml_element(cls, element: ElementTree.Element, ns: dict):
|
|
1634
|
-
"""Create
|
|
1641
|
+
"""Create a *ParameterType from an <xtce:*ParameterType> XML element.
|
|
1635
1642
|
|
|
1636
1643
|
Parameters
|
|
1637
1644
|
----------
|
|
@@ -1642,7 +1649,7 @@ class ParameterType(AttrComparable, metaclass=ABCMeta):
|
|
|
1642
1649
|
|
|
1643
1650
|
Returns
|
|
1644
1651
|
-------
|
|
1645
|
-
:
|
|
1652
|
+
: ParameterType
|
|
1646
1653
|
"""
|
|
1647
1654
|
name = element.attrib['name']
|
|
1648
1655
|
unit = cls.get_units(element, ns)
|
|
@@ -1666,6 +1673,7 @@ class ParameterType(AttrComparable, metaclass=ABCMeta):
|
|
|
1666
1673
|
-------
|
|
1667
1674
|
: str or None
|
|
1668
1675
|
"""
|
|
1676
|
+
# Assume we are not parsing a Time Parameter Type, which stores units differently
|
|
1669
1677
|
units = parameter_type_element.findall('xtce:UnitSet/xtce:Unit', ns)
|
|
1670
1678
|
# TODO: Implement multiple unit elements for compound unit definitions
|
|
1671
1679
|
assert len(units) <= 1, f"Found {len(units)} <xtce:Unit> elements in a single <xtce:UnitSet>." \
|
|
@@ -1673,6 +1681,7 @@ class ParameterType(AttrComparable, metaclass=ABCMeta):
|
|
|
1673
1681
|
f"and is not yet supported by this library."
|
|
1674
1682
|
if units:
|
|
1675
1683
|
return " ".join([u.text for u in units])
|
|
1684
|
+
# Units are optional so return None if they aren't specified
|
|
1676
1685
|
return None
|
|
1677
1686
|
|
|
1678
1687
|
@staticmethod
|
|
@@ -1693,9 +1702,10 @@ class ParameterType(AttrComparable, metaclass=ABCMeta):
|
|
|
1693
1702
|
"""
|
|
1694
1703
|
for data_encoding in [StringDataEncoding, IntegerDataEncoding, FloatDataEncoding, BinaryDataEncoding]:
|
|
1695
1704
|
# Try to find each type of data encoding element. If we find one, we assume it's the only one.
|
|
1696
|
-
element = parameter_type_element.find(f"xtce:{data_encoding.__name__}", ns)
|
|
1705
|
+
element = parameter_type_element.find(f".//xtce:{data_encoding.__name__}", ns)
|
|
1697
1706
|
if element is not None:
|
|
1698
1707
|
return data_encoding.from_data_encoding_xml_element(element, ns)
|
|
1708
|
+
return None
|
|
1699
1709
|
|
|
1700
1710
|
def parse_value(self, packet_data: bitstring.ConstBitStream, parsed_data: dict, **kwargs):
|
|
1701
1711
|
"""Using the parameter type definition and associated data encoding, parse a value from a bit stream starting
|
|
@@ -1868,10 +1878,222 @@ class BinaryParameterType(ParameterType):
|
|
|
1868
1878
|
self.encoding = encoding
|
|
1869
1879
|
|
|
1870
1880
|
|
|
1871
|
-
class
|
|
1881
|
+
class BooleanParameterType(ParameterType):
|
|
1882
|
+
"""<xtce:BooleanParameterType>"""
|
|
1883
|
+
|
|
1884
|
+
def __init__(self, name: str, encoding: DataEncoding, unit: str = None):
|
|
1885
|
+
"""Constructor that just issues a warning if the encoding is String or Binary"""
|
|
1886
|
+
if isinstance(encoding, (BinaryDataEncoding, StringDataEncoding)):
|
|
1887
|
+
warnings.warn(f"You are encoding a BooleanParameterType with a {type(encoding)} encoding."
|
|
1888
|
+
f"This is almost certainly a very bad idea because the behavior of string and binary "
|
|
1889
|
+
f"encoded booleans is not specified in XTCE. e.g. is the string \"0\" truthy?")
|
|
1890
|
+
super().__init__(name, encoding, unit)
|
|
1891
|
+
|
|
1892
|
+
def parse_value(self, packet_data: bitstring.ConstBitStream, parsed_data: dict, **kwargs):
|
|
1893
|
+
"""Using the parameter type definition and associated data encoding, parse a value from a bit stream starting
|
|
1894
|
+
at the current cursor position.
|
|
1895
|
+
|
|
1896
|
+
Parameters
|
|
1897
|
+
----------
|
|
1898
|
+
packet_data : bitstring.ConstBitStream
|
|
1899
|
+
Binary packet data with cursor at the beginning of this parameter's data field.
|
|
1900
|
+
parsed_data : dict
|
|
1901
|
+
Previously parsed data
|
|
1902
|
+
|
|
1903
|
+
Returns
|
|
1904
|
+
-------
|
|
1905
|
+
parsed_value : int
|
|
1906
|
+
Raw encoded value
|
|
1907
|
+
derived_value : str
|
|
1908
|
+
Resulting boolean representation of the encoded raw value
|
|
1909
|
+
"""
|
|
1910
|
+
raw, _ = super().parse_value(packet_data, parsed_data, **kwargs)
|
|
1911
|
+
# Note: This behaves very strangely for String and Binary data encodings.
|
|
1912
|
+
# Don't use those for Boolean parameters. The behavior isn't specified well in XTCE.
|
|
1913
|
+
return raw, bool(raw)
|
|
1914
|
+
|
|
1915
|
+
|
|
1916
|
+
class TimeParameterType(ParameterType, metaclass=ABCMeta):
|
|
1917
|
+
"""Abstract class for time parameter types"""
|
|
1918
|
+
|
|
1919
|
+
def __init__(self, name: str, encoding: DataEncoding, unit: str = None,
|
|
1920
|
+
epoch: str = None, offset_from: str = None):
|
|
1921
|
+
"""Constructor
|
|
1922
|
+
|
|
1923
|
+
Parameters
|
|
1924
|
+
----------
|
|
1925
|
+
name : str
|
|
1926
|
+
Parameter type name. Usually something like 'MSN__PARAM_Type'.
|
|
1927
|
+
encoding : DataEncoding
|
|
1928
|
+
How the data is encoded. e.g. IntegerDataEncoding, StringDataEncoding, etc.
|
|
1929
|
+
unit : str, Optional
|
|
1930
|
+
String describing the unit for the stored value. Note that if a scale and offset are provided on
|
|
1931
|
+
the Encoding element, the unit applies to the scaled value, not the raw value.
|
|
1932
|
+
epoch : str, Optional
|
|
1933
|
+
String describing the starting epoch for the date or datetime encoded in the parameter.
|
|
1934
|
+
Must be xs:date, xs:dateTime, or one of the following: "TAI", "J2000", "UNIX", "POSIX", "GPS".
|
|
1935
|
+
offset_from : str, Optional
|
|
1936
|
+
Used to reference another time parameter by name. It allows
|
|
1937
|
+
for the stringing together of several dissimilar but related time parameters.
|
|
1938
|
+
|
|
1939
|
+
Notes
|
|
1940
|
+
-----
|
|
1941
|
+
The XTCE spec is not very clear about OffsetFrom or what it is for. We parse it but don't use it for
|
|
1942
|
+
anything.
|
|
1943
|
+
"""
|
|
1944
|
+
super().__init__(name, encoding, unit=unit)
|
|
1945
|
+
self.epoch = epoch
|
|
1946
|
+
self.offset_from = offset_from
|
|
1947
|
+
|
|
1948
|
+
@classmethod
|
|
1949
|
+
def from_parameter_type_xml_element(cls, element: ElementTree.Element, ns: dict):
|
|
1950
|
+
"""Create a *TimeParameterType from an <xtce:*TimeParameterType> XML element.
|
|
1951
|
+
|
|
1952
|
+
Parameters
|
|
1953
|
+
----------
|
|
1954
|
+
element : ElementTree.Element
|
|
1955
|
+
The XML element from which to create the object.
|
|
1956
|
+
ns: dict
|
|
1957
|
+
XML namespace dict
|
|
1958
|
+
|
|
1959
|
+
Returns
|
|
1960
|
+
-------
|
|
1961
|
+
: TimeParameterType
|
|
1962
|
+
"""
|
|
1963
|
+
name = element.attrib['name']
|
|
1964
|
+
unit = cls.get_units(element, ns)
|
|
1965
|
+
encoding = cls.get_data_encoding(element, ns)
|
|
1966
|
+
encoding_unit_scaler = cls.get_time_unit_linear_scaler(element, ns)
|
|
1967
|
+
if encoding_unit_scaler:
|
|
1968
|
+
encoding.default_calibrator = encoding_unit_scaler
|
|
1969
|
+
epoch = cls.get_epoch(element, ns)
|
|
1970
|
+
offset_from = cls.get_offset_from(element, ns)
|
|
1971
|
+
return cls(name, encoding, unit, epoch, offset_from)
|
|
1972
|
+
|
|
1973
|
+
@staticmethod
|
|
1974
|
+
def get_units(parameter_type_element: ElementTree.Element, ns: dict) -> str or None:
|
|
1975
|
+
"""Finds the units associated with a parameter type element and parsed them to return a unit string.
|
|
1976
|
+
We assume only one <xtce:Unit> but this could be extended to support multiple units.
|
|
1977
|
+
See section 4.3.2.2.4 of CCSDS 660.1-G-1
|
|
1978
|
+
|
|
1979
|
+
Parameters
|
|
1980
|
+
----------
|
|
1981
|
+
parameter_type_element : ElementTree.Element
|
|
1982
|
+
The parameter type element
|
|
1983
|
+
ns : dict
|
|
1984
|
+
XML namespace dictionary
|
|
1985
|
+
|
|
1986
|
+
Returns
|
|
1987
|
+
-------
|
|
1988
|
+
: str or None
|
|
1989
|
+
"""
|
|
1990
|
+
encoding_element = parameter_type_element.find('xtce:Encoding', ns)
|
|
1991
|
+
if encoding_element and "units" in encoding_element.attrib:
|
|
1992
|
+
units = encoding_element.attrib["units"]
|
|
1993
|
+
return units
|
|
1994
|
+
# Units are optional so return None if they aren't specified
|
|
1995
|
+
return None
|
|
1996
|
+
|
|
1997
|
+
@staticmethod
|
|
1998
|
+
def get_time_unit_linear_scaler(
|
|
1999
|
+
parameter_type_element: ElementTree.Element, ns: dict) -> PolynomialCalibrator or None:
|
|
2000
|
+
"""Finds the linear calibrator associated with the Encoding element for the parameter type element.
|
|
2001
|
+
See section 4.3.2.4.8.3 of CCSDS 660.1-G-2
|
|
2002
|
+
|
|
2003
|
+
Parameters
|
|
2004
|
+
----------
|
|
2005
|
+
parameter_type_element : ElementTree.Element
|
|
2006
|
+
The parameter type element
|
|
2007
|
+
ns : dict
|
|
2008
|
+
XML namespace dictionary
|
|
2009
|
+
|
|
2010
|
+
Returns
|
|
2011
|
+
-------
|
|
2012
|
+
: PolynomialCalibrator or None
|
|
2013
|
+
"""
|
|
2014
|
+
encoding_element = parameter_type_element.find('xtce:Encoding', ns)
|
|
2015
|
+
coefficients = []
|
|
2016
|
+
|
|
2017
|
+
if "offset" in encoding_element.attrib:
|
|
2018
|
+
offset = encoding_element.attrib["offset"]
|
|
2019
|
+
c0 = PolynomialCoefficient(coefficient=float(offset), exponent=0)
|
|
2020
|
+
coefficients.append(c0)
|
|
2021
|
+
|
|
2022
|
+
if "scale" in encoding_element.attrib:
|
|
2023
|
+
scale = encoding_element.attrib["scale"]
|
|
2024
|
+
c1 = PolynomialCoefficient(coefficient=float(scale), exponent=1)
|
|
2025
|
+
coefficients.append(c1)
|
|
2026
|
+
# If we have an offset but not a scale, we need to add a first order term with coefficient 1
|
|
2027
|
+
elif "offset" in encoding_element.attrib:
|
|
2028
|
+
c1 = PolynomialCoefficient(coefficient=1, exponent=1)
|
|
2029
|
+
coefficients.append(c1)
|
|
2030
|
+
|
|
2031
|
+
if coefficients:
|
|
2032
|
+
return PolynomialCalibrator(coefficients=coefficients)
|
|
2033
|
+
# If we didn't find offset nor scale, return None (no calibrator)
|
|
2034
|
+
return None
|
|
2035
|
+
|
|
2036
|
+
@staticmethod
|
|
2037
|
+
def get_epoch(parameter_type_element: ElementTree.Element, ns: dict) -> str or None:
|
|
2038
|
+
"""Finds the epoch associated with a parameter type element and parses them to return an epoch string.
|
|
2039
|
+
See section 4.3.2.4.9 of CCSDS 660.1-G-2
|
|
2040
|
+
|
|
2041
|
+
Parameters
|
|
2042
|
+
----------
|
|
2043
|
+
parameter_type_element : ElementTree.Element
|
|
2044
|
+
The parameter type element
|
|
2045
|
+
ns : dict
|
|
2046
|
+
XML namespace dictionary
|
|
2047
|
+
|
|
2048
|
+
Returns
|
|
2049
|
+
-------
|
|
2050
|
+
: str or None
|
|
2051
|
+
The epoch string, which may be a datetime string or a named epoch such as TAI.
|
|
2052
|
+
"""
|
|
2053
|
+
epoch_element = parameter_type_element.find('xtce:ReferenceTime/xtce:Epoch', ns)
|
|
2054
|
+
if epoch_element is not None:
|
|
2055
|
+
return epoch_element.text
|
|
2056
|
+
return None
|
|
2057
|
+
|
|
2058
|
+
@staticmethod
|
|
2059
|
+
def get_offset_from(parameter_type_element: ElementTree.Element, ns: dict) -> str or None:
|
|
2060
|
+
"""Finds the parameter referenced in OffsetFrom in a parameter type element and returns the name of the
|
|
2061
|
+
referenced parameter (which must be of type TimeParameterType).
|
|
2062
|
+
See section 4.3.2.4.9 of CCSDS 660.1-G-1
|
|
2063
|
+
|
|
2064
|
+
Parameters
|
|
2065
|
+
----------
|
|
2066
|
+
parameter_type_element : ElementTree.Element
|
|
2067
|
+
The parameter type element
|
|
2068
|
+
ns : dict
|
|
2069
|
+
XML namespace dictionary
|
|
2070
|
+
|
|
2071
|
+
Returns
|
|
2072
|
+
-------
|
|
2073
|
+
: str or None
|
|
2074
|
+
The named of the referenced parameter.
|
|
2075
|
+
"""
|
|
2076
|
+
offset_from_element = parameter_type_element.find('xtce:ReferenceTime/xtce:OffsetFrom', ns)
|
|
2077
|
+
if offset_from_element is not None:
|
|
2078
|
+
return offset_from_element.attrib['parameterRef']
|
|
2079
|
+
return None
|
|
2080
|
+
|
|
2081
|
+
|
|
2082
|
+
class AbsoluteTimeParameterType(TimeParameterType):
|
|
2083
|
+
"""<xtce:AbsoluteTimeParameterType>"""
|
|
2084
|
+
pass
|
|
2085
|
+
|
|
2086
|
+
|
|
2087
|
+
class RelativeTimeParameterType(TimeParameterType):
|
|
2088
|
+
"""<xtce:RelativeTimeParameterType>"""
|
|
2089
|
+
pass
|
|
2090
|
+
|
|
2091
|
+
|
|
2092
|
+
class Parameter(AttrComparable):
|
|
1872
2093
|
"""<xtce:Parameter>"""
|
|
1873
2094
|
|
|
1874
|
-
def __init__(self, name: str, parameter_type: ParameterType
|
|
2095
|
+
def __init__(self, name: str, parameter_type: ParameterType,
|
|
2096
|
+
short_description: str = None, long_description: str = None):
|
|
1875
2097
|
"""Constructor
|
|
1876
2098
|
|
|
1877
2099
|
Parameters
|
|
@@ -1880,9 +2102,15 @@ class Parameter:
|
|
|
1880
2102
|
Parameter name. Typically something like MSN__PARAMNAME
|
|
1881
2103
|
parameter_type : ParameterType
|
|
1882
2104
|
Parameter type object that describes how the parameter is stored.
|
|
2105
|
+
short_description : str
|
|
2106
|
+
Short description of parameter as parsed from XTCE
|
|
2107
|
+
long_description : str
|
|
2108
|
+
Long description of parameter as parsed from XTCE
|
|
1883
2109
|
"""
|
|
1884
2110
|
self.name = name
|
|
1885
2111
|
self.parameter_type = parameter_type
|
|
2112
|
+
self.short_description = short_description
|
|
2113
|
+
self.long_description = long_description
|
|
1886
2114
|
|
|
1887
2115
|
def __repr__(self):
|
|
1888
2116
|
module = self.__class__.__module__
|
|
@@ -1890,7 +2118,7 @@ class Parameter:
|
|
|
1890
2118
|
return f"<{module}.{qualname} {self.name}>"
|
|
1891
2119
|
|
|
1892
2120
|
|
|
1893
|
-
class SequenceContainer:
|
|
2121
|
+
class SequenceContainer(AttrComparable):
|
|
1894
2122
|
"""<xtce:SequenceContainer>"""
|
|
1895
2123
|
|
|
1896
2124
|
def __init__(self,
|
|
@@ -1951,6 +2179,9 @@ class XtcePacketDefinition:
|
|
|
1951
2179
|
'{{{xtce}}}FloatParameterType': FloatParameterType,
|
|
1952
2180
|
'{{{xtce}}}EnumeratedParameterType': EnumeratedParameterType,
|
|
1953
2181
|
'{{{xtce}}}BinaryParameterType': BinaryParameterType,
|
|
2182
|
+
'{{{xtce}}}BooleanParameterType': BooleanParameterType,
|
|
2183
|
+
'{{{xtce}}}AbsoluteTimeParameterType': AbsoluteTimeParameterType,
|
|
2184
|
+
'{{{xtce}}}RelativeTimeParameterType': RelativeTimeParameterType,
|
|
1954
2185
|
}
|
|
1955
2186
|
|
|
1956
2187
|
def __init__(self, xtce_document: str or Path, ns: dict = None):
|
|
@@ -1970,6 +2201,7 @@ class XtcePacketDefinition:
|
|
|
1970
2201
|
"""
|
|
1971
2202
|
self._sequence_container_cache = {} # Lookup for parsed sequence container objects
|
|
1972
2203
|
self._parameter_cache = {} # Lookup for parsed parameter objects
|
|
2204
|
+
self._parameter_type_cache = {} # Lookup for parsed parameter type objects
|
|
1973
2205
|
self.ns = ns or self._default_namespace
|
|
1974
2206
|
self.type_tag_to_object = {k.format(**self.ns): v for k, v in
|
|
1975
2207
|
self._tag_to_type_template.items()}
|
|
@@ -2008,10 +2240,7 @@ class XtcePacketDefinition:
|
|
|
2008
2240
|
try:
|
|
2009
2241
|
base_container, restriction_criteria = self._get_container_base_container(sequence_container)
|
|
2010
2242
|
base_sequence_container = self.parse_sequence_container_contents(base_container)
|
|
2011
|
-
#base_sequence_container.restriction_criteria = restriction_criteria
|
|
2012
2243
|
base_container_name = base_sequence_container.name
|
|
2013
|
-
# Prepend the base container. This is necessary for handling multiple inheritance.
|
|
2014
|
-
#entry_list.insert(0, base_sequence_container)
|
|
2015
2244
|
except ElementNotFoundError:
|
|
2016
2245
|
base_container_name = None
|
|
2017
2246
|
restriction_criteria = None
|
|
@@ -2028,14 +2257,32 @@ class XtcePacketDefinition:
|
|
|
2028
2257
|
else:
|
|
2029
2258
|
parameter_element = self._find_parameter(parameter_name)
|
|
2030
2259
|
parameter_type_name = parameter_element.attrib['parameterTypeRef']
|
|
2031
|
-
|
|
2032
|
-
|
|
2260
|
+
|
|
2261
|
+
# If we've already parsed this parameter type for a different parameter
|
|
2262
|
+
if parameter_type_name in self._parameter_type_cache:
|
|
2263
|
+
parameter_type_object = self._parameter_type_cache[parameter_type_name]
|
|
2264
|
+
else:
|
|
2265
|
+
parameter_type_element = self._find_parameter_type(parameter_type_name)
|
|
2266
|
+
parameter_type_class = self.type_tag_to_object[parameter_type_element.tag]
|
|
2267
|
+
parameter_type_object = parameter_type_class.from_parameter_type_xml_element(
|
|
2268
|
+
parameter_type_element, self.ns)
|
|
2269
|
+
self._parameter_type_cache[parameter_type_name] = parameter_type_object # Add to cache
|
|
2270
|
+
|
|
2271
|
+
parameter_short_description = parameter_element.attrib['shortDescription'] if (
|
|
2272
|
+
'shortDescription' in parameter_element.attrib
|
|
2273
|
+
) else None
|
|
2274
|
+
parameter_long_description = parameter_element.find('xtce:LongDescription', self.ns).text if (
|
|
2275
|
+
parameter_element.find('xtce:LongDescription', self.ns) is not None
|
|
2276
|
+
) else None
|
|
2277
|
+
|
|
2033
2278
|
parameter_object = Parameter(
|
|
2034
2279
|
name=parameter_name,
|
|
2035
|
-
parameter_type=
|
|
2036
|
-
|
|
2280
|
+
parameter_type=parameter_type_object,
|
|
2281
|
+
short_description=parameter_short_description,
|
|
2282
|
+
long_description=parameter_long_description
|
|
2283
|
+
)
|
|
2037
2284
|
entry_list.append(parameter_object)
|
|
2038
|
-
self._parameter_cache[parameter_name] = parameter_object
|
|
2285
|
+
self._parameter_cache[parameter_name] = parameter_object # Add to cache
|
|
2039
2286
|
elif entry.tag == '{{{xtce}}}ContainerRefEntry'.format(**self.ns): # pylint: disable=consider-using-f-string
|
|
2040
2287
|
nested_container = self._find_container(name=entry.attrib['containerRef'])
|
|
2041
2288
|
entry_list.append(self.parse_sequence_container_contents(nested_container))
|
|
@@ -2057,9 +2304,19 @@ class XtcePacketDefinition:
|
|
|
2057
2304
|
|
|
2058
2305
|
@property
|
|
2059
2306
|
def named_containers(self):
|
|
2060
|
-
"""Property accessor that returns the dict cache of SequenceContainer
|
|
2307
|
+
"""Property accessor that returns the dict cache of SequenceContainer objects"""
|
|
2061
2308
|
return self._sequence_container_cache
|
|
2062
2309
|
|
|
2310
|
+
@property
|
|
2311
|
+
def named_parameters(self):
|
|
2312
|
+
"""Property accessor that returns the dict cache of Parameter objects"""
|
|
2313
|
+
return self._parameter_cache
|
|
2314
|
+
|
|
2315
|
+
@property
|
|
2316
|
+
def named_parameter_types(self):
|
|
2317
|
+
"""Property accessor that returns the dict cache of ParameterType objects"""
|
|
2318
|
+
return self._parameter_type_cache
|
|
2319
|
+
|
|
2063
2320
|
@property
|
|
2064
2321
|
def flattened_containers(self):
|
|
2065
2322
|
"""Accesses a flattened, generic representation of non-abstract packet definitions along with their
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: space-packet-parser
|
|
3
|
-
Version: 4.0.2
|
|
4
|
-
Summary: A CCSDS telemetry packet decoding library based on the XTCE packet format description standard.
|
|
5
|
-
Home-page: https://github.com/medley56/space_packet_parser
|
|
6
|
-
License: BSD-3-Clause
|
|
7
|
-
Keywords: ccsds,xtce,space data systems,space packet protocol,packet parsing,lasp,university of colorado,data processing,data extraction,data manipulation,data transformation,data encoding,data decoding,packet inspection,binary data,python
|
|
8
|
-
Author: Gavin Medley
|
|
9
|
-
Author-email: gavin.medley@lasp.colorado.edu
|
|
10
|
-
Maintainer: Gavin Medley
|
|
11
|
-
Maintainer-email: gavin.medley@lasp.colorado.edu
|
|
12
|
-
Requires-Python: >=3.8,<3.12
|
|
13
|
-
Classifier: License :: OSI Approved :: BSD License
|
|
14
|
-
Classifier: Programming Language :: Python :: 3
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
-
Requires-Dist: bitstring (>=3.0.0,<5)
|
|
20
|
-
Project-URL: Repository, https://github.com/medley56/space_packet_parser
|
|
21
|
-
Description-Content-Type: text/markdown
|
|
22
|
-
|
|
23
|
-
# Space Packet Parser - a configurable parser for CCSDS telemetry packets
|
|
24
|
-
This is a package for decoding CCSDS telemetry packets according to an XTCE or CSV packet structure definition.
|
|
25
|
-
It is based on the UML model of the XTCE spec and aims to support all but the most esoteric elements of the
|
|
26
|
-
XTCE telemetry packet specification.
|
|
27
|
-
|
|
28
|
-
Resources:
|
|
29
|
-
- [XTCE (Green Book - Informational Report)](https://public.ccsds.org/Pubs/660x2g2.pdf)
|
|
30
|
-
- [XTCE Element Description (Green Book - Informational Report)](https://public.ccsds.org/Pubs/660x1g2.pdf)
|
|
31
|
-
- [XTCE (Blue Book - Recommended Standard)](https://public.ccsds.org/Pubs/660x0b2.pdf)
|
|
32
|
-
|
|
33
|
-
## Installation
|
|
34
|
-
```bash
|
|
35
|
-
pip install space_packet_parser
|
|
36
|
-
```
|
|
37
|
-
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
# Space Packet Parser - a configurable parser for CCSDS telemetry packets
|
|
2
|
-
This is a package for decoding CCSDS telemetry packets according to an XTCE or CSV packet structure definition.
|
|
3
|
-
It is based on the UML model of the XTCE spec and aims to support all but the most esoteric elements of the
|
|
4
|
-
XTCE telemetry packet specification.
|
|
5
|
-
|
|
6
|
-
Resources:
|
|
7
|
-
- [XTCE (Green Book - Informational Report)](https://public.ccsds.org/Pubs/660x2g2.pdf)
|
|
8
|
-
- [XTCE Element Description (Green Book - Informational Report)](https://public.ccsds.org/Pubs/660x1g2.pdf)
|
|
9
|
-
- [XTCE (Blue Book - Recommended Standard)](https://public.ccsds.org/Pubs/660x0b2.pdf)
|
|
10
|
-
|
|
11
|
-
## Installation
|
|
12
|
-
```bash
|
|
13
|
-
pip install space_packet_parser
|
|
14
|
-
```
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
from setuptools import setup
|
|
3
|
-
|
|
4
|
-
packages = \
|
|
5
|
-
['space_packet_parser']
|
|
6
|
-
|
|
7
|
-
package_data = \
|
|
8
|
-
{'': ['*']}
|
|
9
|
-
|
|
10
|
-
install_requires = \
|
|
11
|
-
['bitstring>=3.0.0,<5']
|
|
12
|
-
|
|
13
|
-
setup_kwargs = {
|
|
14
|
-
'name': 'space-packet-parser',
|
|
15
|
-
'version': '4.0.2',
|
|
16
|
-
'description': 'A CCSDS telemetry packet decoding library based on the XTCE packet format description standard.',
|
|
17
|
-
'long_description': '# Space Packet Parser - a configurable parser for CCSDS telemetry packets\nThis is a package for decoding CCSDS telemetry packets according to an XTCE or CSV packet structure definition. \nIt is based on the UML model of the XTCE spec and aims to support all but the most esoteric elements of the \nXTCE telemetry packet specification.\n\nResources:\n- [XTCE (Green Book - Informational Report)](https://public.ccsds.org/Pubs/660x2g2.pdf)\n- [XTCE Element Description (Green Book - Informational Report)](https://public.ccsds.org/Pubs/660x1g2.pdf)\n- [XTCE (Blue Book - Recommended Standard)](https://public.ccsds.org/Pubs/660x0b2.pdf)\n\n## Installation\n```bash\npip install space_packet_parser\n```\n',
|
|
18
|
-
'author': 'Gavin Medley',
|
|
19
|
-
'author_email': 'gavin.medley@lasp.colorado.edu',
|
|
20
|
-
'maintainer': 'Gavin Medley',
|
|
21
|
-
'maintainer_email': 'gavin.medley@lasp.colorado.edu',
|
|
22
|
-
'url': 'https://github.com/medley56/space_packet_parser',
|
|
23
|
-
'packages': packages,
|
|
24
|
-
'package_data': package_data,
|
|
25
|
-
'install_requires': install_requires,
|
|
26
|
-
'python_requires': '>=3.8,<3.12',
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
setup(**setup_kwargs)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|