puslib 0.3.0__tar.gz → 0.4.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.
- {puslib-0.3.0/src/puslib.egg-info → puslib-0.4.0}/PKG-INFO +11 -12
- {puslib-0.3.0 → puslib-0.4.0}/README.md +10 -10
- {puslib-0.3.0 → puslib-0.4.0}/setup.cfg +9 -0
- puslib-0.4.0/setup.py +3 -0
- {puslib-0.3.0 → puslib-0.4.0}/src/puslib/__init__.py +10 -4
- puslib-0.4.0/src/puslib/crc_ccitt.py +20 -0
- {puslib-0.3.0 → puslib-0.4.0}/src/puslib/ident.py +7 -4
- {puslib-0.3.0 → puslib-0.4.0}/src/puslib/packet.py +51 -46
- {puslib-0.3.0 → puslib-0.4.0}/src/puslib/parameter.py +75 -64
- {puslib-0.3.0 → puslib-0.4.0}/src/puslib/process.py +53 -11
- {puslib-0.3.0 → puslib-0.4.0}/src/puslib/pus_policy.py +3 -3
- {puslib-0.3.0 → puslib-0.4.0}/src/puslib/services/error_codes.py +2 -0
- {puslib-0.3.0 → puslib-0.4.0}/src/puslib/services/param_report.py +6 -5
- {puslib-0.3.0 → puslib-0.4.0}/src/puslib/services/pus_001_request_verification.py +9 -9
- {puslib-0.3.0 → puslib-0.4.0}/src/puslib/services/pus_003_housekeeping.py +45 -24
- {puslib-0.3.0 → puslib-0.4.0}/src/puslib/services/pus_005_event_reporting.py +11 -10
- {puslib-0.3.0 → puslib-0.4.0}/src/puslib/services/pus_008_function_management.py +8 -8
- {puslib-0.3.0 → puslib-0.4.0}/src/puslib/services/pus_017_test.py +3 -4
- {puslib-0.3.0 → puslib-0.4.0}/src/puslib/services/pus_020_parameter_management.py +7 -6
- {puslib-0.3.0 → puslib-0.4.0}/src/puslib/services/service.py +17 -8
- puslib-0.4.0/src/puslib/streams/buffer.py +34 -0
- puslib-0.4.0/src/puslib/streams/console.py +10 -0
- puslib-0.4.0/src/puslib/streams/file.py +56 -0
- {puslib-0.3.0 → puslib-0.4.0}/src/puslib/streams/stream.py +12 -1
- {puslib-0.3.0 → puslib-0.4.0}/src/puslib/time.py +29 -25
- {puslib-0.3.0 → puslib-0.4.0/src/puslib.egg-info}/PKG-INFO +11 -12
- puslib-0.4.0/tests/test_ident.py +12 -0
- {puslib-0.3.0 → puslib-0.4.0}/tests/test_packet.py +64 -0
- puslib-0.3.0/setup.py +0 -9
- puslib-0.3.0/src/puslib/crc_ccitt.py +0 -44
- puslib-0.3.0/src/puslib/streams/buffer.py +0 -23
- puslib-0.3.0/src/puslib/streams/console.py +0 -6
- puslib-0.3.0/src/puslib/streams/file.py +0 -32
- puslib-0.3.0/tests/test_ident.py +0 -12
- {puslib-0.3.0 → puslib-0.4.0}/LICENSE +0 -0
- {puslib-0.3.0 → puslib-0.4.0}/pyproject.toml +0 -0
- {puslib-0.3.0 → puslib-0.4.0}/src/puslib/exceptions.py +0 -0
- {puslib-0.3.0 → puslib-0.4.0}/src/puslib/services/__init__.py +0 -0
- {puslib-0.3.0 → puslib-0.4.0}/src/puslib/streams/__init__.py +0 -0
- {puslib-0.3.0 → puslib-0.4.0}/src/puslib.egg-info/SOURCES.txt +0 -0
- {puslib-0.3.0 → puslib-0.4.0}/src/puslib.egg-info/dependency_links.txt +0 -0
- {puslib-0.3.0 → puslib-0.4.0}/src/puslib.egg-info/requires.txt +0 -0
- {puslib-0.3.0 → puslib-0.4.0}/src/puslib.egg-info/top_level.txt +0 -0
- {puslib-0.3.0 → puslib-0.4.0}/tests/test_crc.py +0 -0
- {puslib-0.3.0 → puslib-0.4.0}/tests/test_parameter.py +0 -0
- {puslib-0.3.0 → puslib-0.4.0}/tests/test_policy.py +0 -0
- {puslib-0.3.0 → puslib-0.4.0}/tests/test_time.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: puslib
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: An implementation of the telemetry and telecommand packet utilization standard (PUS).
|
|
5
5
|
Home-page: https://github.com/pxntus/puslib
|
|
6
6
|
Author: Pontus Nyman
|
|
@@ -26,24 +26,23 @@ Description-Content-Type: text/markdown
|
|
|
26
26
|
License-File: LICENSE
|
|
27
27
|
Requires-Dist: bitstring
|
|
28
28
|
Dynamic: license-file
|
|
29
|
-
Dynamic: requires-dist
|
|
30
29
|
|
|
31
30
|
# Puslib
|
|
32
31
|
|
|
33
|
-
**Puslib** is a Python
|
|
32
|
+
**Puslib** is a Python library for working with the ECSS Packet Utilization Standard (PUS), a protocol widely used in the space industry.
|
|
34
33
|
|
|
35
|
-
|
|
34
|
+
PUS defines an application-level interface between ground and space, covering both segments. It specifies a binary packet format for telecommands and telemetry (based on CCSDS space packets) and a routing mechanism for those packets, as well as a software architecture built around application processes and a set of standard services.
|
|
36
35
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
|
|
42
|
-
|
|
36
|
+
Puslib covers two main use cases:
|
|
37
|
+
|
|
38
|
+
* **TM/TC packet parsing** — deserialize raw packet streams, inspect packet fields, and extract parameter data for post-processing or analysis.
|
|
39
|
+
* **PUS application stack** — implement a PUS-compliant backend that receives telecommands and emits telemetry, for use in EGSE equipment, simulators, or any system that communicates over TM/TC.
|
|
40
|
+
|
|
41
|
+
The PUS stack is designed from the perspective of the system being monitored and controlled. It receives telecommands and produces telemetry, making it better suited for simulators and EGSE than for mission control systems. It targets ground tooling and test equipment rather than flight software, but don't let the sky be the limit.
|
|
43
42
|
|
|
44
43
|
## Install
|
|
45
44
|
|
|
46
|
-
```
|
|
45
|
+
```shell
|
|
47
46
|
pip install puslib
|
|
48
47
|
```
|
|
49
48
|
|
|
@@ -51,7 +50,7 @@ Python 3.10 or later is required.
|
|
|
51
50
|
|
|
52
51
|
## Basic Example
|
|
53
52
|
|
|
54
|
-
```
|
|
53
|
+
```python
|
|
55
54
|
from datetime import datetime
|
|
56
55
|
from functools import partial
|
|
57
56
|
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
# Puslib
|
|
2
2
|
|
|
3
|
-
**Puslib** is a Python
|
|
3
|
+
**Puslib** is a Python library for working with the ECSS Packet Utilization Standard (PUS), a protocol widely used in the space industry.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
PUS defines an application-level interface between ground and space, covering both segments. It specifies a binary packet format for telecommands and telemetry (based on CCSDS space packets) and a routing mechanism for those packets, as well as a software architecture built around application processes and a set of standard services.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
Puslib covers two main use cases:
|
|
8
|
+
|
|
9
|
+
* **TM/TC packet parsing** — deserialize raw packet streams, inspect packet fields, and extract parameter data for post-processing or analysis.
|
|
10
|
+
* **PUS application stack** — implement a PUS-compliant backend that receives telecommands and emits telemetry, for use in EGSE equipment, simulators, or any system that communicates over TM/TC.
|
|
11
|
+
|
|
12
|
+
The PUS stack is designed from the perspective of the system being monitored and controlled. It receives telecommands and produces telemetry, making it better suited for simulators and EGSE than for mission control systems. It targets ground tooling and test equipment rather than flight software, but don't let the sky be the limit.
|
|
13
13
|
|
|
14
14
|
## Install
|
|
15
15
|
|
|
16
|
-
```
|
|
16
|
+
```shell
|
|
17
17
|
pip install puslib
|
|
18
18
|
```
|
|
19
19
|
|
|
@@ -21,7 +21,7 @@ Python 3.10 or later is required.
|
|
|
21
21
|
|
|
22
22
|
## Basic Example
|
|
23
23
|
|
|
24
|
-
```
|
|
24
|
+
```python
|
|
25
25
|
from datetime import datetime
|
|
26
26
|
from functools import partial
|
|
27
27
|
|
|
@@ -31,6 +31,8 @@ package_dir = = src
|
|
|
31
31
|
packages = find:
|
|
32
32
|
include_package_data = True
|
|
33
33
|
python_requires = >=3.10
|
|
34
|
+
install_requires =
|
|
35
|
+
bitstring
|
|
34
36
|
|
|
35
37
|
[options.packages.find]
|
|
36
38
|
where = src
|
|
@@ -38,6 +40,13 @@ where = src
|
|
|
38
40
|
[tool:pytest]
|
|
39
41
|
testpaths = tests
|
|
40
42
|
|
|
43
|
+
[mypy]
|
|
44
|
+
files = src/puslib
|
|
45
|
+
python_version = 3.10
|
|
46
|
+
warn_return_any = True
|
|
47
|
+
warn_unused_ignores = True
|
|
48
|
+
check_untyped_defs = True
|
|
49
|
+
|
|
41
50
|
[egg_info]
|
|
42
51
|
tag_build =
|
|
43
52
|
tag_date = 0
|
puslib-0.4.0/setup.py
ADDED
|
@@ -2,7 +2,7 @@ from puslib.packet import PusTcPacket, PusTmPacket, AckFlag # noqa: F401
|
|
|
2
2
|
from puslib.time import CucTime # noqa: F401
|
|
3
3
|
from puslib.pus_policy import PusPolicy
|
|
4
4
|
|
|
5
|
-
__version__ = "0.
|
|
5
|
+
__version__ = "0.4.0"
|
|
6
6
|
|
|
7
7
|
_pus_policy = PusPolicy()
|
|
8
8
|
|
|
@@ -14,8 +14,14 @@ def set_policy(policy):
|
|
|
14
14
|
e.g., various field lengths. This is due to that many parts of the PUS standard
|
|
15
15
|
are mission-specific.
|
|
16
16
|
|
|
17
|
-
The
|
|
18
|
-
|
|
17
|
+
The policy is stored as a module-level global and is not thread-safe. This is a
|
|
18
|
+
deliberate design choice: the library assumes a single-threaded execution model
|
|
19
|
+
where the policy is set once at startup and remains unchanged for the lifetime of
|
|
20
|
+
the application. Calling set_policy() from multiple threads, or changing the policy
|
|
21
|
+
while packets are being processed, results in undefined behavior.
|
|
22
|
+
|
|
23
|
+
If per-thread policies are ever required, replace this global with a
|
|
24
|
+
contextvars.ContextVar to give each thread or async task its own policy instance.
|
|
19
25
|
|
|
20
26
|
Arguments:
|
|
21
27
|
policy -- PUS policy
|
|
@@ -24,7 +30,7 @@ def set_policy(policy):
|
|
|
24
30
|
_pus_policy = policy
|
|
25
31
|
|
|
26
32
|
|
|
27
|
-
def get_policy():
|
|
33
|
+
def get_policy() -> PusPolicy:
|
|
28
34
|
"""Get the current selected PUS policy.
|
|
29
35
|
|
|
30
36
|
Returns:
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""CRC-16-CCITT checksum (polynomial 0x1021, preset 0xFFFF)."""
|
|
2
|
+
|
|
3
|
+
import binascii
|
|
4
|
+
|
|
5
|
+
_PRESET = 0xffff
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def calculate(buffer: bytes | bytearray | memoryview) -> int:
|
|
9
|
+
"""Calculate the CRC-16-CCITT checksum on a buffer.
|
|
10
|
+
|
|
11
|
+
Using polynomial 0x1021 (default for crc_hqx) and preset (or initial value)
|
|
12
|
+
0xffff according to ECSS and CCSDS standards.
|
|
13
|
+
|
|
14
|
+
Arguments:
|
|
15
|
+
buffer -- input buffer to calculate checksum for
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
16 bit CRC checksum
|
|
19
|
+
"""
|
|
20
|
+
return binascii.crc_hqx(buffer, _PRESET)
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
from typing import Iterator
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def _seq_count_generator() -> Iterator[int]:
|
|
2
5
|
seq_count = -1
|
|
3
6
|
max_count = 2 ** 14
|
|
4
7
|
while True:
|
|
@@ -14,7 +17,7 @@ class PusIdent:
|
|
|
14
17
|
- APID (Application ID)
|
|
15
18
|
- packet sequence count
|
|
16
19
|
|
|
17
|
-
The sequence count is per APID and spans [0..
|
|
20
|
+
The sequence count is per APID and spans [0..16383].
|
|
18
21
|
|
|
19
22
|
The APID-sequence pair, thus, uniquely identifies a PUS packet for the foreseeable future.
|
|
20
23
|
"""
|
|
@@ -33,10 +36,10 @@ class PusIdent:
|
|
|
33
36
|
def apid(self) -> int:
|
|
34
37
|
return self._apid
|
|
35
38
|
|
|
36
|
-
def
|
|
39
|
+
def next_seq_count(self) -> int:
|
|
37
40
|
"""Returns next sequence count.
|
|
38
41
|
|
|
39
42
|
Returns:
|
|
40
|
-
sequence count [0..
|
|
43
|
+
sequence count [0..16383]
|
|
41
44
|
"""
|
|
42
45
|
return next(self._seq_count_generator)
|
|
@@ -10,11 +10,10 @@ Implementations of the following packet formats:
|
|
|
10
10
|
import struct
|
|
11
11
|
from enum import IntEnum, IntFlag
|
|
12
12
|
from dataclasses import dataclass
|
|
13
|
-
from typing import Optional,
|
|
14
|
-
|
|
13
|
+
from typing import Optional, cast
|
|
14
|
+
from puslib.crc_ccitt import calculate as crc_ccitt_calculate
|
|
15
15
|
from puslib.exceptions import CrcException, IncompletePacketException, InvalidPacketException
|
|
16
16
|
from puslib.time import CucTime
|
|
17
|
-
from puslib.crc_ccitt import calculate as crc_ccitt_calculate
|
|
18
17
|
|
|
19
18
|
_CCSDS_PACKET_VERSION_NUMBER = 0
|
|
20
19
|
_CCSDS_MAX_PACKET_SIZE = 65542
|
|
@@ -26,15 +25,15 @@ _COMMON_SEC_HDR_STRUCT = struct.Struct('>BBB')
|
|
|
26
25
|
_PEC_FIELD_SIZE = 2
|
|
27
26
|
|
|
28
27
|
|
|
29
|
-
def _validate_int_field(field_name: str, val: int, min_val: int, max_val: int):
|
|
30
|
-
if isinstance(val, int):
|
|
28
|
+
def _validate_int_field(field_name: str, val: int, min_val: int, max_val: int) -> None:
|
|
29
|
+
if isinstance(val, int) and not isinstance(val, bool):
|
|
31
30
|
if not min_val <= val <= max_val:
|
|
32
31
|
raise InvalidPacketException(f"{field_name} must be between {min_val} and {max_val}")
|
|
33
32
|
else:
|
|
34
33
|
raise TypeError(f"{field_name} must be an integer")
|
|
35
34
|
|
|
36
35
|
|
|
37
|
-
def _validate_bool_field(field_name: str, val: bool):
|
|
36
|
+
def _validate_bool_field(field_name: str, val: bool) -> None:
|
|
38
37
|
if not isinstance(val, bool):
|
|
39
38
|
raise TypeError(f"{field_name} must be a bool")
|
|
40
39
|
|
|
@@ -81,8 +80,8 @@ class CcsdsSpacePacket:
|
|
|
81
80
|
has_pec -- true if a PEC field should be present (default: {True})
|
|
82
81
|
"""
|
|
83
82
|
self.header: _PacketPrimaryHeader = _PacketPrimaryHeader()
|
|
84
|
-
self.secondary_header = None
|
|
85
|
-
self.payload:
|
|
83
|
+
self.secondary_header: _PacketSecondaryHeaderTc | _PacketSecondaryHeaderTm | None = None
|
|
84
|
+
self.payload: bytes | None = None
|
|
86
85
|
self._has_pec = has_pec
|
|
87
86
|
|
|
88
87
|
def __len__(self):
|
|
@@ -109,7 +108,7 @@ class CcsdsSpacePacket:
|
|
|
109
108
|
def has_pec(self) -> bool:
|
|
110
109
|
return self._has_pec
|
|
111
110
|
|
|
112
|
-
def serialize(self) ->
|
|
111
|
+
def serialize(self) -> bytes:
|
|
113
112
|
"""Serialize the packet to its binary format.
|
|
114
113
|
|
|
115
114
|
Returns:
|
|
@@ -121,17 +120,17 @@ class CcsdsSpacePacket:
|
|
|
121
120
|
if self.header.secondary_header_flag:
|
|
122
121
|
packet_data_field = b'' # Leave it to subclasses to handle data field
|
|
123
122
|
else:
|
|
124
|
-
packet_data_field = self.payload
|
|
123
|
+
packet_data_field = self.payload or b''
|
|
125
124
|
return ccsds_header + packet_data_field
|
|
126
125
|
|
|
127
|
-
def request_id(self) ->
|
|
126
|
+
def request_id(self) -> bytes:
|
|
128
127
|
packet_id = self.header.packet_version_number << 13 | (1 if self.header.packet_type == PacketType.TC else 0) << 12 | (1 if self.header.secondary_header_flag else 0) << 11 | self.header.apid
|
|
129
128
|
seq_ctrl = self.header.seq_flags << 14 | self.header.seq_count_or_name
|
|
130
129
|
return struct.pack('>HH', packet_id, seq_ctrl)
|
|
131
130
|
|
|
132
131
|
@classmethod
|
|
133
|
-
def
|
|
134
|
-
"""
|
|
132
|
+
def _parse_primary_header(cls, buffer: bytes | bytearray | memoryview, has_pec: bool = True, validate_pec: bool = True) -> tuple[int, PacketType, bool, int, SequenceFlag, int, int]:
|
|
133
|
+
"""Parse and validate the CCSDS primary header from a buffer.
|
|
135
134
|
|
|
136
135
|
Arguments:
|
|
137
136
|
buffer -- packet in binary format
|
|
@@ -141,9 +140,12 @@ class CcsdsSpacePacket:
|
|
|
141
140
|
validate_pec -- true if PEC field should be verified during operation (default: {True})
|
|
142
141
|
|
|
143
142
|
Returns:
|
|
144
|
-
|
|
143
|
+
tuple of (packet_version_number, packet_type, secondary_header_flag, apid, seq_flags, seq_count_or_name, data_length)
|
|
145
144
|
"""
|
|
146
|
-
|
|
145
|
+
try:
|
|
146
|
+
packet_id, seq_ctrl, data_length = cls._CCSDS_HDR_STRUCT.unpack_from(buffer)
|
|
147
|
+
except struct.error:
|
|
148
|
+
raise IncompletePacketException()
|
|
147
149
|
|
|
148
150
|
packet_size = cls._CCSDS_HDR_STRUCT.size + data_length + 1
|
|
149
151
|
if packet_size > len(buffer):
|
|
@@ -156,10 +158,10 @@ class CcsdsSpacePacket:
|
|
|
156
158
|
raise CrcException
|
|
157
159
|
|
|
158
160
|
packet_version_number = (packet_id >> 13) & 0b111
|
|
159
|
-
packet_type = PacketType
|
|
160
|
-
secondary_header_flag =
|
|
161
|
+
packet_type = PacketType((packet_id >> 12) & 0b1)
|
|
162
|
+
secondary_header_flag = bool((packet_id >> 11) & 0b1)
|
|
161
163
|
apid = packet_id & 0x7ff
|
|
162
|
-
seq_flags = (seq_ctrl >> 14) & 0b11
|
|
164
|
+
seq_flags = SequenceFlag((seq_ctrl >> 14) & 0b11)
|
|
163
165
|
seq_count_or_name = seq_ctrl & 0x3fff # count or name
|
|
164
166
|
return packet_version_number, packet_type, secondary_header_flag, apid, seq_flags, seq_count_or_name, data_length
|
|
165
167
|
|
|
@@ -199,6 +201,10 @@ class CcsdsSpacePacket:
|
|
|
199
201
|
packet.header.apid = apid
|
|
200
202
|
|
|
201
203
|
seq_flags = kwargs.get('seq_flags', SequenceFlag.UNSEGMENTED)
|
|
204
|
+
try:
|
|
205
|
+
seq_flags = SequenceFlag(seq_flags)
|
|
206
|
+
except ValueError:
|
|
207
|
+
raise ValueError(f"seq_flags must be a valid SequenceFlag value (0–3), got {seq_flags!r}")
|
|
202
208
|
packet.header.seq_flags = seq_flags
|
|
203
209
|
|
|
204
210
|
seq_count = kwargs.get('seq_count_or_name', 0)
|
|
@@ -209,7 +215,7 @@ class CcsdsSpacePacket:
|
|
|
209
215
|
if isinstance(data, (bytearray, bytes, type(None))):
|
|
210
216
|
if data is None:
|
|
211
217
|
data = bytes()
|
|
212
|
-
packet.payload = data
|
|
218
|
+
packet.payload = bytes(data)
|
|
213
219
|
else:
|
|
214
220
|
raise TypeError("Application data must be None, bytes or a bytearray")
|
|
215
221
|
|
|
@@ -257,7 +263,7 @@ class PusTcPacket(CcsdsSpacePacket):
|
|
|
257
263
|
|
|
258
264
|
def __init__(self, has_pec: bool = True):
|
|
259
265
|
super().__init__(has_pec)
|
|
260
|
-
self.secondary_header = _PacketSecondaryHeaderTc()
|
|
266
|
+
self.secondary_header: _PacketSecondaryHeaderTc = _PacketSecondaryHeaderTc()
|
|
261
267
|
|
|
262
268
|
def __len__(self):
|
|
263
269
|
size = super().__len__()
|
|
@@ -277,7 +283,7 @@ class PusTcPacket(CcsdsSpacePacket):
|
|
|
277
283
|
return pkt_info
|
|
278
284
|
|
|
279
285
|
@property
|
|
280
|
-
def name(self) ->
|
|
286
|
+
def name(self) -> int:
|
|
281
287
|
return self.header.seq_count_or_name
|
|
282
288
|
|
|
283
289
|
def ack(self, ack_flag: AckFlag) -> bool:
|
|
@@ -304,10 +310,10 @@ class PusTcPacket(CcsdsSpacePacket):
|
|
|
304
310
|
return self.secondary_header.source
|
|
305
311
|
|
|
306
312
|
@property
|
|
307
|
-
def app_data(self) ->
|
|
313
|
+
def app_data(self) -> bytes | None:
|
|
308
314
|
return self.payload
|
|
309
315
|
|
|
310
|
-
def serialize(self):
|
|
316
|
+
def serialize(self) -> bytes:
|
|
311
317
|
ccsds_header = super().serialize()
|
|
312
318
|
|
|
313
319
|
if self.header.secondary_header_flag:
|
|
@@ -326,18 +332,17 @@ class PusTcPacket(CcsdsSpacePacket):
|
|
|
326
332
|
ccsds_sec_header_static = bytes()
|
|
327
333
|
|
|
328
334
|
# Packet error control
|
|
329
|
-
packet_without_pec = ccsds_header + (ccsds_sec_header_static + ccsds_sec_header_source if self.header.secondary_header_flag else bytes()) + (self.payload if self.header.secondary_header_flag else bytes())
|
|
335
|
+
packet_without_pec = ccsds_header + (ccsds_sec_header_static + ccsds_sec_header_source if self.header.secondary_header_flag else bytes()) + (self.payload or b'' if self.header.secondary_header_flag else bytes())
|
|
330
336
|
if self.has_pec:
|
|
331
337
|
mem_view = memoryview(packet_without_pec)
|
|
332
|
-
pec = crc_ccitt_calculate(mem_view)
|
|
333
|
-
pec = pec.to_bytes(_PEC_FIELD_SIZE, byteorder='big')
|
|
338
|
+
pec = crc_ccitt_calculate(mem_view).to_bytes(_PEC_FIELD_SIZE, byteorder='big')
|
|
334
339
|
else:
|
|
335
340
|
pec = bytes()
|
|
336
341
|
|
|
337
342
|
return packet_without_pec + pec
|
|
338
343
|
|
|
339
344
|
@classmethod
|
|
340
|
-
def deserialize(cls, buffer:
|
|
345
|
+
def deserialize(cls, buffer: bytes | bytearray | memoryview, has_pec: bool = True, validate_pec: bool = True, has_source_field: bool = True, validate_fields: bool = True) -> "PusTcPacket":
|
|
341
346
|
"""Deserialize a packet from its binary format to a packet object.
|
|
342
347
|
|
|
343
348
|
Arguments:
|
|
@@ -352,7 +357,7 @@ class PusTcPacket(CcsdsSpacePacket):
|
|
|
352
357
|
Returns:
|
|
353
358
|
packet object
|
|
354
359
|
"""
|
|
355
|
-
packet_version_number, packet_type, secondary_header_flag, apid, seq_flags, seq_count_or_name, data_length =
|
|
360
|
+
packet_version_number, packet_type, secondary_header_flag, apid, seq_flags, seq_count_or_name, data_length = cls._parse_primary_header(buffer, has_pec, validate_pec)
|
|
356
361
|
offset = cls._CCSDS_HDR_STRUCT.size
|
|
357
362
|
|
|
358
363
|
data_field_except_source_length = ((_COMMON_SEC_HDR_STRUCT.size + (cls._SOURCE_FIELD_SIZE if has_source_field else 0)) if secondary_header_flag else 0) + (2 if has_pec else 0)
|
|
@@ -409,7 +414,8 @@ class PusTcPacket(CcsdsSpacePacket):
|
|
|
409
414
|
packet.header.data_length = data_length
|
|
410
415
|
packet.payload = app_data
|
|
411
416
|
packet.secondary_header.pus_version = pus_version
|
|
412
|
-
|
|
417
|
+
if ack_flags is not None:
|
|
418
|
+
packet.secondary_header.ack_flags = ack_flags
|
|
413
419
|
packet.secondary_header.service_type = service_type
|
|
414
420
|
packet.secondary_header.service_subtype = service_subtype
|
|
415
421
|
packet.secondary_header.source = source
|
|
@@ -417,7 +423,7 @@ class PusTcPacket(CcsdsSpacePacket):
|
|
|
417
423
|
return packet
|
|
418
424
|
|
|
419
425
|
@classmethod
|
|
420
|
-
def create(cls, **kwargs):
|
|
426
|
+
def create(cls, **kwargs) -> "PusTcPacket":
|
|
421
427
|
"""A factory method to create a PUS TC packet instance.
|
|
422
428
|
|
|
423
429
|
Keyword Arguments:
|
|
@@ -444,7 +450,7 @@ class PusTcPacket(CcsdsSpacePacket):
|
|
|
444
450
|
kwargs['secondary_header_length'] = secondary_header_length
|
|
445
451
|
kwargs['packet_type'] = PacketType.TC
|
|
446
452
|
kwargs['seq_count_or_name'] = kwargs.get('name', 0)
|
|
447
|
-
packet = super(
|
|
453
|
+
packet = super().create(**kwargs)
|
|
448
454
|
|
|
449
455
|
if packet.header.secondary_header_flag:
|
|
450
456
|
pus_version = kwargs.get('pus_version', _TC_PACKET_PUS_VERSION_NUMBER)
|
|
@@ -471,7 +477,7 @@ class PusTcPacket(CcsdsSpacePacket):
|
|
|
471
477
|
_validate_int_field('Source ID', source, 0, 0xffff)
|
|
472
478
|
packet.secondary_header.source = source
|
|
473
479
|
|
|
474
|
-
return packet
|
|
480
|
+
return cast("PusTcPacket", packet)
|
|
475
481
|
|
|
476
482
|
|
|
477
483
|
@dataclass(slots=True)
|
|
@@ -493,7 +499,7 @@ class PusTmPacket(CcsdsSpacePacket):
|
|
|
493
499
|
|
|
494
500
|
def __init__(self, has_pec: bool = True):
|
|
495
501
|
super().__init__(has_pec)
|
|
496
|
-
self.secondary_header = _PacketSecondaryHeaderTm()
|
|
502
|
+
self.secondary_header: _PacketSecondaryHeaderTm = _PacketSecondaryHeaderTm()
|
|
497
503
|
|
|
498
504
|
def __len__(self):
|
|
499
505
|
size = super().__len__()
|
|
@@ -503,7 +509,7 @@ class PusTmPacket(CcsdsSpacePacket):
|
|
|
503
509
|
size += self._MSG_TYPE_COUNTER_FIELD_SIZE
|
|
504
510
|
if self.secondary_header.destination is not None:
|
|
505
511
|
size += self._DESTINATION_FIELD_SIZE
|
|
506
|
-
size += len(self.secondary_header.time)
|
|
512
|
+
size += len(self.secondary_header.time) if self.secondary_header.time else 0
|
|
507
513
|
return size
|
|
508
514
|
|
|
509
515
|
def __str__(self):
|
|
@@ -537,14 +543,14 @@ class PusTmPacket(CcsdsSpacePacket):
|
|
|
537
543
|
return self.secondary_header.destination
|
|
538
544
|
|
|
539
545
|
@property
|
|
540
|
-
def time(self) -> CucTime:
|
|
546
|
+
def time(self) -> CucTime | None:
|
|
541
547
|
return self.secondary_header.time
|
|
542
548
|
|
|
543
549
|
@property
|
|
544
|
-
def source_data(self) ->
|
|
550
|
+
def source_data(self) -> bytes | None:
|
|
545
551
|
return self.payload
|
|
546
552
|
|
|
547
|
-
def serialize(self):
|
|
553
|
+
def serialize(self) -> bytes:
|
|
548
554
|
ccsds_header = super().serialize()
|
|
549
555
|
|
|
550
556
|
# First static part of secondary header
|
|
@@ -563,18 +569,17 @@ class PusTmPacket(CcsdsSpacePacket):
|
|
|
563
569
|
ccsds_sec_header_destination = bytes()
|
|
564
570
|
|
|
565
571
|
# Packet error control
|
|
566
|
-
packet_without_pec = ccsds_header + ccsds_sec_header_static + ccsds_sec_header_msg_type_counter + ccsds_sec_header_destination + bytes(self.secondary_header.time) + self.payload
|
|
572
|
+
packet_without_pec = ccsds_header + ccsds_sec_header_static + ccsds_sec_header_msg_type_counter + ccsds_sec_header_destination + (bytes(self.secondary_header.time) if self.secondary_header.time else b'') + (self.payload or b'')
|
|
567
573
|
if self.has_pec:
|
|
568
574
|
mem_view = memoryview(packet_without_pec)
|
|
569
|
-
pec = crc_ccitt_calculate(mem_view)
|
|
570
|
-
pec = pec.to_bytes(_PEC_FIELD_SIZE, byteorder='big')
|
|
575
|
+
pec = crc_ccitt_calculate(mem_view).to_bytes(_PEC_FIELD_SIZE, byteorder='big')
|
|
571
576
|
else:
|
|
572
577
|
pec = bytes()
|
|
573
578
|
|
|
574
579
|
return packet_without_pec + pec
|
|
575
580
|
|
|
576
581
|
@classmethod
|
|
577
|
-
def deserialize(cls, buffer:
|
|
582
|
+
def deserialize(cls, buffer: bytes | bytearray | memoryview, has_pec: bool = True, validate_pec: bool = True, cuc_time: CucTime | None = None, has_type_counter_field: bool = True, has_destination_field: bool = True, validate_fields: bool = True) -> "PusTmPacket":
|
|
578
583
|
"""Deserialize a packet from its binary format to a packet object.
|
|
579
584
|
|
|
580
585
|
Arguments:
|
|
@@ -591,7 +596,7 @@ class PusTmPacket(CcsdsSpacePacket):
|
|
|
591
596
|
Returns:
|
|
592
597
|
packet object
|
|
593
598
|
"""
|
|
594
|
-
packet_version_number, packet_type, secondary_header_flag, apid, seq_flags, seq_count_or_name, data_length =
|
|
599
|
+
packet_version_number, packet_type, secondary_header_flag, apid, seq_flags, seq_count_or_name, data_length = cls._parse_primary_header(buffer, has_pec, validate_pec)
|
|
595
600
|
offset = cls._CCSDS_HDR_STRUCT.size
|
|
596
601
|
|
|
597
602
|
if secondary_header_flag:
|
|
@@ -682,7 +687,7 @@ class PusTmPacket(CcsdsSpacePacket):
|
|
|
682
687
|
return packet
|
|
683
688
|
|
|
684
689
|
@classmethod
|
|
685
|
-
def create(cls, **kwargs):
|
|
690
|
+
def create(cls, **kwargs) -> "PusTmPacket":
|
|
686
691
|
"""A factory method to create a PUS TM packet instance.
|
|
687
692
|
|
|
688
693
|
Keyword Arguments:
|
|
@@ -709,11 +714,11 @@ class PusTmPacket(CcsdsSpacePacket):
|
|
|
709
714
|
time = kwargs.get('time', None)
|
|
710
715
|
|
|
711
716
|
if kwargs.get('secondary_header_flag', True):
|
|
712
|
-
secondary_header_length = _COMMON_SEC_HDR_STRUCT.size + (2 if msg_type_counter is not None else 0) + (2 if destination is not None else 0) + len(time)
|
|
717
|
+
secondary_header_length = _COMMON_SEC_HDR_STRUCT.size + (2 if msg_type_counter is not None else 0) + (2 if destination is not None else 0) + (len(time) if time else 0)
|
|
713
718
|
kwargs['secondary_header_length'] = secondary_header_length
|
|
714
719
|
kwargs['packet_type'] = PacketType.TM
|
|
715
720
|
kwargs['seq_count_or_name'] = kwargs.get('seq_count', 0)
|
|
716
|
-
packet = super(
|
|
721
|
+
packet = super().create(**kwargs)
|
|
717
722
|
|
|
718
723
|
if packet.header.secondary_header_flag:
|
|
719
724
|
pus_version = kwargs.get('pus_version', _TM_PACKET_PUS_VERSION_NUMBER)
|
|
@@ -746,4 +751,4 @@ class PusTmPacket(CcsdsSpacePacket):
|
|
|
746
751
|
if time:
|
|
747
752
|
packet.secondary_header.time = time
|
|
748
753
|
|
|
749
|
-
return packet
|
|
754
|
+
return cast("PusTmPacket", packet)
|