test: make firmware logic bugs catchable without hardware (Workstream F)
The firmware has never been flashed, and a real bug already reached the repo because of it: RD03E_FRAME_LEN was 5 for a 6-byte frame, so the footer check collided with the distance high byte and EVERY distance reading was garbage — always `lo | 0x5500`, about 218 metres, regardless of what the sensor saw. That was pure logic with no hardware dependency. It should have been catchable on a laptop, and there was simply no way to run the code. Extracted the hardware-free logic out of the three drivers — rd03e_parse, bmp280_compensate, mems_level — as moves rather than rewrites, carrying the explanatory comments along with the code they explain. The drivers now own only their bus I/O and call into the pure units, so nothing changes for the real device. `./run_tests.sh` builds them with gcc -Wall -Wextra -Werror plus a dependency-free assert harness: 175 checks, 0 failed, from a clean tree. Proven to catch the actual bug rather than assumed to: reintroducing FRAME_LEN 5 fails four checks, including one that reads "a simple-report frame is 6 bytes, not 5", plus the truncated-frame and 5-byte-window cases. Restored, green again. This does NOT make the firmware verified, and the README says so plainly — it is called a narrow exception and scoped to pure logic. Wiring, timing, real register behaviour and the reconstructed RD-03E frame format all still need the physical board. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
43
firmware/esp32p4-sensor-node/main/rd03e_parse.c
Normal file
43
firmware/esp32p4-sensor-node/main/rd03e_parse.c
Normal file
@@ -0,0 +1,43 @@
|
||||
// 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;
|
||||
}
|
||||
Reference in New Issue
Block a user