// RD-03E frame scanner — pure logic, host-testable. See rd03e_parse.h. #include "rd03e_parse.h" bool rd03e_parse_latest(const uint8_t *buf, size_t len, rd03e_frame_t *out) { if (buf == NULL || out == NULL || len < RD03E_FRAME_LEN) { return false; } bool parsed_any = false; rd03e_frame_t latest = {0}; // Scan for the newest complete, validated frame in whatever arrived // this cycle; keep overwriting `latest` so we report the freshest one. // // The `i + RD03E_FRAME_LEN <= len` bound is what makes a truncated // trailing frame get ignored rather than read past the buffer: a // partial frame at the end simply never satisfies the bound. for (size_t i = 0; i + RD03E_FRAME_LEN <= len; i++) { if (buf[i] != RD03E_FRAME_HEADER) { continue; } // Footer lives at [4] and [5] of the frame. If this offset only // *looks* like a header (a 0xAA that is really a distance byte, a // gesture code, or line noise), the footer check rejects it and // the scan resynchronises on the next byte. if (buf[i + 4] != RD03E_FRAME_FOOTER0 || buf[i + 5] != RD03E_FRAME_FOOTER1) { continue; // not a real header byte, or a corrupted frame } latest.gesture = buf[i + 1]; // Little-endian: low byte first. Getting this backwards, or // letting the footer bytes bleed into the high byte, is exactly // the bug this unit exists to make testable. latest.distance_cm = (uint16_t)((uint16_t)buf[i + 2] | ((uint16_t)buf[i + 3] << 8)); parsed_any = true; i += RD03E_FRAME_LEN - 1; // loop's i++ moves past this frame } if (parsed_any) { *out = latest; } return parsed_any; }