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( |
idf_component_register( |
||||||
SRCS "audio_decoder.cpp" "audio_task.cpp" "chunk.cpp" "fatfs_audio_input.cpp" |
SRCS "audio_decoder.cpp" "audio_task.cpp" "chunk.cpp" "fatfs_audio_input.cpp" |
||||||
"stream_message.cpp" "i2s_audio_output.cpp" "stream_buffer.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" |
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}) |
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( |
idf_component_register( |
||||||
SRCS "touchwheel.cpp" "dac.cpp" "gpio_expander.cpp" "battery.cpp" "storage.cpp" "i2c.cpp" |
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" |
INCLUDE_DIRS "include" |
||||||
REQUIRES "esp_adc" "fatfs" "result" "lvgl" "span") |
REQUIRES "esp_adc" "fatfs" "result" "lvgl" "span") |
||||||
target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS}) |
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( |
idf_component_register( |
||||||
SRCS "main.cpp" "app_console.cpp" |
SRCS "main.cpp" |
||||||
INCLUDE_DIRS "." |
INCLUDE_DIRS "." |
||||||
REQUIRES "audio" "drivers" "dev_console" "drivers" "database" "ui") |
REQUIRES "audio" "ui" "system_fsm" "events") |
||||||
target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS}) |
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/portmacro.h" |
||||||
#include "freertos/projdefs.h" |
|
||||||
#include "hal/gpio_types.h" |
|
||||||
#include "hal/spi_types.h" |
|
||||||
|
|
||||||
#include "app_console.hpp" |
#include "tinyfsm.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" |
|
||||||
|
|
||||||
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) { |
extern "C" void app_main(void) { |
||||||
ESP_LOGI(TAG, "Initialising peripherals"); |
tinyfsm::FsmList<system_fsm::SystemState, ui::UiState, |
||||||
|
audio::AudioState>::start(); |
||||||
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?"); |
|
||||||
} |
|
||||||
|
|
||||||
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) { |
while (1) { |
||||||
touchwheel->Update(); |
queue.Service(portMAX_DELAY); |
||||||
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)); |
|
||||||
} |
} |
||||||
} |
} |
||||||
|
@ -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( |
idf_component_register( |
||||||
SRCS "lvgl_task.cpp" |
SRCS "lvgl_task.cpp" "ui_fsm.cpp" |
||||||
INCLUDE_DIRS "include" |
INCLUDE_DIRS "include" |
||||||
REQUIRES "drivers") |
REQUIRES "drivers" "lvgl" "tinyfsm" "events" "system_fsm" "database") |
||||||
target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS}) |
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