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:
77
firmware/esp32p4-sensor-node/main/rd03e_parse.h
Normal file
77
firmware/esp32p4-sensor-node/main/rd03e_parse.h
Normal file
@@ -0,0 +1,77 @@
|
||||
// Ai-Thinker RD-03E "simple report" frame scanner — PURE LOGIC.
|
||||
//
|
||||
// This unit is deliberately free of ESP-IDF: no UART, no esp_err_t, no
|
||||
// FreeRTOS, no logging. It includes only <stdint.h>/<stddef.h>/<stdbool.h>
|
||||
// so it can be compiled and tested on a host with plain gcc (see
|
||||
// ../test/). `rd03e.c` does the UART I/O and calls in here to do the
|
||||
// actual parsing.
|
||||
//
|
||||
// The reason this split exists: the first version of this firmware had
|
||||
// RD03E_FRAME_LEN set to 5 for a 6-byte frame, so the footer check read
|
||||
// the distance high byte instead of the second footer byte and every
|
||||
// distance reading came back as `lo | 0x5500` (~218 m). That was pure
|
||||
// logic with zero hardware dependency and should have been catchable on a
|
||||
// laptop. Now it is.
|
||||
//
|
||||
// UNVERIFIED AGAINST REAL HARDWARE, and the frame format below is
|
||||
// reconstructed from a third-party bring-up write-up (electroniclinic.com's
|
||||
// RD-03E/ESP32 tutorial), not Ai-Thinker's own datasheet PDF (not available
|
||||
// while writing this) — treat this as the least-certain protocol detail in
|
||||
// this driver. What's cross-confirmed from multiple independent sources:
|
||||
// UART is 256000 baud / 8N1, and the module also has a separate, more
|
||||
// complex configuration-frame protocol (0xFD 0xFC 0xFB 0xFA header /
|
||||
// 0x04 0x03 0x02 0x01 footer) for calibration and firmware queries — this
|
||||
// driver does NOT implement that; it only reads the module's free-running
|
||||
// "simple report" output frames, which need no configuration to start
|
||||
// streaming after power-up.
|
||||
//
|
||||
// Simple report frame, as reconstructed (6 bytes total):
|
||||
// [0] 0xAA frame header
|
||||
// [1] gesture code raw value, meaning not confirmed against an
|
||||
// official datasheet — reported as-is in
|
||||
// metadata rather than translated to a label
|
||||
// that might be wrong
|
||||
// [2] distance lo byte distance_cm = lo | (hi << 8), little-endian
|
||||
// [3] distance hi byte
|
||||
// [4..5] 0x55 0x55 frame footer
|
||||
// Real bring-up should verify this against a logic analyzer capture before
|
||||
// trusting field values, same as the LD2410 driver this replaced.
|
||||
//
|
||||
// Host tests prove the SHAPE of the parse (byte order, frame length,
|
||||
// footer validation, resynchronisation) against this reconstructed spec.
|
||||
// They cannot prove the reconstructed spec is what the silicon emits.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Total bytes in one simple-report frame: header + gesture + 2 distance
|
||||
// bytes + 2 footer bytes. Six, not five — see the note above.
|
||||
#define RD03E_FRAME_LEN 6
|
||||
|
||||
#define RD03E_FRAME_HEADER 0xAAu
|
||||
#define RD03E_FRAME_FOOTER0 0x55u
|
||||
#define RD03E_FRAME_FOOTER1 0x55u
|
||||
|
||||
typedef struct {
|
||||
uint8_t gesture;
|
||||
uint16_t distance_cm;
|
||||
} rd03e_frame_t;
|
||||
|
||||
// Scan `buf` (`len` bytes) for complete, footer-validated simple-report
|
||||
// frames and write the NEWEST one (highest offset) to *out.
|
||||
//
|
||||
// Returns true if at least one valid frame was found, false otherwise
|
||||
// (in which case *out is untouched). A NULL buf, a NULL out, or a buffer
|
||||
// shorter than one frame are all "no frame", not a crash.
|
||||
bool rd03e_parse_latest(const uint8_t *buf, size_t len, rd03e_frame_t *out);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user