pyfastnet 2.0.1__tar.gz → 2.0.2__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.
Files changed (27) hide show
  1. {pyfastnet-2.0.1/pyfastnet.egg-info → pyfastnet-2.0.2}/PKG-INFO +1 -1
  2. {pyfastnet-2.0.1 → pyfastnet-2.0.2}/fastnet_decoder/decode_fastnet.py +36 -41
  3. pyfastnet-2.0.2/fastnet_decoder/frame_buffer.py +103 -0
  4. {pyfastnet-2.0.1 → pyfastnet-2.0.2}/fastnet_decoder/logger.py +4 -10
  5. {pyfastnet-2.0.1 → pyfastnet-2.0.2/pyfastnet.egg-info}/PKG-INFO +1 -1
  6. {pyfastnet-2.0.1 → pyfastnet-2.0.2}/setup.py +1 -1
  7. pyfastnet-2.0.1/fastnet_decoder/frame_buffer.py +0 -198
  8. {pyfastnet-2.0.1 → pyfastnet-2.0.2}/LICENSE +0 -0
  9. {pyfastnet-2.0.1 → pyfastnet-2.0.2}/MANIFEST.in +0 -0
  10. {pyfastnet-2.0.1 → pyfastnet-2.0.2}/README.md +0 -0
  11. {pyfastnet-2.0.1 → pyfastnet-2.0.2}/fastnet_decoder/__init__.py +0 -0
  12. {pyfastnet-2.0.1 → pyfastnet-2.0.2}/fastnet_decoder/mappings.py +0 -0
  13. {pyfastnet-2.0.1 → pyfastnet-2.0.2}/fastnet_decoder/utils.py +0 -0
  14. {pyfastnet-2.0.1 → pyfastnet-2.0.2}/pyfastnet.egg-info/SOURCES.txt +0 -0
  15. {pyfastnet-2.0.1 → pyfastnet-2.0.2}/pyfastnet.egg-info/dependency_links.txt +0 -0
  16. {pyfastnet-2.0.1 → pyfastnet-2.0.2}/pyfastnet.egg-info/top_level.txt +0 -0
  17. {pyfastnet-2.0.1 → pyfastnet-2.0.2}/setup.cfg +0 -0
  18. {pyfastnet-2.0.1 → pyfastnet-2.0.2}/tests/test_apparent_frame.py +0 -0
  19. {pyfastnet-2.0.1 → pyfastnet-2.0.2}/tests/test_autopilot_frame.py +0 -0
  20. {pyfastnet-2.0.1 → pyfastnet-2.0.2}/tests/test_channels.py +0 -0
  21. {pyfastnet-2.0.1 → pyfastnet-2.0.2}/tests/test_depth_frame.py +0 -0
  22. {pyfastnet-2.0.1 → pyfastnet-2.0.2}/tests/test_format07_msb_regression.py +0 -0
  23. {pyfastnet-2.0.1 → pyfastnet-2.0.2}/tests/test_format08_layout.py +0 -0
  24. {pyfastnet-2.0.1 → pyfastnet-2.0.2}/tests/test_heel_frame.py +0 -0
  25. {pyfastnet-2.0.1 → pyfastnet-2.0.2}/tests/test_rudder_frame.py +0 -0
  26. {pyfastnet-2.0.1 → pyfastnet-2.0.2}/tests/test_tide_frame.py +0 -0
  27. {pyfastnet-2.0.1 → pyfastnet-2.0.2}/tests/test_true_frame.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyfastnet
3
- Version: 2.0.1
3
+ Version: 2.0.2
4
4
  Summary: A Python library for decoding FastNet protocol data streams.
5
5
  Home-page: https://github.com/ghotihook/pyfastnet
6
6
  Author: Alex Salmon
@@ -24,8 +24,6 @@ def _display_from_layout(layout: str, formatted: str) -> str:
24
24
 
25
25
  def decode_frame(frame: bytes) -> dict:
26
26
  try:
27
- logger.debug(f"Starting frame decoding. Frame length: {len(frame)}, Frame contents: {frame.hex()}")
28
-
29
27
  to_address = frame[0]
30
28
  from_address = frame[1]
31
29
  body_size = frame[2]
@@ -34,76 +32,75 @@ def decode_frame(frame: bytes) -> dict:
34
32
  body = frame[5:-1]
35
33
  body_checksum = frame[-1]
36
34
 
37
- logger.debug(
38
- f"Parsed header: to_address=0x{to_address:02X}, from_address=0x{from_address:02X}, "
39
- f"body_size={body_size}, command=0x{command:02X}, header_checksum=0x{header_checksum:02X}"
40
- )
41
-
42
35
  if calculate_checksum(frame[:4]) != header_checksum:
43
- logger.debug(f"Header checksum mismatch. Frame dropped: {frame.hex()}")
36
+ logger.debug(f"FRAME discard header-checksum [{frame.hex()[:16]}...]")
44
37
  return {"error": "Header checksum mismatch"}
45
38
 
46
39
  if calculate_checksum(body) != body_checksum:
47
- logger.debug(f"Body checksum mismatch. Frame dropped: {frame.hex()}")
40
+ logger.debug(f"FRAME discard body-checksum [{frame.hex()[:16]}...]")
48
41
  return {"error": "Body checksum mismatch"}
49
42
 
50
43
  if len(body) < 2 or len(body) != body_size:
51
- logger.debug(f"Invalid body size: Expected {body_size}, Actual {len(body)}. Frame: {frame.hex()}")
44
+ logger.debug(f"FRAME discard body-size expected={body_size} actual={len(body)}")
52
45
  return {"error": "Invalid body size"}
53
46
 
54
- logger.debug("Header and body checksums are valid.")
55
-
56
47
  decoded_data = {
57
- "to_address": ADDRESS_LOOKUP.get(to_address, f"Unknown (0x{to_address:02X})"),
48
+ "to_address": ADDRESS_LOOKUP.get(to_address, f"Unknown (0x{to_address:02X})"),
58
49
  "from_address": ADDRESS_LOOKUP.get(from_address, f"Unknown (0x{from_address:02X})"),
59
- "command": COMMAND_LOOKUP.get(command, f"Unknown (0x{command:02X})"),
60
- "values": {}
50
+ "command": COMMAND_LOOKUP.get(command, f"Unknown (0x{command:02X})"),
51
+ "values": {}
61
52
  }
62
53
 
63
54
  index = 0
64
55
  while index < len(body):
65
56
  if index + 1 >= len(body):
66
- logger.debug(
67
- f"Insufficient bytes to decode channel ID and format byte at index {index}. "
68
- f"Remaining length: {len(body) - index}"
69
- )
57
+ logger.debug(f" CH incomplete header at index={index}")
70
58
  return {"error": "Insufficient bytes for channel header"}
71
59
 
72
- channel_id = body[index]
73
- format_byte = body[index + 1]
74
- index += 2
60
+ channel_id = body[index]
61
+ format_byte = body[index + 1]
62
+ channel_name = CHANNEL_LOOKUP.get(channel_id, f"Unknown (0x{channel_id:02X})")
63
+ index += 2
75
64
 
76
65
  data_length = FORMAT_SIZE_MAP.get(format_byte & 0x0F, 0)
77
66
  if index + data_length > len(body):
78
67
  logger.debug(
79
- f"Incomplete data for channel 0x{channel_id:02X}. "
80
- f"Expected length: {data_length}, Available: {len(body) - index}"
68
+ f" CH 0x{channel_id:02X} {channel_name} "
69
+ f"incomplete need={data_length}B have={len(body) - index}B"
81
70
  )
82
71
  return {"error": f"Incomplete data for channel 0x{channel_id:02X}"}
83
72
 
84
73
  data_bytes = body[index:index + data_length]
85
74
  index += data_length
86
75
 
87
- decoded_value = decode_format_and_data(channel_id, format_byte, data_bytes)
88
- channel_name = CHANNEL_LOOKUP.get(channel_id, f"Unknown (0x{channel_id:02X})")
76
+ logger.debug(
77
+ f" CH 0x{channel_id:02X} {channel_name} "
78
+ f"fmt=0x{format_byte:02X} data=[{data_bytes.hex()}]"
79
+ )
89
80
 
81
+ decoded_value = decode_format_and_data(channel_id, format_byte, data_bytes)
90
82
  decoded_data["values"][channel_name] = decoded_value
91
- logger.debug(f"Decoded value for channel {channel_name}: {decoded_value}")
83
+
84
+ if decoded_value:
85
+ logger.debug(
86
+ f" value={decoded_value['value']} "
87
+ f"display='{decoded_value['display_text']}' "
88
+ f"layout={decoded_value['layout']}"
89
+ )
92
90
 
93
91
  return decoded_data
94
92
 
95
93
  except Exception as e:
96
- logger.error(f"Unexpected error during frame decoding: {e}. Frame contents: {frame.hex()}")
94
+ logger.error(f"Unexpected error decoding frame: {e} [{frame.hex()}]")
97
95
  return {"error": "Decoding failure"}
98
96
 
99
97
 
100
98
  def decode_ascii_frame(frame: bytes) -> dict:
101
99
  try:
102
- to_address = frame[0]
103
- from_address = frame[1]
104
- command = frame[3]
105
- header_checksum = frame[4]
106
- body = frame[5:-1]
100
+ to_address = frame[0]
101
+ from_address = frame[1]
102
+ command = frame[3]
103
+ body = frame[5:-1]
107
104
 
108
105
  channel_id = body[0]
109
106
  data_bytes = body[2:]
@@ -112,13 +109,15 @@ def decode_ascii_frame(frame: bytes) -> dict:
112
109
  try:
113
110
  ascii_text = data_bytes.decode("ascii").strip()
114
111
  except UnicodeDecodeError as e:
115
- logger.warning(f"Failed to decode ASCII text: {e}")
112
+ logger.warning(f" CH 0x{channel_id:02X} {channel_name} ASCII decode failed: {e}")
116
113
  return {"error": "ASCII decode failed"}
117
114
 
115
+ logger.debug(f" CH 0x{channel_id:02X} {channel_name} ascii='{ascii_text}'")
116
+
118
117
  return {
119
- "to_address": ADDRESS_LOOKUP.get(to_address, f"Unknown (0x{to_address:02X})"),
118
+ "to_address": ADDRESS_LOOKUP.get(to_address, f"Unknown (0x{to_address:02X})"),
120
119
  "from_address": ADDRESS_LOOKUP.get(from_address, f"Unknown (0x{from_address:02X})"),
121
- "command": COMMAND_LOOKUP.get(command, f"Unknown (0x{command:02X})"),
120
+ "command": COMMAND_LOOKUP.get(command, f"Unknown (0x{command:02X})"),
122
121
  "values": {
123
122
  channel_name: {
124
123
  "channel_id": f"0x{channel_id:02X}",
@@ -136,14 +135,11 @@ def decode_ascii_frame(frame: bytes) -> dict:
136
135
 
137
136
  def decode_format_and_data(channel_id, format_byte, data_bytes):
138
137
  try:
139
- logger.debug(f"Decoding channel ID: 0x{channel_id:02X}, format byte: 0x{format_byte:02X}, data: {data_bytes.hex()}")
140
-
141
138
  divisor = _DIVISOR_MAP[(format_byte >> 6) & 0b11]
142
139
  dp = _DP_MAP[divisor]
143
140
  format_bits = format_byte & 0b1111
144
141
 
145
142
  if len(data_bytes) == 0:
146
- logger.debug("decode_format_and_data: Empty data bytes; cannot decode.")
147
143
  return None
148
144
 
149
145
  layout = None
@@ -193,7 +189,6 @@ def decode_format_and_data(channel_id, format_byte, data_bytes):
193
189
  return None
194
190
  value = None
195
191
  display_text = "".join(SEGMENT_B.get(b, "?") for b in data_bytes)
196
- logger.debug(f"Decoded 7-segment text: {display_text}")
197
192
 
198
193
  elif format_bits == 0x07:
199
194
  if len(data_bytes) != 4:
@@ -222,7 +217,7 @@ def decode_format_and_data(channel_id, format_byte, data_bytes):
222
217
  display_text = f"{first:.{dp}f} / {second:.{dp}f}"
223
218
 
224
219
  else:
225
- logger.debug(f"Unsupported format: 0x{format_bits:02X}.")
220
+ logger.debug(f" unsupported format 0x{format_bits:02X}")
226
221
  return None
227
222
 
228
223
  return {
@@ -0,0 +1,103 @@
1
+ from .utils import calculate_checksum
2
+ from .mappings import COMMAND_LOOKUP, IGNORED_COMMANDS
3
+ from .decode_fastnet import decode_frame, decode_ascii_frame
4
+ from .logger import logger
5
+ from queue import Queue, Full
6
+
7
+
8
+ class FrameBuffer:
9
+ """
10
+ Manages an incoming byte stream, extracts valid FastNet frames,
11
+ decodes them, and queues the results.
12
+
13
+ Typical workflow:
14
+ 1. Feed raw bytes via add_to_buffer()
15
+ 2. Call get_complete_frames() to process the buffer
16
+ 3. Pull decoded frames from frame_queue
17
+ """
18
+
19
+ def __init__(self, max_buffer_size=8192, max_queue_size=1000):
20
+ self.buffer = bytearray()
21
+ self.max_buffer_size = max_buffer_size
22
+ self.frame_queue = Queue(maxsize=max_queue_size)
23
+
24
+ def add_to_buffer(self, new_data):
25
+ if not isinstance(new_data, (bytes, bytearray)):
26
+ logger.error("Invalid data type passed to add_to_buffer. Expected bytes or bytearray.")
27
+ return
28
+
29
+ self.buffer.extend(new_data)
30
+ logger.debug(f"BUF +{len(new_data)}B total={len(self.buffer)}B")
31
+
32
+ if len(self.buffer) > self.max_buffer_size:
33
+ logger.warning("Buffer size exceeded maximum limit. Trimming the oldest data.")
34
+ self.buffer = self.buffer[-self.max_buffer_size:]
35
+
36
+ def get_complete_frames(self):
37
+ """Extract, validate, and queue complete frames from the buffer."""
38
+ while len(self.buffer) >= 6:
39
+ to_address = self.buffer[0]
40
+ from_address = self.buffer[1]
41
+ body_size = self.buffer[2]
42
+ command = self.buffer[3]
43
+ header_checksum = self.buffer[4]
44
+
45
+ command_name = COMMAND_LOOKUP.get(command, f"Unknown (0x{command:02X})")
46
+ full_frame_length = 5 + body_size + 1
47
+
48
+ if len(self.buffer) < full_frame_length:
49
+ logger.debug(f"FRAME wait need={full_frame_length}B have={len(self.buffer)}B")
50
+ break
51
+
52
+ frame = self.buffer[:full_frame_length]
53
+ body = self.buffer[5:full_frame_length - 1]
54
+ body_checksum = self.buffer[full_frame_length - 1]
55
+ frame_hex = bytes(frame[:8]).hex()
56
+
57
+ if calculate_checksum(self.buffer[:4]) != header_checksum:
58
+ logger.debug(f"FRAME discard header-checksum [{frame_hex}...]")
59
+ self.buffer = self.buffer[1:]
60
+ continue
61
+
62
+ if calculate_checksum(body) != body_checksum:
63
+ logger.debug(f"FRAME discard body-checksum [{frame_hex}...]")
64
+ self.buffer = self.buffer[1:]
65
+ continue
66
+
67
+ self.buffer = self.buffer[full_frame_length:]
68
+
69
+ if command_name in IGNORED_COMMANDS:
70
+ logger.debug(f"FRAME skip cmd={command_name}")
71
+ continue
72
+
73
+ logger.debug(
74
+ f"FRAME cmd={command_name} "
75
+ f"0x{to_address:02X}←0x{from_address:02X} "
76
+ f"body={body_size}B [{frame_hex}...]"
77
+ )
78
+ self.decode_and_queue_frame(frame, command_name)
79
+
80
+ def decode_and_queue_frame(self, frame, command_name):
81
+ """Decode a frame and add it to the queue if valid."""
82
+ decoder = decode_ascii_frame if command_name == "LatLon" else decode_frame
83
+ decoded_frame = decoder(frame)
84
+ if decoded_frame and "values" in decoded_frame:
85
+ channel_names = list(decoded_frame["values"].keys())
86
+ names_str = ", ".join(channel_names[:4])
87
+ if len(channel_names) > 4:
88
+ names_str += f", +{len(channel_names) - 4} more"
89
+ try:
90
+ self.frame_queue.put_nowait(decoded_frame)
91
+ logger.debug(f"QUEUE {len(channel_names)} channel(s) [{names_str}]")
92
+ except Full:
93
+ logger.warning("Frame queue full, dropping frame.")
94
+ else:
95
+ logger.debug(f"QUEUE fail decode error [{frame.hex()[:16]}...]")
96
+
97
+ def get_buffer_size(self):
98
+ return len(self.buffer)
99
+
100
+ def get_buffer_contents(self):
101
+ hex_contents = self.buffer.hex()
102
+ logger.debug(f"BUF contents [{hex_contents}]")
103
+ return hex_contents
@@ -1,26 +1,20 @@
1
1
  import logging
2
2
 
3
- # Create a logger
4
- logger = logging.getLogger("fastnet_decoder")
3
+ logger = logging.getLogger("pyfastnet")
5
4
 
6
- # Default log level
7
5
  DEFAULT_LOG_LEVEL = logging.INFO
8
6
 
9
- # Avoid duplicate handlers
10
7
  if not logger.hasHandlers():
11
8
  handler = logging.StreamHandler()
12
- formatter = logging.Formatter("%(asctime)s [%(levelname)s] [%(module)s] %(message)s")
9
+ formatter = logging.Formatter("%(asctime)s [pyfastnet] %(levelname)-5s %(message)s")
13
10
  handler.setFormatter(formatter)
14
11
  logger.addHandler(handler)
15
12
 
16
13
  logger.setLevel(DEFAULT_LOG_LEVEL)
17
14
 
15
+
18
16
  def set_log_level(level_name: str):
19
- """
20
- Sets the log level dynamically at runtime.
21
- Args:
22
- level_name (str): Name of the log level (e.g., "DEBUG", "INFO", "WARNING").
23
- """
17
+ """Sets the log level dynamically at runtime."""
24
18
  level = getattr(logging, level_name.upper(), DEFAULT_LOG_LEVEL)
25
19
  logger.setLevel(level)
26
20
  logger.info(f"Log level set to {level_name.upper()}.")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyfastnet
3
- Version: 2.0.1
3
+ Version: 2.0.2
4
4
  Summary: A Python library for decoding FastNet protocol data streams.
5
5
  Home-page: https://github.com/ghotihook/pyfastnet
6
6
  Author: Alex Salmon
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name="pyfastnet",
5
- version="2.0.1", # Ensure this matches your intended version
5
+ version="2.0.2", # Ensure this matches your intended version
6
6
  author="Alex Salmon",
7
7
  author_email="alex@ivila.net",
8
8
  description="A Python library for decoding FastNet protocol data streams.",
@@ -1,198 +0,0 @@
1
- from .utils import calculate_checksum # Import checksum function from utils.py
2
- from .mappings import COMMAND_LOOKUP, IGNORED_COMMANDS
3
- from .decode_fastnet import decode_frame, decode_ascii_frame
4
- from .logger import logger
5
- from queue import Queue
6
-
7
-
8
- """
9
- FrameBuffer Class
10
- =================
11
-
12
- The `FrameBuffer` class is responsible for managing a stream of incoming data, validating and extracting complete
13
- frames, and decoding those frames using the FastNet protocol. It is designed to handle real-time data input, making
14
- it suitable for scenarios like serial communication with hardware devices.
15
-
16
- Core Responsibilities:
17
- -----------------------
18
- 1. **Buffer Management**:
19
- - The class maintains an internal `bytearray` buffer to store incoming raw data.
20
- - New data can be added to the buffer using the `add_to_buffer` method.
21
- - The buffer automatically trims its size to a configurable maximum (`max_buffer_size`) to avoid unbounded memory usage.
22
-
23
- 2. **Frame Extraction**:
24
- - The `get_complete_frames` method scans the buffer for complete frames based on the FastNet protocol structure:
25
- - A frame consists of a 5-byte header, a variable-length body, and a checksum.
26
- - Both the header and body checksums are validated before processing the frame.
27
- - Frames that are incomplete or fail checksum validation are skipped, and the buffer is adjusted to search for the next valid frame.
28
-
29
- 3. **Frame Decoding**:
30
- - After extracting a valid frame, the method determines the appropriate decoder (ASCII or standard) based on the command type.
31
- - Decoded frames are added to an internal queue (`frame_queue`) for further processing.
32
-
33
- 4. **Command Filtering**:
34
- - Certain command types, such as "Keep Alive" or "Light Intensity," can be ignored based on a configurable list of ignored commands
35
- (managed in the `mappings.py` file).
36
-
37
- Key Features:
38
- -------------
39
- - **Thread-Safe Queue**:
40
- The `frame_queue` is implemented using Python's `queue.Queue`, enabling safe concurrent access from multiple threads
41
- (if needed).
42
-
43
- - **Error Handling**:
44
- - The class is robust against malformed or corrupted data. Frames with invalid checksums are discarded without crashing the application.
45
- - Warnings and errors are logged for debugging purposes.
46
-
47
- - **Modularity**:
48
- - The decoding logic is separated from the frame extraction, adhering to the single-responsibility principle.
49
- - The list of ignored commands is managed in `mappings.py`, promoting modularity and ease of configuration.
50
-
51
- Typical Workflow:
52
- -----------------
53
- 1. Raw data (e.g., from a serial port) is fed into the buffer using the `add_to_buffer` method.
54
- 2. The `get_complete_frames` method scans the buffer for valid frames, processes them, and adds decoded frames to the `frame_queue`.
55
- 3. Another part of the application (e.g., a main loop) retrieves and processes frames from the queue for further action (e.g., broadcasting NMEA sentences).
56
-
57
- Configuration Parameters:
58
- -------------------------
59
- - `max_buffer_size`: Limits the size of the internal buffer. Older data is discarded when the buffer exceeds this size.
60
- - `max_queue_size`: Specifies the maximum number of frames that can be stored in the `frame_queue`.
61
-
62
- Use Cases:
63
- ----------
64
- The `FrameBuffer` class is ideal for:
65
- - Real-time data processing systems where incoming data must be validated and decoded before use.
66
- - Applications where reliability is critical, such as navigation systems or hardware communication protocols.
67
-
68
- Example:
69
- --------
70
- # Initialize the FrameBuffer
71
- frame_buffer = FrameBuffer(max_buffer_size=8192, max_queue_size=1000)
72
-
73
- # Add raw data to the buffer
74
- frame_buffer.add_to_buffer(new_data)
75
-
76
- # Extract and decode complete frames
77
- frame_buffer.get_complete_frames()
78
-
79
- # Process frames from the queue
80
- while not frame_buffer.frame_queue.empty():
81
- frame = frame_buffer.frame_queue.get()
82
- # Process the decoded frame
83
- """
84
-
85
-
86
- class FrameBuffer:
87
- """
88
- A class that manages an incoming data stream, extracts valid frames,
89
- and decodes them using the FastNet protocol.
90
- """
91
- def __init__(self, max_buffer_size=8192, max_queue_size=1000):
92
- self.buffer = bytearray()
93
- self.max_buffer_size = max_buffer_size
94
- self.frame_queue = Queue(maxsize=max_queue_size) # Shared instance for frames
95
-
96
- def add_to_buffer(self, new_data):
97
- """
98
- Adds new data to the buffer.
99
-
100
- Args:
101
- new_data (bytes): New data from the serial input.
102
- """
103
- if not isinstance(new_data, (bytes, bytearray)):
104
- logger.error("Invalid data type passed to add_to_buffer. Expected bytes or bytearray.")
105
- return
106
-
107
- self.buffer.extend(new_data)
108
- logger.debug(f"Added {len(new_data)} bytes to buffer. Buffer size: {len(self.buffer)} bytes.")
109
-
110
- # Prevent the buffer from growing indefinitely
111
- if len(self.buffer) > self.max_buffer_size:
112
- logger.warning("Buffer size exceeded maximum limit. Trimming the oldest data.")
113
- self.buffer = self.buffer[-self.max_buffer_size:] # Keep the latest data only
114
-
115
-
116
- def get_complete_frames(self):
117
- """
118
- Extract and validate complete frames from the buffer, then add them to the internal queue.
119
- """
120
- while len(self.buffer) >= 6: # Minimum frame size (5 header + 1 body checksum)
121
- to_address = self.buffer[0]
122
- from_address = self.buffer[1]
123
- body_size = self.buffer[2]
124
- command = self.buffer[3]
125
- header_checksum = self.buffer[4]
126
-
127
- # Identify command name from lookup
128
- command_name = COMMAND_LOOKUP.get(command, f"Unknown (0x{command:02X})")
129
-
130
- # Calculate full frame length
131
- full_frame_length = 5 + body_size + 1 # Header (5 bytes) + body + body checksum
132
- if len(self.buffer) < full_frame_length:
133
- logger.debug(f"Incomplete frame: waiting for more bytes (needed {full_frame_length}, got {len(self.buffer)})")
134
- break
135
-
136
- # Extract frame data
137
- frame = self.buffer[:full_frame_length]
138
- body = self.buffer[5:full_frame_length - 1]
139
- body_checksum = self.buffer[full_frame_length - 1]
140
-
141
- # Verify header and body checksums
142
- if calculate_checksum(self.buffer[:4]) != header_checksum:
143
- logger.debug("Header checksum mismatch. Dropping first byte.")
144
- self.buffer = self.buffer[1:]
145
- continue
146
-
147
- if calculate_checksum(body) != body_checksum:
148
- logger.debug("Body checksum mismatch. Dropping first byte.")
149
- self.buffer = self.buffer[1:]
150
- continue
151
-
152
- # Remove frame from buffer after validation
153
- self.buffer = self.buffer[full_frame_length:]
154
-
155
- # Skip ignored commands
156
- if command_name in IGNORED_COMMANDS:
157
- logger.debug(f"Skipping ignored command: {command_name}")
158
- continue
159
-
160
- # Decode the frame
161
- self.decode_and_queue_frame(frame, command_name)
162
-
163
-
164
- def decode_and_queue_frame(self, frame, command_name):
165
- """Decode a frame and add it to the queue if valid."""
166
- decoder = decode_ascii_frame if command_name == "LatLon" else decode_frame
167
- decoded_frame = decoder(frame)
168
- if decoded_frame:
169
- try:
170
- self.frame_queue.put_nowait(decoded_frame)
171
- logger.debug(f"Added frame to queue: {decoded_frame}")
172
- except queue.Full:
173
- logger.warning("Frame queue is full. Dropping frame.")
174
- else:
175
- logger.debug(f"Failed to decode frame: {frame.hex()}")
176
-
177
-
178
-
179
- def get_buffer_size(self):
180
- """
181
- Returns the current size of the buffer.
182
-
183
- Returns:
184
- int: The number of bytes currently in the buffer.
185
- """
186
- return len(self.buffer)
187
-
188
-
189
- def get_buffer_contents(self):
190
- """
191
- Returns the contents of the buffer as a hex string.
192
-
193
- Returns:
194
- str: The hexadecimal representation of the buffer contents.
195
- """
196
- hex_contents = self.buffer.hex()
197
- logger.debug(f"Buffer contents: {hex_contents}")
198
- return hex_contents
File without changes
File without changes
File without changes
File without changes