Skip to content

Commit

Permalink
Implement different Repeat transforms
Browse files Browse the repository at this point in the history
  • Loading branch information
mairas committed Jun 6, 2024
1 parent fd25416 commit 5bbd746
Show file tree
Hide file tree
Showing 4 changed files with 329 additions and 94 deletions.
143 changes: 143 additions & 0 deletions examples/repeat_transform.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/**
* @file repeat_transform.cpp
* @brief Example of different Repeat transforms.
*
* Repeat transforms transmit their input value at regular intervals, ensuring
* output even at the absence of input.
*
* Try running this code with the serial monitor open ("Upload and Monitor"
* in PlatformIO menu). The program will produce capital letters every second,
* lowercase letters every 3 seconds, and integers every 10 seconds. Comment
* out and enable different Repeat transform variants to see how they affect the
* output.
*
*/

#include "sensesp.h"

#include <math.h>

#include "sensesp/sensors/sensor.h"
#include "sensesp/system/lambda_consumer.h"
#include "sensesp/transforms/repeat.h"
#include "sensesp_minimal_app_builder.h"

using namespace sensesp;

ReactESP app;

SensESPMinimalApp* sensesp_app;

void setup() {
SetupLogging();

// Note: SensESPMinimalAppBuilder is used to build the app. This creates
// a minimal app with no networking or other bells and whistles which
// would be distracting in this example. In normal use, this is not what
// you would use. Unless, of course, you know that is what you want.
SensESPMinimalAppBuilder builder;

sensesp_app = builder.get_app();

// Produce capital letters every second
auto sensor_A = new RepeatSensor<char>(1000, []() {
static char value = 'Z';
if (value == 'Z') {
value = 'A';
} else {
value += 1;
}
return value;
});

sensor_A->connect_to(new LambdaConsumer<char>(
[](char value) { ESP_LOGD("App", " %c", value); }));

// Produce lowercase letters every 3 seconds
auto sensor_a = new RepeatSensor<char>(3000, []() {
static char value = 'z';
if (value == 'z') {
value = 'a';
} else {
value += 1;
}
return value;
});

sensor_a->connect_to(new LambdaConsumer<char>(
[](char value) { ESP_LOGD("App", " %c", value); }));

// Produce integers every 10 seconds
auto sensor_int = new RepeatSensor<int>(10000, []() {
static int value = 0;
value += 1;
return value;
});

sensor_int->connect_to(new LambdaConsumer<int>(
[](int value) { ESP_LOGD("App", " %d", value); }));

// Repeat the values every 2 seconds

auto repeat_A = new Repeat<char>(2000);
auto repeat_a = new Repeat<char>(2000);
auto repeat_int = new Repeat<int>(2000);

// Pay attention to the individual columns of the program console output.
// Capital letters are produced every second. Repeat gets always triggered as
// a result, but never on its own because the repeat timer always is reset
// before it expires. Lowercase letters are produced every 3 seconds. Repeat
// gets triggered immediately and then again after 2 seconds. Integers are
// produced every 10 seconds. Repeat gets triggered immediately and then
// again after every 2 seconds until the next integer is produced.

// Try commenting out the Repeat lines above and uncommenting the
// RepeatStopping lines below.

// auto repeat_A = new RepeatStopping<char>(2000, 5000);
// auto repeat_a = new RepeatStopping<char>(2000, 5000);
// auto repeat_int = new RepeatStopping<int>(2000, 5000);

// The maximum age is set to 5 seconds. Both the capital and lowercase
// letters are produced like before because their repetition rates are
// faster than the expiration time. However, the integers are produced
// only every 10 seconds, and they stop being repeated after 5 seconds
// until a new integer is produced.

// auto repeat_A = new RepeatExpiring<char>(2000, 5000, '?');
// auto repeat_a = new RepeatExpiring<char>(2000, 5000, '?');
// auto repeat_int = new RepeatExpiring<int>(2000, 5000, -1);

// The expiration time is set to 5 seconds. Both the capital and lowercase
// letters are produced like before because their repetition rates are
// faster than the expiration time. However, the integers are produced
// only every 10 seconds, and the value does expire after 5 seconds, indicated
// by the -1 value that gets output after the expiry, until a new integer is
// produced.

// Finally, try commenting out the RepeatExpiring lines above and uncommenting
// the RepeatConstantRate lines below.

// auto repeat_A = new RepeatConstantRate<char>(2000, 5000, '?');
// auto repeat_a = new RepeatConstantRate<char>(2000, 5000, '?');
// auto repeat_int = new RepeatConstantRate<int>(2000, 5000, -1);

// Notice how the repetitions are no longer triggered by the sensors but
// are produced at a constant rate, in clusters. The letters still
// never expire, but the integers do.

sensor_A->connect_to(repeat_A);
sensor_a->connect_to(repeat_a);
sensor_int->connect_to(repeat_int);

repeat_A->connect_to(new LambdaConsumer<char>(
[](char value) { ESP_LOGD("App", "Repeat: %c", value); }));

repeat_a->connect_to(new LambdaConsumer<char>(
[](char value) { ESP_LOGD("App", "Repeat: %c", value); }));

repeat_int->connect_to(new LambdaConsumer<int>(
[](int value) { ESP_LOGD("App", "Repeat: %d", value); }));
}

void loop() { app.tick(); }
180 changes: 180 additions & 0 deletions src/sensesp/transforms/repeat.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
#ifndef SENSESP_SRC_SENSESP_TRANSFORMS_REPEAT_H_
#define SENSESP_SRC_SENSESP_TRANSFORMS_REPEAT_H_

#include <elapsedMillis.h>

#include "transform.h"

namespace sensesp {

/**
* @brief Repeat the input at specific intervals.
*
* Ensures that values that do not change frequently are still
* reported at a specified interval. If the value has not
* changed in interval milliseconds, the current value is emmitted
* again.
*
* The repetition only occurs if the value has changed.
*
* @param interval Maximum time, in ms, before the previous value
* is emitted again.
*
* @param config_path Path to configure this transform in the Config UI.
*/
template <typename T>
class Repeat : public SymmetricTransform<T> {
public:
Repeat(long interval) : SymmetricTransform<T>(), interval_{interval} {}

void set(T input, uint8_t inputChannel = 0) override {
this->emit(input);
if (repeat_reaction_ != nullptr) {
// Delete the old repeat reaction
repeat_reaction_->remove();
}
repeat_reaction_ =
ReactESP::app->onRepeat(interval_, [this]() { this->notify(); });
}

protected:
long interval_;
RepeatReaction* repeat_reaction_ = nullptr;
};

// For compatibility with the old RepeatReport class
template <typename T>
class RepeatReport : public Repeat<T> {};

/**
* @brief Repeat transform that stops emitting if the value age exceeds
* max_age.
*
* @tparam T
*/
template <typename T>
class RepeatStopping : public Repeat<T> {
public:
RepeatStopping(long interval, long max_age)
: Repeat<T>(interval), max_age_{max_age} {
age_ = max_age;

if (this->repeat_reaction_ != nullptr) {
// Delete the old repeat reaction
this->repeat_reaction_->remove();
}
this->repeat_reaction_ = ReactESP::app->onRepeat(
this->interval_, [this]() { this->repeat_function(); });
}

virtual void set(T input, uint8_t inputChannel = 0) override {
this->emit(input);
age_ = 0;
if (this->repeat_reaction_ != nullptr) {
// Delete the old repeat reaction
this->repeat_reaction_->remove();
}
this->repeat_reaction_ = ReactESP::app->onRepeat(
this->interval_, [this]() { this->repeat_function(); });
}

protected:
elapsedMillis age_;
long max_age_;

protected:
void repeat_function() {
if (age_ < max_age_) {
this->notify();
} else {
if (this->repeat_reaction_ != nullptr) {
// Delete the old repeat reaction
this->repeat_reaction_->remove();
this->repeat_reaction_ = nullptr;
}
}
};
};


/**
* @brief Repeat transform that emits an expired value if the value age exceeds
* max_age.
*
* @tparam T
*/
template <typename T>
class RepeatExpiring : public Repeat<T> {
public:
RepeatExpiring(long interval, long max_age, T expired_value)
: Repeat<T>(interval), max_age_{max_age}, expired_value_{expired_value} {
age_ = max_age;

if (this->repeat_reaction_ != nullptr) {
// Delete the old repeat reaction
this->repeat_reaction_->remove();
}
this->repeat_reaction_ = ReactESP::app->onRepeat(
this->interval_, [this]() { this->repeat_function(); });
}

virtual void set(T input, uint8_t inputChannel = 0) override {
this->emit(input);
age_ = 0;
if (this->repeat_reaction_ != nullptr) {
// Delete the old repeat reaction
this->repeat_reaction_->remove();
}
this->repeat_reaction_ = ReactESP::app->onRepeat(
this->interval_, [this]() { this->repeat_function(); });
}

protected:
elapsedMillis age_;
long max_age_;
T expired_value_;

protected:
void repeat_function() {
if (age_ < max_age_) {
this->notify();
} else {
this->emit(expired_value_);
}
};
};

/**
* @brief Repeat transform that emits the last value at a constant interval.
*
* The last value is emitted at a constant interval, regardless of whether the
* value has changed or not. If the value age exceeds max_age, expired_value is
* emitted.
*
* This is particularly useful for outputs that expect a value at a constant
* rate such as NMEA 2000.
*
*/
template <typename T>
class RepeatConstantRate : public RepeatExpiring<T> {
public:
RepeatConstantRate(long interval, long max_age, T expired_value)
: RepeatExpiring<T>(interval, max_age, expired_value) {
if (this->repeat_reaction_ != nullptr) {
// Delete the old repeat reaction
this->repeat_reaction_->remove();
}

this->repeat_reaction_ = ReactESP::app->onRepeat(
interval, [this]() { this->repeat_function(); });
}

void set(T input, uint8_t inputChannel = 0) override {
this->output = input;
this->age_ = 0;
}
};

} // namespace sensesp

#endif // SENSESP_SRC_SENSESP_TRANSFORMS_REPEAT_H_
46 changes: 0 additions & 46 deletions src/sensesp/transforms/repeat_report.cpp

This file was deleted.

Loading

0 comments on commit 5bbd746

Please sign in to comment.