#include #include "hal.h" #include "sim_hal.h" // ============================================================================ // Example: I2C Temperature Sensor Simulator // // Demonstrates how to mock an I2C device. This simulates a simple // temperature sensor at address 0x48 (like a TMP102 or LM75). // // Protocol (simplified): // Write register pointer (1 byte), then read 2 bytes back. // Register 0x00 = temperature (MSB:LSB, 12-bit, 0.0625 deg/LSB) // Register 0x01 = config register // ============================================================================ class TempSensorSim : public I2cDeviceSim { public: static const uint8_t ADDR = 0x48; static const uint8_t REG_TEMP = 0x00; static const uint8_t REG_CONFIG = 0x01; TempSensorSim() : reg_pointer_(0), temp_raw_(0) {} // Set temperature in degrees C (converted to 12-bit raw) void setTemperature(float deg_c) { temp_raw_ = static_cast(deg_c / 0.0625f); } // I2cDeviceSim interface void onReceive(const uint8_t* data, size_t len) override { if (len >= 1) { reg_pointer_ = data[0]; } } size_t onRequest(uint8_t* buf, size_t max_len) override { if (max_len < 2) return 0; if (reg_pointer_ == REG_TEMP) { // 12-bit left-aligned in 16 bits int16_t raw = temp_raw_ << 4; buf[0] = static_cast((raw >> 8) & 0xFF); buf[1] = static_cast(raw & 0xFF); return 2; } if (reg_pointer_ == REG_CONFIG) { buf[0] = 0x60; // default config buf[1] = 0xA0; return 2; } return 0; } private: uint8_t reg_pointer_; int16_t temp_raw_; }; // ----------------------------------------------------------------------- // Helper: read temperature the way real Arduino code would // ----------------------------------------------------------------------- static float readTemperature(Hal* hal, uint8_t addr) { // Set register pointer to 0x00 (temperature) hal->i2cBeginTransmission(addr); hal->i2cWrite(TempSensorSim::REG_TEMP); hal->i2cEndTransmission(); // Read 2 bytes uint8_t count = hal->i2cRequestFrom(addr, 2); if (count < 2) return -999.0f; uint8_t msb = static_cast(hal->i2cRead()); uint8_t lsb = static_cast(hal->i2cRead()); // Convert 12-bit left-aligned to temperature int16_t raw = (static_cast(msb) << 8) | lsb; raw >>= 4; // right-align the 12 bits return raw * 0.0625f; } // ============================================================================ // Tests // ============================================================================ class I2cTempSensorTest : public ::testing::Test { protected: void SetUp() override { sensor_.setTemperature(25.0f); sim_.attachI2cDevice(TempSensorSim::ADDR, &sensor_); sim_.i2cBegin(); } SimHal sim_; TempSensorSim sensor_; }; TEST_F(I2cTempSensorTest, ReadsRoomTemperature) { sensor_.setTemperature(25.0f); float temp = readTemperature(&sim_, TempSensorSim::ADDR); EXPECT_NEAR(temp, 25.0f, 0.1f); } TEST_F(I2cTempSensorTest, ReadsFreezing) { sensor_.setTemperature(0.0f); float temp = readTemperature(&sim_, TempSensorSim::ADDR); EXPECT_NEAR(temp, 0.0f, 0.1f); } TEST_F(I2cTempSensorTest, ReadsNegativeTemperature) { sensor_.setTemperature(-10.5f); float temp = readTemperature(&sim_, TempSensorSim::ADDR); EXPECT_NEAR(temp, -10.5f, 0.1f); } TEST_F(I2cTempSensorTest, ReadsHighTemperature) { sensor_.setTemperature(85.0f); float temp = readTemperature(&sim_, TempSensorSim::ADDR); EXPECT_NEAR(temp, 85.0f, 0.1f); } TEST_F(I2cTempSensorTest, UnregisteredDeviceReturnsNack) { // Try to talk to a device that does not exist sim_.i2cBeginTransmission(0x50); sim_.i2cWrite(0x00); uint8_t result = sim_.i2cEndTransmission(); EXPECT_NE(result, 0); // non-zero = error } TEST_F(I2cTempSensorTest, TemperatureUpdatesLive) { sensor_.setTemperature(20.0f); float t1 = readTemperature(&sim_, TempSensorSim::ADDR); sensor_.setTemperature(30.0f); float t2 = readTemperature(&sim_, TempSensorSim::ADDR); EXPECT_NEAR(t1, 20.0f, 0.1f); EXPECT_NEAR(t2, 30.0f, 0.1f); }