Merge pull request 'Merge manager task into statemachine task' (#41) from refactor/statemachine into main

Reviewed-on: https://git.intern.spaceteamaachen.de/ALPAKA/TACOS/pulls/41
Reviewed-by: dario <dario@noreply.git.intern.spaceteamaachen.de>
This commit is contained in:
dario 2024-11-30 17:08:54 +00:00
commit 235dd0a6d7
18 changed files with 662 additions and 318 deletions

188
README.md
View File

@ -1,18 +1,63 @@
# TACOS
This is the Trajectory Analysis Control OS (TACOS) that serves as a starting point for developing TACOS-based projects, by implementing threads in the App directory. TACOS is a subset of the features provided by ALPAKA. In particular, TACOS consists of the STM32-specific components of sta-core, rtos-2 and rtos2-utils.
This is the Trajectory Analysis Control OS (TACOS) that serves as a framework for flight computer development. TACOS offers a state machine, a CAN bus interface, a watchdog and other HAL features through it's submodules. It runs on cmsis-rtos2 FreeRTOS on STM32 microcontrollers with C++ (maybe future versions will offer external C interfaces to support a wide array of languages...).
## Setting Up TACOS
To use TACOS one should implement threads, which fulfill the various roles of the module in the App directory. TACOS utilizes [ALPAKA](https://git.intern.spaceteamaachen.de/ALPAKA) features, in particular requiring [sta-core](https://git.intern.spaceteamaachen.de/ALPAKA/sta-core) and [rtos2-utils](https://git.intern.spaceteamaachen.de/ALPAKA/rtos2-utils), as such it requires these to be in it's include path.
Add this as a library an existing CubeIDE project using FreeRTOS. Generally, we advise you to add it as a submodule. Make sure that you add the include paths for Tacos, i.e. sta-core and rtos2-utils, to the project with the following steps:
## Setting up a TACOS project
### Setting up the project
First one must create a new CubeIDE project with FreeRTOS. To avoid doing that however we recommend using the [ioc-collection](https://git.intern.spaceteamaachen.de/ALPAKA/ioc-collection) to get a preconfigured IOC for the STM microcontroller you are using. From here follow the following steps:
1. ```Import -> General -> Import an Existing STM32CubeMX Configuration File (.ioc)```
2. Select the .ioc file from the ioc-collection
3. Enter the project name and location you want to save the project to
4. Select C++ as the target language
5. Click "Finish"
### Setting up the folder structure
Now it is necessary to setup the dependencies and include paths for TACOS. For this first create a new folder in the project directory called `Libs`. Then create another folder in the project directory called `App` with the subfolders `Inc` and `Src`. Now also create a folder called `sta` in the `Inc` folder. Finally add the empty files `App/Inc/sta/config.hpp` and `App/Src/startup.cpp`.
Now your project should look like this:
```
Properties -> C/C++ General -> Paths and Symbols -> Includes -> GNU C -> Add...
Properties -> C/C++ General -> Paths and Symbols -> Includes -> GNU C++ -> Add...
Properties -> C/C++ General -> Paths and Symbols -> Source Location -> Add Folder...
...
App/
├── Inc/
│ ├── sta/
│ │ └── config.hpp
├── Src/
│ └── startup.cpp
Libs/
...
```
Create a new thread via the project's IOC and call `startTACOS()` from this thread. If your thread is called `defaultTask`, the corresponding function `StartDefaultTask` generated in `Core/Src/freertos.c` should look like this:
### Setting up the dependencies
First it is recommended to initialize a git repository in the project folder with `git init`. Then add the TACOS, sta-core and rtos2-utils repositories as submodules in the `Libs` folder with the following commands:
```bash
cd Libs
git submodule add https://git.intern.spaceteamaachen.de/ALPAKA/TACOS.git
git submodule add https://git.intern.spaceteamaachen.de/ALPAKA/sta-core.git
git submodule add https://git.intern.spaceteamaachen.de/ALPAKA/rtos2-utils.git
```
Make sure that you add the include paths for TACOS, sta-core and rtos2-utils to the project with the following steps:
1. `Properties -> C/C++ General -> Paths and Symbols -> Includes -> GNU C -> Add...`
2. Select `Add to all languages` and `Is a workspace path`
3. Click on `Workspace` and select a folder from the `YOUR_PROJECT_FOLDER/(Libs|App)` directory
- Always select the `include` or `Inc` folder for the include paths
- If the path you want to add is not in the list, refresh the project with `F5` in the `Project Explorer` and try again
4. Repeat for TACOS, sta-core, rtos2-utils and the App folder
5. `Properties -> C/C++ General -> Paths and Symbols -> Source Location -> Add Folder...`
- Add the `App` and `Libs` folders
### Starting TACOS
Navigate to the `Core/Src/freertos.c` file and add the following code to the `StartDefaultTask` function:
```cpp
void StartDefaultTask(void *argument)
{
/* USER CODE BEGIN StartDefaultTask */
@ -28,9 +73,11 @@ void StartDefaultTask(void *argument)
}
```
## Configuring TACOS
This will start the TACOS startup and initialize all TACOS threads (which will then initialize yours).
### Configuring TACOS
In order to use TACOS, you need to provide a configuration file in the path `sta/config.hpp`. The following code is an example for a TACOS-project using default configuration:
```
```cpp
#ifndef INC_STA_CONFIG_HPP_
#define INC_STA_CONFIG_HPP_
@ -50,6 +97,7 @@ In order to use TACOS, you need to provide a configuration file in the path `sta
#define STA_RTOS_SYSTEM_EVENTS_ENABLE
// #define STA_RTOS_SYSTEM_WATCHDOG_ENABLE
// #define STA_RTOS_WATCHDOG_ENABLE
// #define STA_TACOS_WATCHDOG_FREQUENCY 10000
#define STA_CAN_BUS_ENABLE
// Statemachine settings.
@ -61,12 +109,85 @@ In order to use TACOS, you need to provide a configuration file in the path `sta
#endif /* INC_STA_CONFIG_HPP_ */
```
PS: For not officially supported chips use this as the include:
```
```cpp
#include <sta/devices/stm32/mcu/common.hpp>
#define STA_MCU_LITTLE_ENDIAN
#define STA_PLATFORM_STM32
```
## Setting up the CAN Bus
### Implementing your own threads
Let's create a simple thread that prints "Hello World" every second. First create a new file in the `App/Inc/tasks` folder called `spam_task.hpp`. Then add the following code:
```cpp
#ifndef INC_TASKS_SPAM_TASK_HPP_
#define INC_TASKS_SPAM_TASK_HPP_
#include <sta/tacos.hpp>
namespace tasks
{
class SpamTask : public sta::tacos::TacosThread {
public:
SpamTask();
// One time function that is called when the thread is created.
void init() override;
// Repeatable function that is called every time the thread is executed.
void func() override;
};
} // namespace tasks
#endif /* INC_TASKS_SPAM_TASK_HPP_ */
```
This code defines a new thread that inherits from `TacosThread` and implements the `init` and `func` functions. The `init` function is called once when the thread is created and the `func` function is called every time the thread is executed.
Now create a new file in the `App/Src/tasks` folder called `spam_task.cpp` and add the following code:
```cpp
#include <tasks/spam_task.hpp>
namespace tasks {
SpamTask::SpamTask() :
TacosThread("SPAM", osPriorityNormal){}
void SpamTask::init() {
// Nothing to init...
}
void SpamTask::func() {
// Print "Hello World" every second.
STA_DEBUG_PRINTLN("Hello World");
this->periodicDelay(1); // Execute this function with 1 Hz.
}
} // namespace tasks
```
To start this thread, we first need to fill out the `startup.cpp` file. This file may look like this:
```cpp
#include <sta/tacos.hpp>
#include <tasks/spam_task.hpp>
#include <sta/debug/debug.hpp>
namespace sta
{
namespace tacos
{
void onStatemachineInit()
{
// ###### Register different threads for different states here. ######
// Register a "Spam Task" thread for all states except 1 and 2.
sta::tacos::addThread<tasks::SpamTask>(ALL_STATES - state_set{1,2});
STA_DEBUG_PRINTF("The answer to everything is %d", 42);
}
} // namespace tacos
} // namespace sta
```
And that's it! Now you have a thread that prints "Hello World" every second. Simply build the project and flash it to your microcontroller and be amazed by the Spam!
### Setting up the CAN Bus
To enable the CAN Bus two things need to be done:
1. Enable CAN in the IOC with the RX0 and RX1 Interrupts enabled.
@ -79,4 +200,47 @@ PS: For not officially supported chips add this:
#define STA_STM32_CAN_HANDLE {YOUR_HCAN_HANDLE}
```
After this messages will automatically be forwarded to the task with it's ID. To send messages use the interface defined in `tacos.hpp`.
There are two options for handling incoming CAN messages:
1. If `#define STA_CAN_BUS_FWD_ENABLE` is set, the messages will be forwarded to the task with the ID of the message.
- Tasks set their ID with `setID(uint32_t id)` in their constructor.
- From here they can handle the message by going through their `CAN_queue_` with `CanSysMsg msg; CAN_queue_.get(&msg);`
2. All messages will trigger the weakly defined handleSysMessage callback.
- This could be implemented like this:
```cpp
namespace sta
{
namespace tacos
{
bool handleSysMessage(CanMsgHeader &header, uint8_t *payload)
{
// Print the message ID and the first byte of the payload.
//(please don't do this in production, it will crash the system sooner or later)
STA_DEBUG_PRINTF("> ID: %d", header.sid);
switch (header.sid)
{
// State transition message
case STA_TACOS_CAN_BUS_SYS_MSG_ID:
// First byte of payload is the origin state, second byte is the destination state
tacos::setState(payload[0], payload[1], 0, true);
return true;
case MODULE_SW_RESET_CAN_ID:
HAL_NVIC_SystemReset();
return true; // :)
// ...
default:
return false;
}
return false; // I know, i know, this is not necessary, but it's good practice. And you know what they say about good practice: Do it!
}
}
}
```
### Further information
To look into other function of TACOS please consult the READMEs in the include folder or the doxygen documentation. Also consult the sta-core and rtos2-utils READMEs for further information on the features that TACOS uses.

93
include/sta/README.md Normal file
View File

@ -0,0 +1,93 @@
# TACOS.hpp
The TACOS API is defined in the tacos.hpp, in normal use cases you should only need to include this file.
## Functions
```cpp
uint16_t getState()
```
Retrieves the current state of the TACOS state machine.
---
```cpp
void requestState(uint32_t from, uint32_t to, uint32_t lockout = 0, bool publish = true)
```
Requests a state transition. Invalid state transitions will be dismissed. First come, first serve.
- **Parameters**:
- `from`: The starting state for the transition.
- `to`: The target state to transition to.
- `lockout`: (Optional) A timer to block further transitions.
- `publish`: (Optional) Whether to publish the state transition to the CAN bus.
```cpp
void forceState(uint32_t from, uint32_t to, uint32_t lockout = 0, bool publish = true)
```
Forces a state transition. Will be ignored if already in the given state. Triggers instantly.
- **Parameters**:
- `from`: The starting state for the transition.
- `to`: The target state to transition to.
- `lockout`: (Optional) A timer to block further transitions.
- `publish`: (Optional) Whether to publish the state transition to the CAN bus.
---
```cpp
void setStateTimed(uint32_t from, uint32_t to, uint32_t millis, uint32_t lockout = 0, bool publish = false)
```
Requests a state transition after a specified time.
- **Parameters**:
- `from`: The starting state.
- `to`: The target state.
- `millis`: The wait time in milliseconds before requesting the transition.
- `lockout`: (Optional) A timer for blocking subsequent transitions.
---
```cpp
template<typename T, typename ... Args> std::shared_ptr<T> addThread(std::set<uint16_t> states, Args ... args)
```
Registers a new thread to be run by TACOS.
- **Template Parameters**:
- `T`: The class type of the thread, which should inherit from `TacosThread`.
- **Parameters**:
- `states`: A set of states in which the thread should be active.
- `args`: The constructor arguments for the thread class.
---
### CAN Bus Functions (Conditional)
The following functions are available only if `STA_TACOS_CAN_BUS_ENABLED` is defined:
```cpp
bool queueCanBusMsg(CanSysMsg & msg, uint32_t timeout)
```
Queues a message to be sent over the CAN bus.
- **Parameters**:
- `msg`: The message to be sent.
- `timeout`: The maximum time to wait for sending the message.
---
```cpp
bool publishState(uint32_t from, uint32_t to, uint32_t timeout = 0)
```
Publishes a state transition message to the CAN bus.
- **Parameters**:
- `from`: The starting state for the transition.
- `to`: The target state.
- `timeout`: (Optional) A timeout for CAN communication.

View File

@ -9,7 +9,6 @@
#define STA_TACOS_HPP
#include <sta/tacos/thread.hpp>
#include <sta/tacos/manager.hpp>
#include <sta/tacos/statemachine.hpp>
#include <sta/tacos/can_bus.hpp>
@ -42,11 +41,23 @@ namespace sta
* @param from The start we want to transition from.
* @param to The state we want to transition to.
* @param lockout An optional timer blocking state transition for a given time.
* @param force If true, the state transition will be executed regardless of the current state.
* @param publish If true, the state transition will be published via CAN.
*
* @ingroup tacos_api
*/
void setState(uint32_t from, uint32_t to, uint32_t lockout = 0, bool force = false, bool publish = false);
void requestState(uint32_t from, uint32_t to, uint32_t lockout = 0, bool publish = true);
/**
* @brief Request a state transition. Invalid state transitions will be dismissed.
*
* @param from The start we want to transition from.
* @param to The state we want to transition to.
* @param lockout An optional timer blocking state transition for a given time.
* @param publish If true, the state transition will be published via CAN.
*
* @ingroup tacos_api
*/
void forceState(uint32_t from, uint32_t to, uint32_t lockout = 0, bool publish = true);
/**
* @brief Request a state transition after a given time has passed. Invalid state transitions will be dismissed.
@ -74,7 +85,7 @@ namespace sta
{
std::shared_ptr<T> thread_ptr = std::make_shared<T>(args...);
Manager::instance()->registerThread(thread_ptr, states);
Statemachine::instance()->registerThread(thread_ptr, states);
return thread_ptr;
}

View File

@ -0,0 +1,23 @@
# TACOS tasks and prototypes
## Statemachine
The statemachine is the core of TACOS. It is responsible for managing the state of the system and executing the threads that are registered to it. The statemachine is a singleton and can be accessed via `sta::tacos::Statemachine::instance()`.
For further info check the file or the doxygen documentation.
## Watchdog
The watchdog checks if all threads are setting a flag after every func call. If a thread does not set the flag in the given interval the thread is restarted.
The watchdog is enabled and configured with the following defines:
```cpp
#define STA_RTOS_WATCHDOG_ENABLE
#define STA_TACOS_WATCHDOG_FREQUENCY 10000
```
## CAN Bus
The CAN bus is used to communicate between different devices. The CAN bus task is triggered by IRQ from the transceiver or if something is inserted into it's output queue.
This thing is a huge steaming (but functioning) pile of shit. Further documentation is on hold until rework.
## Thread.hpp
Here the TacosThread is defined. Very straight forward. Just take a look inside.

View File

@ -0,0 +1,3 @@
# TACOS C API
Currently the only C API is the entry point for starting up TACOS. This will be extended to provide full functionality via C calling conventions.

View File

@ -2,13 +2,11 @@
#define STA_TACOS_CONFIGS_DEFAULT_HPP
// Generally, we assume the TACOS threads to have the highest priorties.
#define STA_TACOS_MANAGER_PRIORITY osPriorityHigh
#define STA_TACOS_STATEMACHINE_PRIORITY osPriorityHigh
#define STA_TACOS_WATCHDOG_PRIORITY osPriorityHigh
#define STA_TACOS_CAN_BUS_PRIORITY osPriorityHigh
// Set the Stack size for the TACOS threads, to 0 to use the default stack size, set in the ioc
#define STA_TACOS_MANAGER_STACK_SIZE 0
#define STA_TACOS_STATEMACHINE_STACK_SIZE 0
// Per default, we assume state 0 to be the initial state.
@ -22,7 +20,4 @@
// State transition message define with highest CAN priority
#define STA_TACOS_CAN_BUS_SYS_MSG_ID 0x0
// TACOS requires system events to start threads
#define STA_RTOS_SYSTEM_EVENTS_ENABLE
#endif // STA_TACOS_CONFIGS_DEFAULT_HPP

View File

@ -1,124 +0,0 @@
/*
* manager.hpp
*
* Created on: Sep 19, 2023
* Author: Dario
*/
#ifndef INCLUDE_STA_TACOS_MANAGER_HPP_
#define INCLUDE_STA_TACOS_MANAGER_HPP_
#include <sta/config.hpp>
#if !defined(STA_TACOS_MANAGER_PRIORITY) && !defined(DOXYGEN)
# error "Manger task priority not specified in config.hpp"
#else
#include <set>
#include <list>
#include <vector>
#include <memory>
#include <sta/tacos/thread.hpp>
/**
* @defgroup tacos_manager Manager Task
* @ingroup tacos
* @brief Manager task for TACOS.
*/
namespace sta
{
namespace tacos
{
/**
* @brief Manager class for Tacos.
*
* @ingroup tacos_manager
*/
class Manager : public TacosThread
{
public:
/**
* @brief Get the singleton instance of the manager.
*
* @ingroup tacos_manager
*/
static Manager* instance()
{
static CGuard g;
if (!_instance)
{
// Create a the manager singleton instance.
Manager::_instance = new Manager();
}
return _instance;
}
/**
* @brief Register a thread to be managed by the manager.
*/
void registerThread(std::shared_ptr<TacosThread> thread, std::set<uint16_t> states);
/**
* @brief Get the Active Threads object
*
* @return std::vector<std::shared_ptr<TacosThread>>
*/
std::vector<std::shared_ptr<TacosThread>> getActiveThreads();
void init() override;
void func() override;
private:
static Manager* _instance;
class CGuard
{
public:
~CGuard()
{
if( NULL != Manager::_instance )
{
delete Manager::_instance;
Manager::_instance = NULL;
}
}
};
Manager();
Manager(const Manager&);
//~Manager();
/**
* @brief Forces only threads of current state to run.
*/
void updateThreads();
/**
* @brief Starts all threads which should be running in the given state. Does nothing if the state is already running.
*/
void startThreads(uint16_t state);
/**
* @brief Stops all threads which should not be running in the given state.
*/
void stopThreads(uint16_t state);
/**
* @brief Pointers to all threads which are managed by the manager.
*
* @ingroup tacos_manager
*/
std::vector<std::shared_ptr<TacosThread>> threads_[STA_TACOS_NUM_STATES];
};
} // namespace tacos
} // namespace sta
#endif // STA_TACOS_MANAGER_PRIORITY
#endif /* INCLUDE_STA_TACOS_MANAGER_HPP_ */

View File

@ -66,9 +66,13 @@
#include <sta/rtos/event.hpp>
#include <sta/event.hpp>
#include <sta/debug/assert.hpp>
#include <sta/lang.hpp>
#include <functional>
#include <tuple>
#include <list>
#include <vector>
#include <memory>
#include <set>
#include <algorithm>
@ -134,11 +138,15 @@ namespace sta
public:
/**
* @brief The global event signaling a state change.
*
* @ingroup tacos_statemachine
*/
static RtosEvent stateChangeEvent;
/**
* @brief Getter function for the singleton instance.
*
* @ingroup tacos_statemachine
*/
static Statemachine* instance()
{
@ -146,7 +154,7 @@ namespace sta
if (!_instance)
{
// Create the manager singleton instance.
// Create the statemachine singleton instance.
Statemachine::_instance = new Statemachine();
}
@ -155,6 +163,8 @@ namespace sta
/**
* @brief Returns the statemachine's current state.
*
* @ingroup tacos_statemachine
*/
uint16_t getCurrentState() const;
@ -164,9 +174,23 @@ namespace sta
* @param from The state which we want to leave. This is used to filter out obsolete transitions.
* @param to The state to transition to.
* @param lockout The minimum number of milliseconds we expect to stay in this state. This is used to block premature transitions.
* @param force If true, the state transition will be executed regardless of the current state.
* @param publish If true, the state transition will be published via CAN.
*
* @ingroup tacos_statemachine
*/
void requestStateTransition(uint32_t from, uint32_t to, uint32_t lockout = 0, bool force = false, bool publish = true);
void requestStateTransition(uint32_t from, uint32_t to, uint32_t lockout = 0, bool publish = true);
/**
* @brief Request a state transition from a state to another.
*
* @param from The state which we want to leave. This is used to filter out obsolete transitions.
* @param to The state to transition to.
* @param lockout The minimum number of milliseconds we expect to stay in this state. This is used to block premature transitions.
* @param publish If true, the state transition will be published via CAN.
*
* @ingroup tacos_statemachine
*/
void forceStateTransition(uint32_t from, uint32_t to, uint32_t lockout = 0, bool publish = true);
/**
* @brief Request a state transition after a given time has passed.
@ -175,9 +199,28 @@ namespace sta
* @param to The state to transition to.
* @param millis the number of milliseconds to wait before triggering the transition.
* @param lockout The minimum number of milliseconds we expect to stay in this state. This is used to block premature transitions.
* @param publish If true, the state transition will be published via CAN.
*
* @ingroup tacos_statemachine
*/
void requestTimedStateTransition(uint32_t from, uint32_t to, uint32_t millis, uint32_t lockout = 0, bool publish = true);
/**
* @brief Register a thread to be managed by the statemachine.
*
* @ingroup tacos_statemachine
*/
void registerThread(std::shared_ptr<TacosThread> thread, std::set<uint16_t> states);
/**
* @brief Get the Active Threads object
*
* @return std::vector<std::shared_ptr<TacosThread>>
*
* @ingroup tacos_statemachine
*/
std::vector<std::shared_ptr<TacosThread>> getActiveThreads();
void init() override;
void func() override;
@ -208,9 +251,32 @@ namespace sta
* @brief Starts the lockoutTimer for the desired duration.
*
* @param millis The duration of the timer in milliseconds.
*
* @ingroup tacos_statemachine
*/
void setLockoutTimer(uint32_t millis);
/**
* @brief Forces only threads of current state to run.
*
* @ingroup tacos_statemachine
*/
void updateThreads();
/**
* @brief Starts all threads which should be running in the given state. Does nothing if the state is already running.
*
* @ingroup tacos_statemachine
*/
void startThreads(uint16_t state);
/**
* @brief Stops all threads which should not be running in the given state.
*
* @ingroup tacos_statemachine
*/
void stopThreads(uint16_t state);
private:
uint16_t currentState_;
@ -218,7 +284,26 @@ namespace sta
RtosTimer failsafeTimer_;
RtosQueue<StateTransition> queue_;
/**
* @brief Pointers to all threads which are managed by the statemachine.
*
* @ingroup tacos_statemachine
*/
std::vector<std::shared_ptr<TacosThread>> threads_[STA_TACOS_NUM_STATES];
};
/**
* @brief Callback that is called when a state transition occurs.
*
* @param from The state we transitioned from.
* @param to The state we transitioned to.
* @param lockout The lockout time after the transition.
*
* @ingroup tacos_statemachine
*/
STA_WEAK
void onStateTransition(uint16_t from, uint16_t to, uint32_t lockout) {}
} // namespace tacos
} // namespace sta

View File

@ -0,0 +1,82 @@
/**
* @file
* @brief Implementation of system events.
*/
#ifndef STA_TACOS_SYSTEM_EVENTS_HPP
#define STA_TACOS_SYSTEM_EVENTS_HPP
/**
* @defgroup STA_RTOS_SysEvent System Events
* @ingroup STA_RTOS_API
* @brief System events.
*
* Check @ref STA_RTOS_BuildConfig for configuration options.
*/
#include <cstdint>
// System event flags
//
/**
* @brief Startup system event flag.
*
* @ingroup STA_RTOS_SysEvent
*/
#define STA_TACOS_SYSTEM_EVENTS_STARTUP 0x100000U
namespace sta
{
namespace tacos
{
/**
* @brief Initialize system events.
*/
void initSystemEvents();
/**
* @brief Signal system events.
*
* @param flags System event flags
*
* @ingroup STA_RTOS_SysEvent
*/
void signalSystemEvents(uint32_t flags);
/**
* @brief Wait for system events.
*
* @param flags System event flags
* @param options osFlagsWaitAll or osFlagsWaitAny (osFlagsNoClear always set)
* @param timeout Wait timeout (0 = instant, osWaitForever = infinite)
*
* @ingroup STA_RTOS_SysEvent
*/
void waitForSystemEvents(uint32_t flags, uint32_t options, uint32_t timeout);
/**
* @brief Signal startup system event.
*
* @ingroup STA_RTOS_SysEvent
*/
void signalStartupEvent();
/**
* @brief Wait for startup system event.
*
* Blocking while waiting
*
* @ingroup STA_RTOS_SysEvent
*/
void waitForStartupEvent();
} // namespace tacos
} // namespace sta
#endif // STA_RTOS_SYSTEM_EVENTS_HPP

View File

@ -79,7 +79,7 @@ namespace sta
* @brief Create a new thread with the given name and priority.
*
* @param name The thread's name. This is used for debugging.
* @param prio The thread's priority. Generally, this should be lower than the manager and statemachine priority.
* @param prio The thread's priority. Generally, this should be lower than the statemachine priority.
* @param stack_size The stack size for the task. The default is 0, i.e. the stack size specified in the FreeRTOS settings.
* @param cb_size The control block size for the task. The default is 0, i.e. the size specified in the FreeRTOS settings.
*/

View File

@ -3,7 +3,7 @@
#include <sta/tacos/can_bus.hpp>
#include <sta/debug/assert.hpp>
#include <sta/tacos/manager.hpp>
#include <sta/tacos/statemachine.hpp>
#include <sta/tacos.hpp>
extern CAN_HandleTypeDef STA_STM32_CAN_HANDLE;
@ -64,7 +64,7 @@ namespace sta
{
// Append to the correct thread's queue
for (std::shared_ptr<TacosThread> thread : Manager::instance()->getActiveThreads())
for (std::shared_ptr<TacosThread> thread : Statemachine::instance()->getActiveThreads())
{
if (thread->getCanID() == sysMsg.header.sid)
{
@ -160,7 +160,7 @@ namespace sta
STA_ASSERT(header.payloadLength == 2);
// First byte of payload is the origin state, second byte is the destination state. Transition is forced
tacos::setState(payload[0], payload[1], 0, true);
tacos::forceState(payload[0], payload[1], 0, true);
return true;
}

51
src/events.cpp Normal file
View File

@ -0,0 +1,51 @@
#include <sta/tacos/system/events.hpp>
#include <sta/rtos/event.hpp>
#include <sta/debug/assert.hpp>
#include <cmsis_os2.h>
#include <FreeRTOS.h>
namespace
{
// Event handle
sta::RtosEvent * systemEvents = nullptr;
}
namespace sta
{
namespace tacos
{
void initSystemEvents()
{
if (systemEvents == nullptr)
{
systemEvents = new sta::RtosEvent();
}
}
void signalSystemEvents(uint32_t flags)
{
STA_ASSERT_MSG(systemEvents != nullptr, "System events not initialized");
systemEvents->set(flags);
}
void waitForSystemEvents(uint32_t flags, uint32_t options, uint32_t timeout)
{
STA_ASSERT_MSG(systemEvents != nullptr, "System events not initialized");
systemEvents->peek(flags, timeout);
}
void signalStartupEvent()
{
signalSystemEvents(STA_TACOS_SYSTEM_EVENTS_STARTUP);
}
void waitForStartupEvent()
{
waitForSystemEvents(STA_TACOS_SYSTEM_EVENTS_STARTUP, osFlagsWaitAll, osWaitForever);
}
} // namespace tacos
} // namespace sta

View File

@ -1,110 +0,0 @@
/*
* manager.cpp
*
* Created on: Sep 19, 2023
* Author: Dario
*/
#include <sta/tacos/manager.hpp>
#include <sta/tacos/statemachine.hpp>
#include <sta/debug/debug.hpp>
#include <algorithm>
#include <FreeRTOS.h>
#include <sta/tacos.hpp>
namespace sta
{
namespace tacos
{
void Manager::registerThread(std::shared_ptr<TacosThread> thread, std::set<uint16_t> states)
{
for (uint16_t state : states)
{
STA_ASSERT(state < STA_TACOS_NUM_STATES);
threads_[state].push_back(thread);
}
}
std::vector<std::shared_ptr<TacosThread>> Manager::getActiveThreads()
{
return threads_[tacos::getState()];
}
void Manager::startThreads(uint16_t state)
{
STA_ASSERT(state < STA_TACOS_NUM_STATES);
for (std::shared_ptr<TacosThread> thread : threads_[state])
{
if (!thread->isRunning())
{
thread->start();
}
}
}
void Manager::stopThreads(uint16_t state)
{
std::set<std::shared_ptr<TacosThread>> terminated;
for (uint16_t other = 0; other < STA_TACOS_NUM_STATES; ++other)
{
if (other == state)
{
continue;
}
for (std::shared_ptr<TacosThread> thread : threads_[other])
{
// If the thread is currently running but not part of the set of threads that should be running...
if (thread->isRunning() && terminated.count(thread) == 0 && std::count(threads_[state].begin(), threads_[state].end(), thread) == 0)
{
// ...politely request termination.
thread->requestTermination();
terminated.emplace(thread);
}
}
}
}
void Manager::updateThreads()
{
uint16_t state = Statemachine::instance()->getCurrentState();
stopThreads(state);
startThreads(state);
}
void Manager::init()
{
startThreads(Statemachine::instance()->getCurrentState());
}
void Manager::func()
{
Statemachine::stateChangeEvent.wait(EventFlags::ALL, osWaitForever);
HeapStats_t stats;
vPortGetHeapStats(&stats);
// Start all new tasks and stop all the tasks that aren't supposed to be running.
updateThreads();
}
Manager::Manager()
: TacosThread{"Manager", STA_TACOS_MANAGER_PRIORITY, STA_TACOS_MANAGER_STACK_SIZE},
threads_{}
{
}
Manager* Manager::_instance = nullptr;
} // namespace tacos
} // namespace sta

View File

@ -28,12 +28,10 @@
#include <sta/lang.hpp>
// rtos2-utils-specific includes.
#include <sta/rtos/system/startup.hpp>
#include <sta/rtos/system/events.hpp>
#include <sta/tacos/system/events.hpp>
// Tacos-specific includes.
#include <sta/tacos/c_api/startup.h>
#include <sta/tacos/manager.hpp>
#include <sta/tacos/statemachine.hpp>
#include <sta/tacos/watchdog.hpp>
#include <sta/tacos/can_bus.hpp>
@ -85,39 +83,22 @@ namespace sta
namespace tacos
{
/**
* @brief Function that is called before the statemachine task is started. Override it to
* adjust the statemachine to your specifications.
* @brief Function that is called before the statemachine task is started. It serves as an entry point for the user to
* define the threads the statemachine should run.
*
* @ingroup tacos_startup
*/
STA_WEAK
void onStatemachineInit()
void startup()
{}
void initStatemachine()
{
onStatemachineInit();
startup();
Statemachine::instance()->start();
}
/**
* @brief Function that is called before the manager task is started. Override it to adjust
* the manager to your specifications.
*
* @ingroup tacos_startup
*/
STA_WEAK
void onManagerInit()
{}
void initManager()
{
onManagerInit();
Manager::instance()->start();
}
#ifdef STA_TACOS_WATCHDOG_ENABLED
STA_WEAK
void onWatchdogInit()
@ -158,8 +139,6 @@ namespace sta
tacos::initStatemachine();
tacos::initManager();
#ifdef STA_TACOS_WATCHDOG_ENABLED
tacos::initWatchdog();
#endif // STA_TACOS_WATCHDOG_ENABLED
@ -178,16 +157,14 @@ void startTACOS(void * arg)
// Initialize HAL
sta::initHAL();
// Initialize RTOS system resources
sta::rtos::initSystem();
// Initialize RTOS system events
sta::tacos::initSystemEvents();
// Call further initialization code
sta::tacos::startupExtras(arg);
// Wake threads
#ifdef STA_RTOS_SYSTEM_EVENTS_ENABLE
sta::rtos::signalStartupEvent();
#endif // STA_RTOS_SYSTEM_EVENTS_ENABLE
sta::tacos::signalStartupEvent();
// Check if called from thread
if (osThreadGetId() != nullptr)

View File

@ -9,6 +9,7 @@
#include <sta/tacos/statemachine.hpp>
#include <sta/debug/debug.hpp>
#include <FreeRTOS.h>
namespace sta
@ -20,14 +21,15 @@ namespace sta
currentState_{STA_TACOS_INITIAL_STATE},
lockoutTimer_{[](void *){}, nullptr},
failsafeTimer_{[](void *){}, nullptr},
queue_{STA_TACOS_STATEMACHINE_QUEUE_LENGTH}
queue_{STA_TACOS_STATEMACHINE_QUEUE_LENGTH},
threads_{}
{
STA_ASSERT(STA_TACOS_INITIAL_STATE < STA_TACOS_NUM_STATES);
}
void Statemachine::init()
{
startThreads(getCurrentState());
}
void Statemachine::func()
@ -42,8 +44,11 @@ namespace sta
STA_ASSERT(transition.to < STA_TACOS_NUM_STATES);
#ifdef STA_TACOS_CAN_BUS_ENABLED
if (transition.publish)
{
// Publish the state via CAN bus.
tacos::publishState(transition.from, transition.to, 0);
}
#endif // STA_TACOS_CAN_BUS_ENABLED
// Perform the transition and notify the threads. The event flags are set
@ -64,6 +69,16 @@ namespace sta
{
setLockoutTimer(transition.lockout);
}
// get heap stats at the end of the state transition
HeapStats_t stats;
vPortGetHeapStats(&stats);
// Execute the user-defined callback.
sta::tacos::onStateTransition(transition.from, transition.to, transition.lockout);
// Start all new tasks and stop all the tasks that aren't supposed to be running.
updateThreads();
}
}
@ -72,7 +87,7 @@ namespace sta
return currentState_;
}
void Statemachine::requestStateTransition(uint32_t from, uint32_t to, uint32_t lockout /* = 0 */, bool force /* = 0 */, bool publish /* = true */)
void Statemachine::requestStateTransition(uint32_t from, uint32_t to, uint32_t lockout /* = 0 */, bool publish /* = true */)
{
StateTransition transition;
transition.from = from;
@ -81,17 +96,28 @@ namespace sta
transition.lockout = lockout;
transition.publish = publish;
// Force the transition if requested, but only if the requested state is different from the current one.
if (force && transition.to != currentState_){
// Try to add a state transition request to the queue. Don't wait if another
// thread is already requesting a state change.
queue_.put(transition, 0);
}
void Statemachine::forceStateTransition(uint32_t from, uint32_t to, uint32_t lockout /* = 0 */, bool publish /* = true */)
{
// Force the transition, but only if the requested state is different from the current one.
if (to != currentState_){
// Perform the transition and notify the threads. The event flags are set
// here in order to allow threads to react immediately.
currentState_ = transition.to;
currentState_ = to;
#ifdef STA_TACOS_CAN_BUS_ENABLED
tacos::publishState(transition.from, transition.to, transition.lockout);
if (publish)
{
// Publish the state via CAN bus.
tacos::publishState(from, to, 0);
}
#endif // STA_TACOS_CAN_BUS_ENABLED
Statemachine::stateChangeEvent.set(transition.event);
Statemachine::stateChangeEvent.set(EventFlags::NORMAL);
Statemachine::stateChangeEvent.clear(EventFlags::ALL);
if (failsafeTimer_.isRunning())
@ -100,14 +126,16 @@ namespace sta
}
// Start the lockout timer if requested.
if (transition.lockout != 0)
if (lockout != 0)
{
setLockoutTimer(transition.lockout);
setLockoutTimer(lockout);
}
} else {
// Try to add a state transition request to the queue. Don't wait if another
// thread is already requesting a state change.
queue_.put(transition, 0);
// Execute the user-defined callback.
sta::tacos::onStateTransition(from, to, lockout);
// Start all new tasks and stop all the tasks that aren't supposed to be running.
updateThreads();
}
}
@ -116,7 +144,7 @@ namespace sta
STA_ASSERT(to < STA_TACOS_NUM_STATES);
failsafeTimer_.setCallback([from, to, lockout, publish](void* arg) {
Statemachine::instance()->requestStateTransition(from, to, lockout, false, publish);
Statemachine::instance()->requestStateTransition(from, to, lockout, publish);
}, NULL);
failsafeTimer_.start(millis);
@ -130,6 +158,66 @@ namespace sta
lockoutTimer_.start(millis);
}
void Statemachine::registerThread(std::shared_ptr<TacosThread> thread, std::set<uint16_t> states)
{
for (uint16_t state : states)
{
STA_ASSERT(state < STA_TACOS_NUM_STATES);
threads_[state].push_back(thread);
}
}
std::vector<std::shared_ptr<TacosThread>> Statemachine::getActiveThreads()
{
return threads_[tacos::getState()];
}
void Statemachine::startThreads(uint16_t state)
{
STA_ASSERT(state < STA_TACOS_NUM_STATES);
for (std::shared_ptr<TacosThread> thread : threads_[state])
{
if (!thread->isRunning())
{
thread->start();
}
}
}
void Statemachine::stopThreads(uint16_t state)
{
std::set<std::shared_ptr<TacosThread>> terminated;
for (uint16_t other = 0; other < STA_TACOS_NUM_STATES; ++other)
{
if (other == state)
{
continue;
}
for (std::shared_ptr<TacosThread> thread : threads_[other])
{
// If the thread is currently running but not part of the set of threads that should be running...
if (thread->isRunning() && terminated.count(thread) == 0 && std::count(threads_[state].begin(), threads_[state].end(), thread) == 0)
{
// ...politely request termination.
thread->requestTermination();
terminated.emplace(thread);
}
}
}
}
void Statemachine::updateThreads()
{
uint16_t state = getCurrentState();
stopThreads(state);
startThreads(state);
}
Statemachine* Statemachine::_instance = nullptr;
RtosEvent Statemachine::stateChangeEvent;

View File

@ -16,9 +16,14 @@ namespace sta
return Statemachine::instance()->getCurrentState();
}
void setState(uint32_t from, uint32_t to, uint32_t lockout /* = 0 */, bool force /* = false */, bool publish /* = false */)
void requestState(uint32_t from, uint32_t to, uint32_t lockout /* = 0 */, bool publish /* = true */)
{
Statemachine::instance()->requestStateTransition(from, to, lockout, force, publish);
Statemachine::instance()->requestStateTransition(from, to, lockout, publish);
}
void forceState(uint32_t from, uint32_t to, uint32_t lockout /* = 0 */, bool publish /* = true */)
{
Statemachine::instance()->forceStateTransition(from, to, lockout, publish);
}
void setStateTimed(uint32_t from, uint32_t to, uint32_t millis, uint32_t lockout /* = 0 */, bool publish /* = false */)

View File

@ -7,9 +7,10 @@
#include <sta/tacos/thread.hpp>
#include <sta/tacos/system/events.hpp>
#include <sta/debug/assert.hpp>
#include <sta/debug/debug.hpp>
#include <sta/rtos/system/events.hpp>
#include <functional>
#include <cstring>
@ -63,7 +64,7 @@ namespace sta
while (true)
{
// The thread has to wait until the system startup has been completed.
rtos::waitForStartupEvent();
sta::tacos::waitForStartupEvent();
// Wait for a thread start flag.
wait(STA_RTOS_THREAD_FLAG_START);

View File

@ -2,7 +2,7 @@
#ifdef STA_TACOS_WATCHDOG_ENABLED
#include <sta/tacos/manager.hpp>
#include <sta/tacos/statemachine.hpp>
namespace sta
{
@ -10,7 +10,7 @@ namespace sta
{
void Watchdog::func()
{
for (std::shared_ptr<TacosThread> thread : Manager::instance()->getActiveThreads())
for (std::shared_ptr<TacosThread> thread : Statemachine::instance()->getActiveThreads())
{
switch (thread->getStatus())
{