#ifndef SIM_HAL_H #define SIM_HAL_H #include "hal.h" #include #include #include #include #include #include #include /* * Simulated HAL for system tests. * * Unlike MockHal (which verifies call expectations), SimHal actually * maintains state: pin values, a virtual clock, serial output capture, * and pluggable I2C device simulators. * * This lets you write system tests that exercise full application logic * against simulated hardware: * * SimHal sim; * BlinkApp app(&sim); * app.begin(); * * sim.setPin(2, LOW); // simulate button press * sim.advanceMillis(600); // advance clock * app.update(); * * EXPECT_EQ(sim.getPin(13), HIGH); // check LED state */ // -------------------------------------------------------------------- // I2C device simulator interface // -------------------------------------------------------------------- class I2cDeviceSim { public: virtual ~I2cDeviceSim() = default; // Called when master writes bytes to this device virtual void onReceive(const uint8_t* data, size_t len) = 0; // Called when master requests bytes; fill response buffer virtual size_t onRequest(uint8_t* buf, size_t max_len) = 0; }; // -------------------------------------------------------------------- // Simulated HAL // -------------------------------------------------------------------- class SimHal : public Hal { public: static const int NUM_PINS = 20; // D0-D13 + A0-A5 SimHal() : clock_ms_(0), clock_us_(0) { memset(pin_modes_, 0, sizeof(pin_modes_)); memset(pin_values_, 0, sizeof(pin_values_)); } // -- GPIO --------------------------------------------------------------- void pinMode(uint8_t pin, uint8_t mode) override { if (pin < NUM_PINS) { pin_modes_[pin] = mode; // INPUT_PULLUP defaults to HIGH if (mode == INPUT_PULLUP) { pin_values_[pin] = HIGH; } } } void digitalWrite(uint8_t pin, uint8_t value) override { if (pin < NUM_PINS) { pin_values_[pin] = value; gpio_log_.push_back({clock_ms_, pin, value}); } } uint8_t digitalRead(uint8_t pin) override { if (pin < NUM_PINS) return pin_values_[pin]; return LOW; } int analogRead(uint8_t pin) override { if (pin < NUM_PINS) return analog_values_[pin]; return 0; } void analogWrite(uint8_t pin, int value) override { if (pin < NUM_PINS) pin_values_[pin] = (value > 0) ? HIGH : LOW; } // -- Timing ------------------------------------------------------------- unsigned long millis() override { return clock_ms_; } unsigned long micros() override { return clock_us_; } void delay(unsigned long ms) override { advanceMillis(ms); } void delayMicroseconds(unsigned long us) override { clock_us_ += us; } // -- Serial ------------------------------------------------------------- void serialBegin(unsigned long baud) override { (void)baud; } void serialPrint(const char* msg) override { serial_output_ += msg; } void serialPrintln(const char* msg) override { serial_output_ += msg; serial_output_ += "\n"; } int serialAvailable() override { return static_cast(serial_input_.size()); } int serialRead() override { if (serial_input_.empty()) return -1; int c = serial_input_.front(); serial_input_.erase(serial_input_.begin()); return c; } // -- I2C ---------------------------------------------------------------- void i2cBegin() override {} void i2cBeginTransmission(uint8_t addr) override { i2c_addr_ = addr; i2c_tx_buf_.clear(); } size_t i2cWrite(uint8_t data) override { i2c_tx_buf_.push_back(data); return 1; } uint8_t i2cEndTransmission() override { auto it = i2c_devices_.find(i2c_addr_); if (it == i2c_devices_.end()) return 2; // NACK on address it->second->onReceive(i2c_tx_buf_.data(), i2c_tx_buf_.size()); return 0; // success } uint8_t i2cRequestFrom(uint8_t addr, uint8_t count) override { i2c_rx_buf_.clear(); auto it = i2c_devices_.find(addr); if (it == i2c_devices_.end()) return 0; uint8_t tmp[256]; size_t n = it->second->onRequest(tmp, count); for (size_t i = 0; i < n; ++i) { i2c_rx_buf_.push_back(tmp[i]); } return static_cast(n); } int i2cAvailable() override { return static_cast(i2c_rx_buf_.size()); } int i2cRead() override { if (i2c_rx_buf_.empty()) return -1; int val = i2c_rx_buf_.front(); i2c_rx_buf_.erase(i2c_rx_buf_.begin()); return val; } // ==================================================================== // Test control API (not part of Hal interface) // ==================================================================== // -- Clock control ------------------------------------------------------ void advanceMillis(unsigned long ms) { clock_ms_ += ms; clock_us_ += ms * 1000; } void setMillis(unsigned long ms) { clock_ms_ = ms; clock_us_ = ms * 1000; } // -- GPIO control ------------------------------------------------------- void setPin(uint8_t pin, uint8_t value) { if (pin < NUM_PINS) pin_values_[pin] = value; } uint8_t getPin(uint8_t pin) const { if (pin < NUM_PINS) return pin_values_[pin]; return LOW; } uint8_t getPinMode(uint8_t pin) const { if (pin < NUM_PINS) return pin_modes_[pin]; return 0; } void setAnalog(uint8_t pin, int value) { analog_values_[pin] = value; } // -- GPIO history ------------------------------------------------------- struct GpioEvent { unsigned long timestamp_ms; uint8_t pin; uint8_t value; }; const std::vector& gpioLog() const { return gpio_log_; } void clearGpioLog() { gpio_log_.clear(); } // Count how many times a pin was set to a specific value int countWrites(uint8_t pin, uint8_t value) const { int count = 0; for (const auto& e : gpio_log_) { if (e.pin == pin && e.value == value) ++count; } return count; } // -- Serial control ----------------------------------------------------- const std::string& serialOutput() const { return serial_output_; } void clearSerialOutput() { serial_output_.clear(); } void injectSerialInput(const std::string& data) { for (char c : data) { serial_input_.push_back(static_cast(c)); } } // -- I2C device registration -------------------------------------------- void attachI2cDevice(uint8_t addr, I2cDeviceSim* device) { i2c_devices_[addr] = device; } void detachI2cDevice(uint8_t addr) { i2c_devices_.erase(addr); } private: // GPIO uint8_t pin_modes_[NUM_PINS]; uint8_t pin_values_[NUM_PINS]; std::map analog_values_; std::vector gpio_log_; // Timing unsigned long clock_ms_; unsigned long clock_us_; // Serial std::string serial_output_; std::vector serial_input_; // I2C uint8_t i2c_addr_ = 0; std::vector i2c_tx_buf_; std::vector i2c_rx_buf_; std::map i2c_devices_; }; #endif // SIM_HAL_H