LCOV - code coverage report
Current view: top level - extras/test/unit/include - TestHal.hpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 75 91 82.4 %
Date: 2026-11-08 19:43:08 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           7 :     TestHal() : RadioLibHal(TEST_HAL_INPUT, TEST_HAL_OUTPUT, TEST_HAL_LOW, TEST_HAL_HIGH, TEST_HAL_RISING, TEST_HAL_FALLING) { }
      38             : 
      39           7 :     int16_t init() override {
      40             :       HAL_LOG("TestHal::init()");
      41             : 
      42             :       // save program start timestamp
      43           7 :       start = std::chrono::high_resolution_clock::now();
      44             : 
      45             :       // init emulated GPIO
      46         231 :       for(int i = 0; i < TEST_HAL_NUM_GPIO_PINS; i++) {
      47         224 :         this->gpio[i].mode = 0;
      48         224 :         this->gpio[i].value = 0;
      49         224 :         this->gpio[i].event = false;
      50         224 :         this->gpio[i].func = PIN_UNASSIGNED;
      51             :       }
      52             : 
      53             :       // wipe history log
      54           7 :       this->spiLogWipe();
      55           7 :       return(RADIOLIB_ERR_NONE);
      56             :     }
      57             : 
      58           7 :     int16_t term() override {
      59             :       HAL_LOG("TestHal::term()");
      60           7 :       return(RADIOLIB_ERR_NONE);
      61             :     }
      62             : 
      63           7 :     void pinMode(uint32_t pin, uint32_t mode) override {
      64             :       HAL_LOG("TestHal::pinMode(pin=" << pin << ", mode=" << mode << " [" << ((mode == TEST_HAL_INPUT) ? "INPUT" : "OUTPUT") << "])");
      65             :       
      66             :       // check the range
      67           7 :       BOOST_ASSERT_MSG(pin < TEST_HAL_NUM_GPIO_PINS, "Pin number out of range");
      68             : 
      69             :       // check known modes
      70           7 :       BOOST_ASSERT_MSG(((mode == TEST_HAL_INPUT) || (mode == TEST_HAL_OUTPUT)), "Invalid pin mode");
      71             : 
      72             :       // set mode
      73           7 :       this->gpio[pin].mode = mode;
      74           7 :     }
      75             : 
      76        6863 :     void digitalWrite(uint32_t pin, uint32_t value) override {
      77             :       HAL_LOG("TestHal::digitalWrite(pin=" << pin << ", value=" << value << " [" << ((value == TEST_HAL_LOW) ? "LOW" : "HIGH") << "])");
      78             : 
      79             :       // check the range
      80        6863 :       BOOST_ASSERT_MSG(pin < TEST_HAL_NUM_GPIO_PINS, "Pin number out of range");
      81             : 
      82             :       // check it is output
      83        6863 :       BOOST_ASSERT_MSG(this->gpio[pin].mode == TEST_HAL_OUTPUT, "GPIO is not output!");
      84             : 
      85             :       // check known values
      86        6863 :       BOOST_ASSERT_MSG(((value == TEST_HAL_LOW) || (value == TEST_HAL_HIGH)), "Invalid output value");
      87             : 
      88             :       // set value
      89        6863 :       this->gpio[pin].value = value;
      90        6863 :       this->gpio[pin].event = true;
      91        6863 :       if(radio) {
      92        6863 :         this->radio->HandleGPIO();
      93             :       }
      94        6863 :       this->gpio[pin].event = false;
      95        6863 :     }
      96             : 
      97        6474 :     uint32_t digitalRead(uint32_t pin) override {
      98             :       HAL_LOG("TestHal::digitalRead(pin=" << pin << ")");
      99             : 
     100             :       // check the range
     101        6474 :       BOOST_ASSERT_MSG(pin < TEST_HAL_NUM_GPIO_PINS, "Pin number out of range");
     102             : 
     103             :       // check it is input
     104        6474 :       BOOST_ASSERT_MSG(this->gpio[pin].mode == TEST_HAL_INPUT, "GPIO is not input");
     105             : 
     106             :       // read the value
     107        6474 :       uint32_t value = this->gpio[pin].value;
     108             :       HAL_LOG("TestHal::digitalRead(pin=" << pin << ")=" << value << " [" << ((value == TEST_HAL_LOW) ? "LOW" : "HIGH") << "]");
     109        6474 :       return(value);
     110             :     }
     111             : 
     112           0 :     void attachInterrupt(uint32_t interruptNum, void (*interruptCb)(void), uint32_t mode) override {
     113             :       HAL_LOG("TestHal::attachInterrupt(interruptNum=" << interruptNum << ", interruptCb=" << interruptCb << ", mode=" << mode << ")");
     114             : 
     115             :       // TODO implement
     116             :       (void)interruptNum;
     117             :       (void)interruptCb;
     118             :       (void)mode;
     119           0 :     }
     120             : 
     121           0 :     void detachInterrupt(uint32_t interruptNum) override {
     122             :       HAL_LOG("TestHal::detachInterrupt(interruptNum=" << interruptNum << ")");
     123             : 
     124             :       // TODO implement
     125             :       (void)interruptNum;
     126           0 :     }
     127             : 
     128          23 :     void delay(unsigned long ms) override {
     129             :       HAL_LOG("TestHal::delay(ms=" << ms << ")");
     130          23 :       const auto start = std::chrono::high_resolution_clock::now();
     131             : 
     132             :       // sleep_for is sufficient for ms-precision sleep
     133          23 :       std::this_thread::sleep_for(std::chrono::duration<unsigned long, std::milli>(ms));
     134             : 
     135             :       // measure and print
     136          23 :       const auto end = std::chrono::high_resolution_clock::now();
     137          23 :       const std::chrono::duration<double, std::milli> elapsed = end - start;
     138             :       HAL_LOG("TestHal::delay(ms=" << ms << ")=" << elapsed.count() << "ms");
     139          23 :     }
     140             : 
     141       18906 :     void delayMicroseconds(unsigned long us) override {
     142             :       HAL_LOG("TestHal::delayMicroseconds(us=" << us << ")");
     143       18906 :       const auto start = std::chrono::high_resolution_clock::now();
     144             : 
     145             :       // this is incredibly hacky, but there is no reliable way for sub-ms sleeping
     146       18906 :       std::this_thread::sleep_for(std::chrono::duration<unsigned long, std::milli>(1));
     147             : 
     148             :       // measure and print
     149       18906 :       const auto end = std::chrono::high_resolution_clock::now();
     150       18906 :       const std::chrono::duration<double, std::milli> elapsed = end - start;
     151             : 
     152             :       HAL_LOG("TestHal::delayMicroseconds(us=" << us << ")=" << elapsed.count() << "ms");
     153             :       (void)us;
     154       18906 :     }
     155             : 
     156           0 :     void yield() override {
     157             :       HAL_LOG("TestHal::yield()");
     158           0 :     }
     159             : 
     160        6474 :     unsigned long millis() override {
     161             :       HAL_LOG("TestHal::millis()");
     162        6474 :       std::chrono::time_point now = std::chrono::high_resolution_clock::now();
     163        6474 :       auto res = std::chrono::duration_cast<std::chrono::milliseconds>(now - this->start);
     164             :       HAL_LOG("TestHal::millis()=" << res.count());
     165        6474 :       return(res.count());
     166             :     }
     167             : 
     168         732 :     unsigned long micros() override {
     169             :       HAL_LOG("TestHal::micros()");
     170         732 :       std::chrono::time_point now = std::chrono::high_resolution_clock::now();
     171         732 :       auto res = std::chrono::duration_cast<std::chrono::microseconds>(now - this->start);
     172             :       HAL_LOG("TestHal::micros()=" << res.count());
     173         732 :       return(res.count());
     174             :     }
     175             : 
     176           0 :     long pulseIn(uint32_t pin, uint32_t state, unsigned long timeout) override {
     177             :       HAL_LOG("TestHal::pulseIn(pin=" << pin << ", state=" << state << ", timeout=" << timeout << ")");
     178             : 
     179             :       // TODO implement
     180             :       (void)pin;
     181             :       (void)state;
     182             :       (void)timeout;
     183           0 :       return(0);
     184             :     }
     185             : 
     186           0 :     int16_t spiBegin() {
     187             :       HAL_LOG("TestHal::spiBegin()");
     188           0 :       return(RADIOLIB_ERR_NONE);
     189             :     }
     190             : 
     191        3428 :     void spiBeginTransaction() {
     192             :       HAL_LOG("TestHal::spiBeginTransaction()");
     193        3428 :     }
     194             : 
     195        3428 :     int16_t spiTransfer(uint8_t* out, size_t len, uint8_t* in) {
     196             :       HAL_LOG("TestHal::spiTransfer(len=" << len << ")");
     197             :       
     198       19091 :       for(size_t i = 0; i < len; i++) {
     199             :         // append to log
     200       15663 :         if(this->spiLogEnabled) {
     201         455 :           (*this->spiLogPtr++) = out[i];
     202             :         }
     203             : 
     204             :         // process the SPI byte
     205       15663 :         in[i] = this->radio->HandleSPI(out[i]);
     206             : 
     207             :         // artificial delay to emulate SPI running at a finite speed
     208             :         // this is added because timeouts are based on time duration,
     209             :         // so we need to make sure some time actually elapses
     210       15663 :         this->delayMicroseconds(100);
     211             : 
     212             :         // output debug
     213             :         HAL_LOG(fmt::format("out={:#02x}, in={:#02x}", out[i], in[i]));
     214             :       }
     215        3428 :       return(RADIOLIB_ERR_NONE);
     216             :     }
     217             : 
     218        3428 :     void spiEndTransaction() {
     219             :       HAL_LOG("TestHal::spiEndTransaction()");
     220        3428 :     }
     221             : 
     222           0 :     int16_t spiEnd() {
     223             :       HAL_LOG("TestHal::spiEnd()");
     224           0 :       return(RADIOLIB_ERR_NONE);
     225             :     }
     226             : 
     227           0 :     void tone(uint32_t pin, unsigned int frequency, unsigned long duration = 0) {
     228             :       HAL_LOG("TestHal::tone(pin=" << pin << ", frequency=" << frequency << ", duration=" << duration << ")");
     229             : 
     230             :       // TODO implement
     231             :       (void)pin;
     232             :       (void)frequency;
     233             :       (void)duration;
     234           0 :     }
     235             : 
     236           0 :     void noTone(uint32_t pin) {
     237             :       HAL_LOG("TestHal::noTone(pin=" << pin << ")");
     238             : 
     239             :       // TODO implement
     240             :       (void)pin;
     241           0 :     }
     242             : 
     243             :     // method to compare buffer to the internal SPI log, for verifying SPI transactions
     244          12 :     int spiLogMemcmp(const void* in, size_t n) {
     245          12 :       int ret = memcmp(this->spiLog, in, n);
     246          12 :       this->spiLogWipe();
     247          12 :       return(ret);
     248             :     }
     249             : 
     250          19 :     void spiLogWipe() {
     251          19 :       memset(this->spiLog, 0x00, TEST_HAL_SPI_LOG_LENGTH);
     252          19 :       this->spiLogPtr = this->spiLog;
     253          19 :     }
     254             : 
     255             :     // method that "connects" the emualted radio hardware to this HAL
     256           7 :     void connectRadio(EmulatedRadio* r) {
     257           7 :       this->radio = r;
     258           7 :       this->radio->connect(&this->gpio[EMULATED_RADIO_NSS_PIN],
     259             :                            &this->gpio[EMULATED_RADIO_IRQ_PIN],
     260             :                            &this->gpio[EMULATED_RADIO_RST_PIN],
     261             :                            &this->gpio[EMULATED_RADIO_GPIO_PIN]);
     262           7 :     }
     263             : 
     264             :   private:
     265             :     // array of emulated GPIO pins
     266             :     EmulatedPin_t gpio[TEST_HAL_NUM_GPIO_PINS];
     267             : 
     268             :     // start time point
     269             :     std::chrono::time_point<std::chrono::high_resolution_clock> start;
     270             : 
     271             :     // emulated radio hardware
     272             :     EmulatedRadio* radio;
     273             : 
     274             :     // SPI history log
     275             :     uint8_t spiLog[TEST_HAL_SPI_LOG_LENGTH];
     276             :     uint8_t* spiLogPtr;
     277             : };
     278             : 
     279             : #endif

Generated by: LCOV version 1.14