Files
qtalker---/firmware/esp32p4-sensor-node/main/rd03e_parse.h
Indiana 0966fa8cfc 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>
2026-07-31 13:17:35 +00:00

78 lines
3.3 KiB
C

// 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