From 0513123532cf4844f9492f056e931ec7e27b3a65 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Wed, 28 Sep 2022 19:44:30 +0200 Subject: [PATCH] first chapter done --- fsfw | 2 +- start/01-tasks.md | 53 ++++++++++++++++++---- start/main.cpp | 40 ++++++++++------- start/tasks-srcs/main-04.cpp | 85 ++++++++++++++++++++++++++++++++++++ 4 files changed, 155 insertions(+), 25 deletions(-) create mode 100644 start/tasks-srcs/main-04.cpp diff --git a/fsfw b/fsfw index 715d5ac..62f638a 160000 --- a/fsfw +++ b/fsfw @@ -1 +1 @@ -Subproject commit 715d5ac39f0ea23e5edc28c653fac8b4561f5526 +Subproject commit 62f638a3d2585e4a23dea2d5a72c4f3de55467b8 diff --git a/start/01-tasks.md b/start/01-tasks.md index 69f029f..b17d6a1 100644 --- a/start/01-tasks.md +++ b/start/01-tasks.md @@ -160,7 +160,7 @@ int main() { Where the three tasks do their tasks with different frequencies. -## 3. Using the framework abstractions +## 4. Using the framework abstractions We now use framework components to perform the tasks shown above. The framework exposes an abstractions for executable tasks called [`ExecutableObjectIF`](https://documentation.irs.uni-stuttgart.de/fsfw/development/api/task.html). @@ -184,14 +184,51 @@ In summary, task abstractions have the following advantages: ### Subtasks 1. Load the required interfaces: - - `#include "fsfw/tasks/ExecutableObjectIF"` - - `#include "fsfw/tasks/PeriodicTaskIF` - - `#include "fsfw/tasks/TaskFactory` + - `#include "fsfw/tasks/ExecutableObjectIF.h"` + - `#include "fsfw/tasks/PeriodicTaskIF.h` + - `#include "fsfw/tasks/TaskFactory.h` 2. For your three custom objects, implement the executable object IF provided by the framework - instead of your custom interface - 3. Create two periodic tasks using the `TaskFactory::createPeriodicTask` function + instead of your custom interface. + 3. In your main function, create an instance of the `TaskFactory`. `TaskFactory` + is implemented as a singleton: The object will create itself when using + `TaskFactory::instance`. All subsequent calls will return the same intance. + There are other similar singletons to create other objects like mutexes or message queues. + 3. Create two periodic tasks using the `TaskFactory::createPeriodicTask` function. + Some notes on the expected arguments: + - Each task has a name. This is useful for debugging, especially because the framework + abstractons can detect missed deadlines. + - The task priority parameter is OS dependent. This parameter is currently ignored for the + Linux OSAL so you can pass 0 here. + On Windows, you can retrieve the priority by using `tasks::makeWinPriority` + which can be loaded by including `#include "fsfw/osal/windows/winTaskHelpers.h"` + - The stack space parameter is generally ignored or unimportant for host systems. + You can simply pass `PeriodicTaskIF::MINIMUM_STACK_SIZE` here. This parameter becomes + important on resource constrained OSes and systems, for example FreeRTOS. + - The frequency is expected as floating point seconds + - You can pass a function which will be called when a deadline is missed. This is the case + when a tasks took longer than its designated slot frequency. This is useful to detect + bugs in the software or generally detect when tasks require a large amount of time. + You can also pass `nullptr` here if you do not want any function to be called. 4. Add the first two of your custom exec objects to the first periodic task. Those tasks - will be executed in the same thread consecutively + will be executed in the same thread consecutively. You can use the `PeriodicTaskIF::addComponent` + method to do this. 5. Add the third custom exec object to the second periodic task. The third object gets an own thread - 6. Start both periodic tasks + 6. Start both periodic tasks and add a permanent loop at the end of the main + method which puts the main thread into sleep. + +You successfully scheduled some objects using the framework! +The general concept of executble objects is used heavily throughout the framework. +For example, each device handler or controller is an executable object, as the bases +classes exposed by the framework implement `ExecutableObjectIF`. + +There is also another type of periodic task handler called `FixedTimeslotTask`. Here, +you can explicitely specify (multiple) execution slots with a specified relative time within +the execution slot. This is useful for objects where there are multiple processing steps +but the steps take different amount of times. + +In examples or other OBSW implementations using the framework, you will often +see the distrinction between an `ObjectFactory.cpp` and an `InitMission.cpp`. +In the first file, all global (executable) objects will be created. In the second file, +all of these objects will be scheduled. Another chapter will introduce the Object Manager +to show what exactly is happening here. diff --git a/start/main.cpp b/start/main.cpp index 1d3f81a..bf104aa 100644 --- a/start/main.cpp +++ b/start/main.cpp @@ -1,6 +1,10 @@ #include #include +#include "fsfw/tasks/ExecutableObjectIF.h" +#include "fsfw/tasks/PeriodicTaskIF.h" +#include "fsfw/tasks/TaskFactory.h" + using namespace std; class MyExecutableObjectIF { @@ -9,32 +13,35 @@ public: virtual void performOperation() = 0; }; -class MyExecutableObject0: public MyExecutableObjectIF { +class MyExecutableObject0: public ExecutableObjectIF { public: MyExecutableObject0() = default; - void performOperation() override { + ReturnValue_t performOperation(uint8_t opCode) override { cout << "Task 0" << endl; + return returnvalue::OK; } private: }; -class MyExecutableObject1: public MyExecutableObjectIF { +class MyExecutableObject1: public ExecutableObjectIF { public: MyExecutableObject1() = default; - void performOperation() override { + ReturnValue_t performOperation(uint8_t opCode) override { cout << "Task 1" << endl; + return returnvalue::OK; } private: }; -class MyExecutableObject2: public MyExecutableObjectIF { +class MyExecutableObject2: public ExecutableObjectIF { public: MyExecutableObject2() = default; - void performOperation() override { + ReturnValue_t performOperation(uint8_t opCode) override { cout << "Task 2" << endl; + return returnvalue::OK; } private: }; @@ -64,14 +71,15 @@ int main() { MyExecutableObject0 myExecutableObject0; MyExecutableObject1 myExecutableObject1; MyExecutableObject2 myExecutableObject2; - MyPeriodicTask task0(myExecutableObject0, 1000); - MyPeriodicTask task1(myExecutableObject1, 2000); - MyPeriodicTask task2(myExecutableObject2, 5000); - auto thread0 = task0.start(); - auto thread1 = task1.start(); - auto thread2 = task2.start(); - thread0.join(); - thread1.join(); - thread2.join(); - return 0; + auto* factory = TaskFactory::instance(); + auto* periodicTask0 = factory->createPeriodicTask("TASK_0", 0, PeriodicTaskIF::MINIMUM_STACK_SIZE, 0.5, nullptr); + auto* periodicTask1 = factory->createPeriodicTask("TASK_1", 0, PeriodicTaskIF::MINIMUM_STACK_SIZE, 1.0, nullptr); + periodicTask0->addComponent(&myExecutableObject0); + periodicTask0->addComponent(&myExecutableObject1); + periodicTask1->addComponent(&myExecutableObject2); + periodicTask0->startTask(); + periodicTask1->startTask(); + while(true) { + this_thread::sleep_for(5000ms); + } } diff --git a/start/tasks-srcs/main-04.cpp b/start/tasks-srcs/main-04.cpp new file mode 100644 index 0000000..bf104aa --- /dev/null +++ b/start/tasks-srcs/main-04.cpp @@ -0,0 +1,85 @@ +#include +#include + +#include "fsfw/tasks/ExecutableObjectIF.h" +#include "fsfw/tasks/PeriodicTaskIF.h" +#include "fsfw/tasks/TaskFactory.h" + +using namespace std; + +class MyExecutableObjectIF { +public: + virtual ~MyExecutableObjectIF() = default; + virtual void performOperation() = 0; +}; + +class MyExecutableObject0: public ExecutableObjectIF { +public: + MyExecutableObject0() = default; + + ReturnValue_t performOperation(uint8_t opCode) override { + cout << "Task 0" << endl; + return returnvalue::OK; + } +private: +}; + +class MyExecutableObject1: public ExecutableObjectIF { +public: + MyExecutableObject1() = default; + + ReturnValue_t performOperation(uint8_t opCode) override { + cout << "Task 1" << endl; + return returnvalue::OK; + } +private: +}; + +class MyExecutableObject2: public ExecutableObjectIF { +public: + MyExecutableObject2() = default; + + ReturnValue_t performOperation(uint8_t opCode) override { + cout << "Task 2" << endl; + return returnvalue::OK; + } +private: +}; + +class MyPeriodicTask { +public: + MyPeriodicTask(MyExecutableObjectIF& executable, uint32_t taskFreqMs) + : executable(executable), taskFreqMs(taskFreqMs) {} + + std::thread start() { + return std::thread( + MyPeriodicTask::executeTask, + std::reference_wrapper(*this)); + } +private: + static void executeTask(MyPeriodicTask& self) { + while(true) { + self.executable.performOperation(); + this_thread::sleep_for(std::chrono::milliseconds(self.taskFreqMs)); + } + } + MyExecutableObjectIF& executable; + uint32_t taskFreqMs; +}; + +int main() { + MyExecutableObject0 myExecutableObject0; + MyExecutableObject1 myExecutableObject1; + MyExecutableObject2 myExecutableObject2; + auto* factory = TaskFactory::instance(); + auto* periodicTask0 = factory->createPeriodicTask("TASK_0", 0, PeriodicTaskIF::MINIMUM_STACK_SIZE, 0.5, nullptr); + auto* periodicTask1 = factory->createPeriodicTask("TASK_1", 0, PeriodicTaskIF::MINIMUM_STACK_SIZE, 1.0, nullptr); + periodicTask0->addComponent(&myExecutableObject0); + periodicTask0->addComponent(&myExecutableObject1); + periodicTask1->addComponent(&myExecutableObject2); + periodicTask0->startTask(); + periodicTask1->startTask(); + while(true) { + this_thread::sleep_for(5000ms); + } +}