Merge pull request 'statemachine-rework' (#10) from statemachine-rework into main

Reviewed-on: https://git.intern.spaceteamaachen.de/ALPAKA/TACOS/pulls/10
Reviewed-by: carlwachter <carlwachter@noreply.git.intern.spaceteamaachen.de>
This commit is contained in:
carlwachter 2023-12-03 09:04:13 +00:00
commit f73cd47215
16 changed files with 223 additions and 134 deletions

View File

@ -25,6 +25,7 @@
<option id="com.st.stm32cube.ide.mcu.gnu.managedbuild.option.target_board.1523528544" name="Board" superClass="com.st.stm32cube.ide.mcu.gnu.managedbuild.option.target_board" useByScannerDiscovery="false" value="genericBoard" valueType="string"/>
<option id="com.st.stm32cube.ide.mcu.gnu.managedbuild.option.defaults.1559967274" name="Defaults" superClass="com.st.stm32cube.ide.mcu.gnu.managedbuild.option.defaults" useByScannerDiscovery="false" value="com.st.stm32cube.ide.common.services.build.inputs.revA.1.0.6 || Debug || true || Executable || com.st.stm32cube.ide.mcu.gnu.managedbuild.option.toolchain.value.workspace || STM32F411RETx || 0 || 0 || arm-none-eabi- || ${gnu_tools_for_stm32_compiler_path} || ../Core/Inc | ../Drivers/STM32F4xx_HAL_Driver/Inc | ../Drivers/STM32F4xx_HAL_Driver/Inc/Legacy | ../Drivers/CMSIS/Device/ST/STM32F4xx/Include | ../Drivers/CMSIS/Include | ../Middlewares/Third_Party/FreeRTOS/Source/include | ../Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS_V2 | ../Middlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM4F || || || USE_HAL_DRIVER | STM32F411xE || || Drivers | Core/Startup | Middlewares | Core || || || ${workspace_loc:/${ProjName}/STM32F411RETX_FLASH.ld} || true || NonSecure || || secure_nsclib.o || || None || || || " valueType="string"/>
<option id="com.st.stm32cube.ide.mcu.debug.option.cpuclock.1388963198" name="Cpu clock frequence" superClass="com.st.stm32cube.ide.mcu.debug.option.cpuclock" useByScannerDiscovery="false" value="16" valueType="string"/>
<option id="com.st.stm32cube.ide.mcu.gnu.managedbuild.option.nanoprintffloat.2054601156" superClass="com.st.stm32cube.ide.mcu.gnu.managedbuild.option.nanoprintffloat" value="true" valueType="boolean"/>
<targetPlatform archList="all" binaryParser="org.eclipse.cdt.core.ELF" id="com.st.stm32cube.ide.mcu.gnu.managedbuild.targetplatform.682707397" isAbstract="false" osList="all" superClass="com.st.stm32cube.ide.mcu.gnu.managedbuild.targetplatform"/>
<builder buildPath="${workspace_loc:/TACOS}/Debug" id="com.st.stm32cube.ide.mcu.gnu.managedbuild.builder.1663759602" keepEnvironmentInBuildfile="false" managedBuildOn="true" name="Gnu Make Builder" parallelBuildOn="true" parallelizationNumber="optimal" superClass="com.st.stm32cube.ide.mcu.gnu.managedbuild.builder"/>
<tool id="com.st.stm32cube.ide.mcu.gnu.managedbuild.tool.assembler.1268227203" name="MCU GCC Assembler" superClass="com.st.stm32cube.ide.mcu.gnu.managedbuild.tool.assembler">

View File

@ -5,7 +5,7 @@
<provider copy-of="extension" id="org.eclipse.cdt.ui.UserLanguageSettingsProvider"/>
<provider-reference id="org.eclipse.cdt.core.ReferencedProjectsLanguageSettingsProvider" ref="shared-provider"/>
<provider-reference id="org.eclipse.cdt.managedbuilder.core.MBSLanguageSettingsProvider" ref="shared-provider"/>
<provider class="com.st.stm32cube.ide.mcu.toolchain.armnone.setup.CrossBuiltinSpecsDetector" console="false" env-hash="1090266248531783773" id="com.st.stm32cube.ide.mcu.toolchain.armnone.setup.CrossBuiltinSpecsDetector" keep-relative-paths="false" name="MCU ARM GCC Built-in Compiler Settings" parameter="${COMMAND} ${FLAGS} -E -P -v -dD &quot;${INPUTS}&quot;" prefer-non-shared="true">
<provider class="com.st.stm32cube.ide.mcu.toolchain.armnone.setup.CrossBuiltinSpecsDetector" console="false" env-hash="1366383323889271194" id="com.st.stm32cube.ide.mcu.toolchain.armnone.setup.CrossBuiltinSpecsDetector" keep-relative-paths="false" name="MCU ARM GCC Built-in Compiler Settings" parameter="${COMMAND} ${FLAGS} -E -P -v -dD &quot;${INPUTS}&quot;" prefer-non-shared="true">
<language-scope id="org.eclipse.cdt.core.gcc"/>
<language-scope id="org.eclipse.cdt.core.g++"/>
</provider>
@ -16,7 +16,7 @@
<provider copy-of="extension" id="org.eclipse.cdt.ui.UserLanguageSettingsProvider"/>
<provider-reference id="org.eclipse.cdt.core.ReferencedProjectsLanguageSettingsProvider" ref="shared-provider"/>
<provider-reference id="org.eclipse.cdt.managedbuilder.core.MBSLanguageSettingsProvider" ref="shared-provider"/>
<provider class="com.st.stm32cube.ide.mcu.toolchain.armnone.setup.CrossBuiltinSpecsDetector" console="false" env-hash="1090266248531783773" id="com.st.stm32cube.ide.mcu.toolchain.armnone.setup.CrossBuiltinSpecsDetector" keep-relative-paths="false" name="MCU ARM GCC Built-in Compiler Settings" parameter="${COMMAND} ${FLAGS} -E -P -v -dD &quot;${INPUTS}&quot;" prefer-non-shared="true">
<provider class="com.st.stm32cube.ide.mcu.toolchain.armnone.setup.CrossBuiltinSpecsDetector" console="false" env-hash="1366383323889271194" id="com.st.stm32cube.ide.mcu.toolchain.armnone.setup.CrossBuiltinSpecsDetector" keep-relative-paths="false" name="MCU ARM GCC Built-in Compiler Settings" parameter="${COMMAND} ${FLAGS} -E -P -v -dD &quot;${INPUTS}&quot;" prefer-non-shared="true">
<language-scope id="org.eclipse.cdt.core.gcc"/>
<language-scope id="org.eclipse.cdt.core.g++"/>
</provider>

View File

@ -33,7 +33,7 @@
#define STA_TACOS_STATEMACHINE_PRIORITY osPriorityNormal
// Statemachine settings. Here, we only have a single state which is also the initial state.
#define STA_TACOS_NUM_STATES 1
#define STA_TACOS_NUM_STATES 3
#define STA_TACOS_INITIAL_STATE 0
#endif /* INC_STA_CONFIG_HPP_ */

26
App/Inc/tasks/disturb.hpp Normal file
View File

@ -0,0 +1,26 @@
/*
* disturb.hpp
*
* Created on: Nov 20, 2023
* Author: Dario
*/
#ifndef INC_TASKS_DISTURB_HPP_
#define INC_TASKS_DISTURB_HPP_
#include <sta/tacos/thread.hpp>
namespace demo
{
class DisturbTask : public sta::tacos::TacosThread {
public:
DisturbTask();
void init() override;
void func() override;
};
} // namespace demo
#endif /* INC_TASKS_DISTURB_HPP_ */

View File

@ -15,7 +15,6 @@ namespace demo
class DummyTask : public sta::tacos::TacosThread {
public:
DummyTask(const char* name);
~DummyTask() override;
void init() override;

26
App/Inc/tasks/toggle.hpp Normal file
View File

@ -0,0 +1,26 @@
/*
* toggle.hpp
*
* Created on: Nov 10, 2023
* Author: Dario
*/
#ifndef INC_STA_TOGGLE_HPP_
#define INC_STA_TOGGLE_HPP_
#include <sta/tacos/thread.hpp>
namespace demo
{
class Toggle : public sta::tacos::TacosThread
{
public:
Toggle();
void init() override;
void func() override;
};
}
#endif /* INC_STA_TOGGLE_HPP_ */

View File

@ -12,8 +12,11 @@
#include <sta/tacos/manager.hpp>
#include <sta/tacos/statemachine.hpp>
#include <tasks/dummy.hpp>
#include <tasks/toggle.hpp>
#include <tasks/disturb.hpp>
#include <gpio.h>
#include <sta/devices/stm32/gpio_pin.hpp>
#include <sta/rtos/debug/heap_stats.hpp>
#include <memory>
@ -24,38 +27,6 @@ namespace sta
{
void onStatemachineInit()
{
Statemachine::instance()->setStateFunction([](uint16_t state) -> uint16_t {
// ###### Implement your state transition function here! ######
/**
* This function should return some state within 0 and STA_TACOS_NUM_STATES (specified in the config.hpp).
* Return the same state as given as the argument for no state transition. Make sure, that this function is not
* computationally expensive.
*/
return state;
});
Statemachine::instance()->setTimerFunction([](uint16_t state, uint16_t lastState, uint32_t event) {
// ###### Set the failsafe and lockout timers here ######
/**
* This function is called after a state transition from lastState to state. Event corresponds to EventFlag enum in the Statemachine
* and encode how the state change happened.
*
* Setting a timer here is fully optional and depends on your system's specifications.
*/
uint32_t someFailsafeMillis = 42;
uint16_t someNextState = STA_TACOS_INITIAL_STATE;
uint32_t someLockoutMillis = 21;
// Start the failsafe timer to force a state transition to someNextState after someFailsafeMillis milliseconds.
Statemachine::instance()->setFailsafeTimer(someFailsafeMillis, someNextState);
// Start the lockout timer to block a state transition for the first someLockoutMillis milliseconds.
Statemachine::instance()->setLockoutTimer(someLockoutMillis);
});
}
@ -64,7 +35,15 @@ namespace sta
// ###### Register different threads for different states here. ######
// The dummy task runs for state 0.
Manager::instance()->registerThread(std::make_shared<demo::DummyTask>("Dummy"), {0});
Manager::instance()->registerThread(std::make_shared<demo::DummyTask>("State 0"), {0});
Manager::instance()->registerThread(std::make_shared<demo::DummyTask>("State 1"), {1});
Manager::instance()->registerThread(std::make_shared<demo::DummyTask>("State 2 - FAIL"), {2});
Manager::instance()->registerThread(std::make_shared<demo::Toggle>(), {0, 1});
// Manager::instance()->registerThread(std::make_shared<demo::DisturbTask>(), {0, 1});
STA_DEBUG_PRINTF("The answer to everything is %d", 42);
STA_DEBUG_HEAP_STATS();
}
} // namespace tacos
} // namespace sta

35
App/Src/tasks/disturb.cpp Normal file
View File

@ -0,0 +1,35 @@
/*
* disturb.cpp
*
* Created on: Nov 20, 2023
* Author: Dario
*/
#include <tasks/disturb.hpp>
#include <sta/tacos/statemachine.hpp>
#include <sta/debug/debug.hpp>
namespace demo
{
DisturbTask::DisturbTask()
: sta::tacos::TacosThread("Disturb", osPriorityNormal)
{
}
void DisturbTask::init()
{
}
void DisturbTask::func()
{
STA_DEBUG_PRINTLN(this->getName());
sta::tacos::Statemachine::instance()->stateChangeEvent.wait(sta::tacos::EventFlags::ALL, osWaitForever);
uint16_t currentState = sta::tacos::Statemachine::instance()->getCurrentState();
sta::tacos::Statemachine::instance()->requestTimedStateTransition(currentState, 2, 4000, 0);
}
} // namespace demo

View File

@ -19,8 +19,6 @@ namespace demo
}
DummyTask::~DummyTask(){}
void DummyTask::init()
{

34
App/Src/tasks/toggle.cpp Normal file
View File

@ -0,0 +1,34 @@
/*
* toggle.cpp
*
* Created on: Nov 10, 2023
* Author: Dario
*/
#include <tasks/toggle.hpp>
#include <sta/tacos/statemachine.hpp>
#include <sta/debug/debug.hpp>
namespace demo
{
Toggle::Toggle()
: sta::tacos::TacosThread{ "Toggle", osPriorityNormal }
{
}
void Toggle::init()
{
}
void Toggle::func()
{
uint16_t state = sta::tacos::Statemachine::instance()->getCurrentState();
uint16_t next = 1 - state;
sta::tacos::Statemachine::instance()->requestStateTransition(state, next, 5000);
}
}

@ -1 +1 @@
Subproject commit 0e96b2ec29fde1e56e12fc9f0dceb80012e0caf3
Subproject commit b1d6456dd88f881f68674824339483f69dd280f0

@ -1 +1 @@
Subproject commit 4da1f0bb7dd48f89e9dd5ee0ec181638894e55e2
Subproject commit c4692287cf88e518d22078c1d284be343fc82fba

View File

@ -20,6 +20,8 @@
# define STA_TACOS_INITIAL_STATE 0
#endif
#define STA_TACOS_STATEMACHINE_QUEUE_LENGTH 4
// State transition happened because of
#define STA_TACOS_STATE_CHANGE_NORMAL_FLAG ( 0x1U )
#define STA_TACOS_STATE_CHANGE_FORCED_FLAG ( 0x1U << 1)
@ -29,34 +31,41 @@
#include <sta/tacos/thread.hpp>
#include <sta/rtos/queue.hpp>
#include <sta/rtos/timer.hpp>
#include <sta/rtos/event.hpp>
#include <sta/event.hpp>
#include <sta/debug/assert.hpp>
#include <functional>
#include <tuple>
namespace sta
{
namespace tacos
{
typedef std::function<uint16_t(uint16_t)> state_fn;
typedef std::function<void(uint16_t, uint16_t, uint32_t)> timer_fn;
enum EventFlags
{
NORMAL = 0x1U,
FORCED = 0x1U << 1,
TIMEOUT = 0x1U << 2,
STARTUP = 0x1U << 3,
ALL = NORMAL | FORCED | TIMEOUT | STARTUP
};
struct StateTransition
{
uint16_t from;
uint16_t to;
EventFlags event;
uint32_t lockout;
};
class Statemachine : public TacosThread
{
public:
enum EventFlags
{
NORMAL = 0x1U,
FORCED = 0x1U << 1,
TIMEOUT = 0x1U << 2,
STARTUP = 0x1U << 3,
ALL = NORMAL | FORCED | TIMEOUT | STARTUP
};
/**
* @brief The global event signaling a state change.
*/
@ -84,29 +93,23 @@ namespace sta
uint16_t getCurrentState() const;
/**
* @brief Registers a new state transition function. This is a function for the user to implement state transition rules.
* @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.
*/
void setStateFunction(state_fn func);
void requestStateTransition(uint32_t from, uint32_t to, uint32_t lockout = 0);
/**
* @brief Registers a new timer function. This is a function for the user to set the timers after a state change.
* @brief Request a state transition after a given time has passed.
*
* @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 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.
*/
void setTimerFunction(timer_fn func);
/**
* @brief Starts the failsafe timer for the desired duration and the desired next state.
*/
void setFailsafeTimer(uint32_t millis, uint16_t nextState);
/**
* @brief Starts the lockoutTimer for the desired duration.
*/
void setLockoutTimer(uint32_t millis);
/**
* @brief this function call allows forced state transitions from external and internal sources.
*/
void forceStateTransition(uint16_t state);
void requestTimedStateTransition(uint32_t from, uint32_t to, uint32_t millis, uint32_t lockout = 0);
void init() override;
void func() override;
@ -133,15 +136,19 @@ namespace sta
~Statemachine() {}
private:
/**
* @brief Starts the lockoutTimer for the desired duration.
*/
void setLockoutTimer(uint32_t millis);
private:
uint16_t currentState_;
uint16_t failsafeState_;
RtosTimer lockoutTimer_;
RtosTimer failsafeTimer_;
state_fn transitionFunc_;
timer_fn timerFunc_;
RtosQueue<StateTransition> queue_;
};
} // namespace tacos
} // namespace sta

View File

@ -80,7 +80,7 @@ namespace sta
void Manager::func()
{
Statemachine::stateChangeEvent.wait(Statemachine::EventFlags::ALL, osWaitForever);
Statemachine::stateChangeEvent.wait(EventFlags::ALL, osWaitForever);
HeapStats_t stats;
vPortGetHeapStats(&stats);

View File

@ -17,47 +17,46 @@ namespace sta
Statemachine::Statemachine()
: TacosThread{"Statemachine", STA_TACOS_STATEMACHINE_PRIORITY},
currentState_{STA_TACOS_INITIAL_STATE},
failsafeState_{STA_TACOS_INITIAL_STATE},
lockoutTimer_{[](void *){}, nullptr},
failsafeTimer_{[](void *){}, nullptr},
transitionFunc_{[](uint16_t) -> uint16_t { return Statemachine::instance()->getCurrentState(); }},
timerFunc_{[](uint16_t, uint16_t, uint16_t) {}}
queue_{STA_TACOS_STATEMACHINE_QUEUE_LENGTH}
{
STA_ASSERT(STA_TACOS_INITIAL_STATE < STA_TACOS_NUM_STATES);
}
void Statemachine::init()
{
timerFunc_(0, 0, EventFlags::STARTUP);
}
void Statemachine::func()
{
if (!lockoutTimer_.isRunning())
// Wait for a message to be added to the queue.
StateTransition transition;
queue_.get(&transition, osWaitForever);
// Check if the transition isn't blocked and is legal.
if (!lockoutTimer_.isRunning() && transition.from == currentState_)
{
uint16_t next = transitionFunc_(currentState_);
STA_ASSERT(transition.to < STA_TACOS_NUM_STATES);
STA_ASSERT(next < STA_TACOS_NUM_STATES);
// Perform the transition and notify the threads. The event flags are set
// here in order to allow threads to react immediately.
currentState_ = transition.to;
Statemachine::stateChangeEvent.set(transition.event);
Statemachine::stateChangeEvent.clear(EventFlags::ALL);
// Check if a state change is desired. Block if the lockoutTimer is still running.
if (next != currentState_)
if (failsafeTimer_.isRunning())
{
if (failsafeTimer_.isRunning())
{
failsafeTimer_.stop();
}
failsafeTimer_.stop();
}
// Call user space code to set the timers again.
timerFunc_(next, currentState_, EventFlags::NORMAL);
// Update the state and trigger the global state changed event.
currentState_ = next;
Statemachine::stateChangeEvent.set(EventFlags::NORMAL);
Statemachine::stateChangeEvent.clear(EventFlags::ALL);
// Start the lockout timer if requested.
if (transition.lockout != 0)
{
setLockoutTimer(transition.lockout);
}
}
osThreadYield();
}
uint16_t Statemachine::getCurrentState() const
@ -65,28 +64,27 @@ namespace sta
return currentState_;
}
void Statemachine::setStateFunction(state_fn func)
void Statemachine::requestStateTransition(uint32_t from, uint32_t to, uint32_t lockout /* = 0 */)
{
STA_ASSERT(func != nullptr);
StateTransition transition;
transition.from = from;
transition.to = to;
transition.event = EventFlags::NORMAL;
transition.lockout = lockout;
transitionFunc_ = func;
// 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::setTimerFunction(timer_fn func)
void Statemachine::requestTimedStateTransition(uint32_t from, uint32_t to, uint32_t millis, uint32_t lockout /* = 0 */)
{
STA_ASSERT(func != nullptr);
timerFunc_ = func;
}
void Statemachine::setFailsafeTimer(uint32_t millis, uint16_t nextState)
{
STA_ASSERT(nextState < STA_TACOS_NUM_STATES);
STA_ASSERT(to < STA_TACOS_NUM_STATES);
STA_ASSERT(!failsafeTimer_.isRunning());
failsafeTimer_.setCallback([nextState](void* arg) {
Statemachine::instance()->forceStateTransition(nextState);
}, &failsafeState_);
failsafeTimer_.setCallback([from, to, lockout](void* arg) {
Statemachine::instance()->requestStateTransition(from, to, lockout);
}, NULL);
failsafeTimer_.start(millis);
}
@ -99,25 +97,6 @@ namespace sta
lockoutTimer_.start(millis);
}
void Statemachine::forceStateTransition(uint16_t state)
{
if (failsafeTimer_.isRunning())
{
failsafeTimer_.stop();
}
if (lockoutTimer_.isRunning())
{
lockoutTimer_.stop();
}
timerFunc_(state, currentState_, EventFlags::FORCED);
currentState_ = state;
Statemachine::stateChangeEvent.set(EventFlags::FORCED);
Statemachine::stateChangeEvent.clear(EventFlags::ALL);
}
Statemachine* Statemachine::_instance = nullptr;
RtosEvent Statemachine::stateChangeEvent;

View File

@ -81,21 +81,26 @@ namespace sta
// Infinite loop allowing quick restarts of this thread after termination.
while (true)
{
// The thread has to wait until the startup has been completed.
// The thread has to wait until the system startup has been completed.
rtos::waitForStartupEvent();
// Wait for a thread start flag.
wait(STA_RTOS_THREAD_FLAG_START);
// Call user-defined initialization code.
running_ = true;
init();
// Run the thread until the termination flag is set.
while (!resetTerminationRequest())
// Run the thread until the termination flag is set. Reset this
while (!isTerminationRequested())
{
func();
}
// Clear the termination request flag for this thread.
deleteTerminationRequest();
// Call user-defined cleanup code.
cleanup();
running_ = false;
}