Skip to content
This repository was archived by the owner on Aug 1, 2024. It is now read-only.

Finite State Machine library #418

Merged
merged 24 commits into from
Jul 3, 2019
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
c7aeb70
Add a simple library to help build structured FSMs
Jun 18, 2019
f98c9ad
Pull state-specific logic into functions
Jun 24, 2019
ae99a9f
Use usm to run safe landing planner state machine
Jun 24, 2019
e3d066b
Add a python script to generate .dot graph files from usm state machines
Jun 25, 2019
ea86d2d
Add state machine diagram updates to CI
Jun 25, 2019
fdfe9e7
Fix asserts to use GTest asserts
Jun 25, 2019
d42e196
Move actual state machine logic out of the node
Jun 26, 2019
4f37011
Update state machine diagrams to new state machine names
Jun 26, 2019
e194b6d
Fix style
Jun 26, 2019
575dd93
Remove orphaned definition
Jun 26, 2019
6bfbd50
Add a few tests for the waypoint generator
Jun 26, 2019
496ab86
Fix style
Jun 27, 2019
4db7790
add additional tests for state transitions in safe landing planner
baumanta Jun 28, 2019
e014dba
slp: bugfixes for fsm
mrivi Jun 28, 2019
58dc18b
slp wg bugfix: when landing is initiated lower than the loiter altitude,
mrivi Jun 28, 2019
45ba4c9
fix style
mrivi Jun 28, 2019
8e60d0f
PR comments: enum class, .travis.yml improvement
Jul 1, 2019
881ef64
Fix namespace changes due to enum class
Jul 1, 2019
fb50a72
slp: add debug statement
mrivi Jul 1, 2019
a4a71ea
slp: make landing area symmetric around the vehicle
mrivi Jul 1, 2019
cd07b98
slp: use 80 percentile of the landing area height to change altitude
mrivi Jul 1, 2019
5de8fc1
update generated launch file for structure core and safe landing planner
mrivi Jul 2, 2019
0f691fd
Minor fixes from review: UPPER_CASE, better comments, formatting fix
Jul 2, 2019
6a6ad88
Update transition names in state machine diagram
Jul 2, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ script:
# Check for clang format
- cd src/avoidance
- git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"
- ./check_code_format.sh
- (cd tools && ./check_code_format.sh)
- (cd tools && ./check_state_machine_diagrams.sh)
- catkin config --cmake-args -DCMAKE_BUILD_TYPE=Release -DCATKIN_ENABLE_TESTING=False -DDISABLE_SIMULATION=ON && catkin build
- catkin config --cmake-args -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCATKIN_ENABLE_TESTING=True && catkin build avoidance local_planner global_planner safe_landing_planner --no-deps -v -i --catkin-make-args tests
- status=0 && for f in ~/catkin_ws/devel/lib/*/*-test; do valgrind --leak-check=full --track-origins=yes --error-exitcode=1 $f || status=1; done
Expand Down
1 change: 1 addition & 0 deletions avoidance/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ if(CATKIN_ENABLE_TESTING)
# Add gtest based cpp test target and link libraries
catkin_add_gtest(${PROJECT_NAME}-test test/main.cpp
test/test_common.cpp
test/test_usm.cpp
)

if(TARGET ${PROJECT_NAME}-test)
Expand Down
99 changes: 99 additions & 0 deletions avoidance/include/avoidance/usm.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#pragma once

/*
* Copyright (c) 2019 Julian Kent. All rights reserved.
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of libgnc nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*
*
* For usage examples, look at the tests.
*/

namespace usm {

enum class Transition { REPEAT, NEXT1, NEXT2, NEXT3, NEXT4, ERROR };

template <typename StateEnum>
class StateMachine {
public:
StateMachine(StateEnum startingState);
void iterateOnce();

StateEnum getState();

protected:
virtual Transition runCurrentState(
StateEnum currentState) = 0; // a big switch
virtual StateEnum chooseNextState(
StateEnum currentState, Transition transition) = 0; // nested switches

private:
StateEnum m_currentState;
};

/*---------------IMPLEMENTATION------------------*/

template <typename StateEnum>
StateMachine<StateEnum>::StateMachine(StateEnum startingState)
: m_currentState(startingState) {}

template <typename StateEnum>
void StateMachine<StateEnum>::iterateOnce() {
Transition t = runCurrentState(m_currentState);
if (t != Transition::REPEAT)
m_currentState = chooseNextState(m_currentState, t);
}

template <typename StateEnum>
StateEnum StateMachine<StateEnum>::getState() {
return m_currentState;
}
}

/*---------------MACROS TO MAKE TRANSITION TABLES EASY------------------*/

// clang-format off
#define USM_TABLE(current_state, error, ...) \
switch (current_state) { \
__VA_ARGS__; \
default: break; \
} \
return error

#define USM_STATE(transition, start_state, ...) \
case start_state: \
switch (transition) { \
__VA_ARGS__; \
default: break; \
} \
break

#define USM_MAP(transition, next_state) \
case transition: return next_state
// clang-format on
251 changes: 251 additions & 0 deletions avoidance/test/test_usm.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
#include <gtest/gtest.h>

#include <avoidance/usm.h>

#include <iostream>

using namespace usm;

/*
* Copyright (c) 2019 Julian Kent. All rights reserved.
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of libgnc nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*
*/

/*
* Implement a basic state machine with 2 paths (A & B), and error state,
* and a transition table. Use this as an example for your use case!
*/

enum TestStates {
START,
PATH_A_1,
PATH_A_2,
END,
PATH_B_1,
PATH_B_2,
PATH_B_3,
CLEANUP
};

class TestStateMachine final : public StateMachine<TestStates> {
public:
TestStateMachine() : StateMachine<TestStates>(START) {}

bool path_a = true;
bool trigger_error_once = false;

bool start_called = false;
bool a1_called = false;
bool a2_called = false;
bool b1_called = false;
bool b2_called = false;
bool b3_called = false;
bool end_called = false;
bool cleanup_called = false;

protected:
Transition runCurrentState(TestStates currentState) override {
if (trigger_error_once) {
trigger_error_once = false;
return Transition::ERROR;
}

// clang-format off
switch(currentState) {
case START: return start();
case PATH_A_1: return a1();
case PATH_A_2: return a2();
case END: return end();
case PATH_B_1: return b1();
case PATH_B_2: return b2();
case PATH_B_3: return b3();
case CLEANUP: return cleanup();
}
// clang-format on
}

TestStates chooseNextState(TestStates currentState,
Transition transition) override {
// clang-format off
USM_TABLE(currentState, CLEANUP,
USM_STATE(transition, START, USM_MAP(Transition::NEXT1, PATH_A_1);
USM_MAP(Transition::NEXT2, PATH_B_1));
USM_STATE(transition, PATH_A_1, USM_MAP(Transition::NEXT1, PATH_A_2);
USM_MAP(Transition::ERROR, PATH_B_3));
USM_STATE(transition, PATH_A_2, USM_MAP(Transition::NEXT1, END));
USM_STATE(transition, PATH_B_1, USM_MAP(Transition::NEXT1, PATH_B_2));
USM_STATE(transition, PATH_B_2, USM_MAP(Transition::NEXT1, PATH_B_3));
USM_STATE(transition, PATH_B_3, USM_MAP(Transition::NEXT1, END));
USM_STATE(transition, CLEANUP, USM_MAP(Transition::NEXT1, END))
);
// clang-format on
}

private:
Transition start() {
start_called = true;
return path_a ? Transition::NEXT1 : Transition::NEXT2;
}
Transition a1() {
a1_called = true;
return Transition::NEXT1;
}
Transition a2() {
a2_called = true;
return Transition::NEXT1;
}
Transition b1() {
b1_called = true;
return Transition::NEXT1;
}
Transition b2() {
b2_called = true;
return Transition::NEXT1;
}
Transition b3() {
b3_called = true;
return Transition::NEXT1;
}
Transition end() {
end_called = true;
return Transition::REPEAT;
}
Transition cleanup() {
cleanup_called = true;
return Transition::NEXT1;
}
};

TEST(StateMachine, runPathA) {
TestStateMachine m;
ASSERT_EQ(m.getState(), START);

ASSERT_FALSE(m.start_called);
m.iterateOnce();
ASSERT_TRUE(m.start_called);
ASSERT_EQ(m.getState(), PATH_A_1);

ASSERT_FALSE(m.a1_called);
m.iterateOnce();
ASSERT_TRUE(m.a1_called);
ASSERT_EQ(m.getState(), PATH_A_2);

ASSERT_FALSE(m.a2_called);
m.iterateOnce();
ASSERT_TRUE(m.a2_called);
ASSERT_EQ(m.getState(), END);

ASSERT_FALSE(m.end_called);
m.iterateOnce();
ASSERT_TRUE(m.end_called);
}

TEST(StateMachine, runPathB) {
TestStateMachine m;
m.path_a = false;
ASSERT_EQ(m.getState(), START);

ASSERT_FALSE(m.start_called);
m.iterateOnce();
ASSERT_TRUE(m.start_called);
ASSERT_EQ(m.getState(), PATH_B_1);

ASSERT_FALSE(m.b1_called);
m.iterateOnce();
ASSERT_TRUE(m.b1_called);
ASSERT_EQ(m.getState(), PATH_B_2);

ASSERT_FALSE(m.b2_called);
m.iterateOnce();
ASSERT_TRUE(m.b2_called);
ASSERT_EQ(m.getState(), PATH_B_3);

ASSERT_FALSE(m.b3_called);
m.iterateOnce();
ASSERT_TRUE(m.b3_called);
ASSERT_EQ(m.getState(), END);

ASSERT_FALSE(m.end_called);
m.iterateOnce();
ASSERT_TRUE(m.end_called);
}

TEST(StateMachine, enterDefaultError) {
TestStateMachine m;
ASSERT_EQ(m.getState(), START);

ASSERT_FALSE(m.start_called);
m.iterateOnce();
ASSERT_TRUE(m.start_called);
ASSERT_EQ(m.getState(), PATH_A_1);

ASSERT_FALSE(m.a1_called);
m.iterateOnce();
ASSERT_TRUE(m.a1_called);
ASSERT_EQ(m.getState(), PATH_A_2);

m.trigger_error_once = true;
m.iterateOnce();
ASSERT_EQ(m.getState(), CLEANUP);

ASSERT_FALSE(m.cleanup_called);
m.iterateOnce();
ASSERT_TRUE(m.cleanup_called);
ASSERT_EQ(m.getState(), END);

ASSERT_FALSE(m.end_called);
m.iterateOnce();
ASSERT_TRUE(m.end_called);
}

TEST(StateMachine, enterCustomError) {
TestStateMachine m;
ASSERT_EQ(m.getState(), START);

ASSERT_FALSE(m.start_called);
m.iterateOnce();
ASSERT_TRUE(m.start_called);
ASSERT_EQ(m.getState(), PATH_A_1);

m.trigger_error_once = true;
m.iterateOnce();
ASSERT_EQ(m.getState(), PATH_B_3);

ASSERT_FALSE(m.b3_called);
m.iterateOnce();
ASSERT_TRUE(m.b3_called);
ASSERT_EQ(m.getState(), END);

ASSERT_FALSE(m.end_called);
m.iterateOnce();
ASSERT_TRUE(m.end_called);
ASSERT_EQ(m.getState(), END);
}
17 changes: 17 additions & 0 deletions avoidance/test/test_usm.cpp.dot
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
digraph {
"START" -> "PATH_A_1" [label="NEXT1", style="solid", weight=1]
"START" -> "PATH_B_1" [label="NEXT2", style="solid", weight=1]
"START" -> "CLEANUP" [label="ERROR", style="dotted", weight=0.1]
"PATH_A_1" -> "PATH_A_2" [label="NEXT1", style="solid", weight=1]
"PATH_A_1" -> "PATH_B_3" [label="ERROR", style="solid", weight=1]
"PATH_A_2" -> "END" [label="NEXT1", style="solid", weight=1]
"PATH_A_2" -> "CLEANUP" [label="ERROR", style="dotted", weight=0.1]
"PATH_B_1" -> "PATH_B_2" [label="NEXT1", style="solid", weight=1]
"PATH_B_1" -> "CLEANUP" [label="ERROR", style="dotted", weight=0.1]
"PATH_B_2" -> "PATH_B_3" [label="NEXT1", style="solid", weight=1]
"PATH_B_2" -> "CLEANUP" [label="ERROR", style="dotted", weight=0.1]
"PATH_B_3" -> "END" [label="NEXT1", style="solid", weight=1]
"PATH_B_3" -> "CLEANUP" [label="ERROR", style="dotted", weight=0.1]
"CLEANUP" -> "END" [label="NEXT1", style="solid", weight=1]
"CLEANUP" -> "CLEANUP" [label="ERROR", style="dotted", weight=0.1]
}
Loading