pyfastnet 2.0.16__tar.gz → 2.0.17__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyfastnet-2.0.16/pyfastnet.egg-info → pyfastnet-2.0.17}/PKG-INFO +1 -1
- {pyfastnet-2.0.16 → pyfastnet-2.0.17}/fastnet_decoder/__init__.py +1 -1
- {pyfastnet-2.0.16 → pyfastnet-2.0.17}/fastnet_decoder/decode_fastnet.py +76 -0
- {pyfastnet-2.0.16 → pyfastnet-2.0.17}/fastnet_decoder/frame_buffer.py +17 -4
- {pyfastnet-2.0.16 → pyfastnet-2.0.17/pyfastnet.egg-info}/PKG-INFO +1 -1
- {pyfastnet-2.0.16 → pyfastnet-2.0.17}/LICENSE +0 -0
- {pyfastnet-2.0.16 → pyfastnet-2.0.17}/README.md +0 -0
- {pyfastnet-2.0.16 → pyfastnet-2.0.17}/fastnet_decoder/logger.py +0 -0
- {pyfastnet-2.0.16 → pyfastnet-2.0.17}/fastnet_decoder/mappings.py +0 -0
- {pyfastnet-2.0.16 → pyfastnet-2.0.17}/fastnet_decoder/utils.py +0 -0
- {pyfastnet-2.0.16 → pyfastnet-2.0.17}/pyfastnet.egg-info/SOURCES.txt +0 -0
- {pyfastnet-2.0.16 → pyfastnet-2.0.17}/pyfastnet.egg-info/dependency_links.txt +0 -0
- {pyfastnet-2.0.16 → pyfastnet-2.0.17}/pyfastnet.egg-info/requires.txt +0 -0
- {pyfastnet-2.0.16 → pyfastnet-2.0.17}/pyfastnet.egg-info/top_level.txt +0 -0
- {pyfastnet-2.0.16 → pyfastnet-2.0.17}/pyproject.toml +0 -0
- {pyfastnet-2.0.16 → pyfastnet-2.0.17}/setup.cfg +0 -0
- {pyfastnet-2.0.16 → pyfastnet-2.0.17}/tests/test_autopilot.py +0 -0
- {pyfastnet-2.0.16 → pyfastnet-2.0.17}/tests/test_awa.py +0 -0
- {pyfastnet-2.0.16 → pyfastnet-2.0.17}/tests/test_aws.py +0 -0
- {pyfastnet-2.0.16 → pyfastnet-2.0.17}/tests/test_battery.py +0 -0
- {pyfastnet-2.0.16 → pyfastnet-2.0.17}/tests/test_boatspeed.py +0 -0
- {pyfastnet-2.0.16 → pyfastnet-2.0.17}/tests/test_depth_frame.py +0 -0
- {pyfastnet-2.0.16 → pyfastnet-2.0.17}/tests/test_format07_msb_regression.py +0 -0
- {pyfastnet-2.0.16 → pyfastnet-2.0.17}/tests/test_format08_layout.py +0 -0
- {pyfastnet-2.0.16 → pyfastnet-2.0.17}/tests/test_heading.py +0 -0
- {pyfastnet-2.0.16 → pyfastnet-2.0.17}/tests/test_heel_trim.py +0 -0
- {pyfastnet-2.0.16 → pyfastnet-2.0.17}/tests/test_latlon_frame.py +0 -0
- {pyfastnet-2.0.16 → pyfastnet-2.0.17}/tests/test_sea_temp.py +0 -0
- {pyfastnet-2.0.16 → pyfastnet-2.0.17}/tests/test_sog.py +0 -0
- {pyfastnet-2.0.16 → pyfastnet-2.0.17}/tests/test_tidal.py +0 -0
- {pyfastnet-2.0.16 → pyfastnet-2.0.17}/tests/test_timer.py +0 -0
- {pyfastnet-2.0.16 → pyfastnet-2.0.17}/tests/test_twa.py +0 -0
- {pyfastnet-2.0.16 → pyfastnet-2.0.17}/tests/test_twd.py +0 -0
- {pyfastnet-2.0.16 → pyfastnet-2.0.17}/tests/test_tws.py +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import datetime
|
|
2
|
+
import logging
|
|
2
3
|
from .mappings import ADDRESS_LOOKUP, COMMAND_LOOKUP, CHANNEL_LOOKUP, FORMAT_SIZE_MAP
|
|
3
4
|
from .mappings import SEGMENT_A, SEGMENT_B, AUTOPILOT_MODES
|
|
4
5
|
from .logger import logger
|
|
@@ -109,6 +110,81 @@ def decode_frame(frame: bytes) -> dict:
|
|
|
109
110
|
return {"error": "Decoding failure"}
|
|
110
111
|
|
|
111
112
|
|
|
113
|
+
def probe_frame(frame: bytes) -> None:
|
|
114
|
+
"""
|
|
115
|
+
Speculatively unpack a non-broadcast frame using the same channel-record
|
|
116
|
+
assembly as Broadcast frames, for reverse-engineering only.
|
|
117
|
+
|
|
118
|
+
The body layout of non-broadcast commands (pilot messages, NMEA-sourced
|
|
119
|
+
data, etc.) is NOT known to match the Broadcast channel-record format, so
|
|
120
|
+
everything decoded here is a guess: results are logged at DEBUG and are
|
|
121
|
+
never queued or assigned to channels. The point is to eyeball whether the
|
|
122
|
+
same (channel_id, format_byte, data...) structure holds for these frames.
|
|
123
|
+
"""
|
|
124
|
+
if not logger.isEnabledFor(logging.DEBUG):
|
|
125
|
+
return
|
|
126
|
+
try:
|
|
127
|
+
to_address = frame[0]
|
|
128
|
+
from_address = frame[1]
|
|
129
|
+
command = frame[3]
|
|
130
|
+
body = frame[5:-1]
|
|
131
|
+
|
|
132
|
+
to_name = ADDRESS_LOOKUP.get(to_address, f"Unknown (0x{to_address:02X})")
|
|
133
|
+
from_name = ADDRESS_LOOKUP.get(from_address, f"Unknown (0x{from_address:02X})")
|
|
134
|
+
cmd_name = COMMAND_LOOKUP.get(command, f"Unknown (0x{command:02X})")
|
|
135
|
+
|
|
136
|
+
logger.debug(
|
|
137
|
+
f" PROBE cmd={cmd_name} {to_name}←{from_name} body=[{body.hex()}]"
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
index = 0
|
|
141
|
+
while index < len(body):
|
|
142
|
+
if index + 1 >= len(body):
|
|
143
|
+
logger.debug(f" PROBE trailing byte [{body[index:].hex()}]")
|
|
144
|
+
break
|
|
145
|
+
|
|
146
|
+
channel_id = body[index]
|
|
147
|
+
format_byte = body[index + 1]
|
|
148
|
+
channel_name = CHANNEL_LOOKUP.get(channel_id, f"Unknown (0x{channel_id:02X})")
|
|
149
|
+
index += 2
|
|
150
|
+
|
|
151
|
+
data_length = FORMAT_SIZE_MAP.get(format_byte & 0x0F, 0)
|
|
152
|
+
if data_length == 0:
|
|
153
|
+
logger.debug(
|
|
154
|
+
f" PROBE 0x{channel_id:02X} {channel_name} "
|
|
155
|
+
f"fmt=0x{format_byte:02X} unknown format, stop "
|
|
156
|
+
f"remaining=[{body[index:].hex()}]"
|
|
157
|
+
)
|
|
158
|
+
break
|
|
159
|
+
if index + data_length > len(body):
|
|
160
|
+
logger.debug(
|
|
161
|
+
f" PROBE 0x{channel_id:02X} {channel_name} "
|
|
162
|
+
f"fmt=0x{format_byte:02X} incomplete need={data_length}B "
|
|
163
|
+
f"have={len(body) - index}B remaining=[{body[index:].hex()}]"
|
|
164
|
+
)
|
|
165
|
+
break
|
|
166
|
+
|
|
167
|
+
data_bytes = body[index:index + data_length]
|
|
168
|
+
index += data_length
|
|
169
|
+
|
|
170
|
+
decoded = decode_format_and_data(channel_id, format_byte, data_bytes)
|
|
171
|
+
if decoded:
|
|
172
|
+
logger.debug(
|
|
173
|
+
f" PROBE 0x{channel_id:02X} {channel_name} "
|
|
174
|
+
f"fmt=0x{format_byte:02X} data=[{data_bytes.hex()}] "
|
|
175
|
+
f"value={decoded['value']} display='{decoded['display_text']}' "
|
|
176
|
+
f"layout={decoded['layout']}"
|
|
177
|
+
)
|
|
178
|
+
else:
|
|
179
|
+
logger.debug(
|
|
180
|
+
f" PROBE 0x{channel_id:02X} {channel_name} "
|
|
181
|
+
f"fmt=0x{format_byte:02X} data=[{data_bytes.hex()}] (no decode)"
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
except Exception as e:
|
|
185
|
+
logger.debug(f" PROBE error: {e} [{frame.hex()}]")
|
|
186
|
+
|
|
187
|
+
|
|
112
188
|
def decode_ascii_frame(frame: bytes) -> dict:
|
|
113
189
|
try:
|
|
114
190
|
to_address = frame[0]
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from .utils import calculate_checksum
|
|
3
3
|
from .mappings import COMMAND_LOOKUP, IGNORED_COMMANDS
|
|
4
|
-
from .decode_fastnet import decode_frame, decode_ascii_frame
|
|
4
|
+
from .decode_fastnet import decode_frame, decode_ascii_frame, probe_frame
|
|
5
5
|
from .logger import logger
|
|
6
6
|
from queue import Queue, Full
|
|
7
7
|
|
|
@@ -84,9 +84,22 @@ class FrameBuffer:
|
|
|
84
84
|
self.decode_and_queue_frame(frame, command_name)
|
|
85
85
|
|
|
86
86
|
def decode_and_queue_frame(self, frame, command_name):
|
|
87
|
-
"""
|
|
88
|
-
|
|
89
|
-
|
|
87
|
+
"""
|
|
88
|
+
Decode a frame and add it to the queue if valid.
|
|
89
|
+
|
|
90
|
+
Only Broadcast frames carry the channel-record body layout, and LatLon
|
|
91
|
+
frames carry ASCII; those are decoded and queued. Every other command
|
|
92
|
+
(pilot messages, NMEA-sourced data, etc.) has an unknown body structure,
|
|
93
|
+
so it is never queued — it is only probed speculatively for debugging.
|
|
94
|
+
"""
|
|
95
|
+
if command_name == "Broadcast":
|
|
96
|
+
decoded_frame = decode_frame(frame)
|
|
97
|
+
elif command_name == "LatLon":
|
|
98
|
+
decoded_frame = decode_ascii_frame(frame)
|
|
99
|
+
else:
|
|
100
|
+
probe_frame(frame)
|
|
101
|
+
return
|
|
102
|
+
|
|
90
103
|
if decoded_frame and "values" in decoded_frame:
|
|
91
104
|
try:
|
|
92
105
|
self.frame_queue.put_nowait(decoded_frame)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|