LCOV - code coverage report
Current view: top level - extras/test/unit/include - TestHal.hpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 76 92 82.6 %
Date: 2026-02-13 13:43:39 Functions: 16 24 66.7 %

          Line data    Source code
       1             : #ifndef TEST_HAL_HPP
       2             : #define TEST_HAL_HPP
       3             : 
       4             : #include <chrono>
       5             : #include <thread>
       6             : #include <fmt/format.h>
       7             : 
       8             : #include <RadioLib.h>
       9             : 
      10             : #include <boost/log/trivial.hpp>
      11             : #include <boost/format.hpp>
      12             : 
      13             : #if defined(TEST_HAL_LOG)
      14             : #define HAL_LOG(...) BOOST_TEST_MESSAGE(__VA_ARGS__)
      15             : #else
      16             : #define HAL_LOG(...) {}
      17             : #endif
      18             : 
      19             : #include "HardwareEmulation.hpp"
      20             : 
      21             : #define TEST_HAL_INPUT          (0)
      22             : #define TEST_HAL_OUTPUT         (1)
      23             : #define TEST_HAL_LOW            (0)
      24             : #define TEST_HAL_HIGH           (1)
      25             : #define TEST_HAL_RISING         (0)
      26             : #define TEST_HAL_FALLING        (1)
      27             : 
      28             : // number of emulated GPIO pins
      29             : #define TEST_HAL_NUM_GPIO_PINS  (32)
      30             : 
      31             : #define TEST_HAL_SPI_LOG_LENGTH (512)
      32             : 
      33             : class TestHal : public RadioLibHal {
      34             :   public:
      35             :     bool spiLogEnabled = true;
      36             : 
      37           5 :     TestHal() : RadioLibHal(TEST_HAL_INPUT, TEST_HAL_OUTPUT, TEST_HAL_LOW, TEST_HAL_HIGH, TEST_HAL_RISING, TEST_HAL_FALLING) { }
      38             : 
      39           5 :     void init() override {
      40             :       HAL_LOG("TestHal::init()");
      41             : 
      42             :       // save program start timestamp
      43           5 :       start = std::chrono::high_resolution_clock::now();
      44             : 
      45             :       // init emulated GPIO
      46         165 :       for(int i = 0; i < TEST_HAL_NUM_GPIO_PINS; i++) {
      47         160 :         this->gpio[i].mode = 0;
      48         160 :         this->gpio[i].value = 0;
      49         160 :         this->gpio[i].event = false;
      50         160 :         this->gpio[i].func = PIN_UNASSIGNED;
      51             :       }
      52             : 
      53             :       // wipe history log
      54           5 :       this->spiLogWipe();
      55           5 :     }
      56             : 
      57           5 :     void term() override {
      58             :       HAL_LOG("TestHal::term()");
      59           5 :     }
      60             : 
      61           5 :     void pinMode(uint32_t pin, uint32_t mode) override {
      62             :       HAL_LOG("TestHal::pinMode(pin=" << pin << ", mode=" << mode << " [" << ((mode == TEST_HAL_INPUT) ? "INPUT" : "OUTPUT") << "])");
      63             :       
      64             :       // check the range
      65           5 :       BOOST_ASSERT_MSG(pin < TEST_HAL_NUM_GPIO_PINS, "Pin number out of range");
      66             : 
      67             :       // check known modes
      68           5 :       BOOST_ASSERT_MSG(((mode == TEST_HAL_INPUT) || (mode == TEST_HAL_OUTPUT)), "Invalid pin mode");
      69             : 
      70             :       // set mode
      71           5 :       this->gpio[pin].mode = mode;
      72           5 :     }
      73             : 
      74       12049 :     void digitalWrite(uint32_t pin, uint32_t value) override {
      75             :       HAL_LOG("TestHal::digitalWrite(pin=" << pin << ", value=" << value << " [" << ((value == TEST_HAL_LOW) ? "LOW" : "HIGH") << "])");
      76             : 
      77             :       // check the range
      78       12049 :       BOOST_ASSERT_MSG(pin < TEST_HAL_NUM_GPIO_PINS, "Pin number out of range");
      79             : 
      80             :       // check it is output
      81       12049 :       BOOST_ASSERT_MSG(this->gpio[pin].mode == TEST_HAL_OUTPUT, "GPIO is not output!");
      82             : 
      83             :       // check known values
      84       12049 :       BOOST_ASSERT_MSG(((value == TEST_HAL_LOW) || (value == TEST_HAL_HIGH)), "Invalid output value");
      85             : 
      86             :       // set value
      87       12049 :       this->gpio[pin].value = value;
      88       12049 :       this->gpio[pin].event = true;
      89       12049 :       if(radio) {
      90       12049 :         this->radio->HandleGPIO();
      91             :       }
      92       12049 :       this->gpio[pin].event = false;
      93       12049 :     }
      94             : 
      95        9792 :     uint32_t digitalRead(uint32_t pin) override {
      96             :       HAL_LOG("TestHal::digitalRead(pin=" << pin << ")");
      97             : 
      98             :       // check the range
      99        9792 :       BOOST_ASSERT_MSG(pin < TEST_HAL_NUM_GPIO_PINS, "Pin number out of range");
     100             : 
     101             :       // check it is input
     102        9792 :       BOOST_ASSERT_MSG(this->gpio[pin].mode == TEST_HAL_INPUT, "GPIO is not input");
     103             : 
     104             :       // read the value
     105        9792 :       uint32_t value = this->gpio[pin].value;
     106             :       HAL_LOG("TestHal::digitalRead(pin=" << pin << ")=" << value << " [" << ((value == TEST_HAL_LOW) ? "LOW" : "HIGH") << "]");
     107        9792 :       return(value);
     108             :     }
     109             : 
     110           0 :     void attachInterrupt(uint32_t interruptNum, void (*interruptCb)(void), uint32_t mode) override {
     111             :       HAL_LOG("TestHal::attachInterrupt(interruptNum=" << interruptNum << ", interruptCb=" << interruptCb << ", mode=" << mode << ")");
     112             : 
     113             :       // TODO implement
     114             :       (void)interruptNum;
     115             :       (void)interruptCb;
     116             :       (void)mode;
     117           0 :     }
     118             : 
     119           0 :     void detachInterrupt(uint32_t interruptNum) override {
     120             :       HAL_LOG("TestHal::detachInterrupt(interruptNum=" << interruptNum << ")");
     121             : 
     122             :       // TODO implement
     123             :       (void)interruptNum;
     124           0 :     }
     125             : 
     126          25 :     void delay(unsigned long ms) override {
     127             :       HAL_LOG("TestHal::delay(ms=" << ms << ")");
     128          25 :       const auto start = std::chrono::high_resolution_clock::now();
     129             : 
     130             :       // sleep_for is sufficient for ms-precision sleep
     131          25 :       std::this_thread::sleep_for(std::chrono::duration<unsigned long, std::milli>(ms));
     132             : 
     133             :       // measure and print
     134          25 :       const auto end = std::chrono::high_resolution_clock::now();
     135          25 :       const std::chrono::duration<double, std::milli> elapsed = end - start;
     136             :       HAL_LOG("TestHal::delay(ms=" << ms << ")=" << elapsed.count() << "ms");
     137          25 :     }
     138             : 
     139       30626 :     void delayMicroseconds(unsigned long us) override {
     140             :       HAL_LOG("TestHal::delayMicroseconds(us=" << us << ")");
     141       30626 :       const auto start = std::chrono::high_resolution_clock::now();
     142             : 
     143             :       // busy wait is needed for microseconds precision
     144       30626 :       const auto len = std::chrono::microseconds(us);
     145    40537078 :       while(std::chrono::high_resolution_clock::now() - start < len);
     146             : 
     147             :       // measure and print
     148       30626 :       const auto end = std::chrono::high_resolution_clock::now();
     149       30626 :       const std::chrono::duration<double, std::micro> elapsed = end - start;
     150             :       HAL_LOG("TestHal::delayMicroseconds(us=" << us << ")=" << elapsed.count() << "us");
     151       30626 :     }
     152             : 
     153           0 :     void yield() override {
     154             :       HAL_LOG("TestHal::yield()");
     155           0 :     }
     156             : 
     157        9792 :     unsigned long millis() override {
     158             :       HAL_LOG("TestHal::millis()");
     159        9792 :       std::chrono::time_point now = std::chrono::high_resolution_clock::now();
     160        9792 :       auto res = std::chrono::duration_cast<std::chrono::milliseconds>(now - this->start);
     161             :       HAL_LOG("TestHal::millis()=" << res.count());
     162        9792 :       return(res.count());
     163             :     }
     164             : 
     165        3334 :     unsigned long micros() override {
     166             :       HAL_LOG("TestHal::micros()");
     167        3334 :       std::chrono::time_point now = std::chrono::high_resolution_clock::now();
     168        3334 :       auto res = std::chrono::duration_cast<std::chrono::microseconds>(now - this->start);
     169             :       HAL_LOG("TestHal::micros()=" << res.count());
     170        3334 :       return(res.count());
     171             :     }
     172             : 
     173           0 :     long pulseIn(uint32_t pin, uint32_t state, unsigned long timeout) override {
     174             :       HAL_LOG("TestHal::pulseIn(pin=" << pin << ", state=" << state << ", timeout=" << timeout << ")");
     175             : 
     176             :       // TODO implement
     177             :       (void)pin;
     178             :       (void)state;
     179             :       (void)timeout;
     180           0 :       return(0);
     181             :     }
     182             : 
     183           0 :     void spiBegin() {
     184             :       HAL_LOG("TestHal::spiBegin()");
     185           0 :     }
     186             : 
     187        6022 :     void spiBeginTransaction() {
     188             :       HAL_LOG("TestHal::spiBeginTransaction()");
     189        6022 :     }
     190             : 
     191        6022 :     void spiTransfer(uint8_t* out, size_t len, uint8_t* in) {
     192             :       HAL_LOG("TestHal::spiTransfer(len=" << len << ")");
     193             :       
     194       31746 :       for(size_t i = 0; i < len; i++) {
     195             :         // append to log
     196       25724 :         if(this->spiLogEnabled) {
     197        4192 :           (*this->spiLogPtr++) = out[i];
     198             :         }
     199             : 
     200             :         // process the SPI byte
     201       25724 :         in[i] = this->radio->HandleSPI(out[i]);
     202             : 
     203             :         // artificial delay to emulate SPI running at a finite speed
     204             :         // this is added because timeouts are based on time duration,
     205             :         // so we need to make sure some time actually elapses
     206       25724 :         this->delayMicroseconds(100);
     207             : 
     208             :         // output debug
     209             :         HAL_LOG(fmt::format("out={:#02x}, in={:#02x}", out[i], in[i]));
     210             :       }
     211        6022 :     }
     212             : 
     213        6022 :     void spiEndTransaction() {
     214             :       HAL_LOG("TestHal::spiEndTransaction()");
     215        6022 :     }
     216             : 
     217           0 :     void spiEnd() {
     218             :       HAL_LOG("TestHal::spiEnd()");
     219           0 :     }
     220             : 
     221           0 :     void tone(uint32_t pin, unsigned int frequency, unsigned long duration = 0) {
     222             :       HAL_LOG("TestHal::tone(pin=" << pin << ", frequency=" << frequency << ", duration=" << duration << ")");
     223             : 
     224             :       // TODO implement
     225             :       (void)pin;
     226             :       (void)frequency;
     227             :       (void)duration;
     228           0 :     }
     229             : 
     230           0 :     void noTone(uint32_t pin) {
     231             :       HAL_LOG("TestHal::noTone(pin=" << pin << ")");
     232             : 
     233             :       // TODO implement
     234             :       (void)pin;
     235           0 :     }
     236             : 
     237             :     // method to compare buffer to the internal SPI log, for verifying SPI transactions
     238          12 :     int spiLogMemcmp(const void* in, size_t n) {
     239          12 :       int ret = memcmp(this->spiLog, in, n);
     240          12 :       this->spiLogWipe();
     241          12 :       return(ret);
     242             :     }
     243             : 
     244          17 :     void spiLogWipe() {
     245          17 :       memset(this->spiLog, 0x00, TEST_HAL_SPI_LOG_LENGTH);
     246          17 :       this->spiLogPtr = this->spiLog;
     247          17 :     }
     248             : 
     249             :     // method that "connects" the emualted radio hardware to this HAL
     250           5 :     void connectRadio(EmulatedRadio* r) {
     251           5 :       this->radio = r;
     252           5 :       this->radio->connect(&this->gpio[EMULATED_RADIO_NSS_PIN],
     253             :                            &this->gpio[EMULATED_RADIO_IRQ_PIN],
     254             :                            &this->gpio[EMULATED_RADIO_RST_PIN],
     255             :                            &this->gpio[EMULATED_RADIO_GPIO_PIN]);
     256           5 :     }
     257             : 
     258             :   private:
     259             :     // array of emulated GPIO pins
     260             :     EmulatedPin_t gpio[TEST_HAL_NUM_GPIO_PINS];
     261             : 
     262             :     // start time point
     263             :     std::chrono::time_point<std::chrono::high_resolution_clock> start;
     264             : 
     265             :     // emulated radio hardware
     266             :     EmulatedRadio* radio;
     267             : 
     268             :     // SPI history log
     269             :     uint8_t spiLog[TEST_HAL_SPI_LOG_LENGTH];
     270             :     uint8_t* spiLogPtr;
     271             : };
     272             : 
     273             : #endif

Generated by: LCOV version 1.14