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 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
@@ -0,0 +1,5 @@
1
+ """Errors raised while parsing stdbin frames."""
2
+
3
+
4
+ class StdBinError(Exception):
5
+ """A stdbin frame was recognized but could not be parsed."""
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,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.33.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -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.