Add tinyfsm, start converting core functions to an FSM-based event loop

custom
jacqueline 2 years ago
parent b320a6a863
commit a6ab150405
  1. 1
      lib/tinyfsm/CMakeLists.txt
  2. 21
      lib/tinyfsm/COPYING
  3. 39
      lib/tinyfsm/ChangeLog
  4. 114
      lib/tinyfsm/README.md
  5. 28
      lib/tinyfsm/doc/10-Introduction.md
  6. 68
      lib/tinyfsm/doc/20-Installation.md
  7. 38
      lib/tinyfsm/doc/30-Concepts.md
  8. 244
      lib/tinyfsm/doc/40-Usage.md
  9. 206
      lib/tinyfsm/doc/50-API.md
  10. 26
      lib/tinyfsm/doc/60-Development.md
  11. 9
      lib/tinyfsm/doc/70-License.md
  12. 63
      lib/tinyfsm/examples/api/Makefile
  13. 122
      lib/tinyfsm/examples/api/debugging_switch.cpp
  14. 70
      lib/tinyfsm/examples/api/mealy_machine.cpp
  15. 64
      lib/tinyfsm/examples/api/moore_machine.cpp
  16. 145
      lib/tinyfsm/examples/api/multiple_switch.cpp
  17. 110
      lib/tinyfsm/examples/api/resetting_switch.cpp
  18. 85
      lib/tinyfsm/examples/api/simple_switch.cpp
  19. 98
      lib/tinyfsm/examples/elevator/Makefile
  20. 86
      lib/tinyfsm/examples/elevator/README.md
  21. 115
      lib/tinyfsm/examples/elevator/elevator.cpp
  22. 55
      lib/tinyfsm/examples/elevator/elevator.hpp
  23. 19
      lib/tinyfsm/examples/elevator/fsmlist.hpp
  24. 40
      lib/tinyfsm/examples/elevator/main.cpp
  25. 60
      lib/tinyfsm/examples/elevator/motor.cpp
  26. 50
      lib/tinyfsm/examples/elevator/motor.hpp
  27. 251
      lib/tinyfsm/include/tinyfsm.hpp
  28. 24
      lib/tinyfsm/library.json
  29. 5
      src/app_console/CMakeLists.txt
  30. 41
      src/app_console/app_console.cpp
  31. 8
      src/app_console/include/app_console.hpp
  32. 4
      src/audio/CMakeLists.txt
  33. 51
      src/audio/audio_fsm.cpp
  34. 50
      src/audio/audio_playback.cpp
  35. 4
      src/audio/i2s_audio_output.cpp
  36. 13
      src/audio/include/audio_events.hpp
  37. 61
      src/audio/include/audio_fsm.hpp
  38. 50
      src/audio/include/audio_playback.hpp
  39. 2
      src/audio/include/i2s_audio_output.hpp
  40. 2
      src/drivers/CMakeLists.txt
  41. 2
      src/drivers/display.cpp
  42. 43
      src/drivers/driver_cache.cpp
  43. 1
      src/drivers/include/battery.hpp
  44. 2
      src/drivers/include/display.hpp
  45. 54
      src/drivers/include/driver_cache.hpp
  46. 3
      src/drivers/include/gpio_expander.hpp
  47. 2
      src/drivers/include/samd.hpp
  48. 2
      src/drivers/include/storage.hpp
  49. 1
      src/drivers/include/touchwheel.hpp
  50. 2
      src/drivers/storage.cpp
  51. 5
      src/events/CMakeLists.txt
  52. 23
      src/events/event_queue.cpp
  53. 45
      src/events/include/event_queue.hpp
  54. 4
      src/main/CMakeLists.txt
  55. 114
      src/main/main.cpp
  56. 5
      src/system_fsm/CMakeLists.txt
  57. 88
      src/system_fsm/booting.cpp
  58. 49
      src/system_fsm/include/system_events.hpp
  59. 90
      src/system_fsm/include/system_fsm.hpp
  60. 54
      src/system_fsm/running.cpp
  61. 24
      src/system_fsm/system_fsm.cpp
  62. 4
      src/ui/CMakeLists.txt
  63. 10
      src/ui/include/lvgl_task.hpp
  64. 17
      src/ui/include/ui_events.hpp
  65. 61
      src/ui/include/ui_fsm.hpp
  66. 31
      src/ui/lvgl_task.cpp
  67. 38
      src/ui/ui_fsm.cpp
  68. 1
      tools/cmake/common.cmake

@ -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!
[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](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})

@ -8,7 +8,6 @@
#include <iostream> #include <iostream>
#include <string> #include <string>
#include "audio_playback.hpp"
#include "database.hpp" #include "database.hpp"
#include "esp_console.h" #include "esp_console.h"
#include "esp_log.h" #include "esp_log.h"
@ -18,7 +17,7 @@ namespace console {
static AppConsole* sInstance = nullptr; static AppConsole* sInstance = nullptr;
std::string toSdPath(const std::string& filepath) { std::string toSdPath(const std::string& filepath) {
return std::string(drivers::kStoragePath) + "/" + filepath; return std::string("/") + filepath;
} }
int CmdListDir(int argc, char** argv) { int CmdListDir(int argc, char** argv) {
@ -55,6 +54,7 @@ void RegisterListDir() {
esp_console_cmd_register(&cmd); esp_console_cmd_register(&cmd);
} }
/*
int CmdPlayFile(int argc, char** argv) { int CmdPlayFile(int argc, char** argv) {
static const std::string usage = "usage: play [file]"; static const std::string usage = "usage: play [file]";
if (argc != 2) { if (argc != 2) {
@ -146,6 +146,7 @@ void RegisterAudioStatus() {
.argtable = NULL}; .argtable = NULL};
esp_console_cmd_register(&cmd); esp_console_cmd_register(&cmd);
} }
*/
int CmdDbInit(int argc, char** argv) { int CmdDbInit(int argc, char** argv) {
static const std::string usage = "usage: db_init"; static const std::string usage = "usage: db_init";
@ -154,7 +155,12 @@ int CmdDbInit(int argc, char** argv) {
return 1; return 1;
} }
sInstance->database_->Update(); auto db = sInstance->database_.lock();
if (!db) {
std::cout << "no database open" << std::endl;
return 1;
}
db->Update();
return 0; return 0;
} }
@ -176,15 +182,19 @@ int CmdDbSongs(int argc, char** argv) {
return 1; return 1;
} }
std::unique_ptr<database::Result<database::Song>> res( auto db = sInstance->database_.lock();
sInstance->database_->GetSongs(5).get()); if (!db) {
std::cout << "no database open" << std::endl;
return 1;
}
std::unique_ptr<database::Result<database::Song>> res(db->GetSongs(5).get());
while (true) { while (true) {
for (database::Song s : res->values()) { for (database::Song s : res->values()) {
std::cout << s.tags().title.value_or("[BLANK]") << std::endl; std::cout << s.tags().title.value_or("[BLANK]") << std::endl;
} }
if (res->next_page()) { if (res->next_page()) {
auto continuation = res->next_page().value(); auto continuation = res->next_page().value();
res.reset(sInstance->database_->GetPage(&continuation).get()); res.reset(db->GetPage(&continuation).get());
} else { } else {
break; break;
} }
@ -209,18 +219,22 @@ int CmdDbDump(int argc, char** argv) {
return 1; return 1;
} }
auto db = sInstance->database_.lock();
if (!db) {
std::cout << "no database open" << std::endl;
return 1;
}
std::cout << "=== BEGIN DUMP ===" << std::endl; std::cout << "=== BEGIN DUMP ===" << std::endl;
std::unique_ptr<database::Result<std::string>> res( std::unique_ptr<database::Result<std::string>> res(db->GetDump(5).get());
sInstance->database_->GetDump(5).get());
while (true) { while (true) {
for (std::string s : res->values()) { for (std::string s : res->values()) {
std::cout << s << std::endl; std::cout << s << std::endl;
} }
if (res->next_page()) { if (res->next_page()) {
auto continuation = res->next_page().value(); auto continuation = res->next_page().value();
res.reset( res.reset(db->GetPage<std::string>(&continuation).get());
sInstance->database_->GetPage<std::string>(&continuation).get());
} else { } else {
break; break;
} }
@ -240,9 +254,8 @@ void RegisterDbDump() {
esp_console_cmd_register(&cmd); esp_console_cmd_register(&cmd);
} }
AppConsole::AppConsole(audio::AudioPlayback* playback, AppConsole::AppConsole(std::weak_ptr<database::Database> database)
database::Database* database) : database_(database) {
: playback_(playback), database_(database) {
sInstance = this; sInstance = this;
} }
AppConsole::~AppConsole() { AppConsole::~AppConsole() {
@ -251,10 +264,12 @@ AppConsole::~AppConsole() {
auto AppConsole::RegisterExtraComponents() -> void { auto AppConsole::RegisterExtraComponents() -> void {
RegisterListDir(); RegisterListDir();
/*
RegisterPlayFile(); RegisterPlayFile();
RegisterToggle(); RegisterToggle();
RegisterVolume(); RegisterVolume();
RegisterAudioStatus(); RegisterAudioStatus();
*/
RegisterDbInit(); RegisterDbInit();
RegisterDbSongs(); RegisterDbSongs();
RegisterDbDump(); RegisterDbDump();

@ -2,21 +2,17 @@
#include <memory> #include <memory>
#include "audio_playback.hpp"
#include "console.hpp" #include "console.hpp"
#include "database.hpp" #include "database.hpp"
#include "storage.hpp"
namespace console { namespace console {
class AppConsole : public Console { class AppConsole : public Console {
public: public:
explicit AppConsole(audio::AudioPlayback* playback, explicit AppConsole(std::weak_ptr<database::Database> database);
database::Database* database);
virtual ~AppConsole(); virtual ~AppConsole();
audio::AudioPlayback* playback_; std::weak_ptr<database::Database> database_;
database::Database* database_;
protected: protected:
virtual auto RegisterExtraComponents() -> void; virtual auto RegisterExtraComponents() -> void;

@ -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

@ -20,8 +20,8 @@ static const char* kTag = "I2SOUT";
namespace audio { namespace audio {
I2SAudioOutput::I2SAudioOutput(drivers::GpioExpander* expander, I2SAudioOutput::I2SAudioOutput(drivers::GpioExpander* expander,
std::shared_ptr<drivers::AudioDac> dac) std::weak_ptr<drivers::AudioDac> dac)
: expander_(expander), dac_(std::move(dac)), current_config_() { : expander_(expander), dac_(dac.lock()), current_config_() {
dac_->WriteVolume(127); // for testing dac_->WriteVolume(127); // for testing
dac_->SetSource(buffer()); dac_->SetSource(buffer());
} }

@ -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

@ -18,7 +18,7 @@ namespace audio {
class I2SAudioOutput : public IAudioSink { class I2SAudioOutput : public IAudioSink {
public: public:
I2SAudioOutput(drivers::GpioExpander* expander, I2SAudioOutput(drivers::GpioExpander* expander,
std::shared_ptr<drivers::AudioDac> dac); std::weak_ptr<drivers::AudioDac> dac);
~I2SAudioOutput(); ~I2SAudioOutput();
auto Configure(const StreamInfo::Format& format) -> bool override; auto Configure(const StreamInfo::Format& format) -> bool override;

@ -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})

@ -61,7 +61,7 @@ extern "C" void FlushDataCallback(lv_disp_drv_t* disp_drv,
instance->OnLvglFlush(disp_drv, area, color_map); instance->OnLvglFlush(disp_drv, area, color_map);
} }
auto Display::create(GpioExpander* expander, auto Display::Create(GpioExpander* expander,
const displays::InitialisationData& init_data) const displays::InitialisationData& init_data)
-> Display* { -> Display* {
ESP_LOGI(kTag, "Init I/O pins"); ESP_LOGI(kTag, "Init I/O pins");

@ -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

@ -11,6 +11,7 @@ namespace drivers {
class Battery { class Battery {
public: public:
static auto Create() -> Battery* { return new Battery(); }
Battery(); Battery();
~Battery(); ~Battery();

@ -22,7 +22,7 @@ class Display {
* over SPI. This never fails, since unfortunately these display don't give * over SPI. This never fails, since unfortunately these display don't give
* us back any kind of signal to tell us we're actually using them correctly. * us back any kind of signal to tell us we're actually using them correctly.
*/ */
static auto create(GpioExpander* expander, static auto Create(GpioExpander* expander,
const displays::InitialisationData& init_data) -> Display*; const displays::InitialisationData& init_data) -> Display*;
Display(GpioExpander* gpio, spi_device_handle_t handle); Display(GpioExpander* gpio, spi_device_handle_t handle);

@ -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

@ -5,6 +5,7 @@
#include <atomic> #include <atomic>
#include <functional> #include <functional>
#include <mutex> #include <mutex>
#include <optional>
#include <tuple> #include <tuple>
#include <utility> #include <utility>
@ -28,6 +29,8 @@ namespace drivers {
*/ */
class GpioExpander { class GpioExpander {
public: public:
static auto Create() -> GpioExpander* { return new GpioExpander(); }
GpioExpander(); GpioExpander();
~GpioExpander(); ~GpioExpander();

@ -6,6 +6,8 @@ namespace drivers {
class Samd { class Samd {
public: public:
static auto Create() -> Samd* { return new Samd(); }
Samd(); Samd();
~Samd(); ~Samd();

@ -25,7 +25,7 @@ class SdStorage {
FAILED_TO_MOUNT, FAILED_TO_MOUNT,
}; };
static auto create(GpioExpander* gpio) -> cpp::result<SdStorage*, Error>; static auto Create(GpioExpander* gpio) -> cpp::result<SdStorage*, Error>;
SdStorage(GpioExpander* gpio, SdStorage(GpioExpander* gpio,
esp_err_t (*do_transaction)(sdspi_dev_handle_t, sdmmc_command_t*), esp_err_t (*do_transaction)(sdspi_dev_handle_t, sdmmc_command_t*),

@ -17,6 +17,7 @@ struct TouchWheelData {
class TouchWheel { class TouchWheel {
public: public:
static auto Create() -> TouchWheel* { return new TouchWheel(); }
TouchWheel(); TouchWheel();
~TouchWheel(); ~TouchWheel();

@ -49,7 +49,7 @@ static esp_err_t do_transaction(sdspi_dev_handle_t handle,
} }
} // namespace callback } // namespace callback
auto SdStorage::create(GpioExpander* gpio) -> cpp::result<SdStorage*, Error> { auto SdStorage::Create(GpioExpander* gpio) -> cpp::result<SdStorage*, Error> {
gpio->set_pin(GpioExpander::SD_CARD_POWER_ENABLE, 0); gpio->set_pin(GpioExpander::SD_CARD_POWER_ENABLE, 0);
gpio->set_pin(GpioExpander::SD_MUX_EN_ACTIVE_LOW, 0); gpio->set_pin(GpioExpander::SD_MUX_EN_ACTIVE_LOW, 0);
gpio->set_pin(GpioExpander::SD_MUX_SWITCH, GpioExpander::SD_MUX_ESP); gpio->set_pin(GpioExpander::SD_MUX_SWITCH, GpioExpander::SD_MUX_ESP);

@ -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})

@ -2,16 +2,18 @@
#include <atomic> #include <atomic>
#include <cstdbool> #include <cstdbool>
#include <memory>
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/task.h" #include "freertos/task.h"
#include "driver_cache.hpp" #include "display.hpp"
#include "touchwheel.hpp"
namespace ui { namespace ui {
auto StartLvgl(drivers::DriverCache* drivers, auto StartLvgl(std::weak_ptr<drivers::TouchWheel> touch_wheel,
std::atomic<bool>* quit, std::weak_ptr<drivers::Display> display,
TaskHandle_t* handle) -> bool; std::atomic<bool>* quit) -> bool;
} // namespace ui } // namespace ui

@ -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

@ -23,10 +23,10 @@
#include "misc/lv_color.h" #include "misc/lv_color.h"
#include "misc/lv_style.h" #include "misc/lv_style.h"
#include "misc/lv_timer.h" #include "misc/lv_timer.h"
#include "touchwheel.hpp"
#include "widgets/lv_label.h" #include "widgets/lv_label.h"
#include "display.hpp" #include "display.hpp"
#include "driver_cache.hpp"
#include "gpio_expander.hpp" #include "gpio_expander.hpp"
namespace ui { namespace ui {
@ -38,27 +38,23 @@ auto tick_hook(TimerHandle_t xTimer) -> void {
} }
struct LvglArgs { struct LvglArgs {
drivers::DriverCache* drivers; std::weak_ptr<drivers::TouchWheel> touch_wheel;
std::weak_ptr<drivers::Display> display;
std::atomic<bool>* quit; std::atomic<bool>* quit;
}; };
void LvglMain(void* voidArgs) { void LvglMain(void* voidArgs) {
LvglArgs* args = reinterpret_cast<LvglArgs*>(voidArgs); LvglArgs* args = reinterpret_cast<LvglArgs*>(voidArgs);
drivers::DriverCache* drivers = args->drivers; std::weak_ptr<drivers::TouchWheel> weak_touch_wheel = args->touch_wheel;
std::weak_ptr<drivers::Display> weak_display = args->display;
std::atomic<bool>* quit = args->quit; std::atomic<bool>* quit = args->quit;
delete args; delete args;
{ {
ESP_LOGI(kTag, "init lvgl");
lv_init();
// LVGL has been initialised, so we can now start reporting ticks to it.
TimerHandle_t tick_timer = TimerHandle_t tick_timer =
xTimerCreate("lv_tick", pdMS_TO_TICKS(1), pdTRUE, NULL, &tick_hook); xTimerCreate("lv_tick", pdMS_TO_TICKS(1), pdTRUE, NULL, &tick_hook);
ESP_LOGI(kTag, "init display");
std::shared_ptr<drivers::Display> display = drivers->AcquireDisplay();
lv_style_t style; lv_style_t style;
lv_style_init(&style); lv_style_init(&style);
lv_style_set_text_color(&style, LV_COLOR_MAKE(0xFF, 0, 0)); lv_style_set_text_color(&style, LV_COLOR_MAKE(0xFF, 0, 0));
@ -74,7 +70,9 @@ void LvglMain(void* voidArgs) {
while (!quit->load()) { while (!quit->load()) {
lv_timer_handler(); lv_timer_handler();
vTaskDelay(pdMS_TO_TICKS(10)); // 30 FPS
// TODO(jacqueline): make this dynamic
vTaskDelay(pdMS_TO_TICKS(33));
} }
// TODO(robin? daniel?): De-init the UI stack here. // TODO(robin? daniel?): De-init the UI stack here.
@ -82,8 +80,6 @@ void LvglMain(void* voidArgs) {
lv_style_reset(&style); lv_style_reset(&style);
xTimerDelete(tick_timer, portMAX_DELAY); xTimerDelete(tick_timer, portMAX_DELAY);
lv_deinit();
} }
vTaskDelete(NULL); vTaskDelete(NULL);
@ -93,11 +89,12 @@ static const size_t kLvglStackSize = 8 * 1024;
static StaticTask_t sLvglTaskBuffer = {}; static StaticTask_t sLvglTaskBuffer = {};
static StackType_t sLvglStack[kLvglStackSize] = {0}; static StackType_t sLvglStack[kLvglStackSize] = {0};
auto StartLvgl(drivers::DriverCache* drivers, auto StartLvgl(std::weak_ptr<drivers::TouchWheel> touch_wheel,
std::atomic<bool>* quit, std::weak_ptr<drivers::Display> display,
TaskHandle_t* handle) -> bool { std::atomic<bool>* quit) -> bool {
LvglArgs* args = new LvglArgs(); LvglArgs* args = new LvglArgs();
args->drivers = drivers; args->touch_wheel = touch_wheel;
args->display = display;
args->quit = quit; args->quit = quit;
return xTaskCreateStaticPinnedToCore(&LvglMain, "LVGL", kLvglStackSize, return xTaskCreateStaticPinnedToCore(&LvglMain, "LVGL", kLvglStackSize,

@ -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)

@ -13,6 +13,7 @@ list(APPEND EXTRA_COMPONENT_DIRS "$ENV{PROJ_PATH}/lib/libtags")
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{PROJ_PATH}/lib/lvgl") list(APPEND EXTRA_COMPONENT_DIRS "$ENV{PROJ_PATH}/lib/lvgl")
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{PROJ_PATH}/lib/result") list(APPEND EXTRA_COMPONENT_DIRS "$ENV{PROJ_PATH}/lib/result")
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{PROJ_PATH}/lib/span") list(APPEND EXTRA_COMPONENT_DIRS "$ENV{PROJ_PATH}/lib/span")
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{PROJ_PATH}/lib/tinyfsm")
include($ENV{IDF_PATH}/tools/cmake/project.cmake) include($ENV{IDF_PATH}/tools/cmake/project.cmake)

Loading…
Cancel
Save