-
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 a time counter transform for measuring engine hours
- Loading branch information
Showing
2 changed files
with
216 additions
and
0 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,118 @@ | ||
/** | ||
* @file constant_sensor.cpp | ||
* @brief Example of a TimeCounter that measures the time its input value is | ||
* true on non-zero. | ||
* | ||
* The main use case for this transform is to measure the total engine hours. | ||
* The value is stored in the flash drive whenever the input state changes to | ||
* false (the engine is turned off). | ||
* | ||
* This example counts the input frequency of GPIO pin 15 and counts the | ||
* time it is non-zero. | ||
* | ||
* GPIO 18 is configured as output. The output frequency is increased every | ||
* 10 seconds. Connect pin 18 to pin 15 to test the example. | ||
* | ||
*/ | ||
|
||
#include "sensesp_app.h" | ||
#include "sensesp/sensors/digital_input.h" | ||
#include "sensesp/transforms/lambda_transform.h" | ||
#include "sensesp/transforms/time_counter.h" | ||
#include "sensesp/transforms/frequency.h" | ||
#include "sensesp_app_builder.h" | ||
|
||
using namespace sensesp; | ||
|
||
reactesp::ReactESP app; | ||
|
||
unsigned long cycle_start_time = 0; | ||
unsigned long freq_start_time = 0; | ||
int freq = 0; | ||
|
||
void setup() { | ||
// Some initialization boilerplate when in debug mode... | ||
#ifndef SERIAL_DEBUG_DISABLED | ||
SetupSerialDebug(115200); | ||
#endif | ||
|
||
// Create the builder object | ||
SensESPAppBuilder builder; | ||
sensesp_app = builder.get_app(); | ||
|
||
// set GPIO 18 to output mode | ||
pinMode(18, OUTPUT); | ||
app.onRepeat(10, []() { | ||
if (freq == 0) { | ||
if (millis() - freq_start_time >= 10000) { | ||
freq = 10; | ||
freq_start_time = millis(); | ||
} else { | ||
return; | ||
} | ||
} else { | ||
if (millis() - freq_start_time >= 1000) { | ||
freq += 10; | ||
freq_start_time = millis(); | ||
} | ||
if (freq > 100) { | ||
freq = 0; | ||
return; | ||
} | ||
|
||
if ((millis() - cycle_start_time) >= 1000 / freq) { | ||
cycle_start_time = millis(); | ||
} else { | ||
return; | ||
} | ||
} | ||
digitalWrite(18, !digitalRead(18)); | ||
}); | ||
|
||
// Create a digital input counter sensor | ||
auto digin_counter = | ||
new DigitalInputCounter(15, INPUT, FALLING, 500, "/Sensors/Counter"); | ||
|
||
// Create a frequency transform | ||
auto* frequency = new Frequency(1, "/Transforms/Frequency"); | ||
digin_counter->connect_to(frequency); | ||
|
||
// create a propulsion state lambda transform | ||
auto* propulsion_state = new LambdaTransform<float, String>( | ||
[](bool freq) { | ||
if (freq > 0) { | ||
return "started"; | ||
} else { | ||
return "stopped"; | ||
} | ||
}, | ||
"/Transforms/Propulsion State"); | ||
|
||
frequency->connect_to(propulsion_state); | ||
|
||
// create engine hours counter using PersistentDuration | ||
auto* engine_hours = | ||
new TimeCounter<float>("/Transforms/Engine Hours"); | ||
|
||
frequency->connect_to(engine_hours); | ||
|
||
// create and connect the frequency output object | ||
frequency->connect_to( | ||
new SKOutput<float>("propulsion.main.revolutions", "", | ||
new SKMetadata("Hz", "Main Engine Revolutions"))); | ||
|
||
// create and connect the propulsion state output object | ||
propulsion_state->connect_to( | ||
new SKOutput<String>("propulsion.main.state", "", | ||
new SKMetadata("", "Main Engine State"))); | ||
|
||
// create and connect the engine hours output object | ||
engine_hours->connect_to( | ||
new SKOutput<float>("propulsion.main.runTime", "", | ||
new SKMetadata("s", "Main Engine running time"))); | ||
|
||
// Start the SensESP application running | ||
sensesp_app->start(); | ||
} | ||
|
||
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,98 @@ | ||
#include "sensesp/transforms/transform.h" | ||
#ifndef SENSESP_TRANSFORMS_TIME_COUNTER_H_ | ||
#define SENSESP_TRANSFORMS_TIME_COUNTER_H_ | ||
|
||
namespace sensesp { | ||
|
||
static const char kTimeCounterSchema[] = R"({ | ||
"type": "object", | ||
"properties": { | ||
"duration": { | ||
"type": "number", | ||
"title": "Total Duration", | ||
"description": "Total accumulated duration while the input state is non-zero or true, in seconds" | ||
} | ||
}, | ||
"required": ["duration"] | ||
})"; | ||
|
||
/** | ||
* @brief A transform that outputs the duration of the input value being | ||
* true or non-null. | ||
* | ||
* The main use case for this transform is to measure the total engine hours | ||
* in a persistent way. The value is stored in the flash drive whenever the | ||
* input state changes (the engine is turned on or off). | ||
* | ||
* @tparam T The type of the input value. Must be castable to a boolean. | ||
*/ | ||
template <typename T> | ||
class TimeCounter : public Transform<T, float> { | ||
public: | ||
TimeCounter(String config_path) | ||
: Transform<T, float>(config_path) { | ||
this->load_configuration(); | ||
} | ||
|
||
virtual void set_input(T input, uint8_t input_channel = 0) override { | ||
if (previous_state_ == -1) { | ||
// Initialize the previous state | ||
previous_state_ = (bool)input; | ||
start_time_ = millis(); | ||
duration_at_start_ = duration_; | ||
} | ||
|
||
// if previous_state_ is true, accumulate duration | ||
if (previous_state_) { | ||
duration_ = duration_at_start_ + (millis() - start_time_); | ||
} | ||
|
||
if (input) { | ||
if (previous_state_ == 0) { | ||
// State change from false to true | ||
previous_state_ = 1; | ||
start_time_ = millis(); | ||
duration_at_start_ = duration_; | ||
} | ||
} else { | ||
if (previous_state_ == 1) { | ||
// State change from true to false | ||
previous_state_ = 0; | ||
duration_ = duration_at_start_ + (millis() - start_time_); | ||
this->save_configuration(); // Save configuration to flash, so that | ||
// the duration is persistent | ||
} | ||
} | ||
this->emit((float)duration_ / 1000.); | ||
} | ||
|
||
virtual void get_configuration(JsonObject& root) override { | ||
root["duration"] = duration_; | ||
} | ||
|
||
virtual bool set_configuration(const JsonObject& config) override { | ||
debugD("Setting TimeCounter configuration"); | ||
if (!config.containsKey("duration")) { | ||
return false; | ||
} | ||
duration_at_start_ = config["duration"]; | ||
duration_ = duration_at_start_; | ||
debugD("duration_at_start_ = %ld", duration_at_start_); | ||
return true; | ||
} | ||
|
||
virtual String get_config_schema() override { | ||
return kTimeCounterSchema; | ||
} | ||
|
||
protected: | ||
int previous_state_ = -1; // -1 means uninitialized | ||
unsigned long start_time_; | ||
unsigned long duration_ = 0.; | ||
unsigned long duration_at_start_ = 0.; | ||
}; | ||
|
||
} // namespace sensesp | ||
|
||
#endif // SENSESP_TRANSFORMS_TIME_COUNTER_H_ |