Telemetry is wokring now on how to deocde it

main
Kynsight 4 weeks ago
parent ac173bf6f3
commit 70eb58531f

@ -17,6 +17,26 @@ from .circular_buffer import (
)
def format_bytes(b: int) -> str:
"""
Format byte count as human-readable string.
Args:
b: Byte count
Returns:
Formatted string (e.g., "120.5KB", "400.0MB", "1.2GB")
"""
if b >= 1024**3: # GB
return f"{b / (1024**3):.1f}GB"
elif b >= 1024**2: # MB
return f"{b / (1024**2):.1f}MB"
elif b >= 1024: # KB
return f"{b / 1024:.1f}KB"
else:
return f"{b}B"
class CompactBufferWidget(QWidget):
"""
Compact one-line buffer display.
@ -68,8 +88,8 @@ class CompactBufferWidget(QWidget):
pct = cb_fill_pct(self.buffer)
ovf = cb_overflows(self.buffer)
# Update labels
self.lbl_size.setText(f"{fill}/{cap}B")
# Update labels with human-readable format
self.lbl_size.setText(f"{format_bytes(fill)}/{format_bytes(cap)}")
self.progress.setValue(pct)
# Overflow label - just show count (no color logic)
@ -147,8 +167,8 @@ class CompactBufferWidgetWithCallback(QWidget):
pct = cb_fill_pct(self.buffer)
ovf = cb_overflows(self.buffer)
# Update UI
self.lbl_size.setText(f"{fill}/{cap}B")
# Update UI with human-readable format
self.lbl_size.setText(f"{format_bytes(fill)}/{format_bytes(cap)}")
self.progress.setValue(pct)
# Overflow label - just show count

Binary file not shown.

317
run.py

@ -27,9 +27,11 @@ from uart.uart_kit.uart_core import (
PacketConfig,
PacketInfo,
uart_write,
uart_send_and_read_pgkomm2,
uart_start_listening_with_packets,
uart_stop_listening,
uart_get_detected_packets,
uart_get_packet_errors,
uart_clear_detected_packets,
uart_read_buffer,
Status as UARTStatus
@ -42,6 +44,9 @@ from i2c.i2c_kit.i2c_core import (
Status as I2CStatus
)
# Import buffer utilities
from buffer_kit.circular_buffer import cb_fill_bytes, cb_capacity
# Import decoder
from decoder import decode_uart_packet, decode_i2c_sample
@ -81,7 +86,10 @@ class RunExecutor:
uart_logger_port: Optional[UARTPort],
i2c_port: Optional[I2CHandle],
packet_config: PacketConfig,
i2c_address: int = 0x40,
i2c_register: int = 0xFE,
stop_timeout_ms: int = 5000,
grace_timeout_ms: int = 1500,
raw_data_callback = None
) -> Tuple[str, int, str]:
"""
@ -120,20 +128,33 @@ class RunExecutor:
# ================================================================
if uart_logger_port and packet_config.enable:
# Debug: Check if I2C is available
if raw_data_callback:
if i2c_port:
raw_data_callback("INFO", f"I2C enabled: will trigger reads on packet detection")
else:
raw_data_callback("INFO", f"I2C disabled: no I2C port available")
# Create callback for I2C triggering
callback_count = [0] # Use list for mutable counter in nested function
def on_uart_packet_detected(timestamp_ns: int):
"""
Called immediately when UART packet detected.
Triggers I2C read for timestamp correlation.
"""
callback_count[0] += 1
# Debug first few callbacks
if callback_count[0] <= 3:
print(f"[DEBUG] I2C callback triggered #{callback_count[0]} at {timestamp_ns}")
if i2c_port:
# Read I2C angle immediately
# Note: i2c_read_block requires (handle, addr, reg, length)
# But we're using the handle's default address
status, i2c_bytes = i2c_read_block(
i2c_port,
i2c_port.config.address, # Use configured address
0xFE, # Angle register
i2c_address, # Device address from session config
i2c_register, # Register address from session config
2 # Read 2 bytes
)
@ -146,6 +167,8 @@ class RunExecutor:
else:
# I2C read failed - count the failure
self.i2c_failures += 1
if callback_count[0] <= 3:
print(f"[DEBUG] I2C read failed: {status}")
# Create packet config with callback
packet_config_with_callback = PacketConfig(
@ -156,6 +179,11 @@ class RunExecutor:
on_packet_callback=on_uart_packet_detected if i2c_port else None
)
# Debug: Verify callback is attached
if raw_data_callback:
has_callback = packet_config_with_callback.on_packet_callback is not None
raw_data_callback("INFO", f"Packet config: callback={'attached' if has_callback else 'None'}")
# Start listening with packet detection on LOGGER PORT
status = uart_start_listening_with_packets(uart_logger_port, packet_config_with_callback)
@ -163,7 +191,7 @@ class RunExecutor:
return ("error", 0, "Failed to start UART packet detection")
# ================================================================
# 2. Send UART command (COMMAND PORT)
# 2. Send UART command (COMMAND PORT) - Using PGKomm2
# ================================================================
# Parse hex string to bytes
@ -173,89 +201,180 @@ class RunExecutor:
uart_stop_listening(uart_logger_port)
return ("error", 0, f"Invalid command hex string: {command_hex}")
# Send command via COMMAND PORT
status, written = uart_write(uart_command_port, command_bytes)
# Emit TX data (command to be sent)
if raw_data_callback:
hex_tx = ' '.join(f'{b:02X}' for b in command_bytes)
# Add ASCII (skip DD 22 magic bytes)
ascii_data = command_bytes[2:] if len(command_bytes) >= 2 else command_bytes
ascii_tx = ''.join(chr(b) if 32 <= b <= 126 else '.' for b in ascii_data)
raw_data_callback("TX", f"{hex_tx} | '{ascii_tx}'")
# Send command via PGKomm2 (always use this mode for sessions)
status, frames = uart_send_and_read_pgkomm2(
uart_command_port,
command_bytes,
capture_max_ms=30, # Default PGKomm2 timeout
log_callback=raw_data_callback # Pass callback for logging
)
if status != UARTStatus.OK:
if uart_logger_port:
uart_stop_listening(uart_logger_port)
return ("error", 0, "Failed to send UART command")
# Emit TX data (command sent)
if raw_data_callback:
hex_tx = ' '.join(f'{b:02X}' for b in command_bytes)
raw_data_callback("TX", hex_tx)
return ("error", 0, f"PGKomm2 command failed: {status}")
# Emit RX data (frames received) - only show Echo and Response, skip SB broadcasts
if raw_data_callback and frames:
for frame in frames:
if len(frame) >= 5:
adr1, adr2 = frame[2], frame[3]
# Skip SB status broadcasts (background noise from device)
if adr1 == 0x53 and adr2 == 0x42: # SB
continue
hex_rx = ' '.join(f'{b:02X}' for b in frame)
# Add ASCII (skip DD 22 magic bytes)
ascii_data = frame[2:] if len(frame) >= 2 else frame
ascii_rx = ''.join(chr(b) if 32 <= b <= 126 else '.' for b in ascii_data)
if adr1 == 0x50 and adr2 == 0x48: # PH echo
raw_data_callback("RX", f"{hex_rx} (Echo) | '{ascii_rx}'")
elif adr1 == 0x48 and adr2 == 0x50: # HP response
raw_data_callback("RX", f"{hex_rx} (Response) | '{ascii_rx}'")
else:
raw_data_callback("RX", f"{hex_rx} | '{ascii_rx}'")
else:
# Unknown frame format
hex_rx = ' '.join(f'{b:02X}' for b in frame)
ascii_data = frame[2:] if len(frame) >= 2 else frame
ascii_rx = ''.join(chr(b) if 32 <= b <= 126 else '.' for b in ascii_data)
raw_data_callback("RX", f"{hex_rx} | '{ascii_rx}'")
# ================================================================
# 3. Wait for stop condition
# 3. Wait for logger packets (polling mode with stop condition)
# ================================================================
# Wait for timeout
time.sleep(stop_timeout_ms / 1000.0)
uart_packets = []
# ================================================================
# 3.5. Handle raw data if packet detection disabled
# ================================================================
if uart_logger_port and packet_config.enable:
# Polling mode: wait for packets with grace period and timeout
# Use defaults if None from database
grace_ms = grace_timeout_ms if grace_timeout_ms is not None else 1500
stop_ms = stop_timeout_ms if stop_timeout_ms is not None else 150
grace_timeout_s = grace_ms / 1000.0 # Wait for first packet
stop_timeout_s = stop_ms / 1000.0 # Silence between packets
if not packet_config.enable:
# No packet detection - read raw buffer from COMMAND PORT (ACK/response)
status_read, raw_data = uart_read_buffer(uart_command_port)
last_packet_count = 0
last_packet_time = 0.0
start_time = time.time()
first_packet_received = False
if status_read == UARTStatus.OK and raw_data:
# Emit RX data
if raw_data_callback:
hex_rx = ' '.join(f'{b:02X}' for b in raw_data)
raw_data_callback("RX", hex_rx)
raw_data_callback("INFO", f"Waiting for logger packets (grace: {grace_timeout_s*1000:.0f}ms, timeout: {stop_timeout_s*1000:.0f}ms)...")
# Polling loop
while True:
time.sleep(0.05) # Poll every 50ms
current_time = time.time()
# Get current packet count
current_packets = uart_get_detected_packets(uart_logger_port)
current_count = len(current_packets)
# Check if new packets arrived
if current_count > last_packet_count:
last_packet_count = current_count
last_packet_time = current_time
if not first_packet_received:
first_packet_received = True
if raw_data_callback:
raw_data_callback("INFO", f"First logger packet received, monitoring for stop condition...")
# Stop listening on logger port (if active)
if uart_logger_port:
# Grace period check (only if no packets yet)
if not first_packet_received:
elapsed = current_time - start_time
if elapsed >= grace_timeout_s:
# Grace period expired, no packets
uart_stop_listening(uart_logger_port)
return ("error", 0, f"Logger not responding (grace timeout: {grace_timeout_s*1000:.0f}ms)")
# ================================================================
# 4. Get detected packets (from LOGGER PORT if exists)
# ================================================================
# Stop timeout check (only after first packet received)
if first_packet_received:
silence = current_time - last_packet_time
if silence >= stop_timeout_s:
# Stop condition met!
if raw_data_callback:
# Report buffer status
if uart_logger_port._rx_buffer:
fill = cb_fill_bytes(uart_logger_port._rx_buffer)
cap = cb_capacity(uart_logger_port._rx_buffer)
fill_mb = fill / (1024 * 1024)
cap_mb = cap / (1024 * 1024)
raw_data_callback("INFO", f"Buffer: {fill_mb:.2f}MB / {cap_mb:.1f}MB")
# Report packet statistics
packet_errors = uart_get_packet_errors(uart_logger_port)
if packet_errors > 0:
raw_data_callback("ERROR", f"⚠ Packet errors: {packet_errors} packets with end marker mismatch")
raw_data_callback("INFO", f"✓ Valid packets: {current_count}")
raw_data_callback("INFO", f"Stop condition: {stop_timeout_s*1000:.0f}ms silence detected")
# Stop listening (but keep port open for next command)
uart_stop_listening(uart_logger_port)
uart_packets = current_packets
break
uart_packets = []
if uart_logger_port:
uart_packets = uart_get_detected_packets(uart_logger_port)
else:
uart_packets = []
packet_count = len(uart_packets)
if packet_count == 0 and packet_config.enable:
# Only error if packet detection was enabled
return ("error", 0, "No packets detected (timeout or no data)")
elif uart_logger_port:
# Logger enabled but packet detection disabled - just stop listening
uart_stop_listening(uart_logger_port)
packet_count = 0
else:
# No logger port
packet_count = 0
# ================================================================
# 5. Decode and save data
# 4. Decode and save data
# ================================================================
# Decode and save UART packets
if raw_data_callback:
i2c_count = len(self.i2c_readings)
if i2c_count == 0 and i2c_port and packet_config.enable:
# Expected I2C but got none - report
raw_data_callback("ERROR", f"⚠ No I2C readings captured (expected ~{packet_count})")
if self.i2c_failures > 0:
raw_data_callback("ERROR", f"I2C failures: {self.i2c_failures}")
raw_data_callback("INFO", f"Decoding and saving {packet_count} UART packets + {i2c_count} I2C readings...")
# Create timestamp → I2C reading map for matching
i2c_by_timestamp = {}
for reading in self.i2c_readings:
i2c_by_timestamp[reading['timestamp_ns']] = reading['i2c_bytes']
# Decode and save UART packets WITH correlated I2C data
for pkt in uart_packets:
self._save_uart_telemetry(
session_id=session_id,
session_name=session_name,
run_no=run_no,
run_command_id=command_id,
packet_info=pkt,
run_start_ns=run_start_ns
)
# Look up matching I2C reading by timestamp
i2c_bytes = i2c_by_timestamp.get(pkt.start_timestamp, None)
# Decode and save I2C readings
for reading in self.i2c_readings:
self._save_i2c_telemetry(
self._save_combined_telemetry(
session_id=session_id,
session_name=session_name,
run_no=run_no,
run_command_id=command_id,
timestamp_ns=reading['timestamp_ns'],
i2c_bytes=reading['i2c_bytes'],
packet_info=pkt,
i2c_bytes=i2c_bytes,
run_start_ns=run_start_ns
)
# Commit database changes
self.db_conn.commit()
if raw_data_callback:
raw_data_callback("INFO", f"✓ Database saved: {packet_count} UART packets, {len(self.i2c_readings)} I2C readings")
# Report errors if any via callback
if self.i2c_failures > 0 and raw_data_callback:
raw_data_callback("ERROR", f"I2C read failures: {self.i2c_failures}")
@ -289,33 +408,36 @@ class RunExecutor:
except:
return None
def _save_uart_telemetry(
def _save_combined_telemetry(
self,
session_id: str,
session_name: str,
run_no: int,
run_command_id: int,
packet_info: PacketInfo,
i2c_bytes: Optional[bytes],
run_start_ns: int
):
"""
Save UART telemetry to database.
Save combined UART + I2C telemetry to database (single row).
Saves to both telemetry_raw and telemetry_decoded tables.
UART and I2C data are correlated by timestamp and saved together.
"""
# Decode packet
decoded = decode_uart_packet(packet_info.data)
# Decode packets
decoded_uart = decode_uart_packet(packet_info.data)
decoded_i2c = decode_i2c_sample(i2c_bytes) if i2c_bytes else None
# Calculate relative time from run start
time_ms = (packet_info.start_timestamp - run_start_ns) / 1_000_000.0
# Save to telemetry_raw (backup)
# Save to telemetry_raw (backup) - BOTH uart_raw_packet AND i2c_raw_bytes in ONE row
cursor = self.db_conn.cursor()
cursor.execute("""
INSERT INTO telemetry_raw (
session_id, session_name, run_no, run_command_id,
t_ns, time_ms, uart_raw_packet
) VALUES (?, ?, ?, ?, ?, ?, ?)
t_ns, time_ms, uart_raw_packet, i2c_raw_bytes
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
""", (
session_id,
session_name,
@ -323,11 +445,12 @@ class RunExecutor:
run_command_id,
packet_info.start_timestamp,
time_ms,
packet_info.data
packet_info.data,
i2c_bytes # Can be None if no I2C
))
# Save to telemetry_decoded (main data)
# For now, just save raw hex (decoder is pass-through)
# For now, just save timestamps (decoder is pass-through)
# TODO: Update when decoder is fully implemented
cursor.execute("""
INSERT INTO telemetry_decoded (
@ -344,65 +467,8 @@ class RunExecutor:
))
# TODO: When decoder is fully implemented, also save:
# motor_current, encoder_value, relative_encoder_value, v24_pec_diff, pwm
def _save_i2c_telemetry(
self,
session_id: str,
session_name: str,
run_no: int,
run_command_id: int,
timestamp_ns: int,
i2c_bytes: bytes,
run_start_ns: int
):
"""
Save I2C telemetry to database.
Saves to both telemetry_raw and telemetry_decoded tables.
"""
# Decode I2C sample
decoded = decode_i2c_sample(i2c_bytes)
# Calculate relative time from run start
time_ms = (timestamp_ns - run_start_ns) / 1_000_000.0
# Save to telemetry_raw (backup)
cursor = self.db_conn.cursor()
cursor.execute("""
INSERT INTO telemetry_raw (
session_id, session_name, run_no, run_command_id,
t_ns, time_ms, i2c_raw_bytes
) VALUES (?, ?, ?, ?, ?, ?, ?)
""", (
session_id,
session_name,
run_no,
run_command_id,
timestamp_ns,
time_ms,
i2c_bytes
))
# Save to telemetry_decoded (main data)
# For now, decoder is pass-through
# TODO: Update when decoder is fully implemented with angle conversion
cursor.execute("""
INSERT INTO telemetry_decoded (
session_id, session_name, run_no, run_command_id,
t_ns, time_ms
) VALUES (?, ?, ?, ?, ?, ?)
""", (
session_id,
session_name,
run_no,
run_command_id,
timestamp_ns,
time_ms
))
# TODO: When decoder is fully implemented, also save:
# i2c_raw14, i2c_angle_deg, i2c_zero_raw14, etc.
# UART: motor_current, encoder_value, relative_encoder_value, v24_pec_diff, pwm
# I2C: i2c_raw14, i2c_angle_deg, i2c_zero_raw14, etc.
# =============================================================================
@ -420,7 +486,10 @@ def execute_run(
uart_logger_port: Optional[UARTPort],
i2c_port: Optional[I2CHandle],
packet_config: PacketConfig,
i2c_address: int = 0x40,
i2c_register: int = 0xFE,
stop_timeout_ms: int = 5000,
grace_timeout_ms: int = 1500,
raw_data_callback = None
) -> Tuple[str, int, str]:
"""
@ -438,6 +507,7 @@ def execute_run(
i2c_port: I2C port (optional)
packet_config: Packet detection configuration
stop_timeout_ms: Stop condition timeout
grace_timeout_ms: Grace period before first packet
raw_data_callback: Callback for raw data display (direction, hex_string)
Returns:
@ -454,7 +524,10 @@ def execute_run(
uart_logger_port=uart_logger_port,
i2c_port=i2c_port,
packet_config=packet_config,
i2c_address=i2c_address,
i2c_register=i2c_register,
stop_timeout_ms=stop_timeout_ms,
grace_timeout_ms=grace_timeout_ms,
raw_data_callback=raw_data_callback
)

@ -761,7 +761,10 @@ class Session(QObject):
uart_logger_port=self.uart_logger_port,
i2c_port=self.i2c_handle,
packet_config=self.packet_config,
i2c_address=int(self.interface_config['i2c_slave_address'], 16) if self.interface_config.get('i2c_slave_address') else 0x40,
i2c_register=int(self.interface_config['i2c_slave_read_register'], 16) if self.interface_config.get('i2c_slave_read_register') else 0xFE,
stop_timeout_ms=self.interface_config['uart_logger_timeout_ms'],
grace_timeout_ms=self.interface_config['uart_logger_grace_ms'],
raw_data_callback=lambda direction, hex_str: self.raw_data_received.emit(direction, hex_str)
)

@ -531,12 +531,18 @@ class SessionWidget(QWidget):
Handle raw UART data display.
Args:
direction: "TX" or "RX"
hex_string: Hex bytes (e.g., "EF FE 01 02 03")
direction: "TX", "RX", or "ERROR"
hex_string: Hex bytes (e.g., "EF FE 01 02 03") or error message
"""
if direction == "TX":
color = "green"
prefix = "→ TX"
elif direction == "ERROR":
color = "red"
prefix = "✗ ERROR"
elif direction == "INFO":
color = "gray"
prefix = " INFO"
else:
color = "blue"
prefix = "← RX"

@ -80,6 +80,7 @@ __all__ = [
# NEW: Listening with packet detection
'uart_start_listening_with_packets',
'uart_get_detected_packets',
'uart_get_packet_errors',
'uart_clear_detected_packets',
# Status
@ -136,8 +137,8 @@ class UARTConfig:
baudrate: int
data_bits: int = 8
stop_bits: int = 1
parity: str = 'N'
buffer_size: int = 40 * 1024 * 1024 # 40MB default buffer
parity: str = 'E'
buffer_size: int = 0.256 * 1024 * 1024 # 4MB default buffer
read_chunk_size: int = 512
stop_mode: StopConditionMode = StopConditionMode.TIMEOUT
@ -263,6 +264,7 @@ class UARTPort:
_packet_buffer: bytearray = field(default_factory=bytearray)
_packet_detection_active: bool = False
_packet_start_timestamp: float = 0.0
_packet_errors: int = 0 # Count of packets with end marker errors
# Timestamp function
_get_timestamp: Callable[[], float] = field(default=time.perf_counter)
@ -510,10 +512,13 @@ def _detect_packets_in_chunk(port: UARTPort, chunk: bytes, timestamp: float) ->
start_idx = port._packet_buffer.find(cfg.start_marker)
if start_idx == -1:
# No start marker found - clear old data, keep last few bytes
# (in case start marker is split across chunks)
if len(port._packet_buffer) > 100:
port._packet_buffer = port._packet_buffer[-10:]
# No start marker found - keep searching in larger buffer
# Only clear if buffer gets excessively large (prevent memory issues)
max_buffer_size = 1024 * 1024 # 1MB should be plenty for packet detection
if len(port._packet_buffer) > max_buffer_size:
# Keep last chunk that might contain start of next packet
keep_size = min(cfg.packet_length * 2, 1024)
port._packet_buffer = port._packet_buffer[-keep_size:]
break
# Remove everything before start marker
@ -533,7 +538,8 @@ def _detect_packets_in_chunk(port: UARTPort, chunk: bytes, timestamp: float) ->
actual_end = packet_bytes[expected_end_pos:]
if actual_end != cfg.end_marker:
# Invalid packet - discard first byte and try again
# Invalid packet - count error, discard first byte and try again
port._packet_errors += 1
port._packet_buffer.pop(0)
continue
@ -562,9 +568,11 @@ def _detect_packets_in_chunk(port: UARTPort, chunk: bytes, timestamp: float) ->
if cfg.on_packet_callback:
try:
cfg.on_packet_callback(packet_info.start_timestamp)
except Exception:
except Exception as e:
# Don't crash reader thread if callback fails
pass
print(f"[ERROR] Packet callback exception: {e}")
import traceback
traceback.print_exc()
# Remove packet from buffer
port._packet_buffer = port._packet_buffer[cfg.packet_length:]
@ -595,9 +603,11 @@ def _detect_packets_in_chunk(port: UARTPort, chunk: bytes, timestamp: float) ->
if cfg.on_packet_callback:
try:
cfg.on_packet_callback(packet_info.start_timestamp)
except Exception:
except Exception as e:
# Don't crash reader thread if callback fails
pass
print(f"[ERROR] Packet callback exception: {e}")
import traceback
traceback.print_exc()
port._packet_buffer = port._packet_buffer[cfg.packet_length:]
else:
@ -690,7 +700,8 @@ def uart_send_and_receive(port: UARTPort, tx_data: bytes,
def uart_send_and_read_pgkomm2(port: UARTPort,
tx_data: bytes,
capture_max_ms: int = 30,
max_frames: int = 10) -> Tuple[Status, Optional[list]]:
max_frames: int = 10,
log_callback = None) -> Tuple[Status, Optional[list]]:
"""
Send PGKomm2 command and read response frames.
@ -713,6 +724,7 @@ def uart_send_and_read_pgkomm2(port: UARTPort,
tx_data: PGKomm2 command to transmit (must start with DD 22)
capture_max_ms: Total capture window in ms (default 30ms, spec says <15ms but real-world needs margin)
max_frames: Maximum number of frames to parse (safety limit)
log_callback: Optional callback(direction, message) for logging (e.g., to GUI)
Returns:
(Status.OK, [frame1, frame2, ...]) on success
@ -779,7 +791,7 @@ def uart_send_and_read_pgkomm2(port: UARTPort,
# Try to extract complete frames as they arrive
while True:
frame = _extract_pgkomm2_frame(rx_buffer)
frame = _extract_pgkomm2_frame(rx_buffer, log_callback)
if frame is None:
break # Need more bytes
@ -816,15 +828,27 @@ def uart_send_and_read_pgkomm2(port: UARTPort,
return (Status.OK, collected_frames)
elif len(collected_frames) > 0:
# Got frames but no HP response (only SB broadcasts or PH echo without answer)
print(f"[PGKOMM2] TIMEOUT: Got {len(collected_frames)} frame(s) but no HP response")
msg = f"[PGKOMM2] TIMEOUT: Got {len(collected_frames)} frame(s) but no HP response"
if log_callback:
log_callback("ERROR", msg)
else:
print(msg)
return (Status.TIMEOUT, None)
elif len(rx_buffer) > 0:
# Unparseable data - log for debugging
print(f"[PGKOMM2] IO_ERROR: Unparsed buffer ({len(rx_buffer)} bytes): {rx_buffer.hex(' ').upper()}")
msg = f"[PGKOMM2] IO_ERROR: Unparsed buffer ({len(rx_buffer)} bytes): {rx_buffer.hex(' ').upper()}"
if log_callback:
log_callback("ERROR", msg)
else:
print(msg)
return (Status.IO_ERROR, None)
else:
# No response
print(f"[PGKOMM2] TIMEOUT: No data received within {capture_max_ms}ms")
msg = f"[PGKOMM2] TIMEOUT: No data received within {capture_max_ms}ms"
if log_callback:
log_callback("ERROR", msg)
else:
print(msg)
return (Status.TIMEOUT, None)
except Exception as e:
@ -843,7 +867,7 @@ def uart_send_and_read_pgkomm2(port: UARTPort,
uart_start_reader(port)
def _extract_pgkomm2_frame(buffer: bytearray) -> Optional[bytes]:
def _extract_pgkomm2_frame(buffer: bytearray, log_callback=None) -> Optional[bytes]:
"""
Extract ONE complete PGKomm2 frame from buffer (destructive).
@ -857,6 +881,7 @@ def _extract_pgkomm2_frame(buffer: bytearray) -> Optional[bytes]:
Args:
buffer: Bytearray to extract from (will be modified!)
log_callback: Optional callback(direction, message) for logging
Returns:
Complete frame as bytes, or None if no complete frame available
@ -896,8 +921,17 @@ def _extract_pgkomm2_frame(buffer: bytearray) -> Optional[bytes]:
if calculated_bcc != received_bcc:
# BCC mismatch - frame corrupted!
adr_str = f"{frame[2]:02X} {frame[3]:02X}" if len(frame) >= 4 else "??"
print(f"[PGKOMM2] ✗ BCC FAIL: ADR={adr_str}, calc={calculated_bcc:02X}, recv={received_bcc:02X}")
print(f"[PGKOMM2] Frame: {frame.hex(' ').upper()}")
error_msg = f"[PGKOMM2] ✗ BCC FAIL: ADR={adr_str}, calc={calculated_bcc:02X}, recv={received_bcc:02X}"
frame_msg = f"[PGKOMM2] Frame: {frame.hex(' ').upper()}"
# Log via callback or print
if log_callback:
log_callback("ERROR", error_msg)
log_callback("ERROR", frame_msg)
else:
print(error_msg)
print(frame_msg)
# Drop this frame and continue searching
del buffer[:i + total]
return None # Reject corrupted frame
@ -1064,6 +1098,7 @@ def uart_start_listening_with_packets(port: UARTPort, packet_config: PacketConfi
port._packet_config = packet_config
port._packet_buffer.clear()
port._packet_start_timestamp = 0.0
port._packet_errors = 0 # Reset error counter
# Mark listening start time
with port._lock:
@ -1096,6 +1131,16 @@ def uart_get_detected_packets(port: UARTPort) -> list:
return port._detected_packets.copy()
def uart_get_packet_errors(port: UARTPort) -> int:
"""
Get number of packet errors (end marker mismatches).
Returns:
Number of packets with end marker errors since listening started
"""
return port._packet_errors
def uart_clear_detected_packets(port: UARTPort) -> Status:
"""
Clear detected packets list.
@ -1108,6 +1153,7 @@ def uart_clear_detected_packets(port: UARTPort) -> Status:
port._detected_packets.clear()
port._packet_buffer.clear()
port._packet_start_timestamp = 0.0
port._packet_errors = 0 # Reset error counter
with port._lock:
port._total_packets = 0

@ -35,7 +35,7 @@ from PyQt6.QtGui import QFont
# Import UART core and buffer widget
from uart.uart_kit.uart_core import *
from buffer_kit.buffer_widget_compact import CompactBufferWidget
from buffer_kit.circular_buffer import cb_overflows
from buffer_kit.circular_buffer import cb_overflows, cb_debug_info, cb_fill_bytes, cb_capacity
class UARTCommandWorker(QThread):
@ -106,6 +106,10 @@ class UARTWidget(QWidget):
self._packet_detection_enabled = False
self._detected_packet_count = 0
# Polling mode timeout tracking (for auto-stop)
self._last_packet_time = 0.0
self._polling_start_time = 0.0
# Build UI
self._init_ui()
self._setup_timers()
@ -301,10 +305,10 @@ class UARTWidget(QWidget):
row3.addWidget(QLabel("Length:"))
self.spin_packet_length = QSpinBox()
self.spin_packet_length.setRange(1, 1024)
self.spin_packet_length.setValue(17)
self.spin_packet_length.setValue(14) # Default: 14 bytes (EF FE + 11 data + EE)
self.spin_packet_length.setSuffix(" B")
self.spin_packet_length.setMaximumWidth(80)
self.spin_packet_length.setToolTip("Total packet length in bytes")
self.spin_packet_length.setToolTip("Total packet length in bytes (including start+data+end)")
row3.addWidget(self.spin_packet_length)
# End marker
@ -515,7 +519,7 @@ class UARTWidget(QWidget):
data_bits=int(self.combo_databits.currentText()),
stop_bits=int(self.combo_stopbits.currentText()),
parity='N' if self.combo_parity.currentText() == 'None' else self.combo_parity.currentText()[0],
buffer_size=4096,
# buffer_size uses default from UARTConfig
stop_mode=stop_mode,
stop_timeout_ms=self.spin_timeout.value(),
stop_terminator=terminator,
@ -603,6 +607,10 @@ class UARTWidget(QWidget):
# Start listening with packet detection
uart_start_listening_with_packets(self.port, packet_config)
self._packet_detection_enabled = True
# Initialize timeout tracking (0 = no packets yet, timeout not started)
self._last_packet_time = 0.0
self.poll_timer.start(50) # Poll for packets
self._log_info(f"Polling mode started with packet detection")
self._log_info(f" Start: {start_marker.hex(' ')}, Length: {packet_length}, End: {end_marker.hex(' ') if end_marker else 'None'}")
@ -610,11 +618,17 @@ class UARTWidget(QWidget):
except Exception as e:
self._show_error(f"Invalid packet config: {e}")
# Fallback to regular polling
# Initialize timeout tracking (0 = no packets yet, timeout not started)
self._last_packet_time = 0.0
self.poll_timer.start(50)
self._packet_detection_enabled = False
self._log_warning("Fallback to polling mode without packet detection")
else:
# Regular polling mode (use uart_poll_packet)
# Initialize timeout tracking (0 = no packets yet, timeout not started)
self._last_packet_time = 0.0
self.poll_timer.start(50)
self._packet_detection_enabled = False
self._log_info("Polling mode started (no packet detection)")
@ -818,6 +832,34 @@ class UARTWidget(QWidget):
if not self.port:
return
# Check timeout (auto-stop if no packets for timeout duration)
import time
current_time = time.time()
timeout_s = self.spin_timeout.value() / 1000.0 # Convert ms to seconds
if self._last_packet_time > 0: # Only check after first packet
silence_duration = current_time - self._last_packet_time
if silence_duration >= timeout_s:
# Timeout expired - print buffer info, packet stats, and auto-disconnect
if self.port and self.port._rx_buffer:
fill = cb_fill_bytes(self.port._rx_buffer)
cap = cb_capacity(self.port._rx_buffer)
fill_mb = fill / (1024 * 1024)
cap_mb = cap / (1024 * 1024)
self._log_info(f"Buffer status: {fill_mb:.2f}MB / {cap_mb:.1f}MB ({fill}/{cap} bytes)")
# Show packet statistics
if self.port:
total_packets = len(uart_get_detected_packets(self.port))
packet_errors = uart_get_packet_errors(self.port)
if packet_errors > 0:
self._log_error(f"⚠ Packet errors: {packet_errors} packets with end marker mismatch")
self._log_info(f"✓ Total valid packets: {total_packets}")
self._log_info(f"Auto-stop: {timeout_s*1000:.0f}ms timeout expired (no new packets)")
self._on_disconnect()
return
# Update buffer widget
if isinstance(self.buffer_widget, CompactBufferWidget):
self.buffer_widget.update_display()
@ -828,8 +870,9 @@ class UARTWidget(QWidget):
current_count = len(packets)
if current_count != self._detected_packet_count:
# New packets detected - update counter only, NO printing
# New packets detected - update counter and timestamp
self._detected_packet_count = current_count
self._last_packet_time = current_time # Update timestamp
self.lbl_packet_count.setText(f"Packets: {current_count}")
self.lbl_packet_count.setStyleSheet("color: green; font-weight: bold;")
return
@ -855,6 +898,9 @@ class UARTWidget(QWidget):
status, packet = uart_poll_packet(self.port)
if status == Status.OK:
# Update packet timestamp
self._last_packet_time = current_time
# Display stop condition message in gray (logging mode only)
stop_msg = (
f"<span style='color: gray;'>"

Loading…
Cancel
Save