diff options
| author | jacqueline <me@jacqueline.id.au> | 2023-05-19 21:21:27 +1000 |
|---|---|---|
| committer | jacqueline <me@jacqueline.id.au> | 2023-05-19 21:21:27 +1000 |
| commit | a6ab1504058304012791281f9eb42c262745888f (patch) | |
| tree | f82379cd1e66a8ae2f1afbae5cf083a8ab7acc53 /lib/tinyfsm/examples | |
| parent | b320a6a863cf1c10dc79254af41f573730935564 (diff) | |
| download | tangara-fw-a6ab1504058304012791281f9eb42c262745888f.tar.gz | |
Add tinyfsm, start converting core functions to an FSM-based event loop
Diffstat (limited to 'lib/tinyfsm/examples')
| -rw-r--r-- | lib/tinyfsm/examples/api/Makefile | 63 | ||||
| -rw-r--r-- | lib/tinyfsm/examples/api/debugging_switch.cpp | 122 | ||||
| -rw-r--r-- | lib/tinyfsm/examples/api/mealy_machine.cpp | 70 | ||||
| -rw-r--r-- | lib/tinyfsm/examples/api/moore_machine.cpp | 64 | ||||
| -rw-r--r-- | lib/tinyfsm/examples/api/multiple_switch.cpp | 145 | ||||
| -rw-r--r-- | lib/tinyfsm/examples/api/resetting_switch.cpp | 110 | ||||
| -rw-r--r-- | lib/tinyfsm/examples/api/simple_switch.cpp | 85 | ||||
| -rw-r--r-- | lib/tinyfsm/examples/elevator/Makefile | 98 | ||||
| -rw-r--r-- | lib/tinyfsm/examples/elevator/README.md | 86 | ||||
| -rw-r--r-- | lib/tinyfsm/examples/elevator/elevator.cpp | 115 | ||||
| -rw-r--r-- | lib/tinyfsm/examples/elevator/elevator.hpp | 55 | ||||
| -rw-r--r-- | lib/tinyfsm/examples/elevator/fsmlist.hpp | 19 | ||||
| -rw-r--r-- | lib/tinyfsm/examples/elevator/main.cpp | 40 | ||||
| -rw-r--r-- | lib/tinyfsm/examples/elevator/motor.cpp | 60 | ||||
| -rw-r--r-- | lib/tinyfsm/examples/elevator/motor.hpp | 50 |
15 files changed, 1182 insertions, 0 deletions
diff --git a/lib/tinyfsm/examples/api/Makefile b/lib/tinyfsm/examples/api/Makefile new file mode 100644 index 00000000..63b110e0 --- /dev/null +++ b/lib/tinyfsm/examples/api/Makefile @@ -0,0 +1,63 @@ +# Compiler prefix, in case your default compiler does not implement all C++11 features: +#CROSS = /opt/toolchain/x86_64-pc-linux-gnu-gcc-4.7.0/bin/x86_64-pc-linux-gnu- + +# HINT: g++ -Q -O2 --help=optimizers +OPTIMIZER = -Os + +CC = $(CROSS)gcc +CXX = $(CROSS)g++ +SIZE = size -d +RM = rm -f + +SRC_DIRS = . +INCLUDE = -I ../../include + +SRCS = $(wildcard $(addsuffix /*.cpp, $(SRC_DIRS))) +OBJS = $(SRCS:.cpp=.o) +DEPENDS = $(OBJS:.o=.d) + +EXE = $(SRCS:.cpp=) + + +#------------------------------------------------------------------------------ +# flags +# + +FLAGS += $(INCLUDE) +FLAGS += -MMD + +CXXFLAGS = $(FLAGS) +CXXFLAGS += $(OPTIMIZER) +CXXFLAGS += -std=c++11 +CXXFLAGS += -fno-exceptions +CXXFLAGS += -fno-rtti + +CXXFLAGS += -Wall -Wextra +CXXFLAGS += -Wctor-dtor-privacy +CXXFLAGS += -Wcast-align -Wpointer-arith -Wredundant-decls +CXXFLAGS += -Wshadow -Wcast-qual -Wcast-align -pedantic + +# Produce debugging information (for use with gdb) +#OPTIMIZER = -Og +#FLAGS += -g + +# Use LLVM +#CXX = $(CROSS)clang++ +#CXXFLAGS += -stdlib=libc++ +#LDFLAGS += -lc++ + + +.PHONY: all clean + +all: $(EXE) + +%: %.cpp + $(CXX) $(CXXFLAGS) -o $@ $< + $(SIZE) $@ + +clean: + $(RM) *.d + $(RM) $(EXE) + + +-include $(DEPENDS) diff --git a/lib/tinyfsm/examples/api/debugging_switch.cpp b/lib/tinyfsm/examples/api/debugging_switch.cpp new file mode 100644 index 00000000..065d0e13 --- /dev/null +++ b/lib/tinyfsm/examples/api/debugging_switch.cpp @@ -0,0 +1,122 @@ +#include <tinyfsm.hpp> +#include <iostream> +#include <cassert> + +struct Off; // forward declaration + + +// ---------------------------------------------------------------------------- +// Event Declarations +// +struct Toggle : tinyfsm::Event { }; // Event Declarations + + +// ---------------------------------------------------------------------------- +// State Machine Declaration +// +struct Switch +: tinyfsm::Fsm<Switch> +{ + static void reset(void); + + // NOTE: on reset: "tinyfsm::StateList<Off, On>::reset()", copy + // constructor is used by default, so "this" points to neither + // "Off" nor "On" (see operator=() below). + Switch() : counter(0) { + std::cout << "* Switch()" << std::endl + << " this = " << this << std::endl; + } + + ~Switch() { + std::cout << "* ~Switch()" << std::endl + << " this = " << this << std::endl; + } + + Switch & operator=(const Switch & other) { + std::cout << "* operator=()" << std::endl + << " this = " << this << std::endl + << " other = " << &other << std::endl; + counter = other.counter; + return *this; + } + + virtual void react(Toggle const &) { }; + void entry(void); + void exit(void); + + int counter; +}; + +struct On : Switch { + void react(Toggle const &) override { transit<Off>(); }; +}; + +struct Off : Switch { + void react(Toggle const &) override { transit<On>(); }; +}; + +FSM_INITIAL_STATE(Switch, Off) + + +// ---------------------------------------------------------------------------- +// State Machine Definitions +// +void Switch::reset() { + tinyfsm::StateList<Off, On>::reset(); +} + +void Switch::entry() { + counter++; + + // debugging only. properly designed state machines don't need this: + if(is_in_state<On>()) { std::cout << "* On::entry()" << std::endl; } + else if(is_in_state<Off>()) { std::cout << "* Off::entry()" << std::endl; } + else assert(true); + + assert(current_state_ptr == this); + std::cout << " this (cur) = " << this << std::endl + << " state<On> = " << &state<On>() << std::endl + << " state<Off> = " << &state<Off>() << std::endl; +} + +void Switch::exit() { + assert(current_state_ptr == this); + std::cout << "* exit()" << std::endl + << " this (cur) = " << this << std::endl + << " state<On> = " << &state<On>() << std::endl + << " state<Off> = " << &state<Off>() << std::endl; +} + + +// ---------------------------------------------------------------------------- +// Main +// +int main() +{ + Switch::start(); + + while(1) + { + char c; + std::cout << "* main()" << std::endl + << " cur_counter = " << Switch::current_state_ptr->counter << std::endl + << " on_counter = " << Switch::state<On>().counter << std::endl + << " off_counter = " << Switch::state<Off>().counter << std::endl; + + std::cout << std::endl << "t=Toggle, r=Restart, q=Quit ? "; + std::cin >> c; + switch(c) { + case 't': + Switch::dispatch(Toggle()); + break; + case 'r': + Switch::reset(); + Switch::start(); + break; + case 'q': + return 0; + default: + std::cout << "> Invalid input" << std::endl; + }; + } +} diff --git a/lib/tinyfsm/examples/api/mealy_machine.cpp b/lib/tinyfsm/examples/api/mealy_machine.cpp new file mode 100644 index 00000000..a4fadff6 --- /dev/null +++ b/lib/tinyfsm/examples/api/mealy_machine.cpp @@ -0,0 +1,70 @@ +#include <tinyfsm.hpp> +#include <iostream> + +// ---------------------------------------------------------------------------- +// 1. Event Declarations +// +struct Toggle : tinyfsm::Event { }; + + +// ---------------------------------------------------------------------------- +// 2. State Machine Base Class Declaration +// +struct Switch : tinyfsm::MealyMachine<Switch> +{ + /* pure virtual reaction (override required in all states) */ + virtual void react(Toggle const &) = 0; + + /* transition actions */ + static void OpenCircuit() { + std::cout << "* Opening ciruit (light goes OFF)" << std::endl; + } + static void CloseCircuit() { + std::cout << "* Closing ciruit (light goes ON)" << std::endl; + } +}; + + +// ---------------------------------------------------------------------------- +// 3. State Declarations +// +struct Off; // forward declaration + +struct On : Switch +{ + void react(Toggle const &) override { transit<Off>(OpenCircuit); }; +}; + +struct Off : Switch +{ + void react(Toggle const &) override { transit<On>(CloseCircuit); }; +}; + +FSM_INITIAL_STATE(Switch, Off) + + +// ---------------------------------------------------------------------------- +// Main +// +int main() +{ + Switch::start(); + + std::cout << "> You are facing a light switch..." << std::endl; + while(1) + { + char c; + std::cout << std::endl << "t=Toggle, q=Quit ? "; + std::cin >> c; + switch(c) { + case 't': + std::cout << "> Toggling switch..." << std::endl; + Switch::dispatch(Toggle()); + break; + case 'q': + return 0; + default: + std::cout << "> Invalid input" << std::endl; + }; + } +} diff --git a/lib/tinyfsm/examples/api/moore_machine.cpp b/lib/tinyfsm/examples/api/moore_machine.cpp new file mode 100644 index 00000000..02a77da2 --- /dev/null +++ b/lib/tinyfsm/examples/api/moore_machine.cpp @@ -0,0 +1,64 @@ +#include <tinyfsm.hpp> +#include <iostream> + +// ---------------------------------------------------------------------------- +// 1. Event Declarations +// +struct Toggle : tinyfsm::Event { }; + + +// ---------------------------------------------------------------------------- +// 2. State Machine Base Class Declaration +// +struct Switch : tinyfsm::MooreMachine<Switch> +{ + /* pure virtual reaction (override required in all states) */ + virtual void react(Toggle const &) = 0; +}; + + +// ---------------------------------------------------------------------------- +// 3. State Declarations +// +struct Off; // forward declaration + +struct On : Switch +{ + void entry() override { std::cout << "* Closing ciruit (light goes ON)" << std::endl; }; + void react(Toggle const &) override { transit<Off>(); }; +}; + +struct Off : Switch +{ + void entry() override { std::cout << "* Opening ciruit (light goes OFF)" << std::endl; }; + void react(Toggle const &) override { transit<On>(); }; +}; + +FSM_INITIAL_STATE(Switch, Off) + + +// ---------------------------------------------------------------------------- +// Main +// +int main() +{ + Switch::start(); + + std::cout << "> You are facing a light switch..." << std::endl; + while(1) + { + char c; + std::cout << std::endl << "t=Toggle, q=Quit ? "; + std::cin >> c; + switch(c) { + case 't': + std::cout << "> Toggling switch..." << std::endl; + Switch::dispatch(Toggle()); + break; + case 'q': + return 0; + default: + std::cout << "> Invalid input" << std::endl; + }; + } +} diff --git a/lib/tinyfsm/examples/api/multiple_switch.cpp b/lib/tinyfsm/examples/api/multiple_switch.cpp new file mode 100644 index 00000000..6bf844fb --- /dev/null +++ b/lib/tinyfsm/examples/api/multiple_switch.cpp @@ -0,0 +1,145 @@ +// +// In this example, we want to use the DefectiveSwitch FSM multiple +// times. As TinyFSM is all about templates, we need to declare it as +// a template class. +// +// This is a bit cumbersome, as the C++ syntax is really ugly when it +// comes to derived template classes. +// +#include <tinyfsm.hpp> +#include <iostream> +#include <stdlib.h> /* rand */ + +template<int inum> +class Off; // forward declaration + +static void DumpState(int inum, const char * state, int on_counter, int defect_level) { + std::cout << "* Switch[" << inum << "] is " << state << " (on_counter=" << on_counter << ", defect_level=" << defect_level << ")" << std::endl; +} + +// ---------------------------------------------------------------------------- +// 1. Event Declarations +// +struct Toggle : tinyfsm::Event { }; + + +// ---------------------------------------------------------------------------- +// 2. State Machine Base Class Declaration +// +template<int inum> +class DefectiveSwitch +: public tinyfsm::Fsm< DefectiveSwitch<inum> > +{ +public: + static constexpr unsigned int defect_level = (inum * 2); + + static void reset(void) { + on_counter = 0; + } + + /* default reaction for unhandled events */ + void react(tinyfsm::Event const &) { }; + + virtual void react(Toggle const &) { }; + virtual void entry(void) { }; /* entry actions in some states */ + void exit(void) { }; /* no exit actions */ + +protected: + static unsigned int on_counter; +}; + +// state variable definitions +template<int inum> +unsigned int DefectiveSwitch<inum>::on_counter{0}; + + +// ---------------------------------------------------------------------------- +// 3. State Declarations +// +template<int inum> +class On +: public DefectiveSwitch<inum> +{ + // note: base class is not known in dependend template + using base = DefectiveSwitch<inum>; + void entry() override { + base::on_counter++; + DumpState(inum, "ON ", base::on_counter, base::defect_level); + }; + void react(Toggle const &) override { + base::template transit< Off<inum> >(); + }; +}; + + +template<int inum> +class Off +: public DefectiveSwitch<inum> +{ + using base = DefectiveSwitch<inum>; + void entry() override { + DumpState(inum, "OFF", base::on_counter, base::defect_level); + }; + void react(Toggle const &) override { + if((rand() % (base::defect_level + 1)) == 0) + base::template transit< On<inum> >(); + else { + std::cout << "* Kzzz kzzzzzz" << std::endl; + base::template transit< Off<inum> >(); + } + }; +}; + +FSM_INITIAL_STATE(DefectiveSwitch<0>, Off<0> ) +FSM_INITIAL_STATE(DefectiveSwitch<1>, Off<1> ) +FSM_INITIAL_STATE(DefectiveSwitch<2>, Off<2> ) + + +// ---------------------------------------------------------------------------- +// 4. State Machine List Declaration +// + +using fsm_handle = tinyfsm::FsmList< + DefectiveSwitch<0>, + DefectiveSwitch<1>, + DefectiveSwitch<2> + >; + +template<int inum> +void ToggleSingle() { + std::cout << "> Toggling switch " << inum << "..." << std::endl; + DefectiveSwitch<inum>::dispatch(Toggle()); +} + + +// ---------------------------------------------------------------------------- +// Main +// +int main() +{ + fsm_handle::start(); + + while(1) + { + char c; + std::cout << std::endl << "0,1,2=Toggle single, a=Toggle all, r=Restart, q=Quit ? "; + std::cin >> c; + switch(c) { + case '0': ToggleSingle<0>(); break; + case '1': ToggleSingle<1>(); break; + case '2': ToggleSingle<2>(); break; + case 'a': + std::cout << "> Toggling all switches..." << std::endl; + fsm_handle::dispatch(Toggle()); + break; + case 'r': + fsm_handle::reset(); + fsm_handle::start(); + break; + case 'q': + return 0; + default: + std::cout << "> Invalid input" << std::endl; + }; + } +} diff --git a/lib/tinyfsm/examples/api/resetting_switch.cpp b/lib/tinyfsm/examples/api/resetting_switch.cpp new file mode 100644 index 00000000..5642fc92 --- /dev/null +++ b/lib/tinyfsm/examples/api/resetting_switch.cpp @@ -0,0 +1,110 @@ +#include <tinyfsm.hpp> +#include <iostream> + +class Off; // forward declaration + + +// ---------------------------------------------------------------------------- +// 1. Event Declarations +// +struct Toggle : tinyfsm::Event { }; + + +// ---------------------------------------------------------------------------- +// 2. State Machine Base Class Declaration +// +class Switch +: public tinyfsm::Fsm<Switch> +{ + // entry(), exit() and react() are called from Fsm<Switch>::transit() + // in derived states, make friends: + friend class tinyfsm::Fsm<Switch>; + + /* default reaction for unhandled events */ + void react(tinyfsm::Event const &) { }; + + virtual void react(Toggle const &) { }; + virtual void entry(void) { }; /* entry actions in some states */ + void exit(void) { }; /* no exit actions */ + +public: + static void reset(void); /* implemented below */ +}; + + +// ---------------------------------------------------------------------------- +// 3. State Declarations +// +class On +: public Switch +{ + void entry() override { counter++; std::cout << "* Switch is ON, counter=" << counter << std::endl; }; + void react(Toggle const &) override { transit<Off>(); }; + int counter; + +public: + On() : counter(0) { std::cout << "** RESET State=On" << std::endl; } +}; + +class Off +: public Switch +{ + void entry() override { counter++; std::cout << "* Switch is OFF, counter=" << counter << std::endl; }; + void react(Toggle const &) override { transit<On>(); }; + int counter; + +public: + Off() : counter(0) { std::cout << "** RESET State=Off" << std::endl; } +}; + + +void Switch::reset() { + std::cout << "** RESET Switch" << std::endl; + // Reset all states (calls constructor on all states in list) + tinyfsm::StateList<Off, On>::reset(); + + // Alternatively, make counter public above and reset the values + // here instead of using a copy-constructor with StateList<>: + //state<On>().counter = 0; + //state<Off>().counter = 0; +} + +FSM_INITIAL_STATE(Switch, Off) + + +// ---------------------------------------------------------------------------- +// 4. State Machine List Declaration (dispatches events to multiple FSM's) +// +// In this example, we only have a single state machine, no need to use FsmList<>: +//using fsm_handle = tinyfsm::FsmList< Switch >; +using fsm_handle = Switch; + + +// ---------------------------------------------------------------------------- +// Main +// +int main() +{ + fsm_handle::start(); + + while(1) + { + char c; + std::cout << std::endl << "t=Toggle, r=Restart, q=Quit ? "; + std::cin >> c; + switch(c) { + case 't': + std::cout << "> Toggling switch..." << std::endl; + fsm_handle::dispatch(Toggle()); + break; + case 'r': + fsm_handle::reset(); + fsm_handle::start(); + break; + case 'q': + return 0; + default: + std::cout << "> Invalid input" << std::endl; + }; + } +} diff --git a/lib/tinyfsm/examples/api/simple_switch.cpp b/lib/tinyfsm/examples/api/simple_switch.cpp new file mode 100644 index 00000000..b20b2abd --- /dev/null +++ b/lib/tinyfsm/examples/api/simple_switch.cpp @@ -0,0 +1,85 @@ +#include <tinyfsm.hpp> +#include <iostream> + +struct Off; // forward declaration + + +// ---------------------------------------------------------------------------- +// 1. Event Declarations +// +struct Toggle : tinyfsm::Event { }; + + +// ---------------------------------------------------------------------------- +// 2. State Machine Base Class Declaration +// +struct Switch : tinyfsm::Fsm<Switch> +{ + virtual void react(Toggle const &) { }; + + // alternative: enforce handling of Toggle in all states (pure virtual) + //virtual void react(Toggle const &) = 0; + + virtual void entry(void) { }; /* entry actions in some states */ + void exit(void) { }; /* no exit actions */ + + // alternative: enforce entry actions in all states (pure virtual) + //virtual void entry(void) = 0; +}; + + +// ---------------------------------------------------------------------------- +// 3. State Declarations +// +struct On : Switch +{ + void entry() override { std::cout << "* Switch is ON" << std::endl; }; + void react(Toggle const &) override { transit<Off>(); }; +}; + +struct Off : Switch +{ + void entry() override { std::cout << "* Switch is OFF" << std::endl; }; + void react(Toggle const &) override { transit<On>(); }; +}; + +FSM_INITIAL_STATE(Switch, Off) + + +// ---------------------------------------------------------------------------- +// 4. State Machine List Declaration (dispatches events to multiple FSM's) +// +// In this example, we only have a single state machine, no need to use FsmList<>: +//using fsm_handle = tinyfsm::FsmList< Switch >; +using fsm_handle = Switch; + + +// ---------------------------------------------------------------------------- +// Main +// +int main() +{ + // instantiate events + Toggle toggle; + + fsm_handle::start(); + + while(1) + { + char c; + std::cout << std::endl << "t=Toggle, q=Quit ? "; + std::cin >> c; + switch(c) { + case 't': + std::cout << "> Toggling switch..." << std::endl; + fsm_handle::dispatch(toggle); + // alternative: instantiating causes no overhead (empty declaration) + //fsm_handle::dispatch(Toggle()); + break; + case 'q': + return 0; + default: + std::cout << "> Invalid input" << std::endl; + }; + } +} diff --git a/lib/tinyfsm/examples/elevator/Makefile b/lib/tinyfsm/examples/elevator/Makefile new file mode 100644 index 00000000..4c979052 --- /dev/null +++ b/lib/tinyfsm/examples/elevator/Makefile @@ -0,0 +1,98 @@ +# Compiler prefix, in case your default compiler does not implement all C++11 features: +#CROSS = /opt/toolchain/x86_64-pc-linux-gnu-gcc-4.7.0/bin/x86_64-pc-linux-gnu- + +PROJECT = elevator + +# HINT: g++ -Q -O2 --help=optimizers +OPTIMIZER = -Os + +CC = $(CROSS)gcc +CXX = $(CROSS)g++ +AS = $(CROSS)gcc -x assembler-with-cpp +LD = $(CROSS)g++ +OBJCOPY = $(CROSS)objcopy +OBJDUMP = $(CROSS)objdump +SIZE = size -d +RM = rm -f +RM_R = rm -rf +CP = cp +MKDIR_P = mkdir -p +DOXYGEN = doxygen + + +SRC_DIRS = . +INCLUDE = -I ../../include + +SRCS = $(wildcard $(addsuffix /*.cpp, $(SRC_DIRS))) +OBJS = $(SRCS:.cpp=.o) +DEPENDS = $(OBJS:.o=.d) + +EXE = $(PROJECT) +MAP = $(PROJECT).map + + +#------------------------------------------------------------------------------ +# flags +# + +# commmon flags propagated to CFLAGS, CXXFLAGS, ASFLAGS (not LDFLAGS) +FLAGS += $(INCLUDE) +FLAGS += -MMD + +CXXFLAGS = $(FLAGS) +CXXFLAGS += $(OPTIMIZER) +CXXFLAGS += -std=c++11 +CXXFLAGS += -fno-exceptions +CXXFLAGS += -fno-rtti + +CXXFLAGS += -Wall -Wextra +CXXFLAGS += -Wctor-dtor-privacy +CXXFLAGS += -Wcast-align -Wpointer-arith -Wredundant-decls +CXXFLAGS += -Wshadow -Wcast-qual -Wcast-align -pedantic + +LDFLAGS += -fno-exceptions +LDFLAGS += -fno-rtti + +# Produce debugging information (for use with gdb) +#OPTIMIZER = -Og +#FLAGS += -g + +# Use LLVM +#CXX = $(CROSS)clang++ +#CXXFLAGS += -stdlib=libc++ +#LDFLAGS += -lc++ + +# Enable link-time optimizer +#CXXFLAGS += -flto +#LDFLAGS += -flto + +# Strip dead code (enable garbage collection) +#OPTIMIZER += -ffunction-sections -fdata-sections +#LDFLAGS += -Wl,$(if $(shell ld -v | grep GNU),--gc-sections,-dead_strip) + +# Enable automatic template instantiation at link time +#CXXFLAGS += -frepo +#LDFLAGS += -frepo + +# Create link map file +#LDFLAGS += -Wl,-Map="$(MAP)",--cref + + +.PHONY: all clean + +all: $(EXE) + +$(EXE): $(OBJS) + $(LD) $(OBJS) $(LDFLAGS) -o $(EXE) + $(SIZE) $@ + +%.o: %.cpp + $(CXX) -c $(CXXFLAGS) -o $@ $< + +clean: + $(RM) *.o + $(RM) *.d + $(RM) $(EXE) + + +-include $(DEPENDS) diff --git a/lib/tinyfsm/examples/elevator/README.md b/lib/tinyfsm/examples/elevator/README.md new file mode 100644 index 00000000..6de14ceb --- /dev/null +++ b/lib/tinyfsm/examples/elevator/README.md @@ -0,0 +1,86 @@ +Elevator Project +================ + +Example implementation of a simplified elevator logic, using [TinyFSM]. + + [TinyFSM]: https://digint.ch/tinyfsm/ + + +Overview +-------- + +Imagine a elevator having: + + - "Call" button on each floor, + - "Floor Sensor" on each floor, triggering an event as soon as the + elevator arrives there, + - "Alarm" button. + + +Implementation +-------------- + +The elevator example implements two state machines interacting with +each other: + + 1. Elevator + - State: Idle + - State: Moving + - State: Panic + + 2. Motor + - State: Stopped + - State: Up + - State: Down + + +A good state machine design avoids circular dependencies at all +cost: While the elevator sends events to the motor, the motor NEVER +sends events to the elevator (top-down only). + + +FAQ +--- + +Did you notice the motor starting twice? This is by design, let's +have a look at the call stack of fsm_list::start() in main.cpp: + + FsmList<Motor, Elevator>::start() + Motor::set_initial_state() + Motor::current_state = Stopped + Elevator::set_initial_state() + Elevator::current_state = Idle + Motor::enter() + Motor:Stopped->entry() + cout << "Motor: stopped" <-- HERE + Motor::direction = 0 + Elevator::enter() + Elevator:Idle->entry() + send_event(MotorStop) + Motor::react(MotorStop) + Motor:Stopped->transit<Stopped> + Motor:Stopped->exit() + Motor::current_state = Stopped + Motor:Stopped->entry() + cout << "Motor: stopped" <-- HERE + Motor::direction = 0 + Elevator::react(MotorStop) + +If we really had to work around this, we could either: + + 1. Change the initialization (bad design practice!) in main.cpp: + + - fsm_list::start(); + + fsm_list::set_initial_state(); + + Elevator::enter(); + + + 2. Modify the Motor:Stopped->entry() function in motor.cpp: + + class Stopped : public Motor { + void entry() override { + + if(direction == 0) + + return; + cout << "Motor: stopped" << endl; + direction = 0; + }; diff --git a/lib/tinyfsm/examples/elevator/elevator.cpp b/lib/tinyfsm/examples/elevator/elevator.cpp new file mode 100644 index 00000000..6e792f64 --- /dev/null +++ b/lib/tinyfsm/examples/elevator/elevator.cpp @@ -0,0 +1,115 @@ +#include <tinyfsm.hpp> + +#include "elevator.hpp" +#include "fsmlist.hpp" + +#include <iostream> + +class Idle; // forward declaration + + +// ---------------------------------------------------------------------------- +// Transition functions +// + +static void CallMaintenance() { + std::cout << "*** calling maintenance ***" << std::endl; +} + +static void CallFirefighters() { + std::cout << "*** calling firefighters ***" << std::endl; +} + + +// ---------------------------------------------------------------------------- +// State: Panic +// + +class Panic +: public Elevator +{ + void entry() override { + send_event(MotorStop()); + } +}; + + +// ---------------------------------------------------------------------------- +// State: Moving +// + +class Moving +: public Elevator +{ + void react(FloorSensor const & e) override { + int floor_expected = current_floor + Motor::getDirection(); + if(floor_expected != e.floor) + { + std::cout << "Floor sensor defect (expected " << floor_expected << ", got " << e.floor << ")" << std::endl; + transit<Panic>(CallMaintenance); + } + else + { + std::cout << "Reached floor " << e.floor << std::endl; + current_floor = e.floor; + if(e.floor == dest_floor) + transit<Idle>(); + } + }; +}; + + +// ---------------------------------------------------------------------------- +// State: Idle +// + +class Idle +: public Elevator +{ + void entry() override { + send_event(MotorStop()); + } + + void react(Call const & e) override { + dest_floor = e.floor; + + if(dest_floor == current_floor) + return; + + /* lambda function used for transition action */ + auto action = [] { + if(dest_floor > current_floor) + send_event(MotorUp()); + else if(dest_floor < current_floor) + send_event(MotorDown()); + }; + + transit<Moving>(action); + }; +}; + + +// ---------------------------------------------------------------------------- +// Base state: default implementations +// + +void Elevator::react(Call const &) { + std::cout << "Call event ignored" << std::endl; +} + +void Elevator::react(FloorSensor const &) { + std::cout << "FloorSensor event ignored" << std::endl; +} + +void Elevator::react(Alarm const &) { + transit<Panic>(CallFirefighters); +} + +int Elevator::current_floor = Elevator::initial_floor; +int Elevator::dest_floor = Elevator::initial_floor; + + +// ---------------------------------------------------------------------------- +// Initial state definition +// +FSM_INITIAL_STATE(Elevator, Idle) diff --git a/lib/tinyfsm/examples/elevator/elevator.hpp b/lib/tinyfsm/examples/elevator/elevator.hpp new file mode 100644 index 00000000..be3d0053 --- /dev/null +++ b/lib/tinyfsm/examples/elevator/elevator.hpp @@ -0,0 +1,55 @@ +#ifndef ELEVATOR_HPP_INCLUDED +#define ELEVATOR_HPP_INCLUDED + +#include <tinyfsm.hpp> + + +// ---------------------------------------------------------------------------- +// Event declarations +// + +struct FloorEvent : tinyfsm::Event +{ + int floor; +}; + +struct Call : FloorEvent { }; +struct FloorSensor : FloorEvent { }; +struct Alarm : tinyfsm::Event { }; + + + +// ---------------------------------------------------------------------------- +// Elevator (FSM base class) declaration +// + +class Elevator +: public tinyfsm::Fsm<Elevator> +{ + /* NOTE: react(), entry() and exit() functions need to be accessible + * from tinyfsm::Fsm class. You might as well declare friendship to + * tinyfsm::Fsm, and make these functions private: + * + * friend class Fsm; + */ +public: + + /* default reaction for unhandled events */ + void react(tinyfsm::Event const &) { }; + + virtual void react(Call const &); + virtual void react(FloorSensor const &); + void react(Alarm const &); + + virtual void entry(void) { }; /* entry actions in some states */ + void exit(void) { }; /* no exit actions at all */ + +protected: + + static constexpr int initial_floor = 0; + static int current_floor; + static int dest_floor; +}; + + +#endif diff --git a/lib/tinyfsm/examples/elevator/fsmlist.hpp b/lib/tinyfsm/examples/elevator/fsmlist.hpp new file mode 100644 index 00000000..d340f8a1 --- /dev/null +++ b/lib/tinyfsm/examples/elevator/fsmlist.hpp @@ -0,0 +1,19 @@ +#ifndef FSMLIST_HPP_INCLUDED +#define FSMLIST_HPP_INCLUDED + +#include <tinyfsm.hpp> + +#include "elevator.hpp" +#include "motor.hpp" + +using fsm_list = tinyfsm::FsmList<Motor, Elevator>; + +/** dispatch event to both "Motor" and "Elevator" */ +template<typename E> +void send_event(E const & event) +{ + fsm_list::template dispatch<E>(event); +} + + +#endif diff --git a/lib/tinyfsm/examples/elevator/main.cpp b/lib/tinyfsm/examples/elevator/main.cpp new file mode 100644 index 00000000..46570153 --- /dev/null +++ b/lib/tinyfsm/examples/elevator/main.cpp @@ -0,0 +1,40 @@ +#include "fsmlist.hpp" + +#include <iostream> + + +int main() +{ + fsm_list::start(); + + Call call; + FloorSensor sensor; + + while(1) + { + char c; + + std::cout << "c=Call, f=FloorSensor, a=Alarm, q=Quit ? "; + std::cin >> c; + switch(c) { + case 'c': + std::cout << "Floor ? "; + std::cin >> call.floor; + send_event(call); + break; + case 'f': + std::cout << "Floor ? "; + std::cin >> sensor.floor; + send_event(sensor); + break; + case 'a': + send_event(Alarm()); + break; + case 'q': + std::cout << "Thanks for playing!" << std::endl; + return 0; + default: + std::cout << "Invalid input" << std::endl; + }; + } +} diff --git a/lib/tinyfsm/examples/elevator/motor.cpp b/lib/tinyfsm/examples/elevator/motor.cpp new file mode 100644 index 00000000..b4668070 --- /dev/null +++ b/lib/tinyfsm/examples/elevator/motor.cpp @@ -0,0 +1,60 @@ +#include <tinyfsm.hpp> +#include "motor.hpp" +#include <iostream> + + +// ---------------------------------------------------------------------------- +// Motor states +// + +class Stopped +: public Motor +{ + void entry() override { + std::cout << "Motor: stopped" << std::endl; + direction = 0; + }; +}; + +class Up +: public Motor +{ + void entry() override { + std::cout << "Motor: moving up" << std::endl; + direction = 1; + }; +}; + +class Down +: public Motor +{ + void entry() override { + std::cout << "Motor: moving down" << std::endl; + direction = -1; + }; +}; + + +// ---------------------------------------------------------------------------- +// Base State: default implementations +// + +void Motor::react(MotorStop const &) { + transit<Stopped>(); +} + +void Motor::react(MotorUp const &) { + transit<Up>(); +} + +void Motor::react(MotorDown const &) { + transit<Down>(); +} + +int Motor::direction{0}; + + +// ---------------------------------------------------------------------------- +// Initial state definition +// +FSM_INITIAL_STATE(Motor, Stopped) diff --git a/lib/tinyfsm/examples/elevator/motor.hpp b/lib/tinyfsm/examples/elevator/motor.hpp new file mode 100644 index 00000000..7d2447cf --- /dev/null +++ b/lib/tinyfsm/examples/elevator/motor.hpp @@ -0,0 +1,50 @@ +#ifndef MOTOR_HPP_INCLUDED +#define MOTOR_HPP_INCLUDED + +#include <tinyfsm.hpp> + + +// ---------------------------------------------------------------------------- +// Event declarations +// + +struct MotorUp : tinyfsm::Event { }; +struct MotorDown : tinyfsm::Event { }; +struct MotorStop : tinyfsm::Event { }; + + +// ---------------------------------------------------------------------------- +// Motor (FSM base class) declaration +// +class Motor +: public tinyfsm::Fsm<Motor> +{ + /* NOTE: react(), entry() and exit() functions need to be accessible + * from tinyfsm::Fsm class. You might as well declare friendship to + * tinyfsm::Fsm, and make these functions private: + * + * friend class Fsm; + */ +public: + + /* default reaction for unhandled events */ + void react(tinyfsm::Event const &) { }; + + /* non-virtual declaration: reactions are the same for all states */ + void react(MotorUp const &); + void react(MotorDown const &); + void react(MotorStop const &); + + virtual void entry(void) = 0; /* pure virtual: enforce implementation in all states */ + void exit(void) { }; /* no exit actions at all */ + +protected: + + static int direction; + +public: + static int getDirection() { return direction; } +}; + + +#endif |
