parent
b320a6a863
commit
a6ab150405
@ -0,0 +1 @@ |
||||
idf_component_register(INCLUDE_DIRS "include") |
@ -0,0 +1,21 @@ |
||||
The MIT License (MIT) |
||||
|
||||
Copyright (c) 2012-2022 Axel Burri |
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
of this software and associated documentation files (the "Software"), to deal |
||||
in the Software without restriction, including without limitation the rights |
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
copies of the Software, and to permit persons to whom the Software is |
||||
furnished to do so, subject to the following conditions: |
||||
|
||||
The above copyright notice and this permission notice shall be included in all |
||||
copies or substantial portions of the Software. |
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
SOFTWARE. |
@ -0,0 +1,39 @@ |
||||
tinyfsm-0.3.3 |
||||
|
||||
* Remove size -B option in makefiles (unsupported on mac). |
||||
* Add library.json for PlatformIO. |
||||
* Tidy elevator example. |
||||
|
||||
tinyfsm-0.3.2 |
||||
|
||||
* Add TINYFSM_NOSTDLIB compile option. |
||||
* Remove static asserts on transit functions. |
||||
* Fix elevator example. |
||||
|
||||
tinyfsm-0.3.1 |
||||
|
||||
* Bugfix (workaround) for compiler bug in gcc < 7.0: define template |
||||
specialization for FSM_INITIAL_STATE in "namespace tinyfsm" block. |
||||
|
||||
tinyfsm-0.3.0 |
||||
|
||||
* Bugfix: set initial state on all state machines before entering |
||||
states in FsmList::start(). |
||||
* Add Fsm::start() function (identical interface as FsmList). |
||||
* Add reset() functionality to Fsm and FsmList class. |
||||
* Add MooreMachine and MealyMachine class. |
||||
* Add API examples: simple_switch, resetting_switch, multiple_switch, |
||||
debugging_switch, mealy_machine, moore_machine. |
||||
* Relax access specifiers for Fsm::state<S>() access function. |
||||
* Remove debug code. |
||||
|
||||
tinyfsm-0.2.0 |
||||
|
||||
* Use Fsm<F>::initialize() for initialization (instead of |
||||
Fsm<F>::initial_state). |
||||
* Change license to MIT |
||||
|
||||
tinyfsm-0.1.0 |
||||
|
||||
* Initial revision |
||||
* Note that this release was originally named "v0.10" |
@ -0,0 +1,114 @@ |
||||
TinyFSM |
||||
======= |
||||
|
||||
TinyFSM is a simple finite state machine library for C++, designed for |
||||
optimal performance and low memory footprint. This makes it ideal for |
||||
real-time operating systems. The concept is very simple, allowing the |
||||
programmer to fully understand what is happening behind the scenes. It |
||||
provides a straightforward way of mapping your state machine charts |
||||
into source code. |
||||
|
||||
TinyFSM basically wraps event dispatching into function calls, making |
||||
event dispatching equally fast to calling (or even inlining) a |
||||
function. Even in the worst case, dispatching leads to nothing more |
||||
than a single vtable lookup and function call! |
||||
|
||||
Key Features: |
||||
|
||||
- Entry/exit actions |
||||
- Event actions |
||||
- Transition functions |
||||
- Transition conditions |
||||
- Event payload (classes) |
||||
- Inheritance of states and action functions |
||||
|
||||
TinyFSM benefits from the C++11 template metaprogramming features like |
||||
variadic templates, and does not depend on RTTI, exceptions or any |
||||
external library. |
||||
|
||||
|
||||
Official home page: <https://digint.ch/tinyfsm> |
||||
|
||||
Current version: `0.3.3` |
||||
|
||||
|
||||
Installation |
||||
------------ |
||||
|
||||
TinyFSM is a header-only library, no special installation steps are |
||||
needed. Just point your compiler to the "include" directory. |
||||
|
||||
|
||||
Documentation |
||||
------------- |
||||
|
||||
You can find the full documentation in the `doc/` directory: |
||||
|
||||
- [Introduction](/doc/10-Introduction.md) |
||||
- [Installation](/doc/20-Installation.md) |
||||
- [Concepts](/doc/30-Concepts.md) |
||||
- [Usage](/doc/40-Usage.md) |
||||
- [API](/doc/50-API.md) |
||||
|
||||
The docmentation is also available on the [official home |
||||
page](https://digint.ch/tinyfsm/doc/introduction.html). |
||||
|
||||
|
||||
### Code examples |
||||
|
||||
- [Elevator Project]: Documented example, two state machines with |
||||
shiny buttons, floor sensors and actors. |
||||
- [Simple Switch]: A generic switch with two states (on/off). |
||||
- [API examples] |
||||
|
||||
[Elevator Project]: /examples/elevator/ |
||||
[Simple Switch]: /examples/api/simple_switch.cpp |
||||
[API Examples]: /examples/api/ |
||||
|
||||
|
||||
Donate |
||||
------ |
||||
|
||||
So TinyFSM has proven useful for you? |
||||
|
||||
I will definitively continue to develop TinyFSM for free. If you want |
||||
to support me with a donation, you are welcome to do so! |
||||
|
||||
[](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=QZQE9HY6QHDHS) |
||||
|
||||
|
||||
Development |
||||
----------- |
||||
|
||||
The source code for TinyFSM is managed using Git: |
||||
|
||||
git clone https://dev.tty0.ch/tinyfsm.git |
||||
|
||||
Mirror on GitHub: |
||||
|
||||
git clone https://github.com/digint/tinyfsm.git |
||||
|
||||
If you would like to contribute or have found bugs, visit the [TinyFSM |
||||
project page on GitHub] and use the [issues tracker] there. |
||||
|
||||
[TinyFSM project page on GitHub]: http://github.com/digint/tinyfsm |
||||
[issues tracker]: http://github.com/digint/tinyfsm/issues |
||||
|
||||
|
||||
Contact |
||||
------- |
||||
|
||||
For questions and suggestions regarding TinyFSM, success or failure |
||||
stories, and any other kind of feedback, please feel free to contact |
||||
the author (the email address can be found in the sources). |
||||
|
||||
|
||||
License |
||||
------- |
||||
|
||||
TinyFSM is [Open Source] software. It may be used for any purpose, |
||||
including commercial purposes, at absolutely no cost. It is |
||||
distributed under the terms of the [MIT license]. |
||||
|
||||
[Open Source]: http://www.opensource.org/docs/definition.html |
||||
[MIT license]: http://www.opensource.org/licenses/mit-license.html |
@ -0,0 +1,28 @@ |
||||
Introduction |
||||
============ |
||||
|
||||
TinyFSM is a simple finite state machine library for C++, designed for |
||||
optimal performance and low memory footprint. This makes it ideal for |
||||
real-time operating systems. The concept is very simple, allowing the |
||||
programmer to fully understand what is happening behind the scenes. It |
||||
provides a straightforward way of mapping your state machine charts |
||||
into source code. |
||||
|
||||
TinyFSM basically wraps event dispatching into function calls, making |
||||
event dispatching equally fast to calling (or even inlining) a |
||||
function. Even in the worst case, dispatching leads to nothing more |
||||
than a single vtable lookup and function call! |
||||
|
||||
Key Features |
||||
------------ |
||||
|
||||
- Entry/exit actions |
||||
- Event actions |
||||
- Transition functions |
||||
- Transition conditions |
||||
- Event payload (classes) |
||||
- Inheritance of states and action functions |
||||
|
||||
TinyFSM benefits from the C++11 template metaprogramming features like |
||||
variadic templates, and does not depend on RTTI, exceptions or any |
||||
external library. |
@ -0,0 +1,68 @@ |
||||
Installation |
||||
============ |
||||
|
||||
TinyFSM is an header-only library, no special installation steps are |
||||
needed. Just point your compiler to the "include" directory, and in |
||||
your source files: |
||||
|
||||
#include <tinyfsm.hpp> |
||||
|
||||
|
||||
Prerequisites |
||||
------------- |
||||
|
||||
TinyFSM requires a compiler supporting the C++11 language standard |
||||
("-std=c++11" in gcc). |
||||
|
||||
TinyFSM does not depend on RTTI, exceptions or any external library. |
||||
If you need to compile without standard libraries (e.g. in conjunction |
||||
with `-nostdlib` linker option), add `-DTINYFSM_NOSTDLIB` to the |
||||
compiler options: this removes all dependencies on the standard |
||||
library by disabling some compile-time type checks. |
||||
|
||||
|
||||
Building the Elevator Example |
||||
----------------------------- |
||||
|
||||
Change to the elevator example directory and compile the sources: |
||||
|
||||
$ cd examples/elevator |
||||
$ make |
||||
|
||||
Our elevator has call buttons on every floor, sensors reporting the |
||||
current position, and an alarm button for emergency. These actors can |
||||
be triggered via a simple command interface: |
||||
|
||||
$ ./elevator |
||||
Motor: stopped |
||||
Motor: stopped |
||||
c=Call, f=FloorSensor, a=Alarm, q=Quit ? |
||||
|
||||
Let's call the elevator to floor 2: |
||||
|
||||
c=Call, f=FloorSensor, a=Alarm, q=Quit ? c |
||||
Floor ? 2 |
||||
Motor: moving up |
||||
c=Call, f=FloorSensor, a=Alarm, q=Quit ? |
||||
|
||||
Now the elevator is moving up, and we need to trigger the floor sensor: |
||||
|
||||
c=Call, f=FloorSensor, a=Alarm, q=Quit ? f |
||||
Floor ? 1 |
||||
Reached floor 1 |
||||
c=Call, f=FloorSensor, a=Alarm, q=Quit ? f |
||||
Floor ? 2 |
||||
Reached floor 2 |
||||
Motor: stopped |
||||
c=Call, f=FloorSensor, a=Alarm, q=Quit ? |
||||
|
||||
Now we simulate a sensor defect: |
||||
|
||||
c=Call, f=FloorSensor, a=Alarm, q=Quit ? c |
||||
Floor ? 1 |
||||
Motor: moving down |
||||
c=Call, f=FloorSensor, a=Alarm, q=Quit ? f |
||||
Floor ? 2 |
||||
Floor sensor defect (expected 1, got 2) |
||||
*** calling maintenance *** |
||||
Motor: stopped |
@ -0,0 +1,38 @@ |
||||
Concepts |
||||
======== |
||||
|
||||
Keep it Simple |
||||
-------------- |
||||
|
||||
By design, TinyFSM implements only the very basics needed for |
||||
designing state machines. For many people, it is important to know |
||||
what a library is doing when making a decision for a specific library. |
||||
|
||||
|
||||
State Definition |
||||
---------------- |
||||
|
||||
States are derived classes from a base FSM state, providing react() |
||||
functions for every event, as well as entry() and exit() functions. |
||||
|
||||
|
||||
Event Dispatching |
||||
----------------- |
||||
|
||||
TinyFSM does not hold state/event function tables like most other |
||||
state machine processors do. Instead, it keeps a pointer to the |
||||
current state (having the type of the state machine base |
||||
class). Dispatching an event simply calls the react() function of the |
||||
current state, with the event class as argument. This results in a |
||||
single vtable lookup and a function call, which is very efficient! |
||||
|
||||
Event dispatching on an FsmList<> are simply dispatch() calls to all |
||||
state machines in the list. |
||||
|
||||
|
||||
Header-Only Library |
||||
------------------- |
||||
|
||||
The TinyFSM library consist entirely of header files containing |
||||
templates, and requires no separately-compiled library binaries or |
||||
special treatment when linking. |
@ -0,0 +1,244 @@ |
||||
Usage |
||||
===== |
||||
|
||||
Refer to the [API examples](/examples/api/) provided with the TinyFSM |
||||
package for a quick overview. Recommended starting points: |
||||
|
||||
- [Elevator Project]: Documented example, two state machines with |
||||
buttons, floor sensors and actors. |
||||
- [Simple Switch]: A generic switch with two states (on/off). |
||||
- [Moore Machine] and [Mealy Machine]: Basic, educational examples. |
||||
|
||||
For an example in an RTOS environment, see the [stm32f103stk-demo] of |
||||
the [OpenMPTL] project. Starting points: |
||||
|
||||
- [screen.hpp](https://github.com/digint/openmptl/tree/master/projects/stm32f103stk-demo/src/screen.hpp) |
||||
: TinyFSM declarations. |
||||
- [kernel.cpp](https://github.com/digint/openmptl/tree/master/projects/stm32f103stk-demo/src/kernel.cpp) |
||||
: Poll input and trigger events. |
||||
|
||||
[OpenMPTL]: https://digint.ch/openmptl/ |
||||
[stm32f103stk-demo]: https://github.com/digint/openmptl/tree/master/projects/stm32f103stk-demo |
||||
|
||||
|
||||
The examples in the documentation below are mainly based on the |
||||
[Elevator Project]. |
||||
|
||||
[Elevator Project]: /examples/elevator/ |
||||
[Simple Switch]: /examples/api/simple_switch.cpp |
||||
[Moore Machine]: /examples/api/moore_machine.cpp |
||||
[Mealy Machine]: /examples/api/mealy_machine.cpp |
||||
|
||||
|
||||
### 1. Declare Events |
||||
|
||||
Declare events that your state machine will listen to. Events are |
||||
classes derived from the tinyfsm::Event class. |
||||
|
||||
Example: |
||||
|
||||
struct FloorEvent : tinyfsm::Event |
||||
{ |
||||
int floor; |
||||
}; |
||||
|
||||
struct Call : FloorEvent { }; |
||||
struct FloorSensor : FloorEvent { }; |
||||
struct Alarm : tinyfsm::Event { }; |
||||
|
||||
In the example above, we declare three events. Note that events are |
||||
regular classes, which are passed as arguments to the react() members |
||||
of a state class. In this example, we use a member variable "floor", |
||||
which is used to specify the floor number on "Call" and "FloorSensors" |
||||
events. |
||||
|
||||
|
||||
### 2. Declare the State Machine Class |
||||
|
||||
Declare your state machine class. State machines are classes derived |
||||
from the tinyfsm::Fsm template class, where T is the type name of the |
||||
state machine itself. |
||||
|
||||
You need to declare the following public members: |
||||
|
||||
- react() function for each event |
||||
- entry() and exit() functions |
||||
|
||||
Example: |
||||
|
||||
class Elevator |
||||
: public tinyfsm::Fsm<Elevator> |
||||
{ |
||||
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 */ |
||||
}; |
||||
|
||||
|
||||
Note that you are free to declare the functions non-virtual if you |
||||
like. This has implications on the execution speed: In the example |
||||
above, the react(Alarm) function is declared non-virtual, as all states |
||||
share the same reaction for this event. This makes code execution |
||||
faster when dispatching the "Alarm" event, since no vtable lookup is |
||||
needed. |
||||
|
||||
|
||||
### 3. Declare the States |
||||
|
||||
Declare the states of your state machine. States are classes derived |
||||
from the state machine class. |
||||
|
||||
Note that state classes are *implicitly instantiated*. If you want to |
||||
reuse states in multiple state machines, you need to declare them as |
||||
templates (see `/examples/api/multiple_switch.cpp`). |
||||
|
||||
Example: |
||||
|
||||
class Panic |
||||
: public Elevator |
||||
{ |
||||
void entry() override; |
||||
}; |
||||
|
||||
class Moving |
||||
: public Elevator |
||||
{ |
||||
void react(FloorSensor const &) override; |
||||
}; |
||||
|
||||
class Idle |
||||
: public Elevator |
||||
{ |
||||
void entry() override; |
||||
void react(Call const & e) override; |
||||
}; |
||||
|
||||
|
||||
In this example, we declare three states. Note that the "elevator" |
||||
example source code does not declare the states separately, but rather |
||||
defines the code directly in the declaration. |
||||
|
||||
|
||||
### 4. Implement Actions and Event Reactions |
||||
|
||||
In most cases, event reactions consist of one or more of the following |
||||
steps: |
||||
|
||||
- Change some local data |
||||
- Send events to other state machines |
||||
- Transit to different state |
||||
|
||||
**Important**: |
||||
Make sure that the `transit<>()` function call is the last command |
||||
executed within a reaction function! |
||||
|
||||
**Important**: |
||||
Don't use `transit<>()` in entry/exit actions! |
||||
|
||||
Example: |
||||
|
||||
void Idle::entry() { |
||||
send_event(MotorStop()); |
||||
} |
||||
|
||||
void Idle::react(Call const & e) { |
||||
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); |
||||
}; |
||||
|
||||
|
||||
In this example, we use a lambda function as transition action. The |
||||
`transit<>()` function does the following: |
||||
|
||||
1. Call the exit() function of the current state |
||||
2. Call the the transition action if provided |
||||
3. Change the current state to the new state |
||||
4. Call the entry() function of the new state |
||||
|
||||
Note that you can also pass condition functions to the `transit<>()` |
||||
function. |
||||
|
||||
|
||||
### 5. Define the Initial State |
||||
|
||||
Use the macro `FSM_INITIAL_STATE(fsm, state)` for defining the initial |
||||
state (or "start state") of your state machine: |
||||
|
||||
Example: |
||||
|
||||
FSM_INITIAL_STATE(Elevator, Idle) |
||||
|
||||
This sets the current state of the "Elevator" state machine to "Idle". |
||||
More specifially, it defines a template specialization for |
||||
`Fsm<Elevator>::set_initial_state()`, setting the current state to |
||||
Idle. |
||||
|
||||
|
||||
### 6. Define Custom Initialization |
||||
|
||||
If you need to perform custom initialization, you can override the |
||||
reset() member function in your state machine class. If you are using |
||||
state variables, you can re-instantiate your states by calling |
||||
`tinyfsm::StateList<MyStates...>::reset()`. |
||||
|
||||
Example: |
||||
|
||||
class Switch : public tinyfsm::Fsm<Switch> |
||||
{ |
||||
public: static void reset(void) { |
||||
tinyfsm::StateList<Off, On>::reset(); // reset all states |
||||
myvar = 0; |
||||
... |
||||
} |
||||
... |
||||
} |
||||
|
||||
Make sure to always set the current state, or you'll end up with a |
||||
null pointer dereference. |
||||
|
||||
|
||||
### 7. Use FsmList for Event Dispatching |
||||
|
||||
You might have noticed some calls to a send_event() function in the |
||||
example above. This is NOT a function provided with TinyFSM. Since |
||||
event dispatching can be implemented in several ways, TinyFSM leaves |
||||
this open to you. The "elevator" example implements the send_event() |
||||
function as *direct event dispatching*, without using event |
||||
queues. This has the advantage that execution is much faster, since no |
||||
RTTI is needed and the decision which function to call for an event |
||||
class is made at compile-time. On the other hand, special care has to |
||||
be taken when designing the state machines, in order to avoid loops. |
||||
|
||||
Code from "fsmlist.hpp": |
||||
|
||||
typedef tinyfsm::FsmList<Motor, Elevator> fsm_list; |
||||
|
||||
template<typename E> |
||||
void send_event(E const & event) |
||||
{ |
||||
fsm_list::template dispatch<E>(event); |
||||
} |
||||
|
||||
Here, send_event() dispatches events to all state machines in the |
||||
list. It is important to understand that this approach comes with no |
||||
performance penalties at all, as long as the default reaction is |
||||
defined empty within the state machine declaration. |
@ -0,0 +1,206 @@ |
||||
API Reference |
||||
============= |
||||
|
||||
`#include <tinyfsm.hpp>` |
||||
|
||||
|
||||
Class Diagram |
||||
------------- |
||||
....... |
||||
+--------------------------------------: T : |
||||
| tinyfsm::FsmList :.....: |
||||
+-----------------------------------------| |
||||
| [+] set_initial_state() <<static>> | |
||||
| [+] reset() <<static>> | |
||||
| [+] enter() <<static>> | |
||||
| [+] start() <<static>> | |
||||
| [+] dispatch(Event) <<static>> | |
||||
+-----------------------------------------+ |
||||
|
||||
|
||||
....... |
||||
+--------------------------------------: T : |
||||
| tinyfsm::Fsm :.....: |
||||
+-----------------------------------------| |
||||
| [+] state<S>() <<static>> | |
||||
| [+] set_initial_state() <<static>> | |
||||
| [+] reset() <<static>> | |
||||
| [+] enter() <<static>> | |
||||
| [+] start() <<static>> | |
||||
| [+] dispatch(Event) <<static>> | |
||||
| [#] transit<S>() | |
||||
| [#] transit<S>(Action) | |
||||
| [#] transit<S>(Action, Condition) | |
||||
+-----------------------------------------+ |
||||
# |
||||
| |
||||
| |
||||
+---------------------+ |
||||
| MyFSM | |
||||
+---------------------+ |
||||
| [+] entry() | |
||||
| [+] exit() | |
||||
| [+] react(EventX) | |
||||
| [+] react(EventY) | |
||||
| ... | |
||||
+---------------------+ |
||||
# |
||||
| |
||||
+-------------+-------------+ |
||||
| | | |
||||
+---------+ +---------+ +---------+ |
||||
| State_A | | State_B | | ... | |
||||
+---------+ +---------+ +---------+ |
||||
|
||||
|
||||
[#] protected |
||||
[+] public |
||||
[-] private |
||||
|
||||
|
||||
template< typename F > class Fsm |
||||
-------------------------------- |
||||
|
||||
### State Machine Functions |
||||
|
||||
* `template< typename S > static constexpr S & state(void)` |
||||
|
||||
Returns a reference to a (implicitly instantiated) state S. Allows |
||||
low-level access to all states; |
||||
|
||||
|
||||
* `static void set_initial_state(void)` |
||||
|
||||
Function prototype, must be defined (explicit template |
||||
specialization) for every state machine class (e.g. by using the |
||||
`FSM_INITIAL_STATE(fsm, state`) macro). Sets current state to |
||||
initial (start) state. |
||||
|
||||
|
||||
* `static void reset(void)` |
||||
|
||||
Empty function, can be overridden by state machine class in order to |
||||
perform custom initialization (e.g. set static state machine |
||||
variables, or reset states using `StateList<MyStates...>::reset()`) |
||||
or directly via the `state<MyState>()` instance). |
||||
|
||||
Note that this function is NOT called on start(). |
||||
|
||||
See example: `/examples/api/resetting_switch.cpp` |
||||
|
||||
* `static void enter(void)` |
||||
|
||||
Helper function, usually not needed to be used directly: |
||||
calls entry() function of current state. |
||||
|
||||
|
||||
* `static void start()` |
||||
|
||||
Sets the initial (start) state and calls its entry() function. |
||||
|
||||
|
||||
* `template< typename E > static void dispatch(E const &)` |
||||
|
||||
Dispatch an event to the current state of this state machine. |
||||
|
||||
|
||||
### State Transition Functions |
||||
|
||||
* `template< typename S > void transit(void)` |
||||
|
||||
Transit to a new state: |
||||
|
||||
1. Call exit() function on current state |
||||
2. Set new current state to S |
||||
3. Call entry() function on new state |
||||
|
||||
|
||||
* `template< typename S, typename ActionFunction > void transit(ActionFunction)` |
||||
|
||||
Transit to a new state, with action function: |
||||
|
||||
1. Call exit() function on current state |
||||
2. Call ActionFunction |
||||
3. Set new current state to S |
||||
4. Call entry() function on new state |
||||
|
||||
|
||||
* `template< typename S, typename ActionFunction, typename ConditionFunction > void transit(ActionFunction, ConditionFunction)` |
||||
|
||||
Transit to a new state only if ConditionFunction returns true. |
||||
Shortcut for: `if(ConditionFunction()) transit<S>(ActionFunction);`. |
||||
|
||||
|
||||
### Derived Classes |
||||
|
||||
#### template< typename F > class MooreMachine |
||||
|
||||
Moore state machines have entry actions, but no exit actions: |
||||
|
||||
* `virtual void entry(void) { }` |
||||
|
||||
Entry action, not enforcing. Can be enforced by declaring pure |
||||
virtual: `virtual void entry(void) = 0` |
||||
|
||||
* `void exit(void) { }` |
||||
|
||||
No exit actions. |
||||
|
||||
See example: `/examples/api/more_machine.cpp` |
||||
|
||||
#### template< typename F > class MealyMachine |
||||
|
||||
Mealy state machines do not have entry/exit actions: |
||||
|
||||
* `void entry(void) { }` |
||||
|
||||
No entry actions. |
||||
|
||||
* `void exit(void) { }` |
||||
|
||||
No exit actions. |
||||
|
||||
*Input actions* are modeled in react(), conditional dependent of event |
||||
type or payload and using `transit<>(ActionFunction)`. |
||||
|
||||
See example: `/examples/api/mealy_machine.cpp` |
||||
|
||||
|
||||
template< typename... FF > struct FsmList |
||||
----------------------------------------- |
||||
|
||||
* `static void set_initial_state(void)` |
||||
|
||||
Calls set_initial_state() on all state machines in the list. |
||||
|
||||
|
||||
* `static void reset()` |
||||
|
||||
Calls reset() on all state machines in the list. |
||||
|
||||
|
||||
* `static void enter()` |
||||
|
||||
Calls enter() on all state machines in the list. |
||||
|
||||
|
||||
* `static void start()` |
||||
|
||||
Sets the initial (start) state for all state machines in list, then |
||||
call all entry() functions. |
||||
|
||||
|
||||
* `template< typename E > static void dispatch(E const &)` |
||||
|
||||
Dispatch an event to the current state of all the state machines in |
||||
the list. |
||||
|
||||
|
||||
template< typename... SS > struct StateList |
||||
------------------------------------------- |
||||
|
||||
* `static void reset(void)` |
||||
|
||||
Re-instantiate all states in the list, using copy-constructor. |
||||
|
||||
See example: `/examples/api/resetting_switch.cpp` |
@ -0,0 +1,26 @@ |
||||
Development |
||||
=========== |
||||
|
||||
Source Code Repository |
||||
---------------------- |
||||
|
||||
The source code for TinyFSM is managed using Git: |
||||
|
||||
git clone https://dev.tty0.ch/tinyfsm.git |
||||
|
||||
Mirror on GitHub: |
||||
|
||||
git clone https://github.com/digint/tinyfsm.git |
||||
|
||||
|
||||
How to Contribute |
||||
----------------- |
||||
|
||||
Your contributions are welcome! |
||||
|
||||
If you would like to contribute or have found bugs, visit the [TinyFSM |
||||
project page on GitHub] and use the [issues tracker] there, or contact |
||||
the author via email. |
||||
|
||||
[TinyFSM project page on GitHub]: http://github.com/digint/tinyfsm |
||||
[issues tracker]: http://github.com/digint/tinyfsm/issues |
@ -0,0 +1,9 @@ |
||||
License |
||||
======= |
||||
|
||||
TinyFSM is [Open Source] software. It may be used for any purpose, |
||||
including commercial purposes, at absolutely no cost. It is |
||||
distributed under the terms of the [MIT license]. |
||||
|
||||
[Open Source]: http://www.opensource.org/docs/definition.html |
||||
[MIT license]: http://www.opensource.org/licenses/mit-license.html |
@ -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) |
@ -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; |
||||
}; |
||||
} |
||||
} |
@ -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; |
||||
}; |
||||
} |
||||
} |
@ -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; |
||||
}; |
||||
} |
||||
} |
@ -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; |
||||
}; |
||||
} |
||||
} |
@ -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; |
||||
}; |
||||
} |
||||
} |
@ -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; |
||||
}; |
||||
} |
||||
} |
@ -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) |
@ -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; |
||||
}; |
@ -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) |
@ -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 |
@ -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 |
@ -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; |
||||
}; |
||||
} |
||||
} |
@ -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) |
@ -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 |
@ -0,0 +1,251 @@ |
||||
/*
|
||||
* TinyFSM - Tiny Finite State Machine Processor |
||||
* |
||||
* Copyright (c) 2012-2022 Axel Burri |
||||
* |
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
* of this software and associated documentation files (the "Software"), to deal |
||||
* in the Software without restriction, including without limitation the rights |
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
* copies of the Software, and to permit persons to whom the Software is |
||||
* furnished to do so, subject to the following conditions: |
||||
* |
||||
* The above copyright notice and this permission notice shall be included in |
||||
* all copies or substantial portions of the Software. |
||||
* |
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
||||
* THE SOFTWARE. |
||||
*/ |
||||
|
||||
/* ---------------------------------------------------------------------
|
||||
* Version: 0.3.3 |
||||
* |
||||
* API documentation: see "../doc/50-API.md" |
||||
* |
||||
* The official TinyFSM website is located at: |
||||
* https://digint.ch/tinyfsm/
|
||||
* |
||||
* Author: |
||||
* Axel Burri <axel@tty0.ch> |
||||
* --------------------------------------------------------------------- |
||||
*/ |
||||
|
||||
#ifndef TINYFSM_HPP_INCLUDED |
||||
#define TINYFSM_HPP_INCLUDED |
||||
|
||||
#ifndef TINYFSM_NOSTDLIB |
||||
#include <type_traits> |
||||
#endif |
||||
|
||||
// #include <iostream>
|
||||
// #define DBG(str) do { std::cerr << str << std::endl; } while( false )
|
||||
// DBG("*** dbg_example *** " << __PRETTY_FUNCTION__);
|
||||
|
||||
namespace tinyfsm |
||||
{ |
||||
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
struct Event { }; |
||||
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
#ifdef TINYFSM_NOSTDLIB |
||||
// remove dependency on standard library (silent fail!).
|
||||
// useful in conjunction with -nostdlib option, e.g. if your compiler
|
||||
// does not provide a standard library.
|
||||
// NOTE: this silently disables all static_assert() calls below!
|
||||
template<typename F, typename S> |
||||
struct is_same_fsm { static constexpr bool value = true; }; |
||||
#else |
||||
// check if both fsm and state class share same fsmtype
|
||||
template<typename F, typename S> |
||||
struct is_same_fsm : std::is_same< typename F::fsmtype, typename S::fsmtype > { }; |
||||
#endif |
||||
|
||||
template<typename S> |
||||
struct _state_instance |
||||
{ |
||||
using value_type = S; |
||||
using type = _state_instance<S>; |
||||
static S value; |
||||
}; |
||||
|
||||
template<typename S> |
||||
typename _state_instance<S>::value_type _state_instance<S>::value; |
||||
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
template<typename F> |
||||
class Fsm |
||||
{ |
||||
public: |
||||
|
||||
using fsmtype = Fsm<F>; |
||||
using state_ptr_t = F *; |
||||
|
||||
static state_ptr_t current_state_ptr; |
||||
|
||||
// public, leaving ability to access state instance (e.g. on reset)
|
||||
template<typename S> |
||||
static constexpr S & state(void) { |
||||
static_assert(is_same_fsm<F, S>::value, "accessing state of different state machine"); |
||||
return _state_instance<S>::value; |
||||
} |
||||
|
||||
template<typename S> |
||||
static constexpr bool is_in_state(void) { |
||||
static_assert(is_same_fsm<F, S>::value, "accessing state of different state machine"); |
||||
return current_state_ptr == &_state_instance<S>::value; |
||||
} |
||||
|
||||
/// state machine functions
|
||||
public: |
||||
|
||||
// explicitely specialized in FSM_INITIAL_STATE macro
|
||||
static void set_initial_state(); |
||||
|
||||
static void reset() { }; |
||||
|
||||
static void enter() { |
||||
current_state_ptr->entry(); |
||||
} |
||||
|
||||
static void start() { |
||||
set_initial_state(); |
||||
enter(); |
||||
} |
||||
|
||||
template<typename E> |
||||
static void dispatch(E const & event) { |
||||
current_state_ptr->react(event); |
||||
} |
||||
|
||||
|
||||
/// state transition functions
|
||||
protected: |
||||
|
||||
template<typename S> |
||||
void transit(void) { |
||||
static_assert(is_same_fsm<F, S>::value, "transit to different state machine"); |
||||
current_state_ptr->exit(); |
||||
current_state_ptr = &_state_instance<S>::value; |
||||
current_state_ptr->entry(); |
||||
} |
||||
|
||||
template<typename S, typename ActionFunction> |
||||
void transit(ActionFunction action_function) { |
||||
static_assert(is_same_fsm<F, S>::value, "transit to different state machine"); |
||||
current_state_ptr->exit(); |
||||
// NOTE: do not send events in action_function definisions.
|
||||
action_function(); |
||||
current_state_ptr = &_state_instance<S>::value; |
||||
current_state_ptr->entry(); |
||||
} |
||||
|
||||
template<typename S, typename ActionFunction, typename ConditionFunction> |
||||
void transit(ActionFunction action_function, ConditionFunction condition_function) { |
||||
if(condition_function()) { |
||||
transit<S>(action_function); |
||||
} |
||||
} |
||||
}; |
||||
|
||||
template<typename F> |
||||
typename Fsm<F>::state_ptr_t Fsm<F>::current_state_ptr; |
||||
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
template<typename... FF> |
||||
struct FsmList; |
||||
|
||||
template<> struct FsmList<> { |
||||
static void set_initial_state() { } |
||||
static void reset() { } |
||||
static void enter() { } |
||||
template<typename E> |
||||
static void dispatch(E const &) { } |
||||
}; |
||||
|
||||
template<typename F, typename... FF> |
||||
struct FsmList<F, FF...> |
||||
{ |
||||
using fsmtype = Fsm<F>; |
||||
|
||||
static void set_initial_state() { |
||||
fsmtype::set_initial_state(); |
||||
FsmList<FF...>::set_initial_state(); |
||||
} |
||||
|
||||
static void reset() { |
||||
F::reset(); |
||||
FsmList<FF...>::reset(); |
||||
} |
||||
|
||||
static void enter() { |
||||
fsmtype::enter(); |
||||
FsmList<FF...>::enter(); |
||||
} |
||||
|
||||
static void start() { |
||||
set_initial_state(); |
||||
enter(); |
||||
} |
||||
|
||||
template<typename E> |
||||
static void dispatch(E const & event) { |
||||
fsmtype::template dispatch<E>(event); |
||||
FsmList<FF...>::template dispatch<E>(event); |
||||
} |
||||
}; |
||||
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
template<typename... SS> struct StateList; |
||||
template<> struct StateList<> { |
||||
static void reset() { } |
||||
}; |
||||
template<typename S, typename... SS> |
||||
struct StateList<S, SS...> |
||||
{ |
||||
static void reset() { |
||||
_state_instance<S>::value = S(); |
||||
StateList<SS...>::reset(); |
||||
} |
||||
}; |
||||
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
template<typename F> |
||||
struct MooreMachine : tinyfsm::Fsm<F> |
||||
{ |
||||
virtual void entry(void) { }; /* entry actions in some states */ |
||||
void exit(void) { }; /* no exit actions */ |
||||
}; |
||||
|
||||
template<typename F> |
||||
struct MealyMachine : tinyfsm::Fsm<F> |
||||
{ |
||||
// input actions are modeled in react():
|
||||
// - conditional dependent of event type or payload
|
||||
// - transit<>(ActionFunction)
|
||||
void entry(void) { }; /* no entry actions */ |
||||
void exit(void) { }; /* no exit actions */ |
||||
}; |
||||
|
||||
} /* namespace tinyfsm */ |
||||
|
||||
|
||||
#define FSM_INITIAL_STATE(_FSM, _STATE) \ |
||||
namespace tinyfsm { \
|
||||
template<> void Fsm< _FSM >::set_initial_state(void) { \
|
||||
current_state_ptr = &_state_instance< _STATE >::value; \
|
||||
} \
|
||||
} |
||||
|
||||
#endif /* TINYFSM_HPP_INCLUDED */ |
@ -0,0 +1,24 @@ |
||||
{ |
||||
"name": "tinyfsm", |
||||
"description": "A simple C++ finite state machine library", |
||||
"version": "0.3.3", |
||||
"repository": { |
||||
"type": "git", |
||||
"url": "https://dev.tty0.ch/tinyfsm.git" |
||||
}, |
||||
"homepage": "https://digint.ch/tinyfsm/", |
||||
"authors": { |
||||
"name": "Axel Burri", |
||||
"email": "axel@tty0.ch", |
||||
"url": "http://digint.ch", |
||||
"maintainer": true |
||||
}, |
||||
"license": "MIT", |
||||
"platforms": "*", |
||||
"frameworks": "*", |
||||
"build": { |
||||
"flags": [ |
||||
"-I include/" |
||||
] |
||||
} |
||||
} |
@ -0,0 +1,5 @@ |
||||
idf_component_register( |
||||
SRCS "app_console.cpp" |
||||
INCLUDE_DIRS "include" |
||||
REQUIRES "dev_console" "events" "database") |
||||
target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS}) |
@ -1,8 +1,8 @@ |
||||
idf_component_register( |
||||
SRCS "audio_decoder.cpp" "audio_task.cpp" "chunk.cpp" "fatfs_audio_input.cpp" |
||||
"stream_message.cpp" "i2s_audio_output.cpp" "stream_buffer.cpp" |
||||
"audio_playback.cpp" "stream_event.cpp" "pipeline.cpp" "stream_info.cpp" |
||||
"stream_event.cpp" "pipeline.cpp" "stream_info.cpp" "audio_fsm.cpp" |
||||
INCLUDE_DIRS "include" |
||||
REQUIRES "codecs" "drivers" "cbor" "result" "tasks" "span" "memory") |
||||
REQUIRES "codecs" "drivers" "cbor" "result" "tasks" "span" "memory" "tinyfsm" "database" "system_fsm") |
||||
|
||||
target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS}) |
||||
|
@ -0,0 +1,51 @@ |
||||
#include "audio_fsm.hpp" |
||||
#include "audio_decoder.hpp" |
||||
#include "audio_task.hpp" |
||||
#include "dac.hpp" |
||||
#include "fatfs_audio_input.hpp" |
||||
#include "i2s_audio_output.hpp" |
||||
#include "pipeline.hpp" |
||||
|
||||
namespace audio { |
||||
|
||||
drivers::GpioExpander* AudioState::sGpioExpander; |
||||
std::weak_ptr<drivers::AudioDac> AudioState::sDac; |
||||
std::weak_ptr<database::Database> AudioState::sDatabase; |
||||
|
||||
std::unique_ptr<FatfsAudioInput> AudioState::sFileSource; |
||||
std::unique_ptr<I2SAudioOutput> AudioState::sI2SOutput; |
||||
std::vector<std::unique_ptr<IAudioElement>> AudioState::sPipeline; |
||||
|
||||
auto AudioState::Init(drivers::GpioExpander* gpio_expander, |
||||
std::weak_ptr<drivers::AudioDac> dac, |
||||
std::weak_ptr<database::Database> database) -> void { |
||||
sGpioExpander = gpio_expander; |
||||
sDac = dac; |
||||
sDatabase = database; |
||||
} |
||||
|
||||
namespace states { |
||||
|
||||
void Uninitialised::react(const system_fsm::BootComplete&) { |
||||
transit<Standby>([&]() { |
||||
sFileSource.reset(new FatfsAudioInput()); |
||||
sI2SOutput.reset(new I2SAudioOutput(sGpioExpander, sDac)); |
||||
|
||||
// Perform initial pipeline configuration.
|
||||
// TODO(jacqueline): Factor this out once we have any kind of dynamic
|
||||
// reconfiguration.
|
||||
AudioDecoder* codec = new AudioDecoder(); |
||||
sPipeline.emplace_back(codec); |
||||
|
||||
Pipeline* pipeline = new Pipeline(sPipeline.front().get()); |
||||
pipeline->AddInput(sFileSource.get()); |
||||
|
||||
task::StartPipeline(pipeline, sI2SOutput.get()); |
||||
}); |
||||
} |
||||
|
||||
} // namespace states
|
||||
|
||||
} // namespace audio
|
||||
|
||||
FSM_INITIAL_STATE(audio::AudioState, audio::states::Uninitialised) |
@ -1,50 +0,0 @@ |
||||
#include "audio_playback.hpp" |
||||
|
||||
#include <algorithm> |
||||
#include <cstdint> |
||||
#include <memory> |
||||
#include <string_view> |
||||
|
||||
#include "driver_cache.hpp" |
||||
#include "freertos/portmacro.h" |
||||
|
||||
#include "audio_decoder.hpp" |
||||
#include "audio_element.hpp" |
||||
#include "audio_task.hpp" |
||||
#include "chunk.hpp" |
||||
#include "fatfs_audio_input.hpp" |
||||
#include "gpio_expander.hpp" |
||||
#include "i2s_audio_output.hpp" |
||||
#include "pipeline.hpp" |
||||
#include "storage.hpp" |
||||
#include "stream_buffer.hpp" |
||||
#include "stream_info.hpp" |
||||
#include "stream_message.hpp" |
||||
|
||||
namespace audio { |
||||
AudioPlayback::AudioPlayback(drivers::DriverCache* drivers) |
||||
: file_source_(std::make_unique<FatfsAudioInput>()), |
||||
i2s_output_(std::make_unique<I2SAudioOutput>(drivers->AcquireGpios(), |
||||
drivers->AcquireDac())) { |
||||
AudioDecoder* codec = new AudioDecoder(); |
||||
elements_.emplace_back(codec); |
||||
|
||||
Pipeline* pipeline = new Pipeline(elements_.front().get()); |
||||
pipeline->AddInput(file_source_.get()); |
||||
|
||||
task::StartPipeline(pipeline, i2s_output_.get()); |
||||
// task::StartDrain(i2s_output_.get());
|
||||
} |
||||
|
||||
AudioPlayback::~AudioPlayback() {} |
||||
|
||||
auto AudioPlayback::Play(const std::string& filename) -> void { |
||||
// TODO: concurrency, yo!
|
||||
file_source_->OpenFile(filename); |
||||
} |
||||
|
||||
auto AudioPlayback::LogStatus() -> void { |
||||
i2s_output_->Log(); |
||||
} |
||||
|
||||
} // namespace audio
|
@ -0,0 +1,13 @@ |
||||
#pragma once |
||||
|
||||
#include "tinyfsm.hpp" |
||||
|
||||
#include "song.hpp" |
||||
|
||||
namespace audio { |
||||
|
||||
struct PlaySong : tinyfsm::Event { |
||||
database::SongId id; |
||||
}; |
||||
|
||||
} // namespace audio
|
@ -0,0 +1,61 @@ |
||||
#pragma once |
||||
#include <memory> |
||||
|
||||
#include "audio_events.hpp" |
||||
#include "dac.hpp" |
||||
#include "database.hpp" |
||||
#include "display.hpp" |
||||
#include "fatfs_audio_input.hpp" |
||||
#include "gpio_expander.hpp" |
||||
#include "i2s_audio_output.hpp" |
||||
#include "storage.hpp" |
||||
#include "tinyfsm.hpp" |
||||
|
||||
#include "system_events.hpp" |
||||
|
||||
namespace audio { |
||||
|
||||
class AudioState : public tinyfsm::Fsm<AudioState> { |
||||
public: |
||||
static auto Init(drivers::GpioExpander* gpio_expander, |
||||
std::weak_ptr<drivers::AudioDac>, |
||||
std::weak_ptr<database::Database>) -> void; |
||||
|
||||
virtual ~AudioState() {} |
||||
|
||||
virtual void entry() {} |
||||
virtual void exit() {} |
||||
|
||||
/* Fallback event handler. Does nothing. */ |
||||
void react(const tinyfsm::Event& ev) {} |
||||
|
||||
virtual void react(const system_fsm::BootComplete&) {} |
||||
virtual void react(const PlaySong&) {} |
||||
|
||||
protected: |
||||
static drivers::GpioExpander* sGpioExpander; |
||||
static std::weak_ptr<drivers::AudioDac> sDac; |
||||
static std::weak_ptr<database::Database> sDatabase; |
||||
|
||||
static std::unique_ptr<FatfsAudioInput> sFileSource; |
||||
static std::unique_ptr<I2SAudioOutput> sI2SOutput; |
||||
static std::vector<std::unique_ptr<IAudioElement>> sPipeline; |
||||
}; |
||||
|
||||
namespace states { |
||||
|
||||
class Uninitialised : public AudioState { |
||||
public: |
||||
void react(const system_fsm::BootComplete&) override; |
||||
using AudioState::react; |
||||
}; |
||||
|
||||
class Standby : public AudioState { |
||||
public: |
||||
void react(const PlaySong&) override {} |
||||
using AudioState::react; |
||||
}; |
||||
|
||||
} // namespace states
|
||||
|
||||
} // namespace audio
|
@ -1,50 +0,0 @@ |
||||
#pragma once |
||||
|
||||
#include <cstdint> |
||||
#include <memory> |
||||
#include <string> |
||||
#include <vector> |
||||
|
||||
#include "audio_task.hpp" |
||||
#include "driver_cache.hpp" |
||||
#include "esp_err.h" |
||||
#include "fatfs_audio_input.hpp" |
||||
#include "i2s_audio_output.hpp" |
||||
#include "result.hpp" |
||||
#include "span.hpp" |
||||
|
||||
#include "audio_element.hpp" |
||||
#include "gpio_expander.hpp" |
||||
#include "storage.hpp" |
||||
#include "stream_buffer.hpp" |
||||
|
||||
namespace audio { |
||||
|
||||
/*
|
||||
* Creates and links together audio elements into a pipeline. This is the main |
||||
* entrypoint to playing audio on the system. |
||||
*/ |
||||
class AudioPlayback { |
||||
public: |
||||
explicit AudioPlayback(drivers::DriverCache* drivers); |
||||
~AudioPlayback(); |
||||
|
||||
/*
|
||||
* Begins playing the file at the given FatFS path. This will interrupt any |
||||
* currently in-progress playback. |
||||
*/ |
||||
auto Play(const std::string& filename) -> void; |
||||
|
||||
auto LogStatus() -> void; |
||||
|
||||
// Not copyable or movable.
|
||||
AudioPlayback(const AudioPlayback&) = delete; |
||||
AudioPlayback& operator=(const AudioPlayback&) = delete; |
||||
|
||||
private: |
||||
std::unique_ptr<FatfsAudioInput> file_source_; |
||||
std::unique_ptr<I2SAudioOutput> i2s_output_; |
||||
std::vector<std::unique_ptr<IAudioElement>> elements_; |
||||
}; |
||||
|
||||
} // namespace audio
|
@ -1,6 +1,6 @@ |
||||
idf_component_register( |
||||
SRCS "touchwheel.cpp" "dac.cpp" "gpio_expander.cpp" "battery.cpp" "storage.cpp" "i2c.cpp" |
||||
"spi.cpp" "display.cpp" "display_init.cpp" "driver_cache.cpp" "samd.cpp" |
||||
"spi.cpp" "display.cpp" "display_init.cpp" "samd.cpp" |
||||
INCLUDE_DIRS "include" |
||||
REQUIRES "esp_adc" "fatfs" "result" "lvgl" "span") |
||||
target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS}) |
||||
|
@ -1,43 +0,0 @@ |
||||
#include "driver_cache.hpp" |
||||
|
||||
#include <memory> |
||||
#include <mutex> |
||||
|
||||
#include "display.hpp" |
||||
#include "display_init.hpp" |
||||
#include "storage.hpp" |
||||
#include "touchwheel.hpp" |
||||
|
||||
namespace drivers { |
||||
|
||||
DriverCache::DriverCache() : gpios_(std::make_unique<GpioExpander>()) {} |
||||
DriverCache::~DriverCache() {} |
||||
|
||||
auto DriverCache::AcquireGpios() -> GpioExpander* { |
||||
return gpios_.get(); |
||||
} |
||||
|
||||
auto DriverCache::AcquireDac() -> std::shared_ptr<AudioDac> { |
||||
return Acquire(dac_, [&]() -> AudioDac* { |
||||
return AudioDac::create(AcquireGpios()).value_or(nullptr); |
||||
}); |
||||
} |
||||
|
||||
auto DriverCache::AcquireDisplay() -> std::shared_ptr<Display> { |
||||
return Acquire(display_, [&]() -> Display* { |
||||
return Display::create(AcquireGpios(), displays::kST7735R); |
||||
}); |
||||
} |
||||
|
||||
auto DriverCache::AcquireStorage() -> std::shared_ptr<SdStorage> { |
||||
return Acquire(storage_, [&]() -> SdStorage* { |
||||
return SdStorage::create(AcquireGpios()).value_or(nullptr); |
||||
}); |
||||
} |
||||
|
||||
auto DriverCache::AcquireTouchWheel() -> std::shared_ptr<TouchWheel> { |
||||
return Acquire(touchwheel_, |
||||
[&]() -> TouchWheel* { return new TouchWheel(); }); |
||||
} |
||||
|
||||
} // namespace drivers
|
@ -1,54 +0,0 @@ |
||||
#pragma once |
||||
|
||||
#include <memory> |
||||
#include <mutex> |
||||
|
||||
#include "dac.hpp" |
||||
#include "display.hpp" |
||||
#include "gpio_expander.hpp" |
||||
#include "storage.hpp" |
||||
#include "touchwheel.hpp" |
||||
|
||||
namespace drivers { |
||||
|
||||
class DriverCache { |
||||
private: |
||||
std::unique_ptr<GpioExpander> gpios_; |
||||
std::weak_ptr<AudioDac> dac_; |
||||
std::weak_ptr<Display> display_; |
||||
std::weak_ptr<SdStorage> storage_; |
||||
std::weak_ptr<TouchWheel> touchwheel_; |
||||
// TODO(jacqueline): Haptics, samd
|
||||
|
||||
std::mutex mutex_; |
||||
|
||||
template <typename T, typename F> |
||||
auto Acquire(std::weak_ptr<T> ptr, F factory) -> std::shared_ptr<T> { |
||||
std::shared_ptr<T> acquired = ptr.lock(); |
||||
if (acquired) { |
||||
return acquired; |
||||
} |
||||
|
||||
std::lock_guard<std::mutex> lock(mutex_); |
||||
|
||||
acquired = ptr.lock(); |
||||
if (acquired) { |
||||
return acquired; |
||||
} |
||||
acquired.reset(factory()); |
||||
ptr = acquired; |
||||
return acquired; |
||||
} |
||||
|
||||
public: |
||||
DriverCache(); |
||||
~DriverCache(); |
||||
|
||||
auto AcquireGpios() -> GpioExpander*; |
||||
auto AcquireDac() -> std::shared_ptr<AudioDac>; |
||||
auto AcquireDisplay() -> std::shared_ptr<Display>; |
||||
auto AcquireStorage() -> std::shared_ptr<SdStorage>; |
||||
auto AcquireTouchWheel() -> std::shared_ptr<TouchWheel>; |
||||
}; |
||||
|
||||
} // namespace drivers
|
@ -0,0 +1,5 @@ |
||||
idf_component_register( |
||||
SRCS "event_queue.cpp" |
||||
INCLUDE_DIRS "include" |
||||
REQUIRES "tinyfsm") |
||||
target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS}) |
@ -0,0 +1,23 @@ |
||||
#include "event_queue.hpp" |
||||
|
||||
#include "freertos/FreeRTOS.h" |
||||
#include "freertos/queue.h" |
||||
|
||||
namespace events { |
||||
|
||||
static const std::size_t kMaxPendingEvents = 16; |
||||
|
||||
EventQueue::EventQueue() |
||||
: handle_(xQueueCreate(kMaxPendingEvents, sizeof(WorkItem*))) {} |
||||
|
||||
auto EventQueue::Service(TickType_t max_wait_time) -> bool { |
||||
WorkItem* item; |
||||
if (xQueueReceive(handle_, &item, max_wait_time)) { |
||||
(*item)(); |
||||
delete item; |
||||
return true; |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
} // namespace events
|
@ -0,0 +1,45 @@ |
||||
#pragma once |
||||
|
||||
#include <functional> |
||||
|
||||
#include "freertos/FreeRTOS.h" |
||||
#include "freertos/portmacro.h" |
||||
#include "freertos/queue.h" |
||||
#include "tinyfsm.hpp" |
||||
|
||||
namespace events { |
||||
|
||||
typedef std::function<void(void)> WorkItem; |
||||
|
||||
class EventQueue { |
||||
public: |
||||
static EventQueue& GetInstance() { |
||||
static EventQueue instance; |
||||
return instance; |
||||
} |
||||
|
||||
template <typename Event, typename... Machines> |
||||
auto Dispatch(const Event& ev) -> void { |
||||
WorkItem* item = new WorkItem( |
||||
[=]() { tinyfsm::FsmList<Machines...>::template dispatch<Event>(ev); }); |
||||
xQueueSend(handle_, &item, portMAX_DELAY); |
||||
} |
||||
|
||||
auto Service(TickType_t max_wait_time) -> bool; |
||||
|
||||
EventQueue(EventQueue const&) = delete; |
||||
void operator=(EventQueue const&) = delete; |
||||
|
||||
private: |
||||
EventQueue(); |
||||
|
||||
QueueHandle_t handle_; |
||||
}; |
||||
|
||||
template <typename Event, typename... Machines> |
||||
auto Dispatch(const Event& ev) -> void { |
||||
EventQueue& queue = EventQueue::GetInstance(); |
||||
queue.Dispatch<Event, Machines...>(ev); |
||||
} |
||||
|
||||
} // namespace events
|
@ -1,5 +1,5 @@ |
||||
idf_component_register( |
||||
SRCS "main.cpp" "app_console.cpp" |
||||
SRCS "main.cpp" |
||||
INCLUDE_DIRS "." |
||||
REQUIRES "audio" "drivers" "dev_console" "drivers" "database" "ui") |
||||
REQUIRES "audio" "ui" "system_fsm" "events") |
||||
target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS}) |
||||
|
@ -1,114 +1,18 @@ |
||||
#include <dirent.h> |
||||
#include <stdint.h> |
||||
#include <stdio.h> |
||||
|
||||
#include <cstddef> |
||||
#include <cstdint> |
||||
#include <memory> |
||||
|
||||
#include "driver/gpio.h" |
||||
#include "driver/i2c.h" |
||||
#include "driver/sdspi_host.h" |
||||
#include "driver/spi_common.h" |
||||
#include "driver/spi_master.h" |
||||
#include "driver_cache.hpp" |
||||
#include "esp_freertos_hooks.h" |
||||
#include "esp_heap_caps.h" |
||||
#include "esp_intr_alloc.h" |
||||
#include "esp_log.h" |
||||
#include "freertos/portmacro.h" |
||||
#include "freertos/projdefs.h" |
||||
#include "hal/gpio_types.h" |
||||
#include "hal/spi_types.h" |
||||
|
||||
#include "app_console.hpp" |
||||
#include "audio_playback.hpp" |
||||
#include "battery.hpp" |
||||
#include "dac.hpp" |
||||
#include "database.hpp" |
||||
#include "display.hpp" |
||||
#include "display_init.hpp" |
||||
#include "gpio_expander.hpp" |
||||
#include "i2c.hpp" |
||||
#include "lvgl_task.hpp" |
||||
#include "samd.hpp" |
||||
#include "spi.hpp" |
||||
#include "storage.hpp" |
||||
#include "touchwheel.hpp" |
||||
#include "tinyfsm.hpp" |
||||
|
||||
static const char* TAG = "MAIN"; |
||||
#include "audio_fsm.hpp" |
||||
#include "event_queue.hpp" |
||||
#include "system_fsm.hpp" |
||||
#include "ui_fsm.hpp" |
||||
|
||||
extern "C" void app_main(void) { |
||||
ESP_LOGI(TAG, "Initialising peripherals"); |
||||
|
||||
ESP_ERROR_CHECK(drivers::init_i2c()); |
||||
ESP_ERROR_CHECK(drivers::init_spi()); |
||||
std::unique_ptr<drivers::DriverCache> drivers = |
||||
std::make_unique<drivers::DriverCache>(); |
||||
|
||||
ESP_LOGI(TAG, "Init GPIOs"); |
||||
drivers::GpioExpander* expander = drivers->AcquireGpios(); |
||||
|
||||
ESP_LOGI(TAG, "Init SAMD comms"); |
||||
drivers::Samd samd; |
||||
ESP_LOGI(TAG, "It might have worked? Let's read something!"); |
||||
auto res = samd.ReadChargeStatus(); |
||||
if (res) { |
||||
ESP_LOGI(TAG, "Charge status is %d", static_cast<uint8_t>(*res)); |
||||
} else { |
||||
ESP_LOGI(TAG, "no charge status?"); |
||||
} |
||||
|
||||
ESP_LOGI(TAG, "Enable power rails for development"); |
||||
expander->with( |
||||
[&](auto& gpio) { gpio.set_pin(drivers::GpioExpander::AMP_EN, 1); }); |
||||
|
||||
ESP_LOGI(TAG, "Init battery measurement"); |
||||
drivers::Battery* battery = new drivers::Battery(); |
||||
ESP_LOGI(TAG, "it's reading %d mV!", (int)battery->Millivolts()); |
||||
|
||||
ESP_LOGI(TAG, "Init SD card"); |
||||
auto storage = drivers->AcquireStorage(); |
||||
if (!storage) { |
||||
ESP_LOGE(TAG, "Failed! Do you have an SD card?"); |
||||
} |
||||
tinyfsm::FsmList<system_fsm::SystemState, ui::UiState, |
||||
audio::AudioState>::start(); |
||||
|
||||
ESP_LOGI(TAG, "Init touch wheel"); |
||||
std::shared_ptr<drivers::TouchWheel> touchwheel = |
||||
drivers->AcquireTouchWheel(); |
||||
|
||||
std::atomic<bool> lvgl_quit; |
||||
TaskHandle_t lvgl_task_handle; |
||||
ui::StartLvgl(drivers.get(), &lvgl_quit, &lvgl_task_handle); |
||||
|
||||
std::unique_ptr<audio::AudioPlayback> playback; |
||||
if (storage) { |
||||
ESP_LOGI(TAG, "Init audio pipeline"); |
||||
playback = std::make_unique<audio::AudioPlayback>(drivers.get()); |
||||
} |
||||
|
||||
ESP_LOGI(TAG, "Init database"); |
||||
std::unique_ptr<database::Database> db; |
||||
auto db_res = database::Database::Open(); |
||||
if (db_res.has_value()) { |
||||
db.reset(db_res.value()); |
||||
} |
||||
|
||||
ESP_LOGI(TAG, "Waiting for background tasks before launching console..."); |
||||
vTaskDelay(pdMS_TO_TICKS(1000)); |
||||
|
||||
ESP_LOGI(TAG, "Launch console"); |
||||
console::AppConsole console(playback.get(), db.get()); |
||||
console.Launch(); |
||||
|
||||
uint8_t prev_position = 0; |
||||
auto& queue = events::EventQueue::GetInstance(); |
||||
while (1) { |
||||
touchwheel->Update(); |
||||
auto wheel_data = touchwheel->GetTouchWheelData(); |
||||
if (wheel_data.wheel_position != prev_position) { |
||||
prev_position = wheel_data.wheel_position; |
||||
ESP_LOGI(TAG, "Touch wheel pos: %u", prev_position); |
||||
} |
||||
vTaskDelay(pdMS_TO_TICKS(100)); |
||||
queue.Service(portMAX_DELAY); |
||||
} |
||||
} |
||||
|
@ -0,0 +1,5 @@ |
||||
idf_component_register( |
||||
SRCS "system_fsm.cpp" "running.cpp" "booting.cpp" |
||||
INCLUDE_DIRS "include" |
||||
REQUIRES "tinyfsm" "drivers" "database" "ui" "result" "events" "audio") |
||||
target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS}) |
@ -0,0 +1,88 @@ |
||||
#include "assert.h" |
||||
#include "audio_fsm.hpp" |
||||
#include "core/lv_obj.h" |
||||
#include "display_init.hpp" |
||||
#include "esp_err.h" |
||||
#include "esp_log.h" |
||||
#include "event_queue.hpp" |
||||
#include "gpio_expander.hpp" |
||||
#include "lvgl/lvgl.h" |
||||
#include "spi.hpp" |
||||
#include "system_events.hpp" |
||||
#include "system_fsm.hpp" |
||||
#include "ui_fsm.hpp" |
||||
|
||||
#include "i2c.hpp" |
||||
#include "touchwheel.hpp" |
||||
|
||||
namespace system_fsm { |
||||
namespace states { |
||||
|
||||
static const char kTag[] = "BOOT"; |
||||
|
||||
auto Booting::entry() -> void { |
||||
ESP_LOGI(kTag, "beginning tangara boot"); |
||||
ESP_LOGI(kTag, "installing bare minimum drivers"); |
||||
|
||||
// I2C and SPI are both always needed. We can't even power down or show an
|
||||
// error without these.
|
||||
ESP_ERROR_CHECK(drivers::init_i2c()); |
||||
ESP_ERROR_CHECK(drivers::init_spi()); |
||||
|
||||
// These drivers are the bare minimum to even show an error. If these fail,
|
||||
// then the system is completely hosed.
|
||||
sGpioExpander.reset(drivers::GpioExpander::Create()); |
||||
assert(sGpioExpander != nullptr); |
||||
|
||||
// Start bringing up LVGL now, since we have all of its prerequisites.
|
||||
ESP_LOGI(kTag, "starting ui"); |
||||
lv_init(); |
||||
sDisplay.reset(drivers::Display::Create(sGpioExpander.get(), |
||||
drivers::displays::kST7735R)); |
||||
assert(sDisplay != nullptr); |
||||
|
||||
// The UI FSM now has everything it needs to start setting up. Do this now,
|
||||
// so that we can properly show the user any errors that appear later.
|
||||
ui::UiState::Init(sGpioExpander.get(), sTouch, sDisplay, sDatabase); |
||||
events::Dispatch<DisplayReady, ui::UiState>(DisplayReady()); |
||||
|
||||
// These drivers are required for normal operation, but aren't critical for
|
||||
// booting. We will transition to the error state if these aren't present.
|
||||
ESP_LOGI(kTag, "installing required drivers"); |
||||
sSamd.reset(drivers::Samd::Create()); |
||||
sTouch.reset(drivers::TouchWheel::Create()); |
||||
|
||||
auto dac_res = drivers::AudioDac::create(sGpioExpander.get()); |
||||
if (dac_res.has_error() || !sSamd || !sTouch) { |
||||
events::Dispatch<FatalError, SystemState, ui::UiState, audio::AudioState>( |
||||
FatalError()); |
||||
return; |
||||
} |
||||
sDac.reset(dac_res.value()); |
||||
|
||||
// These drivers are initialised on boot, but are recoverable (if weird) if
|
||||
// they fail.
|
||||
ESP_LOGI(kTag, "installing extra drivers"); |
||||
sBattery.reset(drivers::Battery::Create()); |
||||
|
||||
// All drivers are now loaded, so we can finish initing the other state
|
||||
// machines.
|
||||
audio::AudioState::Init(sGpioExpander.get(), sDac, sDatabase); |
||||
|
||||
events::Dispatch<BootComplete, SystemState, ui::UiState, audio::AudioState>( |
||||
BootComplete()); |
||||
} |
||||
|
||||
auto Booting::react(const BootComplete& ev) -> void { |
||||
ESP_LOGE(kTag, "bootup completely successfully"); |
||||
// It's possible that the SAMD is currently exposing the SD card as a USB
|
||||
// device. Make sure we don't immediately try to claim it.
|
||||
if (sSamd && sSamd->ReadUsbMscStatus() == |
||||
drivers::Samd::UsbMscStatus::kAttachedMounted) { |
||||
transit<Unmounted>(); |
||||
} |
||||
transit<Running>(); |
||||
} |
||||
|
||||
} // namespace states
|
||||
} // namespace system_fsm
|
@ -0,0 +1,49 @@ |
||||
#pragma once |
||||
|
||||
#include "tinyfsm.hpp" |
||||
|
||||
namespace system_fsm { |
||||
|
||||
struct DisplayReady : tinyfsm::Event {}; |
||||
|
||||
/*
|
||||
* Sent by SysState when the system has finished with its boot and self-test, |
||||
* and is now ready to run normally. |
||||
*/ |
||||
struct BootComplete : tinyfsm::Event {}; |
||||
|
||||
/*
|
||||
* May be sent by any component to indicate that the system has experienced an |
||||
* unrecoverable error. This should be used sparingly, as it essentially brings |
||||
* down the device. |
||||
*/ |
||||
struct FatalError : tinyfsm::Event {}; |
||||
|
||||
/*
|
||||
* Sent before unmounting the system storage. Storage will not be unmounted |
||||
* until each reaction to this even has returned. FSMs should immediately cease |
||||
* their usage of storage. |
||||
* |
||||
* May be emitted either by UiState in response to user action, or by SysState |
||||
* as a part of either entering low-power standby or powering off. |
||||
*/ |
||||
struct StorageUnmountRequested : tinyfsm::Event {}; |
||||
|
||||
/*
|
||||
* Sent by SysState when the system storage has been successfully mounted. |
||||
*/ |
||||
struct StorageMounted : tinyfsm::Event {}; |
||||
|
||||
struct StorageError : tinyfsm::Event {}; |
||||
|
||||
namespace internal { |
||||
|
||||
/*
|
||||
* Sent when the actual unmount operation should be performed. Always dispatched |
||||
* by SysState in response to StoragePrepareToUnmount. |
||||
*/ |
||||
struct ReadyToUnmount : tinyfsm::Event {}; |
||||
|
||||
} // namespace internal
|
||||
|
||||
} // namespace system_fsm
|
@ -0,0 +1,90 @@ |
||||
#pragma once |
||||
|
||||
#include <memory> |
||||
|
||||
#include "battery.hpp" |
||||
#include "dac.hpp" |
||||
#include "database.hpp" |
||||
#include "display.hpp" |
||||
#include "gpio_expander.hpp" |
||||
#include "samd.hpp" |
||||
#include "storage.hpp" |
||||
#include "tinyfsm.hpp" |
||||
#include "touchwheel.hpp" |
||||
|
||||
#include "system_events.hpp" |
||||
|
||||
namespace system_fsm { |
||||
|
||||
/*
|
||||
* State machine for the overall system state. Responsible for managing |
||||
* peripherals, and bringing the rest of the system up and down. |
||||
*/ |
||||
class SystemState : public tinyfsm::Fsm<SystemState> { |
||||
public: |
||||
virtual ~SystemState() {} |
||||
|
||||
virtual void entry() {} |
||||
virtual void exit() {} |
||||
|
||||
/* Fallback event handler. Does nothing. */ |
||||
void react(const tinyfsm::Event& ev) {} |
||||
|
||||
void react(const FatalError&); |
||||
|
||||
virtual void react(const DisplayReady&) {} |
||||
virtual void react(const BootComplete&) {} |
||||
virtual void react(const StorageUnmountRequested&) {} |
||||
virtual void react(const internal::ReadyToUnmount&) {} |
||||
virtual void react(const StorageMounted&) {} |
||||
|
||||
protected: |
||||
static std::shared_ptr<drivers::GpioExpander> sGpioExpander; |
||||
static std::shared_ptr<drivers::Samd> sSamd; |
||||
|
||||
static std::shared_ptr<drivers::TouchWheel> sTouch; |
||||
static std::shared_ptr<drivers::Battery> sBattery; |
||||
static std::shared_ptr<drivers::SdStorage> sStorage; |
||||
static std::shared_ptr<drivers::Display> sDisplay; |
||||
static std::shared_ptr<drivers::AudioDac> sDac; |
||||
static std::shared_ptr<database::Database> sDatabase; |
||||
}; |
||||
|
||||
namespace states { |
||||
|
||||
/*
|
||||
* Initial state. Initialises peripherals, starts up lvgl, checks everything |
||||
* looks good. |
||||
*/ |
||||
class Booting : public SystemState { |
||||
public: |
||||
void entry() override; |
||||
|
||||
void react(const BootComplete&) override; |
||||
using SystemState::react; |
||||
}; |
||||
|
||||
/*
|
||||
* Most common state. Everything is going full bore! |
||||
*/ |
||||
class Running : public SystemState { |
||||
public: |
||||
void entry() override; |
||||
void exit() override; |
||||
|
||||
void react(const StorageUnmountRequested&) override; |
||||
void react(const internal::ReadyToUnmount&) override; |
||||
using SystemState::react; |
||||
}; |
||||
|
||||
class Unmounted : public SystemState {}; |
||||
|
||||
/*
|
||||
* Something unrecoverably bad went wrong. Shows an error (if possible), awaits |
||||
* reboot. |
||||
*/ |
||||
class Error : public SystemState {}; |
||||
|
||||
} // namespace states
|
||||
|
||||
} // namespace system_fsm
|
@ -0,0 +1,54 @@ |
||||
|
||||
#include "result.hpp" |
||||
|
||||
#include "audio_fsm.hpp" |
||||
#include "event_queue.hpp" |
||||
#include "storage.hpp" |
||||
#include "system_events.hpp" |
||||
#include "system_fsm.hpp" |
||||
#include "ui_fsm.hpp" |
||||
|
||||
namespace system_fsm { |
||||
namespace states { |
||||
|
||||
/*
|
||||
* Ensure the storage and database are both available. If either of these fails |
||||
* to open, then we assume it's an issue with the underlying SD card. |
||||
*/ |
||||
void Running::entry() { |
||||
auto storage_res = drivers::SdStorage::Create(sGpioExpander.get()); |
||||
if (storage_res.has_error()) { |
||||
events::Dispatch<StorageError, SystemState, audio::AudioState, ui::UiState>( |
||||
StorageError()); |
||||
return; |
||||
} |
||||
sStorage.reset(storage_res.value()); |
||||
|
||||
auto database_res = database::Database::Open(); |
||||
if (database_res.has_error()) { |
||||
events::Dispatch<StorageError, SystemState, audio::AudioState, ui::UiState>( |
||||
StorageError()); |
||||
return; |
||||
} |
||||
sDatabase.reset(database_res.value()); |
||||
|
||||
events::Dispatch<StorageMounted, SystemState, audio::AudioState, ui::UiState>( |
||||
StorageMounted()); |
||||
} |
||||
|
||||
void Running::exit() { |
||||
sDatabase.reset(); |
||||
sStorage.reset(); |
||||
} |
||||
|
||||
void Running::react(const StorageUnmountRequested& ev) { |
||||
events::Dispatch<internal::ReadyToUnmount, SystemState>( |
||||
internal::ReadyToUnmount()); |
||||
} |
||||
|
||||
void Running::react(const internal::ReadyToUnmount& ev) { |
||||
transit<Unmounted>(); |
||||
} |
||||
|
||||
} // namespace states
|
||||
} // namespace system_fsm
|
@ -0,0 +1,24 @@ |
||||
#include "system_fsm.hpp" |
||||
#include "system_events.hpp" |
||||
|
||||
namespace system_fsm { |
||||
|
||||
std::shared_ptr<drivers::GpioExpander> SystemState::sGpioExpander; |
||||
std::shared_ptr<drivers::Samd> SystemState::sSamd; |
||||
|
||||
std::shared_ptr<drivers::TouchWheel> SystemState::sTouch; |
||||
std::shared_ptr<drivers::Battery> SystemState::sBattery; |
||||
std::shared_ptr<drivers::SdStorage> SystemState::sStorage; |
||||
std::shared_ptr<drivers::Display> SystemState::sDisplay; |
||||
std::shared_ptr<drivers::AudioDac> SystemState::sDac; |
||||
std::shared_ptr<database::Database> SystemState::sDatabase; |
||||
|
||||
void SystemState::react(const FatalError& err) { |
||||
if (!is_in_state<states::Error>()) { |
||||
transit<states::Error>(); |
||||
} |
||||
} |
||||
|
||||
} // namespace system_fsm
|
||||
|
||||
FSM_INITIAL_STATE(system_fsm::SystemState, system_fsm::states::Booting) |
@ -1,5 +1,5 @@ |
||||
idf_component_register( |
||||
SRCS "lvgl_task.cpp" |
||||
SRCS "lvgl_task.cpp" "ui_fsm.cpp" |
||||
INCLUDE_DIRS "include" |
||||
REQUIRES "drivers") |
||||
REQUIRES "drivers" "lvgl" "tinyfsm" "events" "system_fsm" "database") |
||||
target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS}) |
||||
|
@ -0,0 +1,17 @@ |
||||
#pragma once |
||||
|
||||
#include "tinyfsm.hpp" |
||||
|
||||
namespace ui { |
||||
|
||||
// TODO(jacqueline): is this needed? is this good?
|
||||
/*
|
||||
* Event emitted by the main task on heartbeat. |
||||
*/ |
||||
struct OnStorageChange : tinyfsm::Event { |
||||
bool is_mounted; |
||||
}; |
||||
|
||||
struct OnSystemError : tinyfsm::Event {}; |
||||
|
||||
} // namespace ui
|
@ -0,0 +1,61 @@ |
||||
#pragma once |
||||
|
||||
#include <memory> |
||||
|
||||
#include "database.hpp" |
||||
#include "display.hpp" |
||||
#include "storage.hpp" |
||||
#include "tinyfsm.hpp" |
||||
#include "touchwheel.hpp" |
||||
|
||||
#include "system_events.hpp" |
||||
|
||||
namespace ui { |
||||
|
||||
class UiState : public tinyfsm::Fsm<UiState> { |
||||
public: |
||||
static auto Init(drivers::GpioExpander* gpio_expander, |
||||
std::weak_ptr<drivers::TouchWheel> touchwheel, |
||||
std::weak_ptr<drivers::Display> display, |
||||
std::weak_ptr<database::Database> database) -> void; |
||||
|
||||
virtual ~UiState() {} |
||||
|
||||
virtual void entry() {} |
||||
virtual void exit() {} |
||||
|
||||
/* Fallback event handler. Does nothing. */ |
||||
void react(const tinyfsm::Event& ev) {} |
||||
|
||||
virtual void react(const system_fsm::DisplayReady&) {} |
||||
virtual void react(const system_fsm::BootComplete&) {} |
||||
|
||||
protected: |
||||
static drivers::GpioExpander* sGpioExpander; |
||||
static std::weak_ptr<drivers::TouchWheel> sTouchWheel; |
||||
static std::weak_ptr<drivers::Display> sDisplay; |
||||
static std::weak_ptr<database::Database> sDatabase; |
||||
static std::atomic<bool> sTaskQuit; |
||||
}; |
||||
|
||||
namespace states { |
||||
|
||||
class PreBoot : public UiState { |
||||
public: |
||||
void react(const system_fsm::DisplayReady&) override; |
||||
using UiState::react; |
||||
}; |
||||
|
||||
class Splash : public UiState { |
||||
public: |
||||
void react(const system_fsm::BootComplete&) override; |
||||
using UiState::react; |
||||
}; |
||||
|
||||
class Interactive : public UiState {}; |
||||
|
||||
class FatalError : public UiState {}; |
||||
|
||||
} // namespace states
|
||||
|
||||
} // namespace ui
|
@ -0,0 +1,38 @@ |
||||
#include "ui_fsm.hpp" |
||||
#include "display.hpp" |
||||
#include "lvgl_task.hpp" |
||||
#include "system_events.hpp" |
||||
#include "touchwheel.hpp" |
||||
|
||||
namespace ui { |
||||
|
||||
drivers::GpioExpander* UiState::sGpioExpander; |
||||
std::weak_ptr<drivers::TouchWheel> UiState::sTouchWheel; |
||||
std::weak_ptr<drivers::Display> UiState::sDisplay; |
||||
std::weak_ptr<database::Database> UiState::sDatabase; |
||||
std::atomic<bool> UiState::sTaskQuit; |
||||
|
||||
auto UiState::Init(drivers::GpioExpander* gpio_expander, |
||||
std::weak_ptr<drivers::TouchWheel> touchwheel, |
||||
std::weak_ptr<drivers::Display> display, |
||||
std::weak_ptr<database::Database> database) -> void { |
||||
sGpioExpander = gpio_expander; |
||||
sTouchWheel = touchwheel; |
||||
sDisplay = display; |
||||
sDatabase = database; |
||||
} |
||||
|
||||
namespace states { |
||||
|
||||
void PreBoot::react(const system_fsm::DisplayReady& ev) { |
||||
transit<Splash>([&]() { StartLvgl(sTouchWheel, sDisplay, &sTaskQuit); }); |
||||
} |
||||
|
||||
void Splash::react(const system_fsm::BootComplete& ev) { |
||||
transit<Interactive>(); |
||||
} |
||||
|
||||
} // namespace states
|
||||
} // namespace ui
|
||||
|
||||
FSM_INITIAL_STATE(ui::UiState, ui::states::PreBoot) |
Loading…
Reference in new issue