-
Notifications
You must be signed in to change notification settings - Fork 1
State Machine Usage
Since STM does not have any hardware interfaces, it can be run without any prior steps. Therefore, we don't need to document how to use the code in that regard. However, code can be used in other ways. In particular, the behaviour needs to be modified and features need to be added to adjust to changing requirements.
If you want to change a transition condition, you should follow these rough steps:
- Find sources for the required information
- Update
data
function signatures - Change the implementation logic
- Update the documentation
- Update tests
Let's pretend we are not happy with the current way of checking whether the pod has stopped. Instead of completely relying on navigation and their velocity values, we want to add a user command that allows us to manually confirm that the pod has indeed stopped. Further, assume that Telemetry have already done their part by implementing the GUI and adding a stopped_command
field to the data::Telemetry
section of the CDS. It is now up to us to incorporate this value into the transition logic.
Step 1 is easy because we have already mentioned that the value we require lives in data::Telemetry
.
Step 2 is to add it to the function signatures in transitions.hpp
and transitions.cpp
. So we change
// src/state_machine/transitions.hpp
bool checkPodStopped(utils::Logger &log, const data::Navigation &nav_data);
to
// src/state_machine/transitions.hpp
bool checkPodStopped(Logger &log, const data::Navigation &nav_data, const data::Telemetry &telemetry_data);
The same has to be done in transitions.cpp
. Further, we need to make sure that all the checkTransition
implementations in state.cpp
call the function with the right set of arguments. For example, we change
// src/state_machine/state.cpp
State *NominalBraking::checkTransition(utils::Logger &log)
{
updateModuleData();
// ... other conditions ...
bool stopped = checkPodStopped(log, nav_data_);
if (stopped) { return Finished::getInstance(); }
return nullptr;
}
to
// src/state_machine/state.cpp
State *NominalBraking::checkTransition(utils::Logger &log)
{
updateModuleData();
// ... other conditions ...
bool stopped = checkPodStopped(log, nav_data_, telemetry_data_);
if (stopped) { return Finished::getInstance(); }
return nullptr;
}
This needs to be done in all places where checkPodStopped
is being referenced.
In Step 3 we need to modify the actual logic. Currently, we have this:
bool checkPodStopped(utils::Logger &log, const data::Navigation &nav_data, const Telemetry &telemetry_data)
{
if (nav_data.velocity > 0) return false;
// ... logging code ...
return true;
}
To incorporate the command, we can change this to
// src/state_machine/transitions.cpp
bool checkPodStopped(utils::Logger &log, const data::Navigation &nav_data, const data::Telemetry &telemetry_data)
{
if (nav_data.velocity <= 0) {
// ... logging code ...
return true;
} else if (telemetry_data.stopped_command) {
// ... logging code ...
return true;
}
return false;
}
This behaves as intended and also allows us to log which of the conditions was met.
In Step 4 we need to change the comment in transitions.hpp
to match the behaviour. We could end up with something like
// src/state_machine/transitions.hpp
/*
* @brief Returns true iff the pod is close enough to the end of the track or confirmation has been received that the pod has stopped.
*/
bool checkPodStopped(utils::Logger &log, const data::Navigation &nav_data, const data::Telemetry &telemetry_data);
You have now successfully changed the transition conditions for checkPodStopped
. However, if it's not tested, it doesn't work. This leads us to the final step. If you try and run make test
now, the tests will not compile and if they do, they shouldn't pass.
Firstly, we need to change all the references to checkPodStopped
in test/src
to use the new signature as we have already done in step 2. In our case, we then need to make sure that the right preconditions are guaranteed so that the existing tests still work. For example, consider this test:
// test/src/state_machine/transitions.test.cpp
TEST_F(TransitionFunctionality, handlesPositiveVelocity)
{
data::Navigation nav_data;
constexpr int max_velocity = 100; // 100 m/s is pretty fast...
constexpr nav_t step_size = static_cast<data::nav_t>(max_velocity) / static_cast<data::nav_t>(TEST_SIZE);
for (int i = 1; i <= TEST_SIZE; i++) {
nav_data.velocity = step_size * static_cast<data::nav_t>(i);
nav_data.acceleration = static_cast<data::nav_t>(rand());
nav_data.displacement = static_cast<data::nav_t>(rand());
nav_data.braking_distance = static_cast<data::nav_t>(rand());
nav_data.emergency_braking_distance = static_cast<data::nav_t>(rand());
enableOutput();
ASSERT_EQ(false, checkPodStopped(log, nav_data));
disableOutput();
}
}
I would change this as follows:
// test/src/state_machine/transitions.test.cpp
TEST_F(TransitionFunctionality, handlesPositiveVelocity)
{
Navigation nav_data;
data::Telemetry telemetry_data;
telemetry_data.stopped_command = false;
constexpr int max_velocity = 100; // 100 m/s is pretty fast...
constexpr data::nav_t step_size = static_cast<data::nav_t>(max_velocity) / static_cast<data::nav_t>(TEST_SIZE);
for (int i = 1; i <= TEST_SIZE; i++) {
nav_data.velocity = step_size * static_cast<data::nav_t>(i);
nav_data.acceleration = static_cast<data::nav_t>(rand());
nav_data.displacement = static_cast<data::nav_t>(rand());
nav_data.braking_distance = static_cast<data::nav_t>(rand());
nav_data.emergency_braking_distance = static_cast<data::nav_t>(rand());
enableOutput();
ASSERT_EQ(false, checkPodStopped(log, nav_data));
disableOutput();
}
}
Of course, in most cases, you will have to add and/or remove tests. However, since the way to do this will be different in each case, that part has been omitted from this example.
Of course, often the design changes will require more than simply adapting the transition conditions. Here's what you need to do in that case:
- Implement all the transition checks in
src/state_machine/transitions.*
as discussed above. - Adapt the
data::State
enum. Note that adding an internal state may not require a new enum value. - Go to
state.hpp
and use theMAKE_STATE
macro to generate all the boilerplate code. - Set the
instance_
,enum_value_
andstring_representation_
fields instate.cpp
. - Implement the
checkTransition
method for your new state. - Adapt existing
checkTransition
methods of preceding states (i.e. old states that transition into the new one). - Update the documentation
- Update tests (this may require a lot of work if you've added something to the enum)
- Home
- How to add and edit pages on the wiki
- Glossary
- Admin
- Projects & Subsystems
- Motor Controllers
- Navigation
- Quality Assurance
- Sensors
- State Machine
- Telemetry
- Technical Guides
- BeagleBone Black (BBB)
- Configuration
- Contributing
- Testing
- Install VM on Mac
- Makefiles
- Reinstall MacOS Mojave
- Travis Troubleshooting
- Knowledge Base