Skip to content

Commit

Permalink
Implement a time counter transform for measuring engine hours
Browse files Browse the repository at this point in the history
  • Loading branch information
mairas committed Sep 21, 2023
1 parent d8d87fa commit 0e63976
Show file tree
Hide file tree
Showing 2 changed files with 216 additions and 0 deletions.
118 changes: 118 additions & 0 deletions examples/time_counter.cpp
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(); }
98 changes: 98 additions & 0 deletions src/sensesp/transforms/time_counter.h
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_

0 comments on commit 0e63976

Please sign in to comment.