-
Notifications
You must be signed in to change notification settings - Fork 81
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement different Repeat transforms
- Loading branch information
Showing
4 changed files
with
329 additions
and
94 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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_ |
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.