Skip to content

Commit

Permalink
Merge pull request #29 from mairas/rename_classes
Browse files Browse the repository at this point in the history
Rename classes
  • Loading branch information
mairas authored Sep 9, 2024
2 parents 91454cb + 13a0e7d commit d297730
Show file tree
Hide file tree
Showing 7 changed files with 256 additions and 287 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/*
.history
.history
managed_components
60 changes: 33 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@

By [Matti Airas](https://github.com/mairas)

An asynchronous programming library for the ESP32 and other microcontrollers using the Arduino framework.
An asynchronous programming and event loop library for the ESP32 and other microcontrollers using the Arduino framework.

The library is at the core of the [SensESP](https://github.com/SignalK/SensESP) project but is completely generic and can be used for standalone projects without issues.

This library gets much of its inspiration (and some code) from [`Reactduino`](https://github.com/Reactduino/Reactduino). `ReactESP`, however, has been internally re-implemented for maintainability and readability, and has significantly better performance when there are lots of defined reactions. It also supports arbitrary callables as callbacks, allowing parametric creation of callback functions.
This library gets much of its inspiration (and some code) from [`Reactduino`](https://github.com/Reactduino/Reactduino). `ReactESP`, however, has been internally re-implemented for maintainability and readability, and has significantly better performance when there are lots of defined events. It also supports arbitrary callables as callbacks, allowing parametric creation of callback functions.

## Blink

Expand Down Expand Up @@ -37,25 +37,25 @@ Using ReactESP, the sketch can be rewritten to the following:

using namespace reactesp;

ReactESP app;
EventLoop event_loop;

setup() {
pinMode(LED_BUILTIN, OUTPUT);

app.onRepeat(1000, [] () {
event_loop.onRepeat(1000, [] () {
static bool state = false;
digitalWrite(LED_BUILTIN, state = !state);
});
}

void loop() {
app.tick();
event_loop.tick();
}
```

Instead of directly defining the program logic in the `loop()` function, _reactions_ are defined in the `setup()` function. A reaction is a function that is executed when a certain event happens. In this case, the event is that the function should repeat every second, as defined by the `onRepeat()` method call. The second argument to the `onRepeat()` method is a [lambda function](http://en.cppreference.com/w/cpp/language/lambda) that is executed every time the reaction is triggered. If the syntax feels weird, you can also use regular named functions instead of lambdas.
Instead of directly defining the program logic in the `loop()` function, _events_ are defined in the `setup()` function. An event is a function that is executed when a certain event happens. In this case, the event is that the function should repeat every second, as defined by the `onRepeat()` method call. The second argument to the `onRepeat()` method is a [lambda function](http://en.cppreference.com/w/cpp/language/lambda) that is executed every time the event is triggered. If the syntax feels weird, you can also use regular named functions instead of lambdas.

The `app.tick()` call in the `loop()` function is the main loop of the program. It is responsible for calling the reactions that have been defined. You can also add additional code to the `loop()` function, any delays or other long-executing code should be avoided.
The `event_loop.tick()` call in the `loop()` function is the main loop of the program. It is responsible for calling the events that have been defined. You can also add additional code to the `loop()` function, any delays or other long-executing code should be avoided.

## Why Bother?

Expand Down Expand Up @@ -146,36 +146,36 @@ This solves Charlie's problem, but it's quite verbose. Using ReactESP, Charlie c

using namespace reactesp;

ReactESP app;
EventLoop event_loop;

void setup() {
Serial.begin(9600);
pinMode(LED_BUILTIN, OUTPUT);

app.onAvailable(&Serial, [] () {
event_loop.onAvailable(&Serial, [] () {
Serial.write(Serial.read());
digitalWrite(LED_BUILTIN, HIGH);

app.onDelay(1000, [] () { digitalWrite(LED_BUILTIN, LOW); });
event_loop.onDelay(1000, [] () { digitalWrite(LED_BUILTIN, LOW); });
});
}

void loop() {
app.tick();
event_loop.tick();
}
```

## Advanced callback support

Callbacks can be not just void pointers but any callable supported by `std::function`. This means they can use lambda captures or argument binding using `std::bind`. For example, the following code creates 20 different repeating reactions updating different fields of an array:
Callbacks can be not just void pointers but any callable supported by `std::function`. This means they can use lambda captures or argument binding using `std::bind`. For example, the following code creates 20 different repeating events updating different fields of an array:

```c++
static int timer_ticks[20];

for (int i=0; i<20; i++) {
timer_ticks[i] = 0;
int delay = (i+1)*(i+1);
app.onRepeat(delay, [i]() {
event_loop.onRepeat(delay, [i]() {
timer_ticks[i]++;
});
}
Expand All @@ -194,66 +194,72 @@ This can be done either globally by placing the following statement in the sourc

using namespace reactesp;

or by using the `reactesp::` prefix when using the library:
This shouldn't be done in header files, however! Alternatively, the `reactesp::` prefix can be used when using the library:

reactesp::ReactESP app;
reactesp::EventLoop event_loop;

### Event Registration Functions

All of the registration functions return a `Reaction` object pointer. This can be used to store and manipulate
the reaction. `react_callback` is a typedef for `std::function<void()>` and can therefore be any callable supported by the C++ standard template library.
All of the registration functions return an `Event` object pointer. This can be used to store and manipulate
the event. `react_callback` is a typedef for `std::function<void()>` and can therefore be any callable supported by the C++ standard template library.

```cpp
DelayReaction app.onDelay(uint32_t t, react_callback cb);
DelayEvent event_loop.onDelay(uint32_t t, react_callback cb);
```

Delay the executation of a callback by `t` milliseconds.

```cpp
RepeatReaction app.onRepeat(uint32_t t, react_callback cb);
RepeatEvent event_loop.onRepeat(uint32_t t, react_callback cb);
```

Repeatedly execute a callback every `t` milliseconds.

```cpp
StreamReaction app.onAvailable(Stream *stream, react_callback cb);
StreamEvent event_loop.onAvailable(Stream *stream, react_callback cb);
```

Execute a callback when there is data available to read on an input stream (such as `&Serial`).

```cpp
ISRReaction app.onInterrupt(uint8_t pin_number, int mode, react_callback cb);
ISREvent event_loop.onInterrupt(uint8_t pin_number, int mode, react_callback cb);
```

Execute a callback when an interrupt number fires. This uses the same API as the `attachInterrupt()` Arduino function.

```cpp
TickReaction app.onTick(react_callback cb);
TickEvent event_loop.onTick(react_callback cb);
```

Execute a callback on every tick of the event loop.

### Management functions

```cpp
void Reaction::remove();
void Event::remove();
```

Remove the reaction from the execution queue.
Remove the event from the execution queue.

*Note*: Calling `remove()` for `DelayReaction` objects is only safe if the reaction has not been triggered yet. Upon triggering, the `DelayReaction` object is deleted and any pointers to it will be invalidated.
*Note*: Calling `remove()` for `DelayEvent` objects is only safe if the event has not been triggered yet. Upon triggering, the `DelayEvent` object is deleted and any pointers to it will be invalidated.

### Examples

- [`Minimal`](examples/minimal/src/main.cpp): A minimal example with two timers switching the LED state.

- [`Torture test`](examples/torture_test/src/main.cpp): A stress test of twenty simultaneous repeat reactions as well as a couple of interrupts, a stream, and a tick reaction. For kicks, try changing `NUM_TIMERS` to 200. Program performance will be practically unchanged!
- [`Torture test`](examples/torture_test/src/main.cpp): A stress test of twenty simultaneous repeat events as well as a couple of interrupts, a stream, and a tick event. For kicks, try changing `NUM_TIMERS` to 200. Program performance will be practically unchanged!

## Changes between version 2 and 3

- Renamed classes from ReactESP to EventLoop and from *Reaction to *Event to better
reflect their purpose.
- Removed the `app` singleton. The user is now responsible for maintaining the EventLoop object.

## Changes between version 1 and 2

ReactESP version 2 has changed the software initialization approach from version 1.
Version 1 implemented the Arduino framework standard `setup()` and `loop()` functions behind the scenes,
and a user just instantiated a ReactESP object and provided a setup function as an argument:
and a user just instantiated an EventLoop object and provided a setup function as an argument:

ReactESP app([]() {
app.onDelay(...);
Expand Down
14 changes: 7 additions & 7 deletions examples/minimal/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,29 @@ using namespace reactesp;

int led_state = 0;

ReactESP app;
EventLoop event_loop;

void setup() {
Serial.begin(115200);
Serial.println("Starting");
pinMode(LED_PIN, OUTPUT);
Serial.println("Setting up timed reactions");

Serial.println("Setting up timed events");

// toggle LED every 400 ms
app.onRepeat(400, [] () {
event_loop.onRepeat(400, [] () {
led_state = !led_state;
digitalWrite(LED_PIN, led_state);
});

// Additionally, toggle LED every 1020 ms.
// This adds an irregularity to the LED blink pattern.
app.onRepeat(1020, [] () {
event_loop.onRepeat(1020, [] () {
led_state = !led_state;
digitalWrite(LED_PIN, led_state);
});
});
}

void loop() {
app.tick();
event_loop.tick();
}
56 changes: 28 additions & 28 deletions examples/torture_test/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ using namespace reactesp;
int tick_counter = 0;
int timer_ticks[NUM_TIMERS];

ReactESP app;
EventLoop event_loop;

void reporter() {
Serial.printf("Timer ticks: ");
Expand All @@ -28,30 +28,30 @@ void reporter() {
tick_counter = 0;
}

void setup_timers(ReactESP &app) {
void setup_timers(EventLoop &event_loop) {
// create twenty timers

for (int i=0; i<NUM_TIMERS; i++) {
timer_ticks[i] = 0;
int delay = (i+1)*(i+1);
app.onRepeat(delay, [i]() {
event_loop.onRepeat(delay, [i]() {
timer_ticks[i]++;
});
}

// create one more timer to report the counted ticks

app.onRepeat(1000, reporter);
event_loop.onRepeat(1000, reporter);
}

void setup_io_pins(ReactESP &app) {
static ISRReaction* ire2 = nullptr;
void setup_io_pins(EventLoop &event_loop) {
static ISREvent* ire2 = nullptr;
static int out_pin_state = 0;


// change OUT_PIN state every 900 ms
pinMode(OUT_PIN, OUTPUT);
app.onRepeat(900, [] () {
event_loop.onRepeat(900, [] () {
out_pin_state = !out_pin_state;
digitalWrite(OUT_PIN, out_pin_state);
});
Expand All @@ -61,43 +61,43 @@ void setup_io_pins(ReactESP &app) {
};

// create an interrupt that always reports if PIN1 is rising
app.onInterrupt(INPUT_PIN1, RISING, std::bind(reporter, INPUT_PIN1));
event_loop.onInterrupt(INPUT_PIN1, RISING, std::bind(reporter, INPUT_PIN1));

// every 9s, toggle reporting PIN2 falling edge
app.onRepeat(9000, [&app, &reporter]() {
event_loop.onRepeat(9000, [&event_loop, &reporter]() {
if (ire2==nullptr) {
ire2 = app.onInterrupt(INPUT_PIN2, FALLING, std::bind(reporter, INPUT_PIN2));
ire2 = event_loop.onInterrupt(INPUT_PIN2, FALLING, std::bind(reporter, INPUT_PIN2));
} else {
ire2->remove();
ire2 = nullptr;
}
});

}

void setup_serial(ReactESP &app) {
void setup_serial(EventLoop &event_loop) {
// if something is received on the serial port, turn the led off for one second
app.onAvailable(Serial, [&app] () {
static int reaction_counter = 0;
event_loop.onAvailable(Serial, [&event_loop] () {
static int event_counter = 0;

Serial.write(Serial.read());
digitalWrite(LED_PIN, HIGH);

reaction_counter++;
event_counter++;

int current = reaction_counter;
int current = event_counter;

app.onDelay(1000, [current] () {
if (reaction_counter==current) {
digitalWrite(LED_PIN, LOW);
event_loop.onDelay(1000, [current] () {
if (event_counter==current) {
digitalWrite(LED_PIN, LOW);
}
});
});
}

void setup_tick(ReactESP &app) {
void setup_tick(EventLoop &event_loop) {
// increase the tick counter on every tick
app.onTick([]() {
event_loop.onTick([]() {
tick_counter++;
});
}
Expand All @@ -106,13 +106,13 @@ void setup() {
Serial.begin(115200);
Serial.println("Starting");
pinMode(LED_PIN, OUTPUT);
setup_timers(app);
setup_io_pins(app);
setup_serial(app);
setup_tick(app);

setup_timers(event_loop);
setup_io_pins(event_loop);
setup_serial(event_loop);
setup_tick(event_loop);
}

void loop() {
app.tick();
event_loop.tick();
}
5 changes: 2 additions & 3 deletions platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
; framework = arduino

[platformio]
default_envs =
default_envs =
esp32dev

[env]
Expand All @@ -39,12 +39,11 @@ build_flags =
-D LED_BUILTIN=2
board_build.f_cpu = 160000000L
upload_resetmethod = nodemcu
upload_speed = 460800
upload_speed = 460800

[espressif32_base]
;this section has config items common to all ESP32 boards
platform = espressif32
build_unflags = -Werror=reorder
board_build.partitions = min_spiffs.csv
monitor_filters = esp32_exception_decoder

Expand Down
Loading

0 comments on commit d297730

Please sign in to comment.