pyixblue 0.9.1__py3-none-any.whl
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.
- pyixblue/__init__.py +15 -0
- pyixblue/blocks.py +242 -0
- pyixblue/decoder.py +187 -0
- pyixblue/errors.py +5 -0
- pyixblue/models.py +741 -0
- pyixblue-0.9.1.dist-info/METADATA +108 -0
- pyixblue-0.9.1.dist-info/RECORD +9 -0
- pyixblue-0.9.1.dist-info/WHEEL +4 -0
- pyixblue-0.9.1.dist-info/licenses/LICENSE +21 -0
pyixblue/__init__.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Parse iXblue stdbin navigation and answer frames."""
|
|
2
|
+
|
|
3
|
+
from pyixblue.decoder import StdBinDecoder
|
|
4
|
+
from pyixblue.errors import StdBinError
|
|
5
|
+
from pyixblue.models import MessageType, NavData, NavHeader
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"MessageType",
|
|
9
|
+
"NavData",
|
|
10
|
+
"NavHeader",
|
|
11
|
+
"StdBinDecoder",
|
|
12
|
+
"StdBinError",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
__version__ = "0.9.1"
|
pyixblue/blocks.py
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"""Declarative stdbin memory-block table.
|
|
2
|
+
|
|
3
|
+
Sizes and bit positions match the reference ``MemoryBlockParser`` constructors.
|
|
4
|
+
Blocks are stored in bit order, which is the order they are packed on the wire.
|
|
5
|
+
Logbook is the 40 bytes the reference parser actually reads (``int32``,
|
|
6
|
+
``uint32``, and 32 text bytes), not the 12-byte guard declared on that parser.
|
|
7
|
+
``vehicle_attitude_heading_deviation`` is included even though the reference
|
|
8
|
+
decoder constructs no parser for it, so a set extended-mask bit 4 is consumed.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from collections.abc import Callable
|
|
12
|
+
from dataclasses import dataclass, fields
|
|
13
|
+
from struct import calcsize
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
from pyixblue.models import (
|
|
17
|
+
AccelerationGeographicFrame,
|
|
18
|
+
AccelerationVesselFrame,
|
|
19
|
+
AccelerationVesselFrameDeviation,
|
|
20
|
+
AhrsAlgorithmStatus,
|
|
21
|
+
AhrsSystemStatus,
|
|
22
|
+
AhrsUserStatus,
|
|
23
|
+
AttitudeHeading,
|
|
24
|
+
AttitudeHeadingDeviation,
|
|
25
|
+
AttitudeQuaternion,
|
|
26
|
+
AttitudeQuaternionDeviation,
|
|
27
|
+
CourseSpeedOverGround,
|
|
28
|
+
CurrentGeographicFrame,
|
|
29
|
+
CurrentGeographicFrameDeviation,
|
|
30
|
+
Depth,
|
|
31
|
+
Dmi,
|
|
32
|
+
DvlGroundSpeed,
|
|
33
|
+
DvlWaterSpeed,
|
|
34
|
+
Emlog,
|
|
35
|
+
EventMarker,
|
|
36
|
+
Gnss,
|
|
37
|
+
HeadingRollPitchRate,
|
|
38
|
+
HeaveSurgeSwaySpeed,
|
|
39
|
+
InsAlgorithmStatus,
|
|
40
|
+
InsAlgorithmStatus1,
|
|
41
|
+
InsAlgorithmStatus2,
|
|
42
|
+
InsAlgorithmStatus3,
|
|
43
|
+
InsAlgorithmStatus4,
|
|
44
|
+
InsSystemStatus,
|
|
45
|
+
InsSystemStatus1,
|
|
46
|
+
InsSystemStatus2,
|
|
47
|
+
InsSystemStatus3,
|
|
48
|
+
InsUserStatus,
|
|
49
|
+
Lbl,
|
|
50
|
+
LogBook,
|
|
51
|
+
Position,
|
|
52
|
+
PositionDeviation,
|
|
53
|
+
RawAccelerationVesselFrame,
|
|
54
|
+
RawRotationRateVesselFrame,
|
|
55
|
+
RealTimeHeaveSurgeSway,
|
|
56
|
+
RotationAccelerationVesselFrame,
|
|
57
|
+
RotationAccelerationVesselFrameDeviation,
|
|
58
|
+
RotationRateVesselFrame,
|
|
59
|
+
RotationRateVesselFrameDeviation,
|
|
60
|
+
SensorStatus,
|
|
61
|
+
SmartHeave,
|
|
62
|
+
SoundVelocity,
|
|
63
|
+
SpeedGeographicFrame,
|
|
64
|
+
SpeedGeographicFrameDeviation,
|
|
65
|
+
SpeedVesselFrame,
|
|
66
|
+
SystemDate,
|
|
67
|
+
Temperatures,
|
|
68
|
+
TurretAngles,
|
|
69
|
+
Usbl,
|
|
70
|
+
Utc,
|
|
71
|
+
VehicleAttitudeHeading,
|
|
72
|
+
VehicleAttitudeHeadingDeviation,
|
|
73
|
+
VehiclePosition,
|
|
74
|
+
VehiclePositionDeviation,
|
|
75
|
+
Vtg,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@dataclass(frozen=True, slots=True)
|
|
80
|
+
class Block:
|
|
81
|
+
"""One optional memory block identified by a mask bit."""
|
|
82
|
+
|
|
83
|
+
field: str
|
|
84
|
+
bit: int
|
|
85
|
+
fmt: str
|
|
86
|
+
build: Callable[[tuple[Any, ...]], Any]
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def size(self) -> int:
|
|
90
|
+
return calcsize(self.fmt)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _build(
|
|
94
|
+
cls: type[Any], converters: tuple[Callable[[int], Any] | None, ...] = ()
|
|
95
|
+
) -> Callable[[tuple[Any, ...]], Any]:
|
|
96
|
+
names = tuple(item.name for item in fields(cls))
|
|
97
|
+
|
|
98
|
+
def build(values: tuple[Any, ...]) -> Any:
|
|
99
|
+
converted: list[Any] = []
|
|
100
|
+
for index, value in enumerate(values):
|
|
101
|
+
converter = converters[index] if index < len(converters) else None
|
|
102
|
+
converted.append(converter(value) if converter is not None else value)
|
|
103
|
+
return cls(**dict(zip(names, converted, strict=True)))
|
|
104
|
+
|
|
105
|
+
return build
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _block(
|
|
109
|
+
field: str,
|
|
110
|
+
bit: int,
|
|
111
|
+
fmt: str,
|
|
112
|
+
cls: type[Any],
|
|
113
|
+
converters: tuple[Callable[[int], Any] | None, ...] = (),
|
|
114
|
+
) -> Block:
|
|
115
|
+
return Block(field, bit, fmt, _build(cls, converters))
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _sorted(blocks: tuple[Block, ...]) -> tuple[Block, ...]:
|
|
119
|
+
return tuple(sorted(blocks, key=lambda block: block.bit))
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
NAVIGATION_BLOCKS = _sorted(
|
|
123
|
+
(
|
|
124
|
+
_block("attitude_heading", 0, ">fff", AttitudeHeading),
|
|
125
|
+
_block("attitude_heading_deviation", 1, ">fff", AttitudeHeadingDeviation),
|
|
126
|
+
_block("rt_heave_surge_sway", 2, ">ffff", RealTimeHeaveSurgeSway),
|
|
127
|
+
_block("smart_heave", 3, ">If", SmartHeave),
|
|
128
|
+
_block("heading_roll_pitch_rate", 4, ">fff", HeadingRollPitchRate),
|
|
129
|
+
_block("rotation_rate_vessel_frame", 5, ">fff", RotationRateVesselFrame),
|
|
130
|
+
_block("acceleration_vessel_frame", 6, ">fff", AccelerationVesselFrame),
|
|
131
|
+
_block("position", 7, ">ddBf", Position),
|
|
132
|
+
_block("position_deviation", 8, ">ffff", PositionDeviation),
|
|
133
|
+
_block("speed_geographic_frame", 9, ">fff", SpeedGeographicFrame),
|
|
134
|
+
_block("speed_geographic_frame_deviation", 10, ">fff", SpeedGeographicFrameDeviation),
|
|
135
|
+
_block("current_geographic_frame", 11, ">ff", CurrentGeographicFrame),
|
|
136
|
+
_block("current_geographic_frame_deviation", 12, ">ff", CurrentGeographicFrameDeviation),
|
|
137
|
+
_block("system_date", 13, ">BBH", SystemDate),
|
|
138
|
+
_block("sensor_status", 14, ">II", SensorStatus),
|
|
139
|
+
_block(
|
|
140
|
+
"ins_algorithm_status",
|
|
141
|
+
15,
|
|
142
|
+
">IIII",
|
|
143
|
+
InsAlgorithmStatus,
|
|
144
|
+
(
|
|
145
|
+
InsAlgorithmStatus1,
|
|
146
|
+
InsAlgorithmStatus2,
|
|
147
|
+
InsAlgorithmStatus3,
|
|
148
|
+
InsAlgorithmStatus4,
|
|
149
|
+
),
|
|
150
|
+
),
|
|
151
|
+
_block(
|
|
152
|
+
"ins_system_status",
|
|
153
|
+
16,
|
|
154
|
+
">III",
|
|
155
|
+
InsSystemStatus,
|
|
156
|
+
(InsSystemStatus1, InsSystemStatus2, InsSystemStatus3),
|
|
157
|
+
),
|
|
158
|
+
_block("ins_user_status", 17, ">I", InsUserStatus),
|
|
159
|
+
_block("ahrs_algorithm_status", 18, ">I", AhrsAlgorithmStatus),
|
|
160
|
+
_block("ahrs_system_status", 19, ">III", AhrsSystemStatus),
|
|
161
|
+
_block("ahrs_user_status", 20, ">I", AhrsUserStatus),
|
|
162
|
+
_block("heave_surge_sway_speed", 21, ">fff", HeaveSurgeSwaySpeed),
|
|
163
|
+
_block("speed_vessel_frame", 22, ">fff", SpeedVesselFrame),
|
|
164
|
+
_block("acceleration_geographic_frame", 23, ">fff", AccelerationGeographicFrame),
|
|
165
|
+
_block("course_speed_over_ground", 24, ">ff", CourseSpeedOverGround),
|
|
166
|
+
_block("temperatures", 25, ">fff", Temperatures),
|
|
167
|
+
_block("attitude_quaternion", 26, ">ffff", AttitudeQuaternion),
|
|
168
|
+
_block("attitude_quaternion_deviation", 27, ">fff", AttitudeQuaternionDeviation),
|
|
169
|
+
_block("raw_acceleration_vessel_frame", 28, ">fff", RawAccelerationVesselFrame),
|
|
170
|
+
_block(
|
|
171
|
+
"acceleration_vessel_frame_deviation",
|
|
172
|
+
29,
|
|
173
|
+
">fff",
|
|
174
|
+
AccelerationVesselFrameDeviation,
|
|
175
|
+
),
|
|
176
|
+
_block(
|
|
177
|
+
"rotation_rate_vessel_frame_deviation",
|
|
178
|
+
30,
|
|
179
|
+
">fff",
|
|
180
|
+
RotationRateVesselFrameDeviation,
|
|
181
|
+
),
|
|
182
|
+
)
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
EXTENDED_BLOCKS = _sorted(
|
|
186
|
+
(
|
|
187
|
+
_block(
|
|
188
|
+
"rotation_acceleration_vessel_frame",
|
|
189
|
+
0,
|
|
190
|
+
">fff",
|
|
191
|
+
RotationAccelerationVesselFrame,
|
|
192
|
+
),
|
|
193
|
+
_block(
|
|
194
|
+
"rotation_acceleration_vessel_frame_deviation",
|
|
195
|
+
1,
|
|
196
|
+
">fff",
|
|
197
|
+
RotationAccelerationVesselFrameDeviation,
|
|
198
|
+
),
|
|
199
|
+
_block("raw_rotation_rate_vessel_frame", 2, ">fff", RawRotationRateVesselFrame),
|
|
200
|
+
_block("vehicle_attitude_heading", 3, ">fff", VehicleAttitudeHeading),
|
|
201
|
+
_block(
|
|
202
|
+
"vehicle_attitude_heading_deviation",
|
|
203
|
+
4,
|
|
204
|
+
">fff",
|
|
205
|
+
VehicleAttitudeHeadingDeviation,
|
|
206
|
+
),
|
|
207
|
+
_block("vehicle_position", 5, ">ddBf", VehiclePosition),
|
|
208
|
+
_block("vehicle_position_deviation", 6, ">ffff", VehiclePositionDeviation),
|
|
209
|
+
)
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
EXTERNAL_BLOCKS = _sorted(
|
|
213
|
+
(
|
|
214
|
+
_block("utc", 0, ">IB", Utc),
|
|
215
|
+
_block("gnss1", 1, ">iBBddffffff", Gnss),
|
|
216
|
+
_block("gnss2", 2, ">iBBddffffff", Gnss),
|
|
217
|
+
_block("gnss_manual", 3, ">iBBddffffff", Gnss),
|
|
218
|
+
_block("emlog1", 4, ">iBff", Emlog),
|
|
219
|
+
_block("emlog2", 5, ">iBff", Emlog),
|
|
220
|
+
_block("usbl1", 6, ">iB8sddfffff", Usbl),
|
|
221
|
+
_block("usbl2", 7, ">iB8sddfffff", Usbl),
|
|
222
|
+
_block("usbl3", 8, ">iB8sddfffff", Usbl),
|
|
223
|
+
_block("depth", 9, ">iff", Depth),
|
|
224
|
+
_block("dvl_ground_speed1", 10, ">iBffffffff", DvlGroundSpeed),
|
|
225
|
+
_block("dvl_water_speed1", 11, ">iBfffffff", DvlWaterSpeed),
|
|
226
|
+
_block("sound_velocity", 12, ">if", SoundVelocity),
|
|
227
|
+
_block("dmi", 13, ">ii", Dmi),
|
|
228
|
+
_block("lbl1", 14, ">iB8sddfff", Lbl),
|
|
229
|
+
_block("lbl2", 15, ">iB8sddfff", Lbl),
|
|
230
|
+
_block("lbl3", 16, ">iB8sddfff", Lbl),
|
|
231
|
+
_block("lbl4", 17, ">iB8sddfff", Lbl),
|
|
232
|
+
_block("event_marker_a", 18, ">iBI", EventMarker),
|
|
233
|
+
_block("event_marker_b", 19, ">iBI", EventMarker),
|
|
234
|
+
_block("event_marker_c", 20, ">iBI", EventMarker),
|
|
235
|
+
_block("dvl_ground_speed2", 21, ">iBffffffff", DvlGroundSpeed),
|
|
236
|
+
_block("dvl_water_speed2", 22, ">iBfffffff", DvlWaterSpeed),
|
|
237
|
+
_block("turret_angles", 24, ">ifff", TurretAngles),
|
|
238
|
+
_block("vtg1", 25, ">iBfff", Vtg),
|
|
239
|
+
_block("vtg2", 26, ">iBfff", Vtg),
|
|
240
|
+
_block("log_book", 27, ">iI32s", LogBook),
|
|
241
|
+
)
|
|
242
|
+
)
|
pyixblue/decoder.py
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"""Streaming parser for iXblue stdbin output frames."""
|
|
2
|
+
|
|
3
|
+
import struct
|
|
4
|
+
from collections.abc import Iterable
|
|
5
|
+
|
|
6
|
+
from pyixblue.blocks import EXTENDED_BLOCKS, EXTERNAL_BLOCKS, NAVIGATION_BLOCKS, Block
|
|
7
|
+
from pyixblue.errors import StdBinError
|
|
8
|
+
from pyixblue.models import MessageType, NavData, NavHeader
|
|
9
|
+
|
|
10
|
+
_NAV_HEADER_SIZE = {2: 21, 3: 25, 4: 27, 5: 27}
|
|
11
|
+
_ANSWER_HEADER_SIZE = 5
|
|
12
|
+
_CHECKSUM_SIZE = 4
|
|
13
|
+
_IX = b"IX"
|
|
14
|
+
_AN = b"AN"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class StdBinDecoder:
|
|
18
|
+
"""Reassemble and decode stdbin frames from an arbitrary byte stream.
|
|
19
|
+
|
|
20
|
+
Feed whatever arrives from the socket or serial port. ``parse_next_frame``
|
|
21
|
+
returns true once a whole frame is available. A bad protocol version drops
|
|
22
|
+
one byte and raises; a bad checksum drops that frame and raises. The next
|
|
23
|
+
call continues with whatever is still buffered.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(self) -> None:
|
|
27
|
+
self._buffer = bytearray()
|
|
28
|
+
self.last_header: NavHeader | None = None
|
|
29
|
+
self.last_nav = NavData()
|
|
30
|
+
self.last_answer = b""
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def buffered_size(self) -> int:
|
|
34
|
+
"""Bytes still waiting to be parsed."""
|
|
35
|
+
return len(self._buffer)
|
|
36
|
+
|
|
37
|
+
def add_new_data(self, data: bytes | bytearray | memoryview) -> None:
|
|
38
|
+
"""Append bytes. ``data`` may be only part of a frame."""
|
|
39
|
+
if isinstance(data, str):
|
|
40
|
+
raise TypeError("stdbin data must be bytes, not str")
|
|
41
|
+
self._buffer.extend(data)
|
|
42
|
+
|
|
43
|
+
def parse_next_frame(self) -> bool:
|
|
44
|
+
"""Parse one frame from the internal buffer.
|
|
45
|
+
|
|
46
|
+
Returns false when more bytes are needed. Raises ``StdBinError`` when
|
|
47
|
+
a frame header is recognized but the version or checksum is invalid.
|
|
48
|
+
"""
|
|
49
|
+
if not self._find_header():
|
|
50
|
+
return False
|
|
51
|
+
|
|
52
|
+
header = self._parse_header()
|
|
53
|
+
if len(self._buffer) < header.telegram_size:
|
|
54
|
+
return False
|
|
55
|
+
self._compare_checksum(header.telegram_size)
|
|
56
|
+
|
|
57
|
+
if header.message_type is MessageType.NAV:
|
|
58
|
+
self.last_nav = self._parse_nav(header)
|
|
59
|
+
self.last_answer = b""
|
|
60
|
+
else:
|
|
61
|
+
payload_end = header.telegram_size - _CHECKSUM_SIZE
|
|
62
|
+
self.last_answer = bytes(self._buffer[_ANSWER_HEADER_SIZE:payload_end])
|
|
63
|
+
|
|
64
|
+
self.last_header = header
|
|
65
|
+
del self._buffer[: header.telegram_size]
|
|
66
|
+
return True
|
|
67
|
+
|
|
68
|
+
def _find_header(self) -> bool:
|
|
69
|
+
"""Drop leading junk until a supported header is buffered, or wait."""
|
|
70
|
+
while len(self._buffer) > 3:
|
|
71
|
+
version = self._buffer[2]
|
|
72
|
+
magic = bytes(self._buffer[0:2])
|
|
73
|
+
if magic == _IX:
|
|
74
|
+
header_size = _NAV_HEADER_SIZE.get(version)
|
|
75
|
+
if header_size is None:
|
|
76
|
+
del self._buffer[0]
|
|
77
|
+
raise StdBinError("Unhandled protocol version")
|
|
78
|
+
return len(self._buffer) >= header_size
|
|
79
|
+
if magic == _AN:
|
|
80
|
+
if 3 <= version <= 5:
|
|
81
|
+
return len(self._buffer) >= _ANSWER_HEADER_SIZE
|
|
82
|
+
del self._buffer[0]
|
|
83
|
+
raise StdBinError("Unhandled protocol version for an answer")
|
|
84
|
+
del self._buffer[0]
|
|
85
|
+
return False
|
|
86
|
+
|
|
87
|
+
def _parse_header(self) -> NavHeader:
|
|
88
|
+
magic = bytes(self._buffer[0:2])
|
|
89
|
+
version = self._buffer[2]
|
|
90
|
+
if magic == _IX:
|
|
91
|
+
if version not in _NAV_HEADER_SIZE:
|
|
92
|
+
raise StdBinError(
|
|
93
|
+
"Unknown protocol version. Supported protocol are version 2->5"
|
|
94
|
+
)
|
|
95
|
+
if version == 2:
|
|
96
|
+
nav_mask, external_mask, telegram_size, validity, counter = struct.unpack_from(
|
|
97
|
+
">IIHII", self._buffer, 3
|
|
98
|
+
)
|
|
99
|
+
extended_mask = None
|
|
100
|
+
navigation_size = None
|
|
101
|
+
elif version == 3:
|
|
102
|
+
(
|
|
103
|
+
nav_mask,
|
|
104
|
+
extended_mask,
|
|
105
|
+
external_mask,
|
|
106
|
+
telegram_size,
|
|
107
|
+
validity,
|
|
108
|
+
counter,
|
|
109
|
+
) = struct.unpack_from(">IIIHII", self._buffer, 3)
|
|
110
|
+
navigation_size = None
|
|
111
|
+
else:
|
|
112
|
+
(
|
|
113
|
+
nav_mask,
|
|
114
|
+
extended_mask,
|
|
115
|
+
external_mask,
|
|
116
|
+
navigation_size,
|
|
117
|
+
telegram_size,
|
|
118
|
+
validity,
|
|
119
|
+
counter,
|
|
120
|
+
) = struct.unpack_from(">IIIHHII", self._buffer, 3)
|
|
121
|
+
return NavHeader(
|
|
122
|
+
message_type=MessageType.NAV,
|
|
123
|
+
protocol_version=version,
|
|
124
|
+
telegram_size=telegram_size,
|
|
125
|
+
navigation_bit_mask=nav_mask,
|
|
126
|
+
extended_navigation_bit_mask=extended_mask,
|
|
127
|
+
external_sensor_bit_mask=external_mask,
|
|
128
|
+
navigation_size=navigation_size,
|
|
129
|
+
validity_time_100us=validity,
|
|
130
|
+
counter=counter,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
if magic == _AN:
|
|
134
|
+
telegram_size = struct.unpack_from(">H", self._buffer, 3)[0]
|
|
135
|
+
return NavHeader(
|
|
136
|
+
message_type=MessageType.ANSWER,
|
|
137
|
+
protocol_version=version,
|
|
138
|
+
telegram_size=telegram_size,
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
raise StdBinError("Incorrect frame header, expected 'I', 'X' or 'A', 'N'")
|
|
142
|
+
|
|
143
|
+
def _compare_checksum(self, telegram_size: int) -> None:
|
|
144
|
+
if telegram_size < _CHECKSUM_SIZE:
|
|
145
|
+
del self._buffer[0]
|
|
146
|
+
raise StdBinError("Telegram is smaller than a checksum")
|
|
147
|
+
checksum_at = telegram_size - _CHECKSUM_SIZE
|
|
148
|
+
received = struct.unpack_from(">I", self._buffer, checksum_at)[0]
|
|
149
|
+
computed = sum(self._buffer[:checksum_at]) & 0xFFFFFFFF
|
|
150
|
+
if received != computed:
|
|
151
|
+
del self._buffer[:telegram_size]
|
|
152
|
+
raise StdBinError(
|
|
153
|
+
f"Bad checksum. Received: 0x{received:x}, computed: 0x{computed:x}"
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
def _parse_nav(self, header: NavHeader) -> NavData:
|
|
157
|
+
if header.navigation_bit_mask is None or header.external_sensor_bit_mask is None:
|
|
158
|
+
raise StdBinError("Navigation frame is missing its bit masks")
|
|
159
|
+
present: dict[str, object] = {}
|
|
160
|
+
offset = _NAV_HEADER_SIZE[header.protocol_version]
|
|
161
|
+
offset = self._apply_blocks(offset, header.navigation_bit_mask, NAVIGATION_BLOCKS, present)
|
|
162
|
+
if header.extended_navigation_bit_mask is not None:
|
|
163
|
+
offset = self._apply_blocks(
|
|
164
|
+
offset,
|
|
165
|
+
header.extended_navigation_bit_mask,
|
|
166
|
+
EXTENDED_BLOCKS,
|
|
167
|
+
present,
|
|
168
|
+
)
|
|
169
|
+
self._apply_blocks(offset, header.external_sensor_bit_mask, EXTERNAL_BLOCKS, present)
|
|
170
|
+
return NavData(**present)
|
|
171
|
+
|
|
172
|
+
def _apply_blocks(
|
|
173
|
+
self,
|
|
174
|
+
offset: int,
|
|
175
|
+
mask: int,
|
|
176
|
+
blocks: Iterable[Block],
|
|
177
|
+
present: dict[str, object],
|
|
178
|
+
) -> int:
|
|
179
|
+
for block in blocks:
|
|
180
|
+
if (mask >> block.bit) & 1 == 0:
|
|
181
|
+
continue
|
|
182
|
+
if len(self._buffer) - offset < block.size:
|
|
183
|
+
raise StdBinError("Not enough bytes in buffer to parse this memory block")
|
|
184
|
+
values = struct.unpack_from(block.fmt, self._buffer, offset)
|
|
185
|
+
present[block.field] = block.build(values)
|
|
186
|
+
offset += block.size
|
|
187
|
+
return offset
|
pyixblue/errors.py
ADDED
pyixblue/models.py
ADDED
|
@@ -0,0 +1,741 @@
|
|
|
1
|
+
"""Decoded stdbin header and navigation blocks.
|
|
2
|
+
|
|
3
|
+
Status enums follow the bit indexes in the reference decoder. Each member is
|
|
4
|
+
``1 << index``, so a parsed word compares equal to the raw integer.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from enum import FlagBoundary, IntFlag, StrEnum
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _flag(name: str, members: dict[str, int]) -> type[IntFlag]:
|
|
12
|
+
"""Build an IntFlag that keeps unnamed bits from the wire word."""
|
|
13
|
+
return IntFlag(name, members, boundary=FlagBoundary.KEEP) # type: ignore[call-overload]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class MessageType(StrEnum):
|
|
17
|
+
"""Kind of stdbin frame."""
|
|
18
|
+
|
|
19
|
+
NAV = "nav"
|
|
20
|
+
ANSWER = "answer"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
InsAlgorithmStatus1 = _flag(
|
|
24
|
+
"InsAlgorithmStatus1",
|
|
25
|
+
{
|
|
26
|
+
"NAVIGATION": 1 << 0,
|
|
27
|
+
"ALIGNMENT": 1 << 1,
|
|
28
|
+
"FINE_ALIGNMENT": 1 << 2,
|
|
29
|
+
"DEAD_RECKONING": 1 << 3,
|
|
30
|
+
"GPS_ALTITUDE": 1 << 4,
|
|
31
|
+
"DEPTHSENSOR_ALTITUDE": 1 << 5,
|
|
32
|
+
"ZERO_ALTITUDE": 1 << 6,
|
|
33
|
+
"HYDRO_ALTITUDE": 1 << 7,
|
|
34
|
+
"LOG_RECEIVED": 1 << 8,
|
|
35
|
+
"LOG_VALID": 1 << 9,
|
|
36
|
+
"LOG_WAITING": 1 << 10,
|
|
37
|
+
"LOG_REJECTED": 1 << 11,
|
|
38
|
+
"GPS_RECEIVED": 1 << 12,
|
|
39
|
+
"GPS_VALID": 1 << 13,
|
|
40
|
+
"GPS_WAITING": 1 << 14,
|
|
41
|
+
"GPS_REJECTED": 1 << 15,
|
|
42
|
+
"USBL_RECEIVED": 1 << 16,
|
|
43
|
+
"USBL_VALID": 1 << 17,
|
|
44
|
+
"USBL_REJECTED": 1 << 18,
|
|
45
|
+
"DEPTH_RECEIVED": 1 << 19,
|
|
46
|
+
"DEPTH_VALID": 1 << 20,
|
|
47
|
+
"DEPTH_WAITING": 1 << 21,
|
|
48
|
+
"DEPTH_REJECTED": 1 << 22,
|
|
49
|
+
"LBL_RECEIVED": 1 << 23,
|
|
50
|
+
"LBL_VALID": 1 << 24,
|
|
51
|
+
"LBL_WAITING": 1 << 25,
|
|
52
|
+
"LBL_REJECTED": 1 << 26,
|
|
53
|
+
"ALTITUDE_SATURATION": 1 << 27,
|
|
54
|
+
"SPEED_SATURATION": 1 << 28,
|
|
55
|
+
"INTERPOLATION_MISSED": 1 << 29,
|
|
56
|
+
"HEAVE_INITIALISATION": 1 << 30,
|
|
57
|
+
},
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
InsAlgorithmStatus2 = _flag(
|
|
61
|
+
"InsAlgorithmStatus2",
|
|
62
|
+
{
|
|
63
|
+
"WATERTRACK_RECEIVED": 1 << 0,
|
|
64
|
+
"WATERTRACK_VALID": 1 << 1,
|
|
65
|
+
"WATERTRACK_WAITING": 1 << 2,
|
|
66
|
+
"WATERTRACK_REJECTED": 1 << 3,
|
|
67
|
+
"GPS2_RECEIVED": 1 << 4,
|
|
68
|
+
"GPS2_VALID": 1 << 5,
|
|
69
|
+
"GPS2_WAITING": 1 << 6,
|
|
70
|
+
"GPS2_REJECTED": 1 << 7,
|
|
71
|
+
"ALTITUDE_RECEIVED": 1 << 12,
|
|
72
|
+
"ALTITUDE_VALID": 1 << 13,
|
|
73
|
+
"ALTITUDE_WAITING": 1 << 14,
|
|
74
|
+
"ALTITUDE_REJECTED": 1 << 15,
|
|
75
|
+
"ZUPT_ACTIVATED": 1 << 16,
|
|
76
|
+
"ZUPT_VALID": 1 << 17,
|
|
77
|
+
"RO_ZUPT_ACTIVATED": 1 << 18,
|
|
78
|
+
"RO_ZUPT_VALID": 1 << 19,
|
|
79
|
+
"FAST_ALIGNEMENT": 1 << 22,
|
|
80
|
+
"EMULATION_MODE": 1 << 23,
|
|
81
|
+
"EMLOG_RECEIVED": 1 << 24,
|
|
82
|
+
"EMLOG_VALID": 1 << 25,
|
|
83
|
+
"EMLOG_WAITING": 1 << 26,
|
|
84
|
+
"EMLOG_REJECTED": 1 << 27,
|
|
85
|
+
"MANUALGPS_RECEIVED": 1 << 28,
|
|
86
|
+
"MANUALGPS_VALID": 1 << 29,
|
|
87
|
+
"MANUALGPS_WAITING": 1 << 30,
|
|
88
|
+
"MANUALGPS_REJECTED": 1 << 31,
|
|
89
|
+
},
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
InsAlgorithmStatus3 = _flag(
|
|
93
|
+
"InsAlgorithmStatus3",
|
|
94
|
+
{
|
|
95
|
+
"SVL_RECEIVED": 1 << 0,
|
|
96
|
+
"SVL_VALID": 1 << 1,
|
|
97
|
+
"SVL_WAITING": 1 << 2,
|
|
98
|
+
"SVL_REJECTED": 1 << 3,
|
|
99
|
+
"EMLOG2_RECEIVED": 1 << 4,
|
|
100
|
+
"EMLOG2_VALID": 1 << 5,
|
|
101
|
+
"EMLOG2_WAITING": 1 << 6,
|
|
102
|
+
"EMLOG2_REJECTED": 1 << 7,
|
|
103
|
+
"USBL2_RECEIVED": 1 << 8,
|
|
104
|
+
"USBL2_VALID": 1 << 9,
|
|
105
|
+
"USBL2_WAITING": 1 << 10,
|
|
106
|
+
"USBL2_REJECTED": 1 << 11,
|
|
107
|
+
"USBL3_RECEIVED": 1 << 12,
|
|
108
|
+
"USBL3_VALID": 1 << 13,
|
|
109
|
+
"USBL3_WAITING": 1 << 14,
|
|
110
|
+
"USBL3_REJECTED": 1 << 15,
|
|
111
|
+
"CALCHK": 1 << 17,
|
|
112
|
+
"RESTORE_ATTITUDE_FAILED": 1 << 18,
|
|
113
|
+
"REL_SPD_ZUPT_ACTIVATED": 1 << 19,
|
|
114
|
+
"REL_SPD_ZUPT_VALID": 1 << 20,
|
|
115
|
+
"EXT_SENSOR_OUTDATED": 1 << 21,
|
|
116
|
+
"SENSOR_USED_BEFORE_CALIB": 1 << 22,
|
|
117
|
+
"RESTORE_ATTITUDE_REJECTED": 1 << 23,
|
|
118
|
+
"POLAR_VALIDITY": 1 << 27,
|
|
119
|
+
"FIRM_INCOMPATIBLES": 1 << 28,
|
|
120
|
+
"IMO_ALIGNMENT": 1 << 29,
|
|
121
|
+
"OPTIMAL_ALIGNMENT": 1 << 30,
|
|
122
|
+
"STRATEGIC_ALIGNMENT": 1 << 31,
|
|
123
|
+
},
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
InsAlgorithmStatus4 = _flag(
|
|
127
|
+
"InsAlgorithmStatus4",
|
|
128
|
+
{
|
|
129
|
+
"LOG2_RECEIVED": 1 << 0,
|
|
130
|
+
"LOG2_VALID": 1 << 1,
|
|
131
|
+
"LOG2_WAITING": 1 << 2,
|
|
132
|
+
"LOG2_REJECTED": 1 << 3,
|
|
133
|
+
"WATERTRACK2_RECEIVED": 1 << 4,
|
|
134
|
+
"WATERTRACK2_VALID": 1 << 5,
|
|
135
|
+
"WATERTRACK2_WAITING": 1 << 6,
|
|
136
|
+
"WATERTRACK2_REJECTED": 1 << 7,
|
|
137
|
+
"DVL_DIST_TRAVELLED_VALID": 1 << 8,
|
|
138
|
+
"DVL_CALIBRATION_NONE": 1 << 9,
|
|
139
|
+
"DVL_ROUGH_CALIBRATION": 1 << 10,
|
|
140
|
+
"DVL_FINE_CALIBRATION": 1 << 11,
|
|
141
|
+
"DVL_CHECK_CALIBRATION": 1 << 12,
|
|
142
|
+
"MANUAL_ATT_RECEIVED": 1 << 13,
|
|
143
|
+
"MANUAL_ATT_VALID": 1 << 14,
|
|
144
|
+
"MANUAL_ATT_WAITING": 1 << 15,
|
|
145
|
+
"MANUAL_ATT_REJECTED": 1 << 16,
|
|
146
|
+
"FIXED_HEADING": 1 << 17,
|
|
147
|
+
"DEGRADED_MODE": 1 << 18,
|
|
148
|
+
"VTG_RECEIVED": 1 << 19,
|
|
149
|
+
"VTG_VALID": 1 << 20,
|
|
150
|
+
"VTG_WAITING": 1 << 21,
|
|
151
|
+
"VTG_REJECTED": 1 << 22,
|
|
152
|
+
"VTG2_RECEIVED": 1 << 23,
|
|
153
|
+
"VTG2_VALID": 1 << 24,
|
|
154
|
+
"VTG2_WAITING": 1 << 25,
|
|
155
|
+
"VTG2_REJECTED": 1 << 26,
|
|
156
|
+
"ALIX_SYST_SATURATION": 1 << 27,
|
|
157
|
+
"CINT_FLASH_MEMORY_WRERR": 1 << 28,
|
|
158
|
+
"CINT_FLASH_MEMORY_ERERR": 1 << 29,
|
|
159
|
+
"ODO_CALIBRATION_DONE": 1 << 30,
|
|
160
|
+
"INCONSISTENT_SENSOR_DATA": 1 << 31,
|
|
161
|
+
},
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
# The reference parser reads four status words (16 bytes). Status5 and status6
|
|
165
|
+
# are named in the C++ struct but are not part of that block.
|
|
166
|
+
InsAlgorithmStatus5 = _flag(
|
|
167
|
+
"InsAlgorithmStatus5",
|
|
168
|
+
{
|
|
169
|
+
"NAV_MODE_NONE": 1 << 0,
|
|
170
|
+
"TRAVELING_MODE": 1 << 1,
|
|
171
|
+
"FIRING_MODE": 1 << 2,
|
|
172
|
+
"FIRING_ON_MOVE": 1 << 3,
|
|
173
|
+
"AERO_TRANSPORTATION": 1 << 4,
|
|
174
|
+
"MANUAL_ALTITUD_VALID": 1 << 16,
|
|
175
|
+
"MANUAL_ALTITUDE_REJECTED": 1 << 17,
|
|
176
|
+
"SHOCK_ACTIVATED": 1 << 18,
|
|
177
|
+
"SHOCK_DETECTED": 1 << 19,
|
|
178
|
+
"TR_ZUPT_ACTIVATED": 1 << 20,
|
|
179
|
+
"TR_ZUPT_VALID": 1 << 21,
|
|
180
|
+
"PO_ZUPT_ACTIVATED": 1 << 22,
|
|
181
|
+
"PO_ZUPT_VALID": 1 << 23,
|
|
182
|
+
"RELATIVE_ATTITUDE_DEGRADED": 1 << 24,
|
|
183
|
+
"CDSP_FLASH_MEMORY_WRERR": 1 << 25,
|
|
184
|
+
"CDSP_FLASH_MEMORY_ERERR": 1 << 26,
|
|
185
|
+
"CINT_FLASH_MEMORY_VIRGIN": 1 << 27,
|
|
186
|
+
"CDSP_FLASH_MEMORY_VIRGIN": 1 << 28,
|
|
187
|
+
"CINT_FLASH_MEMORY_ERROR": 1 << 29,
|
|
188
|
+
"CDSP_FLASH_MEMORY_ERROR": 1 << 30,
|
|
189
|
+
},
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
InsSystemStatus1 = _flag(
|
|
193
|
+
"InsSystemStatus1",
|
|
194
|
+
{
|
|
195
|
+
"SERIAL_IN_R_ERR": 1 << 0,
|
|
196
|
+
"INPUT_A_ERR": 1 << 1,
|
|
197
|
+
"INPUT_B_ERR": 1 << 2,
|
|
198
|
+
"INPUT_C_ERR": 1 << 3,
|
|
199
|
+
"INPUT_D_ERR": 1 << 4,
|
|
200
|
+
"INPUT_E_ERR": 1 << 5,
|
|
201
|
+
"INPUT_R_ACTIVITY": 1 << 8,
|
|
202
|
+
"INPUT_A_ACTIVITY": 1 << 9,
|
|
203
|
+
"INPUT_B_ACTIVITY": 1 << 10,
|
|
204
|
+
"INPUT_C_ACTIVITY": 1 << 11,
|
|
205
|
+
"INPUT_D_ACTIVITY": 1 << 12,
|
|
206
|
+
"INPUT_E_ACTIVITY": 1 << 13,
|
|
207
|
+
"OUTPUT_R_FULL": 1 << 16,
|
|
208
|
+
"OUTPUT_A_FULL": 1 << 17,
|
|
209
|
+
"OUTPUT_B_FULL": 1 << 18,
|
|
210
|
+
"OUTPUT_C_FULL": 1 << 19,
|
|
211
|
+
"OUTPUT_D_FULL": 1 << 20,
|
|
212
|
+
"OUTPUT_E_FULL": 1 << 21,
|
|
213
|
+
"ETHERNET_PORT_FULL": 1 << 22,
|
|
214
|
+
"INTERNAL_TIME_USED": 1 << 24,
|
|
215
|
+
"ETHERNET_PORT_ACTIVITY": 1 << 26,
|
|
216
|
+
"PULSE_IN_A_ACTIVITY": 1 << 27,
|
|
217
|
+
"PULSE_IN_B_ACTIVITY": 1 << 28,
|
|
218
|
+
},
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
InsSystemStatus2 = _flag(
|
|
222
|
+
"InsSystemStatus2",
|
|
223
|
+
{
|
|
224
|
+
"GPS_DETECTED": 1 << 2,
|
|
225
|
+
"DMI_DETECTED": 1 << 8,
|
|
226
|
+
"UTC_DETECTED": 1 << 9,
|
|
227
|
+
"ALTITUDE_DETECTED": 1 << 10,
|
|
228
|
+
"PPS_DETECTED": 1 << 11,
|
|
229
|
+
"ZUPT_MODE_ACTIVATED": 1 << 12,
|
|
230
|
+
"MANUAL_GPS_DETECTED": 1 << 14,
|
|
231
|
+
"WAIT_FOR_POSITION": 1 << 21,
|
|
232
|
+
"INTERNAL_LOG": 1 << 24,
|
|
233
|
+
"MPC_OVERLOAD": 1 << 27,
|
|
234
|
+
"POWER_SUPPLY_FAILURE": 1 << 28,
|
|
235
|
+
"RD_MODE": 1 << 29,
|
|
236
|
+
"CONFIGURATION_SAVED": 1 << 30,
|
|
237
|
+
},
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
InsSystemStatus3 = _flag(
|
|
241
|
+
"InsSystemStatus3",
|
|
242
|
+
{
|
|
243
|
+
"VTG1_DETECTED": 1 << 13,
|
|
244
|
+
"MANUAL_ATT_DETECTED": 1 << 15,
|
|
245
|
+
"INPUT_CAN_ERR": 1 << 16,
|
|
246
|
+
"INPUT_CAN_ACTIVITY": 1 << 17,
|
|
247
|
+
},
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
@dataclass(frozen=True, slots=True)
|
|
252
|
+
class AttitudeHeading:
|
|
253
|
+
heading_deg: float
|
|
254
|
+
roll_deg: float
|
|
255
|
+
pitch_deg: float
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
@dataclass(frozen=True, slots=True)
|
|
259
|
+
class AttitudeHeadingDeviation:
|
|
260
|
+
heading_stddev_deg: float
|
|
261
|
+
roll_stddev_deg: float
|
|
262
|
+
pitch_stddev_deg: float
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
@dataclass(frozen=True, slots=True)
|
|
266
|
+
class RealTimeHeaveSurgeSway:
|
|
267
|
+
rt_heave_without_bdl: float
|
|
268
|
+
rt_heave_at_bdl: float
|
|
269
|
+
rt_surge_at_bdl: float
|
|
270
|
+
rt_sway_at_bdl: float
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
@dataclass(frozen=True, slots=True)
|
|
274
|
+
class SmartHeave:
|
|
275
|
+
validity_time_100us: int
|
|
276
|
+
smart_heave_m: float
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
@dataclass(frozen=True, slots=True)
|
|
280
|
+
class HeadingRollPitchRate:
|
|
281
|
+
heading_rate: float
|
|
282
|
+
roll_rate: float
|
|
283
|
+
pitch_rate: float
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
@dataclass(frozen=True, slots=True)
|
|
287
|
+
class RotationRateVesselFrame:
|
|
288
|
+
xv1_degsec: float
|
|
289
|
+
xv2_degsec: float
|
|
290
|
+
xv3_degsec: float
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
@dataclass(frozen=True, slots=True)
|
|
294
|
+
class AccelerationVesselFrame:
|
|
295
|
+
xv1_msec2: float
|
|
296
|
+
xv2_msec2: float
|
|
297
|
+
xv3_msec2: float
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
@dataclass(frozen=True, slots=True)
|
|
301
|
+
class Position:
|
|
302
|
+
"""WGS84 position at the selected lever arm."""
|
|
303
|
+
|
|
304
|
+
latitude_deg: float
|
|
305
|
+
longitude_deg: float
|
|
306
|
+
altitude_ref: int # 0: geoid (mean sea level), 1: ellipsoid
|
|
307
|
+
altitude_m: float
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
@dataclass(frozen=True, slots=True)
|
|
311
|
+
class PositionDeviation:
|
|
312
|
+
north_stddev_m: float
|
|
313
|
+
east_stddev_m: float
|
|
314
|
+
north_east_corr: float
|
|
315
|
+
altitude_stddev_m: float
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
@dataclass(frozen=True, slots=True)
|
|
319
|
+
class SpeedGeographicFrame:
|
|
320
|
+
north_msec: float
|
|
321
|
+
east_msec: float
|
|
322
|
+
up_msec: float
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
@dataclass(frozen=True, slots=True)
|
|
326
|
+
class SpeedGeographicFrameDeviation:
|
|
327
|
+
north_stddev_msec: float
|
|
328
|
+
east_stddev_msec: float
|
|
329
|
+
up_stddev_msec: float
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
@dataclass(frozen=True, slots=True)
|
|
333
|
+
class CurrentGeographicFrame:
|
|
334
|
+
north_msec: float
|
|
335
|
+
east_msec: float
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
@dataclass(frozen=True, slots=True)
|
|
339
|
+
class CurrentGeographicFrameDeviation:
|
|
340
|
+
north_stddev_msec: float
|
|
341
|
+
east_stddev_msec: float
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
@dataclass(frozen=True, slots=True)
|
|
345
|
+
class SystemDate:
|
|
346
|
+
day: int
|
|
347
|
+
month: int
|
|
348
|
+
year: int
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
@dataclass(frozen=True, slots=True)
|
|
352
|
+
class SensorStatus:
|
|
353
|
+
status1: int
|
|
354
|
+
status2: int
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
@dataclass(frozen=True, slots=True)
|
|
358
|
+
class InsAlgorithmStatus:
|
|
359
|
+
status1: InsAlgorithmStatus1
|
|
360
|
+
status2: InsAlgorithmStatus2
|
|
361
|
+
status3: InsAlgorithmStatus3
|
|
362
|
+
status4: InsAlgorithmStatus4
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
@dataclass(frozen=True, slots=True)
|
|
366
|
+
class InsSystemStatus:
|
|
367
|
+
status1: InsSystemStatus1
|
|
368
|
+
status2: InsSystemStatus2
|
|
369
|
+
status3: InsSystemStatus3
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
@dataclass(frozen=True, slots=True)
|
|
373
|
+
class InsUserStatus:
|
|
374
|
+
status: int
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
@dataclass(frozen=True, slots=True)
|
|
378
|
+
class AhrsAlgorithmStatus:
|
|
379
|
+
status: int
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
@dataclass(frozen=True, slots=True)
|
|
383
|
+
class AhrsSystemStatus:
|
|
384
|
+
status1: int
|
|
385
|
+
status2: int
|
|
386
|
+
status3: int
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
@dataclass(frozen=True, slots=True)
|
|
390
|
+
class AhrsUserStatus:
|
|
391
|
+
status: int
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
@dataclass(frozen=True, slots=True)
|
|
395
|
+
class HeaveSurgeSwaySpeed:
|
|
396
|
+
realtime_heave_speed: float
|
|
397
|
+
surge_speed: float
|
|
398
|
+
sway_speed: float
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
@dataclass(frozen=True, slots=True)
|
|
402
|
+
class SpeedVesselFrame:
|
|
403
|
+
xv1_msec: float
|
|
404
|
+
xv2_msec: float
|
|
405
|
+
xv3_msec: float
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
@dataclass(frozen=True, slots=True)
|
|
409
|
+
class AccelerationGeographicFrame:
|
|
410
|
+
north_msec2: float
|
|
411
|
+
east_msec2: float
|
|
412
|
+
up_msec2: float
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
@dataclass(frozen=True, slots=True)
|
|
416
|
+
class CourseSpeedOverGround:
|
|
417
|
+
course_over_ground: float
|
|
418
|
+
speed_over_ground: float
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
@dataclass(frozen=True, slots=True)
|
|
422
|
+
class Temperatures:
|
|
423
|
+
mean_temp_fog: float
|
|
424
|
+
mean_temp_acc: float
|
|
425
|
+
board_temperature: float
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
@dataclass(frozen=True, slots=True)
|
|
429
|
+
class AttitudeQuaternion:
|
|
430
|
+
q0: float
|
|
431
|
+
q1: float
|
|
432
|
+
q2: float
|
|
433
|
+
q3: float
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
@dataclass(frozen=True, slots=True)
|
|
437
|
+
class AttitudeQuaternionDeviation:
|
|
438
|
+
quat_stddev_xi1: float
|
|
439
|
+
quat_stddev_xi2: float
|
|
440
|
+
quat_stddev_xi3: float
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
@dataclass(frozen=True, slots=True)
|
|
444
|
+
class RawAccelerationVesselFrame:
|
|
445
|
+
xv1_msec2: float
|
|
446
|
+
xv2_msec2: float
|
|
447
|
+
xv3_msec2: float
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
@dataclass(frozen=True, slots=True)
|
|
451
|
+
class AccelerationVesselFrameDeviation:
|
|
452
|
+
xv1_stddev_msec2: float
|
|
453
|
+
xv2_stddev_msec2: float
|
|
454
|
+
xv3_stddev_msec2: float
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
@dataclass(frozen=True, slots=True)
|
|
458
|
+
class RotationRateVesselFrameDeviation:
|
|
459
|
+
xv1_stddev_degsec: float
|
|
460
|
+
xv2_stddev_degsec: float
|
|
461
|
+
xv3_stddev_degsec: float
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
@dataclass(frozen=True, slots=True)
|
|
465
|
+
class RotationAccelerationVesselFrame:
|
|
466
|
+
xv1_degsec2: float
|
|
467
|
+
xv2_degsec2: float
|
|
468
|
+
xv3_degsec2: float
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
@dataclass(frozen=True, slots=True)
|
|
472
|
+
class RotationAccelerationVesselFrameDeviation:
|
|
473
|
+
xv1_stddev_degsec2: float
|
|
474
|
+
xv2_stddev_degsec2: float
|
|
475
|
+
xv3_stddev_degsec2: float
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
@dataclass(frozen=True, slots=True)
|
|
479
|
+
class RawRotationRateVesselFrame:
|
|
480
|
+
xv1_degsec: float
|
|
481
|
+
xv2_degsec: float
|
|
482
|
+
xv3_degsec: float
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
@dataclass(frozen=True, slots=True)
|
|
486
|
+
class VehicleAttitudeHeading:
|
|
487
|
+
heading_deg: float
|
|
488
|
+
roll_deg: float
|
|
489
|
+
pitch_deg: float
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
@dataclass(frozen=True, slots=True)
|
|
493
|
+
class VehicleAttitudeHeadingDeviation:
|
|
494
|
+
heading_stddev_deg: float
|
|
495
|
+
roll_stddev_deg: float
|
|
496
|
+
pitch_stddev_deg: float
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
@dataclass(frozen=True, slots=True)
|
|
500
|
+
class VehiclePosition:
|
|
501
|
+
latitude_deg: float
|
|
502
|
+
longitude_deg: float
|
|
503
|
+
altitude_ref: int
|
|
504
|
+
altitude_m: float
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
@dataclass(frozen=True, slots=True)
|
|
508
|
+
class VehiclePositionDeviation:
|
|
509
|
+
north_stddev_m: float
|
|
510
|
+
east_stddev_m: float
|
|
511
|
+
north_east_corr: float
|
|
512
|
+
altitude_stddev_m: float
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
@dataclass(frozen=True, slots=True)
|
|
516
|
+
class Utc:
|
|
517
|
+
validity_time_100us: int
|
|
518
|
+
source: int
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
@dataclass(frozen=True, slots=True)
|
|
522
|
+
class Gnss:
|
|
523
|
+
validity_time_100us: int
|
|
524
|
+
gnss_id: int
|
|
525
|
+
gnss_quality: int
|
|
526
|
+
latitude_deg: float
|
|
527
|
+
longitude_deg: float
|
|
528
|
+
altitude_m: float
|
|
529
|
+
latitude_stddev_m: float
|
|
530
|
+
longitude_stddev_m: float
|
|
531
|
+
altitude_stddev_m: float
|
|
532
|
+
lat_lon_stddev_m2: float
|
|
533
|
+
geoidal_separation_m: float
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
@dataclass(frozen=True, slots=True)
|
|
537
|
+
class Emlog:
|
|
538
|
+
validity_time_100us: int
|
|
539
|
+
emlog_id: int
|
|
540
|
+
xv1_water_speed_ms: float
|
|
541
|
+
xv1_speed_stddev_ms: float
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
@dataclass(frozen=True, slots=True)
|
|
545
|
+
class Usbl:
|
|
546
|
+
validity_time_100us: int
|
|
547
|
+
usbl_id: int
|
|
548
|
+
beacon_id: bytes
|
|
549
|
+
latitude_deg: float
|
|
550
|
+
longitude_deg: float
|
|
551
|
+
altitude_m: float
|
|
552
|
+
north_stddev_m: float
|
|
553
|
+
east_stddev_m: float
|
|
554
|
+
lat_lon_cov_m2: float
|
|
555
|
+
altitude_stddev_m: float
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
@dataclass(frozen=True, slots=True)
|
|
559
|
+
class Depth:
|
|
560
|
+
validity_time_100us: int
|
|
561
|
+
depth_m: float
|
|
562
|
+
depth_stddev_m: float
|
|
563
|
+
|
|
564
|
+
|
|
565
|
+
@dataclass(frozen=True, slots=True)
|
|
566
|
+
class DvlGroundSpeed:
|
|
567
|
+
validity_time_100us: int
|
|
568
|
+
dvl_id: int
|
|
569
|
+
xv1_ground_speed_ms: float
|
|
570
|
+
xv2_ground_speed_ms: float
|
|
571
|
+
xv3_ground_speed_ms: float
|
|
572
|
+
dvl_speed_of_sound_ms: float
|
|
573
|
+
dvl_altitude_m: float
|
|
574
|
+
xv1_stddev_ms: float
|
|
575
|
+
xv2_stddev_ms: float
|
|
576
|
+
xv3_stddev_ms: float
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
@dataclass(frozen=True, slots=True)
|
|
580
|
+
class DvlWaterSpeed:
|
|
581
|
+
validity_time_100us: int
|
|
582
|
+
dvl_id: int
|
|
583
|
+
xv1_water_speed_ms: float
|
|
584
|
+
xv2_water_speed_ms: float
|
|
585
|
+
xv3_water_speed_ms: float
|
|
586
|
+
dvl_speed_of_sound_ms: float
|
|
587
|
+
xv1_stddev_ms: float
|
|
588
|
+
xv2_stddev_ms: float
|
|
589
|
+
xv3_stddev_ms: float
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
@dataclass(frozen=True, slots=True)
|
|
593
|
+
class SoundVelocity:
|
|
594
|
+
validity_time_100us: int
|
|
595
|
+
ext_speed_of_sound_ms: float
|
|
596
|
+
|
|
597
|
+
|
|
598
|
+
@dataclass(frozen=True, slots=True)
|
|
599
|
+
class Dmi:
|
|
600
|
+
validity_time_100us: int
|
|
601
|
+
pulse_count: int
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
@dataclass(frozen=True, slots=True)
|
|
605
|
+
class Lbl:
|
|
606
|
+
validity_time_100us: int
|
|
607
|
+
rfu: int
|
|
608
|
+
beacon_id: bytes
|
|
609
|
+
beacon_latitude_deg: float
|
|
610
|
+
beacon_longitude_deg: float
|
|
611
|
+
beacon_altitude_m: float
|
|
612
|
+
range_m: float
|
|
613
|
+
range_stddev_m: float
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
@dataclass(frozen=True, slots=True)
|
|
617
|
+
class EventMarker:
|
|
618
|
+
validity_time_100us: int
|
|
619
|
+
event_id: int
|
|
620
|
+
event_count: int
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
@dataclass(frozen=True, slots=True)
|
|
624
|
+
class TurretAngles:
|
|
625
|
+
validity_time_100us: int
|
|
626
|
+
heading_bearing_drift_angle_deg: float
|
|
627
|
+
roll_deg: float
|
|
628
|
+
elevation_pitch_deg: float
|
|
629
|
+
|
|
630
|
+
|
|
631
|
+
@dataclass(frozen=True, slots=True)
|
|
632
|
+
class Vtg:
|
|
633
|
+
validity_time_100us: int
|
|
634
|
+
vtg_id: int
|
|
635
|
+
true_course_deg: float
|
|
636
|
+
magnetic_course_deg: float
|
|
637
|
+
speed_over_ground_ms: float
|
|
638
|
+
|
|
639
|
+
|
|
640
|
+
@dataclass(frozen=True, slots=True)
|
|
641
|
+
class LogBook:
|
|
642
|
+
validity_time_100us: int
|
|
643
|
+
log_id: int
|
|
644
|
+
custom_text: bytes
|
|
645
|
+
|
|
646
|
+
|
|
647
|
+
@dataclass(frozen=True, slots=True)
|
|
648
|
+
class NavHeader:
|
|
649
|
+
"""Parsed stdbin header.
|
|
650
|
+
|
|
651
|
+
``extended_navigation_bit_mask`` is set on protocol versions 3 and later.
|
|
652
|
+
``navigation_size`` is set on versions 4 and 5. Answer frames leave the
|
|
653
|
+
navigation fields unset. ``counter`` is the frame counter carried in the
|
|
654
|
+
navigation header.
|
|
655
|
+
"""
|
|
656
|
+
|
|
657
|
+
message_type: MessageType
|
|
658
|
+
protocol_version: int
|
|
659
|
+
telegram_size: int
|
|
660
|
+
navigation_bit_mask: int | None = None
|
|
661
|
+
extended_navigation_bit_mask: int | None = None
|
|
662
|
+
external_sensor_bit_mask: int | None = None
|
|
663
|
+
navigation_size: int | None = None
|
|
664
|
+
validity_time_100us: int | None = None
|
|
665
|
+
counter: int | None = None
|
|
666
|
+
|
|
667
|
+
|
|
668
|
+
@dataclass(frozen=True, slots=True)
|
|
669
|
+
class NavData:
|
|
670
|
+
"""Navigation, extended-navigation, and external-sensor blocks.
|
|
671
|
+
|
|
672
|
+
A field is ``None`` when that block's mask bit was clear in the frame.
|
|
673
|
+
"""
|
|
674
|
+
|
|
675
|
+
attitude_heading: AttitudeHeading | None = None
|
|
676
|
+
attitude_heading_deviation: AttitudeHeadingDeviation | None = None
|
|
677
|
+
rt_heave_surge_sway: RealTimeHeaveSurgeSway | None = None
|
|
678
|
+
smart_heave: SmartHeave | None = None
|
|
679
|
+
heading_roll_pitch_rate: HeadingRollPitchRate | None = None
|
|
680
|
+
rotation_rate_vessel_frame: RotationRateVesselFrame | None = None
|
|
681
|
+
acceleration_vessel_frame: AccelerationVesselFrame | None = None
|
|
682
|
+
position: Position | None = None
|
|
683
|
+
position_deviation: PositionDeviation | None = None
|
|
684
|
+
speed_geographic_frame: SpeedGeographicFrame | None = None
|
|
685
|
+
speed_geographic_frame_deviation: SpeedGeographicFrameDeviation | None = None
|
|
686
|
+
current_geographic_frame: CurrentGeographicFrame | None = None
|
|
687
|
+
current_geographic_frame_deviation: CurrentGeographicFrameDeviation | None = None
|
|
688
|
+
system_date: SystemDate | None = None
|
|
689
|
+
sensor_status: SensorStatus | None = None
|
|
690
|
+
ins_algorithm_status: InsAlgorithmStatus | None = None
|
|
691
|
+
ins_system_status: InsSystemStatus | None = None
|
|
692
|
+
ins_user_status: InsUserStatus | None = None
|
|
693
|
+
ahrs_algorithm_status: AhrsAlgorithmStatus | None = None
|
|
694
|
+
ahrs_system_status: AhrsSystemStatus | None = None
|
|
695
|
+
ahrs_user_status: AhrsUserStatus | None = None
|
|
696
|
+
heave_surge_sway_speed: HeaveSurgeSwaySpeed | None = None
|
|
697
|
+
speed_vessel_frame: SpeedVesselFrame | None = None
|
|
698
|
+
acceleration_geographic_frame: AccelerationGeographicFrame | None = None
|
|
699
|
+
course_speed_over_ground: CourseSpeedOverGround | None = None
|
|
700
|
+
temperatures: Temperatures | None = None
|
|
701
|
+
attitude_quaternion: AttitudeQuaternion | None = None
|
|
702
|
+
attitude_quaternion_deviation: AttitudeQuaternionDeviation | None = None
|
|
703
|
+
raw_acceleration_vessel_frame: RawAccelerationVesselFrame | None = None
|
|
704
|
+
acceleration_vessel_frame_deviation: AccelerationVesselFrameDeviation | None = None
|
|
705
|
+
rotation_rate_vessel_frame_deviation: RotationRateVesselFrameDeviation | None = None
|
|
706
|
+
rotation_acceleration_vessel_frame: RotationAccelerationVesselFrame | None = None
|
|
707
|
+
rotation_acceleration_vessel_frame_deviation: (
|
|
708
|
+
RotationAccelerationVesselFrameDeviation | None
|
|
709
|
+
) = None
|
|
710
|
+
raw_rotation_rate_vessel_frame: RawRotationRateVesselFrame | None = None
|
|
711
|
+
vehicle_attitude_heading: VehicleAttitudeHeading | None = None
|
|
712
|
+
vehicle_attitude_heading_deviation: VehicleAttitudeHeadingDeviation | None = None
|
|
713
|
+
vehicle_position: VehiclePosition | None = None
|
|
714
|
+
vehicle_position_deviation: VehiclePositionDeviation | None = None
|
|
715
|
+
utc: Utc | None = None
|
|
716
|
+
gnss1: Gnss | None = None
|
|
717
|
+
gnss2: Gnss | None = None
|
|
718
|
+
gnss_manual: Gnss | None = None
|
|
719
|
+
emlog1: Emlog | None = None
|
|
720
|
+
emlog2: Emlog | None = None
|
|
721
|
+
usbl1: Usbl | None = None
|
|
722
|
+
usbl2: Usbl | None = None
|
|
723
|
+
usbl3: Usbl | None = None
|
|
724
|
+
depth: Depth | None = None
|
|
725
|
+
dvl_ground_speed1: DvlGroundSpeed | None = None
|
|
726
|
+
dvl_water_speed1: DvlWaterSpeed | None = None
|
|
727
|
+
sound_velocity: SoundVelocity | None = None
|
|
728
|
+
dmi: Dmi | None = None
|
|
729
|
+
lbl1: Lbl | None = None
|
|
730
|
+
lbl2: Lbl | None = None
|
|
731
|
+
lbl3: Lbl | None = None
|
|
732
|
+
lbl4: Lbl | None = None
|
|
733
|
+
event_marker_a: EventMarker | None = None
|
|
734
|
+
event_marker_b: EventMarker | None = None
|
|
735
|
+
event_marker_c: EventMarker | None = None
|
|
736
|
+
dvl_ground_speed2: DvlGroundSpeed | None = None
|
|
737
|
+
dvl_water_speed2: DvlWaterSpeed | None = None
|
|
738
|
+
turret_angles: TurretAngles | None = None
|
|
739
|
+
vtg1: Vtg | None = None
|
|
740
|
+
vtg2: Vtg | None = None
|
|
741
|
+
log_book: LogBook | None = None
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: pyixblue
|
|
3
|
+
Version: 0.9.1
|
|
4
|
+
Summary: Python parser for the iXblue stdbin protocol
|
|
5
|
+
License: MIT License
|
|
6
|
+
|
|
7
|
+
Copyright (c) 2026 pyixblue contributors
|
|
8
|
+
|
|
9
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
10
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
11
|
+
in the Software without restriction, including without limitation the rights
|
|
12
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
13
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
14
|
+
furnished to do so, subject to the following conditions:
|
|
15
|
+
|
|
16
|
+
The above copyright notice and this permission notice shall be included in all
|
|
17
|
+
copies or substantial portions of the Software.
|
|
18
|
+
|
|
19
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
20
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
21
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
22
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
23
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
24
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
25
|
+
SOFTWARE.
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Requires-Python: >=3.11
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# pyixblue
|
|
33
|
+
|
|
34
|
+
Python parser for the iXblue stdbin output protocol (versions 2 through 5).
|
|
35
|
+
|
|
36
|
+
stdbin is the binary navigation output of iXblue inertial systems. This package
|
|
37
|
+
reassembles and decodes those frames. It does not open a socket or a serial
|
|
38
|
+
port; pass in the bytes you receive over UDP, TCP, or serial.
|
|
39
|
+
|
|
40
|
+
The block layout and checksum follow the MIT-licensed
|
|
41
|
+
[ixblue_stdbin_decoder](https://github.com/ixblue/ixblue_stdbin_decoder).
|
|
42
|
+
|
|
43
|
+
## Install
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
pip install .
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
From a checkout, with the development tests:
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
uv sync
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Usage
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
from pyixblue import StdBinDecoder
|
|
59
|
+
|
|
60
|
+
decoder = StdBinDecoder()
|
|
61
|
+
decoder.add_new_data(chunk) # bytes-like; a chunk may be a partial frame
|
|
62
|
+
while decoder.parse_next_frame(): # False when more bytes are needed
|
|
63
|
+
header = decoder.last_header
|
|
64
|
+
if header.message_type == "nav":
|
|
65
|
+
nav = decoder.last_nav # missing blocks are None
|
|
66
|
+
if nav.position is not None:
|
|
67
|
+
print(nav.position.latitude_deg, nav.position.longitude_deg)
|
|
68
|
+
else:
|
|
69
|
+
payload = decoder.last_answer
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`parse_next_frame` raises `StdBinError` when a frame is recognized but its
|
|
73
|
+
protocol version or checksum is invalid. The decoder drops the bad version
|
|
74
|
+
byte, or the whole frame after a bad checksum, so the next call can resync.
|
|
75
|
+
|
|
76
|
+
Navigation, extended-navigation, and external-sensor blocks are present only
|
|
77
|
+
when their mask bit is set. INS algorithm and system status words are `IntFlag`
|
|
78
|
+
values (`1 << bit`), so they compare equal to the raw integer:
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
from pyixblue.models import InsAlgorithmStatus1
|
|
82
|
+
|
|
83
|
+
if nav.ins_algorithm_status is not None:
|
|
84
|
+
status = nav.ins_algorithm_status.status1
|
|
85
|
+
if InsAlgorithmStatus1.NAVIGATION in status:
|
|
86
|
+
print(hex(status))
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Protocol
|
|
90
|
+
|
|
91
|
+
Supported output frames:
|
|
92
|
+
|
|
93
|
+
| Kind | Magic | Versions |
|
|
94
|
+
| --- | --- | --- |
|
|
95
|
+
| Navigation | `IX` | 2, 3, 4, 5 |
|
|
96
|
+
| Answer | `AN` | 3, 4, 5 |
|
|
97
|
+
|
|
98
|
+
Multi-byte fields are big-endian. The checksum is the low 32 bits of the sum of
|
|
99
|
+
every preceding byte, stored as a big-endian `uint32`.
|
|
100
|
+
|
|
101
|
+
Version 3 adds the extended-navigation mask. Versions 4 and 5 add the
|
|
102
|
+
navigation-data size. The header also keeps the frame `counter`, which the
|
|
103
|
+
reference C++ header discards.
|
|
104
|
+
|
|
105
|
+
## License
|
|
106
|
+
|
|
107
|
+
MIT. Sample frames under `tests/fixtures/` were converted from the
|
|
108
|
+
ixblue_stdbin_decoder test logs (MIT, Copyright (c) 2019 iXblue SAS).
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
pyixblue/__init__.py,sha256=KpU2LKmNtJDEq0Yotng20qhTaD1UkwaaAEVFGk5fgLg,329
|
|
2
|
+
pyixblue/blocks.py,sha256=WG3UBTIcN2FLKFMPxBftypEONM312A1J16NhHEosKPU,8376
|
|
3
|
+
pyixblue/decoder.py,sha256=-CfLxC8pv12XMBwYi1FcEMNBzgv0rRW7z7bVbv-8Akg,7225
|
|
4
|
+
pyixblue/errors.py,sha256=pdlekg_twMl_yixDsNEYhmusT2lUf8iHs23awiTit7E,146
|
|
5
|
+
pyixblue/models.py,sha256=EcEYWIHNQGvCrEkt_2WV6KDSnyfDQdRNwLTaParLl90,20038
|
|
6
|
+
pyixblue-0.9.1.dist-info/METADATA,sha256=Sy7xjajJJ7ZTqpCmv3Nbov_qciCg0pqSEvRGIdogQdQ,3845
|
|
7
|
+
pyixblue-0.9.1.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
|
|
8
|
+
pyixblue-0.9.1.dist-info/licenses/LICENSE,sha256=pU1irPeAPFXmO_KBMP6ZHVLQESMaGczfZuAAy-GLp54,1078
|
|
9
|
+
pyixblue-0.9.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 pyixblue contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|