diff --git a/components/freeRtosApi/CMakeLists.txt b/components/freeRtosApi/CMakeLists.txt new file mode 100644 index 0000000..e02288d --- /dev/null +++ b/components/freeRtosApi/CMakeLists.txt @@ -0,0 +1,4 @@ +idf_component_register( + SRC_DIRS "src" + INCLUDE_DIRS "include" +) diff --git a/components/freeRtosApi/examples/fullDemo.cpp b/components/freeRtosApi/examples/fullDemo.cpp new file mode 100644 index 0000000..9a5ce47 --- /dev/null +++ b/components/freeRtosApi/examples/fullDemo.cpp @@ -0,0 +1,119 @@ +/** + * @file fullDemo.cpp + * @author Gustice + * @brief Coherent demonstration of FreeRTOS-Abstraction in small environment + * @copyright Copyright (c) 2024 + */ + + +#include "esp_log.h" +#include "esp_timer.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "sdkconfig.h" +#include + +constexpr int StackSize = 1024; +constexpr int LED = 2; + +#define TerminalPrint(format, ...) ESP_LOGI("main", format, __VA_ARGS__) + +#include "Mutex.hpp" +#include "Queue.hpp" +#include "Semaphore.hpp" +#include "Task.hpp" +#include + +using namespace fos; +using namespace std::literals::string_view_literals; + +constexpr int WAIT = 1000; + +/// @brief Arbitrary definition for message type (to be used as queue template parameter) +struct TxMsg { + TxMsg(uint32_t c, std::string m) : cnt(c), message(m){}; + uint32_t cnt; + std::string message; +}; + +/// @brief Global semaphore for blink tasks +Semaphore blinkSem; + +void setupLedPin(); +void toggleLedPin(); + +/// @brief Parameterless task entry for generation of blink signals +/// @note uses global semaphore +void TaskBlinkProvider() { + TerminalPrint("starting blink provider task"); + while (true) { + Task::delayMs(WAIT); // wait + blinkSem.give(); // then signal a blink event + } +} + +/// @brief Parameterless task entry for execution of blink signals +/// @note uses global semaphore +void TaskBlink() { + TerminalPrint("starting blink execution task"); + setupLedPin(); + while (true) { + blinkSem.take(); // wait for blink event signal + toggleLedPin(); // execute blink event + Task::delayMs(100); // wait + toggleLedPin(); // execute blink event + TerminalPrint("Blink tick"); + } +} + +/// @brief Parametrized task entry for message provider (generates message object) +/// @param queue reference to appropriate queue +void MessageProvider(Queue &queue) { + uint32_t cnt{}; + while (true) { + auto e = std::make_unique(cnt++, "tick"); + queue.enqueue(std::move(e)); + Task::delayMs(WAIT); + } +} + +/// @brief Parametrized task entry for message consumer (sends message over UART) +/// @param queue reference to appropriate queue +void MessageConsumer(Queue &queue) { + while (true) { + auto msg = queue.dequeue(); // Wait with infinit timeout, guaranteed to succeed + TerminalPrint("Message: %s %lu", msg->message.c_str(), msg->cnt); + } +} + +void setupTasks() { + /// @note: All definitions as static instances to not be destructed after loosing scope + static Queue txQueue(8); + static Task blinkerTask(TaskBlink, "BlinkExec", StackSize); + static Task blinkeProviderTask(TaskBlinkProvider, "BlinkFlags", StackSize); + + static TaskT &> tickerTask(MessageProvider, txQueue, "Tick", StackSize); + static TaskT &> senderTask(MessageConsumer, txQueue, "Sender", StackSize); +} + + +extern "C" { // This switch allows the ROS C-implementation to find this main +void app_main(void); +} + +void app_main() { + void setupTasks(); + + while (true) { + ESP_LOGI("main", "healt tick"); + vTaskDelay(1000 / portTICK_PERIOD_MS); + } +} + +void setupLedPin() { + // no Buildin-led on XIAO C3 +} +void toggleLedPin() { + // no Buildin-led on XIAO C3 +} + diff --git a/components/freeRtosApi/include/Mutex.hpp b/components/freeRtosApi/include/Mutex.hpp new file mode 100644 index 0000000..a9f8e83 --- /dev/null +++ b/components/freeRtosApi/include/Mutex.hpp @@ -0,0 +1,87 @@ +/** + * @file mutex.hpp + * @author Gustice + * @brief Mutex Wrapper + * + * @copyright Copyright (c) 2024 + */ + +#pragma once + +#include +#include + +namespace fos { + + /** + * @brief Mutex + * @details FreeRTOS implementation uses already priority inheritance + * and are recursively callable + */ + class Mutex { + private: + QueueHandle_t _handle; + + public: + /// @brief Tick type (redefinition for convenience) + using Tick = TickType_t; + /// @brief Definition for endless waittime + static constexpr Tick MaxDelay = portMAX_DELAY; + + /// @brief Constructor + Mutex() { + _handle = xSemaphoreCreateMutex(); + assert(_handle != 0 && "Semaphore create must finish successfully"); + } + + ~Mutex() { + vSemaphoreDelete(_handle); + }; + + /// @brief Relase mutex + void release() { + auto ret = xSemaphoreGive(_handle); + assert(ret == pdPASS && "Underlying semaphore queue must be initialized correcty"); + } + + /// @brief Take mutex + /// @param timeout Timeout to wait for signal (default = wait forever) + /// @return true if successfully claimed (not timeout) + bool claim(Tick timeout = MaxDelay) { + auto ret = xSemaphoreTake(_handle, timeout); + return ret == pdPASS; + } + }; + + /** + * @brief Mutex Guard. Self releasing mutex for atomic operation + */ + class MutexGuard { + private: + Mutex &_mutex; + bool _claimed; + + public: + /// @brief Constructor + /// @param mtx Mutex to claim + /// @param timeout Timeout to wait for signal (default = wait forever) + MutexGuard(Mutex &mtx, Mutex::Tick timeout = Mutex::MaxDelay) : _mutex(mtx) { + _claimed = _mutex.claim(timeout); + }; + + ~MutexGuard() { + if (!_claimed) { + return; + } + + _mutex.release(); + } + + /// @brief Check if mutex was claimed successfully + /// @return true if claimed + bool isActive() { + return _claimed; + } + }; + +} // namespace fos diff --git a/components/freeRtosApi/include/Queue.hpp b/components/freeRtosApi/include/Queue.hpp new file mode 100644 index 0000000..fa20bb6 --- /dev/null +++ b/components/freeRtosApi/include/Queue.hpp @@ -0,0 +1,98 @@ +/** + * @file queue.hpp + * @author Gustice + * @brief Queue Wrapper + * + * @copyright Copyright (c) 2024 + */ + +#pragma once + +#include +#include +#include +#include + +namespace fos { + + /** + * @brief Message Queue + * + * @tparam T Type for message payload + */ + template + class Queue { + private: + bool _isActive; + QueueHandle_t xQueue; + + public: + /// @brief Tick type (redefinition for convenience) + using Tick = TickType_t; + /// @brief Definition for endless waittime + static constexpr Tick MaxDelay = portMAX_DELAY; + + /// @brief Constructor + /// @param size number of slots in queue + Queue(size_t size) { + xQueue = xQueueCreate(size, sizeof(T *)); + assert(xQueue != 0 && "Queue create must finish successfully"); + _isActive = true; + } + + ~Queue() { + vQueueDelete(xQueue); + } + + /// @brief Push new message in queue + /// @param message payload + /// @return nullptr if successful else give back pointer to queued element if pushing failed + std::unique_ptr enqueue(std::unique_ptr message) { + if (uxQueueSpacesAvailable(xQueue) == 0) { + return message; + } + T *o = message.release(); + auto ret = xQueueSend(xQueue, &o, (TickType_t)0); + assert(ret == pdPASS && "Underlying semaphore queue must be initialized correcty"); + + return message; + } + + /// @brief Pop message from queue + /// @param timeout timeout to wait for message (default: wait for ever) + /// @return pointer to dequeued element + std::unique_ptr dequeue(Tick timeout = MaxDelay) { + T *pMsg = nullptr; + if (!xQueueReceive(xQueue, &pMsg, timeout)) { + return {nullptr}; + } + return std::unique_ptr(pMsg); + } + + /// @brief Pop latest element from queue and ignore all older elements + /// @param timeout timeout to wait for message (default: wait for ever) + /// @return pointer to dequeued element + std::unique_ptr dequeueLatest(Tick timeout = MaxDelay) { + T *pMsg = nullptr; + if (!xQueueReceive(xQueue, &pMsg, timeout)) { + return {nullptr}; + } + + while (true == xQueueReceive(xQueue, &pMsg, (TickType_t)0)) { + } + return std::unique_ptr(pMsg); + } + + /// @brief terminate message queue + void signalClose() { + _isActive = false; + } + + /// @brief Check if message queue is active + /// @return true if active + bool isActive() { + return _isActive; + } + }; + +} // namespace fos \ No newline at end of file diff --git a/components/freeRtosApi/include/Semaphore.hpp b/components/freeRtosApi/include/Semaphore.hpp new file mode 100644 index 0000000..4993c7d --- /dev/null +++ b/components/freeRtosApi/include/Semaphore.hpp @@ -0,0 +1,69 @@ +/** + * @file semaphore.hpp + * @author Gustice + * @brief Semaphore Wrapper + * + * @copyright Copyright (c) 2024 + */ +#pragma once + +#include +#include + +namespace fos { + + /** + * @brief Semaphore + */ + class Semaphore { + private: + enum Type { + Binary, ///< Binary semaphore, can only be true or false, no matter how often given + Counting ///< Counting semaphore, keeps track of number of give and takes + }; + + const Type SemType; + QueueHandle_t _handle; + + public: + /// @brief Tick type (redefinition for convenience) + using Tick = TickType_t; + + /// @brief Definition for endless waittime + static constexpr Tick MaxDelay = portMAX_DELAY; + + /// @brief Constructor for binary semaphore + Semaphore() : SemType(Semaphore::Binary) { + _handle = xSemaphoreCreateBinary(); + assert(_handle != 0 && "Semaphore create must finish successfully"); + } + + /// @brief Constructor for conting semaphore + /// @param maxCount Counting limit + /// @param init Initial count + Semaphore(uint32_t maxCount, uint32_t init = 0) : SemType(Semaphore::Counting) { + _handle = xSemaphoreCreateCounting(maxCount, init); + assert(_handle != 0 && "Semaphore create must finish successfully"); + } + + ~Semaphore() { + vSemaphoreDelete(_handle); + }; + + /// @brief Give signal + /// @note Binary semaphore can only be effectively given once + void give() { + // checking for error would lead to false positives in case of multiple gives ... + xSemaphoreGive(_handle); + } + + /// @brief Consume signal + /// @param timeout Timeout to wait for signal (default = wait forever) + /// @return true if successfully consumed (not timeout) + bool take(Tick timeout = MaxDelay) { + auto ret = xSemaphoreTake(_handle, timeout); + return ret == pdPASS; + } + }; + +} // namespace fos diff --git a/components/freeRtosApi/include/Task.hpp b/components/freeRtosApi/include/Task.hpp new file mode 100644 index 0000000..27205c9 --- /dev/null +++ b/components/freeRtosApi/include/Task.hpp @@ -0,0 +1,184 @@ +/** + * @file task.hpp + * @author Gustice + * @brief Task Wrapper + * + * @copyright Copyright (c) 2024 + */ + +#pragma once + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include +#include +#include +#define DEFAULT_STACKSIZE 0x1000uL + +namespace fos { + /** + * @brief Base class for tasks + */ + class TaskBase { + protected: + TaskHandle_t _handle; + + static void disposeFrameConditionally() { + if constexpr (INCLUDE_vTaskDelete != 0) { + vTaskDelete(NULL); + // NOTE: This only compiles if symbol INCLUDE_vTaskDelete is defined + } + } + + Semaphore _finished; + + public: + /// @brief Priority type (redefinition for convenience) + using Priority = unsigned short; + /// @brief Tick type (redefinition for convenience) + using Tick = TickType_t; + /// @brief Definition for endless waittime + static constexpr Tick MaxDelay = portMAX_DELAY; + + /// @brief Maximum delay + static constexpr int MaximumDelay = portMAX_DELAY; + + /// @brief Default Stack size + static constexpr size_t DefaultStackSize = DEFAULT_STACKSIZE; + + /// @brief Lowest priority for new tasks + static constexpr Priority MIN_PRIORITY = tskIDLE_PRIORITY + 1; + + /// @brief Highest priority for new tasks + static constexpr Priority MAX_PRIORITY = configMAX_PRIORITIES; + + /// @brief Kill a task + /// @note It can lead to undefined behaviour when one task deletes another task on a + /// multi-core system. Hence a task should clear itself. + void kill() { // NOTE: this leads to undefined behavior if interferes with Destructor + vTaskDelete(_handle); + } + + /// @brief Join threads + /// @param timeout timeout to wait for join else kill + /// @return joined successfully + bool join(Tick timeout = MaxDelay) { + auto success = _finished.take(timeout); + if(not success) { + kill(); + } + return success; + } + + /// @brief Get task state + /// @return FreeRTOS Task state + eTaskState getState() { + return eTaskGetState(_handle); + } + + /// @brief Get task handle + /// @return FreeRTOS Task handle + TaskHandle_t getHandle() { + return _handle; + } + + /// @brief Delay function + /// @param msToDelay Milliseconds to delay + /// @note The minimal resolution for the delay is tick based + static void delayMs(unsigned int msToDelay) { + // pdMS_TO_TICKS(_timeout) + vTaskDelay(msToDelay / portTICK_PERIOD_MS); + } + + /// @brief Delay function + /// @param ticksToDelay Ticks to delay + static void delayTicks(unsigned int ticksToDelay) { + vTaskDelay(ticksToDelay); + } + + virtual ~TaskBase() { + kill(); + } + }; + + /** + * @brief Simple task without task parameter + */ + class Task : public TaskBase { + private: + /// @brief Signature for task-implementation-callback + using TaskSignature = void (*)(); + + static void TaskFrame(void *_this) { + auto &task = *reinterpret_cast(_this); + task._call(); + // Tasks shall not return, however if this happens dispose task + disposeFrameConditionally(); + task._finished.give(); + } + + TaskSignature _call; + + public: + /// @brief Constructor for task without passed argument + /// @param call Function pointer to implementation + /// @param name Name of task + /// @param stack Stacksize if not default + /// @param prio Priority if not default + Task(TaskSignature call, const char *name, size_t stack = DefaultStackSize, + Priority prio = MIN_PRIORITY) + : _call{call} { + auto ret = xTaskCreate(TaskFrame, name, stack, this, MIN_PRIORITY, &_handle); + assert(pdPASS == ret && "Task create must finish successfully"); + } + }; + + /** + * @brief Task with task parameter + * @tparam T Type of task parameter on call + */ + template + class TaskT : public TaskBase { + private: + /// @brief Signature for task-implementation-callback + using TaskSignature = void (*)(T); + + template + struct Param { + TaskSignature call; + const TS &arg; + }; + Param _param; + + static void TaskFrame(void *_this) { + auto &task = *reinterpret_cast(_this); + auto ¶m = task._param; + param.call(param.arg); + // Tasks shall not return, however if this happens dispose task + disposeFrameConditionally(); + task._finished.give(); + } + + public: + /// @brief Constructor for task with passed argument + /// @param call Function pointer to implementation + /// @param param Parameter ot pass task implementation + /// @param name Name of task + /// @param stack Stacksize if not default + /// @param prio Priority if not default + TaskT(TaskSignature call, const T ¶m, const char *name, size_t stack = DefaultStackSize, + Priority prio = MIN_PRIORITY) + : _param{call, param} { + auto ret = xTaskCreate(TaskFrame, name, stack, this, MIN_PRIORITY, &_handle); + assert(pdPASS == ret && "Task create must finish successfully"); + } + }; + + class Timing { + public: + static uint32_t getTickCount() { + return xTaskGetTickCount(); + } + }; + +} // namespace fos diff --git a/components/freeRtosApi/src/freeRtosApi.cpp b/components/freeRtosApi/src/freeRtosApi.cpp new file mode 100644 index 0000000..ebdac66 --- /dev/null +++ b/components/freeRtosApi/src/freeRtosApi.cpp @@ -0,0 +1,7 @@ +/** + * @file freeRtosApi.cpp + * @author Gustice + * @brief FreeRtos wrapper + * + * @copyright Copyright (c) 2024 + */ \ No newline at end of file diff --git a/components/freeRtosApi/test/CMakeLists.txt b/components/freeRtosApi/test/CMakeLists.txt new file mode 100644 index 0000000..4a1929f --- /dev/null +++ b/components/freeRtosApi/test/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register( + SRC_DIRS "." + INCLUDE_DIRS "." + REQUIRES cmock freeRtosApi esp_timer +) diff --git a/components/freeRtosApi/test/test.hpp b/components/freeRtosApi/test/test.hpp new file mode 100644 index 0000000..cf3ebbd --- /dev/null +++ b/components/freeRtosApi/test/test.hpp @@ -0,0 +1,30 @@ +#include "esp_log.h" +#include "esp_timer.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "sdkconfig.h" +#include +constexpr int StackSize = 1024; + +#include + +class TimeTest { + private: + bool running{}; + TickType_t _begin; + + public: + TimeTest() { + start(); + } + + void start() { + _begin = xTaskGetTickCount(); + running = true; + } + + + TickType_t getRunTime() { + return xTaskGetTickCount() - _begin; + } +}; \ No newline at end of file diff --git a/components/freeRtosApi/test/test_mutex.cpp b/components/freeRtosApi/test/test_mutex.cpp new file mode 100644 index 0000000..771e49e --- /dev/null +++ b/components/freeRtosApi/test/test_mutex.cpp @@ -0,0 +1,85 @@ +#include "test.hpp" + +#include +#include +#include + +using namespace fos; + +constexpr int SHORT_DELAY = 10; + +TEST_CASE("Test mutexes", "[freeRtos][mutex]") { + Mutex eut; + // first take fails + TEST_ASSERT_TRUE(eut.claim(0)); + eut.release(); + + TEST_ASSERT_TRUE(eut.claim(0)); + TEST_ASSERT_FALSE(eut.claim(0)); + + eut.release(); + // eut.release(); // not a good idea + TEST_ASSERT_TRUE(eut.claim(0)); + // second take after give fails + TEST_ASSERT_FALSE(eut.claim(0)); +} + +static Semaphore starter1; +static void testClaimer(Mutex &m) { + m.claim(); + starter1.give(); + vTaskDelay(SHORT_DELAY); + m.release(); + vTaskDelay(SHORT_DELAY); + m.claim(); + starter1.give(); + while (true) { + vTaskDelay(SHORT_DELAY); + }; +} + +TEST_CASE("Test mutex timing", "[freeRtos][mutex]") { + static Mutex eut; + + TaskT claimer(testClaimer, eut, "signal1"); + starter1.take(100); + { // All ok + TimeTest timer; + TEST_ASSERT_TRUE(eut.claim(100)); + TEST_ASSERT_INT_WITHIN(1, SHORT_DELAY, timer.getRunTime()); + eut.release(); + } + starter1.take(100); + { // Timeout + TimeTest timer; + TEST_ASSERT_FALSE(eut.claim(20)); + TEST_ASSERT_INT_WITHIN(1, 20, timer.getRunTime()); + } + + { // NoWeit timeout + Mutex passiveSem; + passiveSem.claim(0); + TimeTest timer; + TEST_ASSERT_FALSE(passiveSem.claim(0)); + TEST_ASSERT_INT_WITHIN(1, 0, timer.getRunTime()); + } +} + +TEST_CASE("Test mutex guard", "[freeRtos][mutex]") { + Mutex mtx; + { + MutexGuard eut(mtx, 100); + TEST_ASSERT_TRUE(eut.isActive()); + // already taken + TEST_ASSERT_FALSE(mtx.claim(0)); + } + // now free to go + TEST_ASSERT_TRUE(mtx.claim(0)); + { // Timeout + TimeTest timer; + MutexGuard eut(mtx, SHORT_DELAY); + TEST_ASSERT_FALSE(eut.isActive()); + TEST_ASSERT_INT_WITHIN(1, SHORT_DELAY, timer.getRunTime()); + } +} + diff --git a/components/freeRtosApi/test/test_queue.cpp b/components/freeRtosApi/test/test_queue.cpp new file mode 100644 index 0000000..61cafe3 --- /dev/null +++ b/components/freeRtosApi/test/test_queue.cpp @@ -0,0 +1,155 @@ +#include "test.hpp" + +#include +#include +#include + +using namespace fos; + +constexpr int SHORT_DELAY = 10; + +struct MessageFrame { + MessageFrame(int v, std::string msg) : value(v), message(msg) {} + int value; + std::string message; +}; + +TEST_CASE("Test enqueueing then dequeueing", "[freeRtos][queue]") { + Queue eut(10); + + auto e = std::make_unique(1, "one"); + eut.enqueue(std::move(e)); + + auto r = eut.dequeue(100); + TEST_ASSERT_NOT_NULL(r.get()); + TEST_ASSERT_EQUAL(1, r->value); + TEST_ASSERT_TRUE(r->message == "one"); +} + +TEST_CASE("Test enqueueing then dequeue multiple", "[freeRtos][queue]") { + Queue eut(10); + + auto e = std::make_unique(1, "one"); + eut.enqueue(std::move(e)); + e = std::make_unique(2, "two"); + eut.enqueue(std::move(e)); + e = std::make_unique(3, "three"); + eut.enqueue(std::move(e)); + + auto r = eut.dequeue(100); + TEST_ASSERT_NOT_NULL(r.get()); + TEST_ASSERT_EQUAL(1, r->value); + TEST_ASSERT_TRUE(r->message == "one"); + r = eut.dequeue(100); + TEST_ASSERT_NOT_NULL(r.get()); + TEST_ASSERT_EQUAL(2, r->value); + TEST_ASSERT_TRUE(r->message == "two"); + r = eut.dequeue(100); + TEST_ASSERT_NOT_NULL(r.get()); + TEST_ASSERT_EQUAL(3, r->value); + TEST_ASSERT_TRUE(r->message == "three"); +} + +TEST_CASE("Test enqueue cyclically", "[freeRtos][queue]") { + constexpr int Size = 10; + Queue eut(Size); + for (size_t i = 0; i < Size * 2; i++) { + auto e = std::make_unique(i, "Some message"); + eut.enqueue(std::move(e)); + + auto r = eut.dequeue(100); + TEST_ASSERT_NOT_NULL(r.get()); + TEST_ASSERT_EQUAL(i, r->value); + } + TEST_ASSERT_TRUE(true); +} + +TEST_CASE("Test enqueueing till overflow", "[freeRtos][queue]") { + Queue eut(3); + + auto e = std::make_unique(1, "one"); + auto r = eut.enqueue(std::move(e)); + TEST_ASSERT_EQUAL(nullptr, r.get()); + + e = std::make_unique(2, "two"); + r = eut.enqueue(std::move(e)); + TEST_ASSERT_EQUAL(nullptr, r.get()); + + e = std::make_unique(3, "three"); + r = eut.enqueue(std::move(e)); + TEST_ASSERT_EQUAL(nullptr, r.get()); + + e = std::make_unique(4, "four"); + auto pE = e.get(); + r = eut.enqueue(std::move(e)); + TEST_ASSERT_NOT_EQUAL(nullptr, r.get()); + // also return old element ... + TEST_ASSERT_EQUAL(pE, r.get()); +} + +TEST_CASE("Test enqueueing then dequeue multiple", "[freeRtos][queue]") { + Queue eut(10); + + auto e = std::make_unique(1, "one"); + eut.enqueue(std::move(e)); + e = std::make_unique(2, "two"); + eut.enqueue(std::move(e)); + + auto r = eut.dequeue(100); + TEST_ASSERT_NOT_NULL(r.get()); + TEST_ASSERT_EQUAL(1, r->value); + r = eut.dequeue(100); + TEST_ASSERT_NOT_NULL(r.get()); + TEST_ASSERT_EQUAL(2, r->value); + r = eut.dequeue(0); // no waiting + TEST_ASSERT_EQUAL(nullptr, r.get()); +} + +static void queue1Gen(Queue &q) { + vTaskDelay(SHORT_DELAY); + auto e = std::make_unique(0, "msg"); + q.enqueue(std::move(e)); + while (true) { + vTaskDelay(SHORT_DELAY); + }; +} + +TEST_CASE("Test timing of queue", "[freeRtos][queue]") { + static Queue eut(8); + TaskT &> signaler(queue1Gen, eut, "queue1Gen"); + { // All ok + TimeTest timer; + auto r = eut.dequeue(100); + TEST_ASSERT_NOT_NULL(r.get()); + TEST_ASSERT_INT_WITHIN(1, SHORT_DELAY, timer.getRunTime()); + } + + { // Timeout + TimeTest timer; + auto r = eut.dequeue(20); + TEST_ASSERT_NULL(r.get()); + TEST_ASSERT_INT_WITHIN(1, 20, timer.getRunTime()); + } +} + +static void senderTask(Queue &q) { + size_t i = 0; + while (q.isActive()) { + auto e = std::make_unique(i++, "Some message"); + q.enqueue(std::move(e)); + vTaskDelay(1); + } +} + +TEST_CASE("Test enqueueing series of message", "[freeRtos][queue]") { + constexpr int Size = 10; + Queue eut(Size); + TaskT &> sender(senderTask, eut, "sender"); + for (size_t i = 0; i < Size * 2; i++) { + auto r = eut.dequeue(100); + TEST_ASSERT_NOT_NULL(r.get()); + TEST_ASSERT_EQUAL(i, r->value); + } + eut.signalClose(); + TEST_ASSERT_TRUE(true); +} diff --git a/components/freeRtosApi/test/test_semaphore.cpp b/components/freeRtosApi/test/test_semaphore.cpp new file mode 100644 index 0000000..10a9acb --- /dev/null +++ b/components/freeRtosApi/test/test_semaphore.cpp @@ -0,0 +1,119 @@ +#include "test.hpp" + +#include +#include + +using namespace fos; + +constexpr int SHORT_DELAY = 10; + +TEST_CASE("Test binary semaphore", "[freeRtos][sem]") { + Semaphore eut; + // first take fails + TEST_ASSERT_FALSE(eut.take(0)); + eut.give(); + // first take after give fails + TEST_ASSERT_TRUE(eut.take(0)); + TEST_ASSERT_FALSE(eut.take(0)); + + eut.give(); + eut.give(); + TEST_ASSERT_TRUE(eut.take(0)); + // second take after give fails + TEST_ASSERT_FALSE(eut.take(0)); +} + +TEST_CASE("Test counting semaphore", "[freeRtos][sem]") { + Semaphore eut(2, 0); + // first take fails + TEST_ASSERT_FALSE(eut.take(0)); + eut.give(); + // first take after give fails + TEST_ASSERT_TRUE(eut.take(0)); + TEST_ASSERT_FALSE(eut.take(0)); + + eut.give(); + eut.give(); + TEST_ASSERT_TRUE(eut.take(0)); + TEST_ASSERT_TRUE(eut.take(0)); + // third take after give fails + TEST_ASSERT_FALSE(eut.take(0)); + + eut.give(); + eut.give(); + eut.give(); + TEST_ASSERT_TRUE(eut.take(0)); + TEST_ASSERT_TRUE(eut.take(0)); + // still third take after give fails + TEST_ASSERT_FALSE(eut.take(0)); +} + +TEST_CASE("Test preset counting semaphore", "[freeRtos][sem]") { + Semaphore eut(2, 2); + TEST_ASSERT_TRUE(eut.take(0)); + TEST_ASSERT_TRUE(eut.take(0)); + // third take fails + TEST_ASSERT_FALSE(eut.take(0)); + + eut.give(); + // first take after give fails + TEST_ASSERT_TRUE(eut.take(0)); + TEST_ASSERT_FALSE(eut.take(0)); + + // ... +} + +static void test1Signal(Semaphore &s) { + vTaskDelay(SHORT_DELAY); + s.give(); + while (true) { + vTaskDelay(SHORT_DELAY); + }; +} + +TEST_CASE("Test timing binary semaphore", "[freeRtos][sem]") { + static Semaphore eut; + + TaskT signaler(test1Signal, eut, "signal1"); + { // All ok + TimeTest timer; + TEST_ASSERT_TRUE(eut.take(100)); + TEST_ASSERT_INT_WITHIN(1, SHORT_DELAY, timer.getRunTime()); + } + + { // Timeout + TimeTest timer; + TEST_ASSERT_FALSE(eut.take(20)); + TEST_ASSERT_INT_WITHIN(1, 20, timer.getRunTime()); + } + + { // NoWait timeout + Semaphore passiveSem; + TimeTest timer; + TEST_ASSERT_FALSE(passiveSem.take(0)); + TEST_ASSERT_INT_WITHIN(1, 0, timer.getRunTime()); + } +} + +static int syncCount = 0; +static void producerTask(Semaphore &s) { + while (true) { + s.give(); + vTaskDelay(SHORT_DELAY); + }; +} +static void consumerTask(Semaphore &s) { + while (true) { + s.take(); + syncCount++; + }; +} + +TEST_CASE("Test if tasks signaling with semaphore", "[freeRtos][sem]") { + static Semaphore eut; + + TaskT producer(producerTask, eut, "producer"); + TaskT consumer(consumerTask, eut, "consumer"); + vTaskDelay(5 * SHORT_DELAY); + TEST_ASSERT_INT_WITHIN(1, 5, syncCount); +} diff --git a/components/freeRtosApi/test/test_task.cpp b/components/freeRtosApi/test/test_task.cpp new file mode 100644 index 0000000..cf9ab08 --- /dev/null +++ b/components/freeRtosApi/test/test_task.cpp @@ -0,0 +1,88 @@ +#include "test.hpp" + +#include +#include + +using namespace fos; + +TEST_CASE("Test Delay", "[freeRtos][task]") { + TickType_t start = xTaskGetTickCount(); + Task::delayMs(5 * portTICK_PERIOD_MS); + TickType_t end = xTaskGetTickCount(); + TEST_ASSERT_GREATER_OR_EQUAL(5, (end - start)); + TEST_ASSERT_LESS_OR_EQUAL(6, (end - start)); +} + +static bool taskWasCalled = false; +static void callableTask() { + taskWasCalled = true; + while (true) { + vTaskDelay(100); + } +} + +TEST_CASE("Test creation and deletion", "[freeRtos][task]") { + static Task task(callableTask, "task1"); + Task::delayMs(50); + TEST_ASSERT_TRUE(taskWasCalled); + task.kill(); +} + +static void callableTask2() { + taskWasCalled = true; + while (true) { + vTaskDelay(100); + } +} + +TEST_CASE("Test if task can be killed", "[freeRtos][task]") { + static Task task(callableTask2, "task2"); + auto handle = task.getHandle(); + configASSERT(handle); + TEST_ASSERT_TRUE(eTaskState::eReady == task.getState() || + eTaskState::eBlocked == task.getState()); + Task::delayMs(50); + TEST_ASSERT_TRUE(taskWasCalled); + task.kill(); + TEST_ASSERT_EQUAL(eTaskState::eDeleted, eTaskGetState(handle)); +} + +static std::array values; + +static void oneOfMultiple(int idx) { + static std::array c{1, -1}; + + for (size_t i = 0; i < 10; i++) { + values.at(idx) += c.at(idx); + } + + while (true) { + vTaskDelay(100); + } +} + +TEST_CASE("Test if tasks do not interfere", "[freeRtos][task]") { + // Both tasks are run by starting the same static function body + int param1 = 0; + TaskT task1(oneOfMultiple, param1, "pTask1"); + int param2 = 1; + TaskT task2(oneOfMultiple, param2, "pTask2"); + + Task::delayMs(50); + + TEST_ASSERT_EQUAL(10, values[0]); + TEST_ASSERT_EQUAL(-10, values[1]); +} + +TEST_CASE("Test if tasks can be joined correctly", "[freeRtos][task]") { + // Both tasks are run by starting the same static function body + int param1 = 0; + Task task1(oneOfMultiple, param1, "pTask1"); + int param2 = 1; + TaskT task2(oneOfMultiple, param2, "pTask2"); + + Task::delayMs(50); + + TEST_ASSERT_EQUAL(10, values[0]); + TEST_ASSERT_EQUAL(-10, values[1]); +} diff --git a/main/watch_main.cpp b/main/watch_main.cpp index 6978d6b..5a482de 100644 --- a/main/watch_main.cpp +++ b/main/watch_main.cpp @@ -26,6 +26,7 @@ #include "ethernet_init.h" #include "esp_netif.h" #include "esp_eth.h" +#include "Task.hpp" static const char *TAG = "Watch"; static esp_eth_handle_t s_eth_handle = NULL; @@ -107,6 +108,15 @@ static void initialize_ethernet(void) ESP_ERROR_CHECK(esp_eth_start(s_eth_handle)); } +void LightingTask() { + ESP_LOGI(TAG, "Start Lighting"); + + while (true) { + ESP_LOGI("Light", "Tick"); + fos::Task::delayMs(1000); + } +} + void app_main(void) { esp_err_t ret = nvs_flash_init(); @@ -117,4 +127,5 @@ void app_main(void) ESP_ERROR_CHECK(ret); // ESP_ERROR_CHECK(esp_event_loop_create_default()); initialize_ethernet(); + static fos::Task lightingTask(LightingTask, "Lights", 0x4000uL); }