ruvnet/RuView · error · CSIParseError

Frame too short: need {self.HEADER_SIZE} bytes, got {len(raw

Error message

Frame too short: need {self.HEADER_SIZE} bytes, got {len(raw_data)}

What it means

ESP32BinaryParser (ADR-018 frames, selected via config parser_format='binary') rejects any buffer shorter than the 20-byte header (HEADER_SIZE=20, format '<IBBHIIBBBB': magic, node_id, antennas, subcarriers, freq, seq, rssi, noise, ppdu, flags) before attempting struct.unpack. This is a framing problem: not enough bytes exist to even read the header.

Source

Thrown at archive/v1/src/hardware/csi_extractor.py:189

    PPDU_HE_MU = 2
    PPDU_HE_TB = 3
    PPDU_UNKNOWN = 0xFF
    _PPDU_NAMES = {0: 'ht_legacy', 1: 'he_su', 2: 'he_mu', 3: 'he_tb', 0xFF: 'unknown'}

    def parse(self, raw_data: bytes) -> CSIData:
        """Parse an ADR-018 binary frame into CSIData.

        Args:
            raw_data: Raw binary frame bytes.

        Returns:
            Parsed CSI data with amplitude/phase arrays shaped (n_antennas, n_subcarriers).

        Raises:
            CSIParseError: If frame is too short, has invalid magic, or malformed I/Q data.
        """
        if len(raw_data) < self.HEADER_SIZE:
            raise CSIParseError(
                f"Frame too short: need {self.HEADER_SIZE} bytes, got {len(raw_data)}"
            )

        magic, node_id, n_antennas, n_subcarriers, freq_mhz, sequence, rssi_u8, noise_u8, \
            ppdu_byte, flags_byte = struct.unpack_from(self.HEADER_FMT, raw_data, 0)

        if magic != self.MAGIC:
            raise CSIParseError(
                f"Invalid magic: expected 0x{self.MAGIC:08X}, got 0x{magic:08X}"
            )

        # Convert unsigned bytes to signed i8
        rssi = rssi_u8 if rssi_u8 < 128 else rssi_u8 - 256
        noise_floor = noise_u8 if noise_u8 < 128 else noise_u8 - 256

        iq_count = n_antennas * n_subcarriers
        iq_bytes = iq_count * 2
        expected_len = self.HEADER_SIZE + iq_bytes

View on GitHub (pinned to 4685618388)

Solutions

  1. Buffer incoming bytes and call parse only when at least HEADER_SIZE (20) bytes are available; for UDP, parse each datagram as one frame
  2. Resync the stream: scan for the 4-byte little-endian magic 0xC5110001 before treating bytes as a frame start
  3. Confirm parser_format matches what the firmware sends (text CSV vs binary ADR-018)

Example fix

# before
frame = await reader.read(64)  # may return a partial frame
data = parser.parse(frame)  # CSIParseError: Frame too short

# after
buf = bytearray()
while len(buf) < parser.HEADER_SIZE:
    buf += await reader.read(parser.HEADER_SIZE - len(buf))
# then read the I/Q payload declared by the header before parsing
Defensive patterns

Strategy: validation

Validate before calling

HEADER_SIZE = ESP32BinaryParser.HEADER_SIZE  # 20

if len(buf) < HEADER_SIZE:
    # not a complete frame yet: keep buffering / skip fragment
    continue
data = parser.parse(buf)

Type guard

def is_frame_sized(buf: bytes) -> bool:
    return len(buf) >= ESP32BinaryParser.HEADER_SIZE

Try / catch

try:
    data = parser.parse(frame_bytes)
except CSIParseError as e:
    logger.debug('dropping short binary frame: %s', e)
    resync_buffer()

Prevention

When it happens

Trigger: parse() on a fragment (e.g. first 8 bytes of a frame from a chunked TCP read); an empty buffer after stream EOF; handing ASCII 'CSI_DATA' text to the binary parser; a UDP datagram split by an intermediary.

Common situations: Starting to read mid-frame so the first buffer is the tail of a previous frame; assuming TCP byte-stream reads align with frame boundaries; collector slicing the stream at a fixed size unrelated to frame length.

Related errors


AI-assisted analysis of ruvnet/RuView@4685618388 (2026-08-16). Data as JSON: /api/errors/b3d1649b7fd535a1. Report an issue: GitHub.