Measurement averaging code with fixed time windows for microcontrollers (C++)
This class performs averaging with a fixed time window (variable number of values).
From the platform, it only needs some method to get the current time. This code contains implementations for both ESP-IDF and Arduino
#pragma once
#include <optional>
// Find the correct driver
#ifdef ARDUINO
#include <Arduino.h>
#define TTAA_GETTIME_MILLIS millis()
#elif defined(IDF_VER) && !defined(ARDUINO)
#include <esp_timer.h>
#define TTAA_GETTIME_MILLIS esp_timer_get_time() / 1000
#else
#error "Could not determine how to get the current time (Arduino or ESP-IDF)"
#endif
/**
* @brief This class is designed to accumulate and average values over a specified time threshold.
*
* The class is templated to allow accumulation of different numerical types, defaulting to float. It uses the Arduino `millis()`
* function to track elapsed time, automatically calculating the average of accumulated values once the predefined time threshold is reached.
* The `add()` method adds a new value to the accumulation, returning the average as a `std::optional<T>` when the threshold is reached,
* and `std::nullopt` otherwise. The `lastAverage()` method allows retrieval of the last calculated average at any time.
*/
template<typename T = float>
class TimeThresholdAccumulatingAverager {
public:
/**
* Constructs a new TimeThresholdAccumulatingAverager object.
*
* @param threshold The time threshold in milliseconds over which to accumulate and average values.
*/
TimeThresholdAccumulatingAverager(unsigned long threshold) : _threshold(threshold), _startTime(TTAA_GETTIME_MILLIS), _sum(), _count() {}
/**
* Adds a value to the accumulator. If the time threshold has been reached,
* calculates and returns the average of the accumulated values since the last threshold reset.
* Resets the accumulator for the next period if the threshold is reached.
*
* The given value is not added to the current average if the threshold has been
* exceeded. It is automatically added to the next period's average.
*
* @param value The value to add to the accumulator.
* @return std::optional<T> The average of accumulated values if the threshold is reached, otherwise std::nullopt.
*/
std::optional<T> add(T value) {
unsigned long currentTime = TTAA_GETTIME_MILLIS;
if (currentTime - _startTime >= _threshold) {
// Time threshold exceeded. Calculcate average
T average = _count > 0 ? _sum / static_cast<T>(_count) : 0;
_lastCount = _count;
// Reset sum & count to zero, but add the current value immediatey
_sum = value;
_count = 1;
_startTime = currentTime;
_lastAverage = average;
return average;
} else { // Time threshold not exceeded
_sum += value;
_count++;
return std::nullopt;
}
}
/**
* Returns the average of the last completed accumulation period.
*
* @return T The average value of the last completed period.
*/
inline T lastAverage() const {
return _lastAverage;
}
/**
* Returns the count value of the last completed accumulation period.
*
* @return The last count value.
*/
inline unsigned int lastCount() const {
return _lastCount;
}
private:
unsigned long _threshold;
unsigned long _startTime;
T _sum;
int _count = 0;
T _lastAverage = 0;
unsigned int _lastCount = 0;
};
Usage example:
#include <optional>
#include <iostream>
#include "TimeThresholdAccumulatingAverager.hpp"
int main() {
TimeThresholdAccumulatingAverager<float> averager(1000); // 1 second threshold
while (true) {
float newValue = acquireValue(); // Pseudocode for acquiring a new value
auto maybeAverage = averager.add(newValue);
if (maybeAverage.has_value()) {
std::cout << "Average over threshold period: " << average.value() << std::endl;
}
// Add a delay or wait for a real new value in a real application
delay(10);
}
}
If this post helped you, please consider buying me a coffee or donating via PayPal to support research & publishing of new posts on TechOverflow