claudeDroneteam-docs
documentation · reference
Docs reference

Structured knowledge from collected_doc_media/claudedrone_docs/. Browse the tree on the left; the source of truth is markdown in the repo.

Host-testable Arduino: pure C++ + #ifdef wrapper

Driver-splitting: pure functions (compile on host, no mocks) + an Arduino wrapper under #if defined(ARDUINO). Unit tests via direct #include of the .cpp.

stablefirmwareupdated 2026-05-11T00:00:00.000ZClaudeDroneFirmwarePatternTestingArduinoPlatformIO

Problem

A sensor driver in the Arduino framework (say, HardwareSerial for UART) physically uses APIs available only on the target board. Host-side unit tests without hardware require Serial / I2C / SPI mocks — a lot of wrapper code that can itself contain bugs.

Solution

Split the driver into two parts:

1. Pure C++ functions

Clean logic: frame parsing, checksum verification, raw → SI conversion. No Arduino API.

// tf_luna.h — pure interface
struct TFLunaFrame {
  uint16_t distance_cm;
  uint16_t amp_raw;
  float    temp_c;
  bool     valid;
};

bool tf_luna_parse_frame(const uint8_t *buf, TFLunaFrame &out);
uint8_t tf_luna_checksum(const uint8_t *buf, size_t len);

These are plain C++ — they compile on the host, on ESP32, wherever. Tested like any other logic.

2. Arduino wrapper under #if defined(ARDUINO)

A class with state, using HardwareSerial. Declared in the same .h but with conditional implementation.

// tf_luna.h
#if defined(ARDUINO)
class TFLuna {
public:
  TFLuna(HardwareSerial &port, int rx, int tx);
  bool tick(TFLunaFrame &out);
private:
  HardwareSerial &port_;
  uint8_t buf_[512];
  size_t  len_ = 0;
};
#endif
// tf_luna.cpp
#if defined(ARDUINO)
TFLuna::TFLuna(HardwareSerial &port, int rx, int tx) : port_(port) {
  port_.begin(115200, SERIAL_8N1, rx, tx);
  port_.setRxBufferSize(512);          // not the 256 default!
}

bool TFLuna::tick(TFLunaFrame &out) {
  while (port_.available()) {
    buf_[len_++] = port_.read();
    if (len_ >= 9) {
      // scan buf_ for 0x59 0x59, drop partial-sync one byte at a time
      ...
    }
  }
  return false;
}
#endif

// pure functions — no #if, available everywhere
bool tf_luna_parse_frame(const uint8_t *buf, TFLunaFrame &out) { ... }
uint8_t tf_luna_checksum(const uint8_t *buf, size_t len) { ... }

How this enables host tests without mocks

platformio.ini:

[env:native]
platform = native
test_framework = unity
test_build_src = no            ; don't pull the whole src/

The test file directly includes the .cpp:

// test/test_tf_luna/test_main.cpp
#include <unity.h>
#include "../../src/drivers/tf_luna/tf_luna.cpp"   // include the .cpp directly!

void test_parse_real_frame() {
  uint8_t packet[] = { 0x59, 0x59, 0x6E, 0x00, 0x68, 0x01, 0x2C, 0x09, 0xEE };
  TFLunaFrame frame;
  TEST_ASSERT_TRUE(tf_luna_parse_frame(packet, frame));
  TEST_ASSERT_EQUAL_UINT16(110, frame.distance_cm);
  TEST_ASSERT_TRUE(frame.valid);
}

The trick: the ARDUINO macro isn’t defined on native — so the entire #if defined(ARDUINO) block vanishes, leaving only the pure functions. The linker only assembles those + the test. No Serial mocks.

Gotchas of this approach

1. test_build_src = no confuses the linker

If you leave the default (test_build_src = yes), pio tries to build all of src/ including the Arduino bits — fails on HardwareSerial without the target toolchain.

With test_build_src = no, the workaround is a direct #include "...tf_luna.cpp" in the test — only the code being tested compiles, no build_src_filter gymnastics.

2. You can be wrong in the expected values

In the checksum-formula test I had expected 0x8E — but it’s actually 0xEE. The sum was 0x18E (right), & 0xFF gave 0x8E (wrong arithmetic on paper). The test caught it — exactly the right behavior.

Tip: in the test comment, write the step-by-step calculation so a reviewer can verify:

// sum = 0x59+0x59+0x6E+0x00+0x68+0x01+0x2C+0x09 = 0x18E
// 0x18E & 0xFF = 0x8E  ← WRONG!
// Real: 0x18E + 0x60(low) - wait, sum 8 bytes ... → 0xEE
TEST_ASSERT_EQUAL_UINT8(0xEE, tf_luna_checksum(packet, 8));

Applicability

This pattern works for any Arduino driver that has clean logic on top of a hardware API: UART parsers, I2C registers, SPI commands, CRC checks. Doesn’t work for logic tied to timing: PWM with microsecond precision, GPIO interrupts, RMT.

© 2026 claudeDrone Team · auto-pipeline · Nuxt 3 SSR