Add mock Arduino for x86_64 host-side testing

Complete Arduino API mock (mock_arduino.h/cpp) enabling application
code to compile and run on PC without hardware. Includes MockSerial,
String class, GPIO/analog/timing/interrupt mocks with state tracking
and test control API.

- Arduino.h, Wire.h, SPI.h shims intercept includes in test builds
- System test template (test_system.cpp) using SimHal
- CMakeLists.txt builds mock_arduino as static lib, links both suites
- Root test.sh/test.bat with --unit/--system/--clean/--verbose flags
- test.bat auto-detects MSVC via vswhere + vcvarsall.bat
- Doctor reports nuanced compiler status (on PATH vs installed)
- Refresh pulls mock infrastructure into existing projects
- 15 tests passing: 7 unit (MockHal) + 8 system (SimHal)
This commit is contained in:
Eric Ratliff
2026-02-20 08:21:11 -06:00
parent 1ae136530f
commit aa1e9d5043
13 changed files with 1831 additions and 16 deletions

View File

@@ -0,0 +1,15 @@
/*
* Arduino.h -- shim for host-side test builds.
*
* When compiling tests on x86_64, this file sits in the mocks/ include
* path and intercepts #include <Arduino.h> so that student code (or
* future libraries) that reference Arduino directly still compiles.
*
* Generated by Anvil.
*/
#ifndef ARDUINO_H_MOCK_SHIM
#define ARDUINO_H_MOCK_SHIM
#include "mock_arduino.h"
#endif // ARDUINO_H_MOCK_SHIM

View File

@@ -0,0 +1,55 @@
/*
* SPI.h -- shim for host-side test builds.
*
* Intercepts #include <SPI.h> so that code referencing SPI compiles
* on x86_64. Provides a minimal mock of the Arduino SPI library.
*
* Generated by Anvil.
*/
#ifndef SPI_H_MOCK_SHIM
#define SPI_H_MOCK_SHIM
#include "mock_arduino.h"
#define SPI_MODE0 0x00
#define SPI_MODE1 0x04
#define SPI_MODE2 0x08
#define SPI_MODE3 0x0C
#define SPI_CLOCK_DIV2 0x04
#define SPI_CLOCK_DIV4 0x00
#define SPI_CLOCK_DIV8 0x05
#define SPI_CLOCK_DIV16 0x01
#define SPI_CLOCK_DIV32 0x06
#define SPI_CLOCK_DIV64 0x02
#define SPI_CLOCK_DIV128 0x03
#define MSBFIRST 1
#define LSBFIRST 0
struct SPISettings {
uint32_t clock;
uint8_t bitOrder;
uint8_t dataMode;
SPISettings() : clock(4000000), bitOrder(MSBFIRST), dataMode(SPI_MODE0) {}
SPISettings(uint32_t c, uint8_t o, uint8_t m)
: clock(c), bitOrder(o), dataMode(m) {}
};
class MockSPI {
public:
void begin() {}
void end() {}
void beginTransaction(SPISettings) {}
void endTransaction() {}
uint8_t transfer(uint8_t data) { (void)data; return 0; }
uint16_t transfer16(uint16_t data) { (void)data; return 0; }
void transfer(void* buf, size_t count) { (void)buf; (void)count; }
void setBitOrder(uint8_t) {}
void setClockDivider(uint8_t) {}
void setDataMode(uint8_t) {}
};
extern MockSPI SPI;
#endif // SPI_H_MOCK_SHIM

View File

@@ -0,0 +1,35 @@
/*
* Wire.h -- shim for host-side test builds.
*
* Intercepts #include <Wire.h> so that code referencing Wire compiles
* on x86_64. The mock Arduino state handles I2C through the HAL layer,
* but if student code or libraries reference Wire directly, this
* prevents a compile error.
*
* Generated by Anvil.
*/
#ifndef WIRE_H_MOCK_SHIM
#define WIRE_H_MOCK_SHIM
#include "mock_arduino.h"
class MockWire {
public:
void begin() {}
void begin(uint8_t) {}
void beginTransmission(uint8_t) {}
uint8_t endTransmission(bool sendStop = true) { (void)sendStop; return 0; }
uint8_t requestFrom(uint8_t addr, uint8_t count, bool sendStop = true) {
(void)addr; (void)count; (void)sendStop;
return 0;
}
size_t write(uint8_t data) { (void)data; return 1; }
size_t write(const uint8_t* data, size_t len) { (void)data; return len; }
int available() { return 0; }
int read() { return -1; }
void setClock(uint32_t freq) { (void)freq; }
};
extern MockWire Wire;
#endif // WIRE_H_MOCK_SHIM

View File

@@ -0,0 +1,579 @@
/*
* mock_arduino.cpp -- Implementation of the Arduino API mock.
*
* This file provides function bodies for all Arduino core functions
* and the test control API. Linked into the test executable via cmake.
*
* Generated by Anvil -- https://github.com/nexus-workshops/anvil
*/
#include "mock_arduino.h"
#include <cstdlib>
#include <cstdio>
#include <algorithm>
#include <cctype>
#include <sstream>
// ============================================================================
// Global state
// ============================================================================
MockArduinoState _mock_arduino;
MockSerial Serial;
// Wire mock (defined in Wire.h shim)
#include "Wire.h"
MockWire Wire;
// SPI mock (defined in SPI.h shim)
#include "SPI.h"
MockSPI SPI;
// ============================================================================
// Test control API
// ============================================================================
void mock_arduino_reset() {
memset(_mock_arduino.pin_modes, 0, sizeof(_mock_arduino.pin_modes));
memset(_mock_arduino.pin_digital, 0, sizeof(_mock_arduino.pin_digital));
memset(_mock_arduino.pin_analog, 0, sizeof(_mock_arduino.pin_analog));
memset(_mock_arduino.pin_pwm, 0, sizeof(_mock_arduino.pin_pwm));
_mock_arduino.millis_value = 0;
_mock_arduino.micros_value = 0;
_mock_arduino.serial_output.clear();
_mock_arduino.serial_input.clear();
_mock_arduino.serial_baud = 0;
_mock_arduino.serial_begun = false;
for (int i = 0; i < 8; ++i) {
_mock_arduino.interrupts[i].callback = nullptr;
_mock_arduino.interrupts[i].mode = 0;
_mock_arduino.interrupts[i].attached = false;
}
_mock_arduino.interrupts_enabled = true;
_mock_arduino.gpio_log.clear();
_mock_arduino.delay_log.clear();
}
void mock_arduino_advance_millis(unsigned long ms) {
_mock_arduino.millis_value += ms;
_mock_arduino.micros_value += ms * 1000;
}
void mock_arduino_set_millis(unsigned long ms) {
_mock_arduino.millis_value = ms;
_mock_arduino.micros_value = ms * 1000;
}
void mock_arduino_set_digital(uint8_t pin, int value) {
if (pin < MOCK_ARDUINO_MAX_PINS) {
_mock_arduino.pin_digital[pin] = value;
}
}
void mock_arduino_set_analog(uint8_t pin, int value) {
if (pin < MOCK_ARDUINO_MAX_PINS) {
_mock_arduino.pin_analog[pin] = value;
}
}
int mock_arduino_get_digital(uint8_t pin) {
if (pin < MOCK_ARDUINO_MAX_PINS) return _mock_arduino.pin_digital[pin];
return LOW;
}
int mock_arduino_get_pwm(uint8_t pin) {
if (pin < MOCK_ARDUINO_MAX_PINS) return _mock_arduino.pin_pwm[pin];
return 0;
}
uint8_t mock_arduino_get_pin_mode(uint8_t pin) {
if (pin < MOCK_ARDUINO_MAX_PINS) return _mock_arduino.pin_modes[pin];
return 0;
}
const std::string& mock_arduino_serial_output() {
return _mock_arduino.serial_output;
}
void mock_arduino_inject_serial(const std::string& data) {
for (char c : data) {
_mock_arduino.serial_input.push_back(static_cast<uint8_t>(c));
}
}
void mock_arduino_clear_serial() {
_mock_arduino.serial_output.clear();
_mock_arduino.serial_input.clear();
}
const std::vector<MockArduinoState::GpioEvent>& mock_arduino_gpio_log() {
return _mock_arduino.gpio_log;
}
void mock_arduino_clear_gpio_log() {
_mock_arduino.gpio_log.clear();
}
int mock_arduino_count_writes(uint8_t pin, int value) {
int count = 0;
for (const auto& e : _mock_arduino.gpio_log) {
if (e.pin == pin && e.value == value && !e.is_analog) ++count;
}
return count;
}
// ============================================================================
// Arduino digital I/O
// ============================================================================
void pinMode(uint8_t pin, uint8_t mode) {
if (pin < MOCK_ARDUINO_MAX_PINS) {
_mock_arduino.pin_modes[pin] = mode;
if (mode == INPUT_PULLUP) {
_mock_arduino.pin_digital[pin] = HIGH;
}
}
}
void digitalWrite(uint8_t pin, uint8_t val) {
if (pin < MOCK_ARDUINO_MAX_PINS) {
_mock_arduino.pin_digital[pin] = val;
_mock_arduino.gpio_log.push_back({
_mock_arduino.millis_value, pin, val, false
});
}
}
int digitalRead(uint8_t pin) {
if (pin < MOCK_ARDUINO_MAX_PINS) return _mock_arduino.pin_digital[pin];
return LOW;
}
// ============================================================================
// Arduino analog I/O
// ============================================================================
int analogRead(uint8_t pin) {
if (pin < MOCK_ARDUINO_MAX_PINS) return _mock_arduino.pin_analog[pin];
return 0;
}
void analogWrite(uint8_t pin, int val) {
if (pin < MOCK_ARDUINO_MAX_PINS) {
_mock_arduino.pin_pwm[pin] = val;
_mock_arduino.pin_digital[pin] = (val > 0) ? HIGH : LOW;
_mock_arduino.gpio_log.push_back({
_mock_arduino.millis_value, pin, val, true
});
}
}
// ============================================================================
// Arduino timing
// ============================================================================
unsigned long millis() {
return _mock_arduino.millis_value;
}
unsigned long micros() {
return _mock_arduino.micros_value;
}
void delay(unsigned long ms) {
_mock_arduino.delay_log.push_back(ms);
_mock_arduino.millis_value += ms;
_mock_arduino.micros_value += ms * 1000;
}
void delayMicroseconds(unsigned long us) {
_mock_arduino.micros_value += us;
}
// ============================================================================
// Arduino math helpers
// ============================================================================
long map(long value, long fromLow, long fromHigh, long toLow, long toHigh) {
if (fromHigh == fromLow) return toLow;
return (value - fromLow) * (toHigh - toLow) / (fromHigh - fromLow) + toLow;
}
long constrain(long value, long low, long high) {
if (value < low) return low;
if (value > high) return high;
return value;
}
static unsigned long _random_seed = 1;
long random(long max) {
if (max <= 0) return 0;
_random_seed = _random_seed * 1103515245 + 12345;
return (long)((_random_seed >> 16) % (unsigned long)max);
}
long random(long min, long max) {
if (max <= min) return min;
return random(max - min) + min;
}
void randomSeed(unsigned long seed) {
_random_seed = seed;
}
// ============================================================================
// Interrupts
// ============================================================================
void attachInterrupt(uint8_t interrupt, void (*callback)(), int mode) {
if (interrupt < 8) {
_mock_arduino.interrupts[interrupt].callback = callback;
_mock_arduino.interrupts[interrupt].mode = mode;
_mock_arduino.interrupts[interrupt].attached = true;
}
}
void detachInterrupt(uint8_t interrupt) {
if (interrupt < 8) {
_mock_arduino.interrupts[interrupt].callback = nullptr;
_mock_arduino.interrupts[interrupt].attached = false;
}
}
void noInterrupts() {
_mock_arduino.interrupts_enabled = false;
}
void interrupts() {
_mock_arduino.interrupts_enabled = true;
}
uint8_t digitalPinToInterrupt(uint8_t pin) {
// Uno/Nano: pin 2 = INT0, pin 3 = INT1
switch (pin) {
case 2: return 0;
case 3: return 1;
default: return 255; // NOT_AN_INTERRUPT
}
}
// ============================================================================
// MockSerial implementation
// ============================================================================
void MockSerial::begin(unsigned long baud) {
_mock_arduino.serial_baud = baud;
_mock_arduino.serial_begun = true;
}
void MockSerial::end() {
_mock_arduino.serial_begun = false;
}
size_t MockSerial::print(const char* str) {
if (!str) return 0;
_mock_arduino.serial_output += str;
return strlen(str);
}
size_t MockSerial::print(char c) {
_mock_arduino.serial_output += c;
return 1;
}
static std::string long_to_string(long val, int base) {
if (base == 16) {
char buf[32];
snprintf(buf, sizeof(buf), "%lx", val);
return buf;
}
if (base == 8) {
char buf[32];
snprintf(buf, sizeof(buf), "%lo", val);
return buf;
}
if (base == 2) {
if (val == 0) return "0";
std::string result;
unsigned long v = (unsigned long)val;
while (v > 0) {
result = (char)('0' + (v & 1)) + result;
v >>= 1;
}
return result;
}
return std::to_string(val);
}
static std::string ulong_to_string(unsigned long val, int base) {
return long_to_string((long)val, base);
}
size_t MockSerial::print(int val, int base) {
std::string s = long_to_string(val, base);
_mock_arduino.serial_output += s;
return s.size();
}
size_t MockSerial::print(unsigned int val, int base) {
std::string s = ulong_to_string(val, base);
_mock_arduino.serial_output += s;
return s.size();
}
size_t MockSerial::print(long val, int base) {
std::string s = long_to_string(val, base);
_mock_arduino.serial_output += s;
return s.size();
}
size_t MockSerial::print(unsigned long val, int base) {
std::string s = ulong_to_string(val, base);
_mock_arduino.serial_output += s;
return s.size();
}
size_t MockSerial::print(double val, int decimals) {
char buf[64];
snprintf(buf, sizeof(buf), "%.*f", decimals, val);
_mock_arduino.serial_output += buf;
return strlen(buf);
}
size_t MockSerial::print(const std::string& str) {
_mock_arduino.serial_output += str;
return str.size();
}
size_t MockSerial::println(const char* str) {
size_t n = print(str);
_mock_arduino.serial_output += "\n";
return n + 1;
}
size_t MockSerial::println(char c) {
size_t n = print(c);
_mock_arduino.serial_output += "\n";
return n + 1;
}
size_t MockSerial::println(int val, int base) {
size_t n = print(val, base);
_mock_arduino.serial_output += "\n";
return n + 1;
}
size_t MockSerial::println(unsigned int val, int base) {
size_t n = print(val, base);
_mock_arduino.serial_output += "\n";
return n + 1;
}
size_t MockSerial::println(long val, int base) {
size_t n = print(val, base);
_mock_arduino.serial_output += "\n";
return n + 1;
}
size_t MockSerial::println(unsigned long val, int base) {
size_t n = print(val, base);
_mock_arduino.serial_output += "\n";
return n + 1;
}
size_t MockSerial::println(double val, int decimals) {
size_t n = print(val, decimals);
_mock_arduino.serial_output += "\n";
return n + 1;
}
size_t MockSerial::println(const std::string& str) {
size_t n = print(str);
_mock_arduino.serial_output += "\n";
return n + 1;
}
size_t MockSerial::write(uint8_t b) {
_mock_arduino.serial_output += static_cast<char>(b);
return 1;
}
size_t MockSerial::write(const uint8_t* buf, size_t len) {
for (size_t i = 0; i < len; ++i) {
_mock_arduino.serial_output += static_cast<char>(buf[i]);
}
return len;
}
int MockSerial::available() {
return static_cast<int>(_mock_arduino.serial_input.size());
}
int MockSerial::read() {
if (_mock_arduino.serial_input.empty()) return -1;
int c = _mock_arduino.serial_input.front();
_mock_arduino.serial_input.erase(_mock_arduino.serial_input.begin());
return c;
}
int MockSerial::peek() {
if (_mock_arduino.serial_input.empty()) return -1;
return _mock_arduino.serial_input.front();
}
void MockSerial::flush() {
// No-op in mock (real Arduino waits for TX buffer to drain)
}
// ============================================================================
// String class implementation
// ============================================================================
String::String(int val, int base) {
data_ = long_to_string(val, base);
}
String::String(unsigned int val, int base) {
data_ = ulong_to_string(val, base);
}
String::String(long val, int base) {
data_ = long_to_string(val, base);
}
String::String(unsigned long val, int base) {
data_ = ulong_to_string(val, base);
}
String::String(double val, int decimals) {
char buf[64];
snprintf(buf, sizeof(buf), "%.*f", decimals, val);
data_ = buf;
}
char String::charAt(unsigned int index) const {
if (index < data_.size()) return data_[index];
return 0;
}
void String::setCharAt(unsigned int index, char c) {
if (index < data_.size()) data_[index] = c;
}
bool String::equalsIgnoreCase(const String& other) const {
if (data_.size() != other.data_.size()) return false;
for (size_t i = 0; i < data_.size(); ++i) {
if (tolower((unsigned char)data_[i]) !=
tolower((unsigned char)other.data_[i])) return false;
}
return true;
}
int String::compareTo(const String& other) const {
return data_.compare(other.data_);
}
int String::indexOf(char ch, unsigned int fromIndex) const {
auto pos = data_.find(ch, fromIndex);
return (pos == std::string::npos) ? -1 : (int)pos;
}
int String::indexOf(const String& str, unsigned int fromIndex) const {
auto pos = data_.find(str.data_, fromIndex);
return (pos == std::string::npos) ? -1 : (int)pos;
}
int String::lastIndexOf(char ch) const {
auto pos = data_.rfind(ch);
return (pos == std::string::npos) ? -1 : (int)pos;
}
String String::substring(unsigned int from, unsigned int to) const {
if (from >= data_.size()) return String();
if (to > data_.size()) to = (unsigned int)data_.size();
return String(data_.substr(from, to - from));
}
void String::toLowerCase() {
for (char& c : data_) c = (char)tolower((unsigned char)c);
}
void String::toUpperCase() {
for (char& c : data_) c = (char)toupper((unsigned char)c);
}
void String::trim() {
size_t start = data_.find_first_not_of(" \t\r\n");
size_t end = data_.find_last_not_of(" \t\r\n");
if (start == std::string::npos) {
data_.clear();
} else {
data_ = data_.substr(start, end - start + 1);
}
}
void String::replace(const String& from, const String& to) {
size_t pos = 0;
while ((pos = data_.find(from.data_, pos)) != std::string::npos) {
data_.replace(pos, from.data_.size(), to.data_);
pos += to.data_.size();
}
}
void String::remove(unsigned int index, unsigned int count) {
if (index < data_.size()) {
data_.erase(index, count);
}
}
long String::toInt() const {
return atol(data_.c_str());
}
float String::toFloat() const {
return (float)atof(data_.c_str());
}
double String::toDouble() const {
return atof(data_.c_str());
}
String& String::concat(const String& other) {
data_ += other.data_;
return *this;
}
String& String::concat(const char* s) {
if (s) data_ += s;
return *this;
}
String& String::concat(char c) {
data_ += c;
return *this;
}
String& String::concat(int val) {
data_ += std::to_string(val);
return *this;
}
String& String::concat(unsigned long val) {
data_ += std::to_string(val);
return *this;
}
String String::operator+(const String& rhs) const {
return String(data_ + rhs.data_);
}
char String::operator[](unsigned int index) const {
if (index < data_.size()) return data_[index];
return 0;
}
char& String::operator[](unsigned int index) {
return data_[index];
}

View File

@@ -0,0 +1,353 @@
#ifndef MOCK_ARDUINO_H
#define MOCK_ARDUINO_H
/*
* mock_arduino.h -- Arduino API mock for host-side (x86_64) testing.
*
* Provides the core Arduino vocabulary (types, constants, functions,
* Serial) so that application code compiles and runs on a PC without
* any AVR toolchain. All state is captured in a global MockArduino
* instance that tests can inspect and manipulate.
*
* This file replaces <Arduino.h> in the test build. The CMakeLists.txt
* adds the mocks/ directory to the include path, so any code that does
* #include <Arduino.h> will pick up this file instead.
*
* Usage in tests:
*
* #include "mock_arduino.h"
*
* TEST(MyTest, ReadsAnalogSensor) {
* mock_arduino_reset();
* mock_arduino_set_analog(A0, 512);
* int val = analogRead(A0);
* EXPECT_EQ(val, 512);
* }
*
* Generated by Anvil -- https://github.com/nexus-workshops/anvil
*/
#include <cstdint>
#include <cstddef>
#include <cstring>
#include <cmath>
#include <string>
#include <vector>
#include <functional>
// ============================================================================
// Arduino types
// ============================================================================
typedef uint8_t byte;
typedef uint16_t word;
typedef bool boolean;
// ============================================================================
// Pin mode and logic constants
// ============================================================================
#ifndef INPUT
#define INPUT 0x0
#define OUTPUT 0x1
#define INPUT_PULLUP 0x2
#endif
#ifndef LOW
#define LOW 0x0
#define HIGH 0x1
#endif
#ifndef LED_BUILTIN
#define LED_BUILTIN 13
#endif
// Analog pins (map to digital pin numbers, Uno layout by default)
#ifndef A0
#define A0 14
#define A1 15
#define A2 16
#define A3 17
#define A4 18
#define A5 19
#define A6 20
#define A7 21
// Extended for Mega
#define A8 54
#define A9 55
#define A10 56
#define A11 57
#define A12 58
#define A13 59
#define A14 60
#define A15 61
#endif
// ============================================================================
// Math constants
// ============================================================================
#ifndef PI
#define PI 3.14159265358979323846
#define HALF_PI 1.57079632679489661923
#define TWO_PI 6.28318530717958647692
#define DEG_TO_RAD 0.017453292519943295
#define RAD_TO_DEG 57.29577951308232
#endif
// ============================================================================
// Interrupt constants
// ============================================================================
#ifndef CHANGE
#define CHANGE 1
#define FALLING 2
#define RISING 3
#endif
// ============================================================================
// MockArduino -- global state for all Arduino functions
// ============================================================================
static const int MOCK_ARDUINO_MAX_PINS = 70; // Enough for Mega
struct MockArduinoState {
// Pin state
uint8_t pin_modes[MOCK_ARDUINO_MAX_PINS];
int pin_digital[MOCK_ARDUINO_MAX_PINS]; // digital values
int pin_analog[MOCK_ARDUINO_MAX_PINS]; // analog read values (0-1023)
int pin_pwm[MOCK_ARDUINO_MAX_PINS]; // analogWrite values (0-255)
// Timing
unsigned long millis_value;
unsigned long micros_value;
// Serial capture
std::string serial_output;
std::vector<uint8_t> serial_input;
unsigned long serial_baud;
bool serial_begun;
// Interrupt tracking
struct InterruptHandler {
void (*callback)();
int mode;
bool attached;
};
InterruptHandler interrupts[8]; // INT0-INT7
bool interrupts_enabled;
// GPIO event log
struct GpioEvent {
unsigned long timestamp;
uint8_t pin;
int value;
bool is_analog; // true = analogWrite, false = digitalWrite
};
std::vector<GpioEvent> gpio_log;
// Delay log (to verify timing behavior)
std::vector<unsigned long> delay_log;
};
// Global state -- tests access this directly
extern MockArduinoState _mock_arduino;
// ============================================================================
// Test control API -- call from tests to set up / inspect state
// ============================================================================
// Reset all state to defaults
void mock_arduino_reset();
// Clock control
void mock_arduino_advance_millis(unsigned long ms);
void mock_arduino_set_millis(unsigned long ms);
// Input injection
void mock_arduino_set_digital(uint8_t pin, int value);
void mock_arduino_set_analog(uint8_t pin, int value);
// Output inspection
int mock_arduino_get_digital(uint8_t pin);
int mock_arduino_get_pwm(uint8_t pin);
uint8_t mock_arduino_get_pin_mode(uint8_t pin);
// Serial inspection
const std::string& mock_arduino_serial_output();
void mock_arduino_inject_serial(const std::string& data);
void mock_arduino_clear_serial();
// GPIO log
const std::vector<MockArduinoState::GpioEvent>& mock_arduino_gpio_log();
void mock_arduino_clear_gpio_log();
int mock_arduino_count_writes(uint8_t pin, int value);
// ============================================================================
// Arduino core functions -- called by application code
// ============================================================================
// Digital I/O
void pinMode(uint8_t pin, uint8_t mode);
void digitalWrite(uint8_t pin, uint8_t val);
int digitalRead(uint8_t pin);
// Analog I/O
int analogRead(uint8_t pin);
void analogWrite(uint8_t pin, int val);
// Timing
unsigned long millis();
unsigned long micros();
void delay(unsigned long ms);
void delayMicroseconds(unsigned long us);
// Math helpers
long map(long value, long fromLow, long fromHigh, long toLow, long toHigh);
long constrain(long value, long low, long high);
long random(long max);
long random(long min, long max);
void randomSeed(unsigned long seed);
// Interrupts
void attachInterrupt(uint8_t interrupt, void (*callback)(), int mode);
void detachInterrupt(uint8_t interrupt);
void noInterrupts();
void interrupts();
uint8_t digitalPinToInterrupt(uint8_t pin);
// Bit manipulation (Arduino built-ins)
#ifndef bitRead
#define bitRead(value, bit) (((value) >> (bit)) & 0x01)
#define bitSet(value, bit) ((value) |= (1UL << (bit)))
#define bitClear(value, bit) ((value) &= ~(1UL << (bit)))
#define bitWrite(value, bit, b) ((b) ? bitSet(value, bit) : bitClear(value, bit))
#define bit(n) (1UL << (n))
#define lowByte(w) ((uint8_t)((w) & 0xFF))
#define highByte(w) ((uint8_t)((w) >> 8))
#endif
// ============================================================================
// MockSerial -- replaces Serial global
// ============================================================================
class MockSerial {
public:
void begin(unsigned long baud);
void end();
size_t print(const char* str);
size_t print(char c);
size_t print(int val, int base = 10);
size_t print(unsigned int val, int base = 10);
size_t print(long val, int base = 10);
size_t print(unsigned long val, int base = 10);
size_t print(double val, int decimals = 2);
size_t print(const std::string& str);
size_t println(const char* str = "");
size_t println(char c);
size_t println(int val, int base = 10);
size_t println(unsigned int val, int base = 10);
size_t println(long val, int base = 10);
size_t println(unsigned long val, int base = 10);
size_t println(double val, int decimals = 2);
size_t println(const std::string& str);
size_t write(uint8_t b);
size_t write(const uint8_t* buf, size_t len);
int available();
int read();
int peek();
void flush();
explicit operator bool() const { return _mock_arduino.serial_begun; }
};
// Global Serial instance (matches Arduino's global)
extern MockSerial Serial;
// ============================================================================
// Minimal String class (Arduino-compatible subset)
// ============================================================================
class String {
public:
String() : data_() {}
String(const char* s) : data_(s ? s : "") {}
String(const std::string& s) : data_(s) {}
String(char c) : data_(1, c) {}
String(int val, int base = 10);
String(unsigned int val, int base = 10);
String(long val, int base = 10);
String(unsigned long val, int base = 10);
String(double val, int decimals = 2);
// Access
unsigned int length() const { return (unsigned int)data_.size(); }
const char* c_str() const { return data_.c_str(); }
char charAt(unsigned int index) const;
void setCharAt(unsigned int index, char c);
// Comparison
bool equals(const String& other) const { return data_ == other.data_; }
bool equalsIgnoreCase(const String& other) const;
int compareTo(const String& other) const;
// Search
int indexOf(char ch, unsigned int fromIndex = 0) const;
int indexOf(const String& str, unsigned int fromIndex = 0) const;
int lastIndexOf(char ch) const;
// Modification
String substring(unsigned int from, unsigned int to = 0xFFFFFFFF) const;
void toLowerCase();
void toUpperCase();
void trim();
void replace(const String& from, const String& to);
void remove(unsigned int index, unsigned int count = 1);
// Conversion
long toInt() const;
float toFloat() const;
double toDouble() const;
// Concatenation
String& concat(const String& other);
String& concat(const char* s);
String& concat(char c);
String& concat(int val);
String& concat(unsigned long val);
// Operators
String& operator+=(const String& rhs) { return concat(rhs); }
String& operator+=(const char* rhs) { return concat(rhs); }
String& operator+=(char rhs) { return concat(rhs); }
String operator+(const String& rhs) const;
bool operator==(const String& rhs) const { return data_ == rhs.data_; }
bool operator==(const char* rhs) const { return data_ == (rhs ? rhs : ""); }
bool operator!=(const String& rhs) const { return data_ != rhs.data_; }
bool operator<(const String& rhs) const { return data_ < rhs.data_; }
char operator[](unsigned int index) const;
char& operator[](unsigned int index);
// Interop with std::string
operator std::string() const { return data_; }
private:
std::string data_;
};
// ============================================================================
// Arduino.h compatibility alias
// ============================================================================
// So that #include <Arduino.h> resolves to this file via include path,
// we provide this comment as a note. The CMakeLists.txt adds the mocks/
// directory to the include path, so #include <Arduino.h> becomes:
// #include "mock_arduino.h" (via the Arduino.h shim in this directory)
#endif // MOCK_ARDUINO_H