Implement OBPRingBuffer class and adjust PageWindPlot accordingly

This commit is contained in:
Ulrich Meine 2025-06-24 00:05:15 +02:00
parent 9ada5be7cb
commit 1f90cefbd6
5 changed files with 424 additions and 11 deletions

View File

@ -1,3 +1,8 @@
{ {
"cmake.configureOnOpen": false "cmake.configureOnOpen": false,
"files.associations": {
"stdexcept": "cpp",
"limits": "cpp",
"functional": "cpp"
}
} }

View File

@ -0,0 +1,52 @@
#pragma once
#include <algorithm>
#include <limits>
#include <stdexcept>
#include <vector>
template <typename T>
class RingBuffer {
private:
std::vector<T> buffer;
size_t capacity;
size_t head; // Points to the next insertion position
size_t first; // Points to the first (oldest) valid element
size_t last; // Points to the last (newest) valid element
size_t count; // Number of valid elements currently in buffer
bool is_Full; // Indicates that all buffer elements are used and ringing is in use
T MIN_VAL; // lowest possible value of buffer
T MAX_VAL; // highest possible value of buffer of type <T>
// metadata for buffer
int updFreq; // Update frequency in milliseconds
int smallest; // Value range of buffer: smallest value
int biggest; // Value range of buffer: biggest value
public:
RingBuffer(size_t size);
void add(const T& value); // Add a new value to buffer
T get(size_t index) const; // Get value at specific position (0-based index from oldest to newest)
T getFirst() const; // Get the first (oldest) value in buffer
T getLast() const; // Get the last (newest) value in buffer
T getMin() const; // Get the lowest value in buffer
T getMin(size_t amount) const; // Get minimum value of the last <amount> values of buffer
T getMax() const; // Get the highest value in buffer
T getMax(size_t amount) const; // Get maximum value of the last <amount> values of buffer
T getMid() const; // Get mid value between <min> and <max> value in buffer
T getMid(size_t amount) const; // Get mid value between <min> and <max> value of the last <amount> values of buffer
T getRng(T center, size_t amount) const; // Get maximum difference of last <amount> of buffer values to center value
T getMedian() const; // Get the median value in buffer
T getMedian(size_t amount) const; // Get the median value of the last <amount> values of buffer
size_t getCapacity() const; // Get the buffer capacity (maximum size)
size_t getCurrentSize() const; // Get the current number of elements in buffer
bool isEmpty() const; // Check if buffer is empty
bool isFull() const; // Check if buffer is full
T getMinVal(); // Get lowest possible value for buffer; used for initialized buffer data
T getMaxVal(); // Get highest possible value for buffer
void clear(); // Clear buffer
T operator[](size_t index) const;
std::vector<T> getAllValues() const; // Operator[] for convenient access (same as get())
};
#include "OBPRingBuffer.tpp"

View File

@ -0,0 +1,356 @@
#include "OBPRingBuffer.h"
template <typename T>
RingBuffer<T>::RingBuffer(size_t size)
: capacity(size)
, head(0)
, first(0)
, last(0)
, count(0)
, is_Full(false)
{
if (size == 0) {
// return false;
}
MIN_VAL = std::numeric_limits<T>::lowest();
MAX_VAL = std::numeric_limits<T>::max();
buffer.resize(size, MIN_VAL);
// return true;
}
// Add a new value to the buffer
template <typename T>
void RingBuffer<T>::add(const T& value)
{
buffer[head] = value;
last = head;
if (is_Full) {
first = (first + 1) % capacity; // Move pointer to oldest element when overwriting
} else {
count++;
if (count == capacity) {
is_Full = true;
}
}
head = (head + 1) % capacity;
}
// Get value at specific position (0-based index from oldest to newest)
template <typename T>
T RingBuffer<T>::get(size_t index) const
{
if (isEmpty()) {
throw std::runtime_error("Buffer is empty");
}
if (index < 0 || index >= count) {
return MIN_VAL;
// throw std::out_of_range("Index out of range");
}
size_t realIndex = (first + index) % capacity;
return buffer[realIndex];
}
// Operator[] for convenient access (same as get())
template <typename T>
T RingBuffer<T>::operator[](size_t index) const
{
return get(index);
}
// Get the first (oldest) value in the buffer
template <typename T>
T RingBuffer<T>::getFirst() const
{
if (isEmpty()) {
return MIN_VAL;
// throw std::runtime_error("Buffer is empty");
}
return buffer[first];
}
// Get the last (newest) value in the buffer
template <typename T>
T RingBuffer<T>::getLast() const
{
if (isEmpty()) {
return MIN_VAL;
// throw std::runtime_error("Buffer is empty");
}
return buffer[last];
}
// Get the lowest value in the buffer
template <typename T>
T RingBuffer<T>::getMin() const
{
if (isEmpty()) {
return MIN_VAL;
// throw std::runtime_error("Buffer is empty");
}
T minVal = get(first);
T value;
for (size_t i = 0; i < count; i++) {
value = get(i);
if (value < minVal) {
minVal = value;
}
}
return minVal;
}
// Get minimum value of the last <amount> values of buffer
template <typename T>
T RingBuffer<T>::getMin(size_t amount) const
{
if (isEmpty() || amount <= 0) {
return MIN_VAL;
// throw std::runtime_error("Buffer is empty");
}
if (amount > count)
amount = count;
T minVal = get(last);
T value;
for (size_t i = 0; i < amount; i++) {
value = get((last + capacity - i) % capacity);
if (value < minVal && value != MIN_VAL) {
minVal = value;
}
}
return minVal;
}
// Get the highest value in the buffer
template <typename T>
T RingBuffer<T>::getMax() const
{
if (isEmpty()) {
return MIN_VAL;
// throw std::runtime_error("Buffer is empty");
}
T maxVal = get(first);
T value;
for (size_t i = 0; i < count; i++) {
value = get(i);
if (value > maxVal) {
maxVal = value;
}
}
return maxVal;
}
// Get maximum value of the last <amount> values of buffer
template <typename T>
T RingBuffer<T>::getMax(size_t amount) const
{
if (isEmpty() || amount <= 0) {
return MIN_VAL;
// throw std::runtime_error("Buffer is empty");
}
if (amount > count)
amount = count;
T maxVal = get(last);
T value;
for (size_t i = 0; i < amount; i++) {
value = get((last + capacity - i) % capacity);
if (value > maxVal && value != MIN_VAL) {
maxVal = value;
}
}
return maxVal;
}
// Get mid value between <min> and <max> value in the buffer
template <typename T>
T RingBuffer<T>::getMid() const
{
if (isEmpty()) {
return MIN_VAL;
// throw std::runtime_error("Buffer is empty");
}
return (getMin() + getMax()) / static_cast<T>(2);
}
// Get mid value between <min> and <max> value of the last <amount> values of buffer
template <typename T>
T RingBuffer<T>::getMid(size_t amount) const
{
if (isEmpty() || amount <= 0) {
return MIN_VAL;
// throw std::runtime_error("Buffer is empty");
}
if (amount > count)
amount = count;
return (getMin(amount) + getMax(amount)) / static_cast<T>(2);
}
// ******************* works for wind direction only -> move out of here *******************************
// Get maximum difference of last <amount> of buffer values to center value
template <typename T>
T RingBuffer<T>::getRng(T center, size_t amount) const
{
if (isEmpty() || amount <= 0) {
return MIN_VAL;
// throw std::runtime_error("Buffer is empty");
}
if (amount > count)
amount = count;
T value = 0;
T rng = 0;
T maxRng = MIN_VAL;
// Start from the newest value (last) and go backwards x times
for (size_t i = 0; i < amount; i++) {
value = get((last + capacity - i) % capacity);
if (value == MIN_VAL) {
continue;
}
rng = abs(((value - center + 540) % 360) - 180);
if (rng > maxRng)
maxRng = rng;
}
if (maxRng > 180) {
maxRng = 180;
}
return maxRng;
}
// Get the median value in the buffer
template <typename T>
T RingBuffer<T>::getMedian() const
{
if (isEmpty()) {
return MIN_VAL;
// throw std::runtime_error("Buffer is empty");
}
// Create a temporary vector with current valid elements
std::vector<T> temp;
temp.reserve(count);
for (size_t i = 0; i < count; i++) {
temp.push_back(get(i));
}
// Sort to find median
std::sort(temp.begin(), temp.end());
if (count % 2 == 1) {
// Odd number of elements
return temp[count / 2];
} else {
// Even number of elements - return average of middle two
// Note: For integer types, this truncates. For floating point, it's exact.
return (temp[count / 2 - 1] + temp[count / 2]) / 2;
}
}
// Get the median value of the last <amount> values of buffer
template <typename T>
T RingBuffer<T>::getMedian(size_t amount) const
{
if (isEmpty() || amount <= 0) {
return MIN_VAL;
// throw std::runtime_error("Buffer is empty");
}
if (amount > count)
amount = count;
// Create a temporary vector with current valid elements
std::vector<T> temp;
temp.reserve(amount);
for (size_t i = 0; i < amount; i++) {
temp.push_back(get(i));
}
// Sort to find median
std::sort(temp.begin(), temp.end());
if (amount % 2 == 1) {
// Odd number of elements
return temp[amount / 2];
} else {
// Even number of elements - return average of middle two
// Note: For integer types, this truncates. For floating point, it's exact.
return (temp[amount / 2 - 1] + temp[amount / 2]) / 2;
}
}
// Get the buffer capacity (maximum size)
template <typename T>
size_t RingBuffer<T>::getCapacity() const
{
return capacity;
}
// Get the current number of elements in the buffer
template <typename T>
size_t RingBuffer<T>::getCurrentSize() const
{
return count;
}
// Check if buffer is empty
template <typename T>
bool RingBuffer<T>::isEmpty() const
{
return count == 0;
}
// Check if buffer is full
template <typename T>
bool RingBuffer<T>::isFull() const
{
return is_Full;
}
// Get lowest possible value for buffer; used for initialized buffer data
template <typename T>
T RingBuffer<T>::getMinVal()
{
return MIN_VAL;
}
// Get highest possible value for buffer
template <typename T>
T RingBuffer<T>::getMaxVal()
{
return MAX_VAL;
}
// Clear buffer
template <typename T>
void RingBuffer<T>::clear()
{
head = 0;
first = 0;
last = 0;
count = 0;
is_Full = false;
}
// Get all current values as a vector
template <typename T>
std::vector<T> RingBuffer<T>::getAllValues() const
{
std::vector<T> result;
result.reserve(count);
for (size_t i = 0; i < count; i++) {
result.push_back(get(i));
}
return result;
}

View File

@ -17,6 +17,7 @@
#include "ObpNmea0183.h" // Check NMEA0183 sentence for uncorrect content #include "ObpNmea0183.h" // Check NMEA0183 sentence for uncorrect content
#include "OBP60Extensions.h" // Lib for hardware extensions #include "OBP60Extensions.h" // Lib for hardware extensions
#include "movingAvg.h" // Lib for moving average building #include "movingAvg.h" // Lib for moving average building
#include "OBPRingBuffer.h" // Lib with ring buffer for history storage of some boat data
#include "time.h" // For getting NTP time #include "time.h" // For getting NTP time
#include <ESP32Time.h> // Internal ESP32 RTC clock #include <ESP32Time.h> // Internal ESP32 RTC clock

View File

@ -3,11 +3,12 @@
#include "BoatDataCalibration.h" #include "BoatDataCalibration.h"
#include "OBP60Extensions.h" #include "OBP60Extensions.h"
#include "Pagedata.h" #include "Pagedata.h"
#include "OBPRingBuffer.h"
#include <vector> #include <vector>
// **************************************************************** // ****************************************************************
class wndHistory { class OldwndHistory {
// provides a circular buffer to store wind history values // provides a circular buffer to store wind history values
private: private:
int SIZE; int SIZE;
@ -273,9 +274,6 @@ public:
GwConfigHandler* config = commonData->config; GwConfigHandler* config = commonData->config;
GwLog* logger = commonData->logger; GwLog* logger = commonData->logger;
static wndHistory windDirHstry; // Circular buffer to store wind direction values
static wndHistory windSpdHstry; // Circular buffer to store wind speed values
GwApi::BoatValue* bvalue; GwApi::BoatValue* bvalue;
const int numCfgValues = 9; const int numCfgValues = 9;
String dataName[numCfgValues]; String dataName[numCfgValues];
@ -325,15 +323,18 @@ public:
int distVals; // helper to check wndCenter crossing int distVals; // helper to check wndCenter crossing
int distMid; // helper to check wndCenter crossing int distMid; // helper to check wndCenter crossing
static RingBuffer<int> windDirHstry(bufSize); // Circular buffer to store wind direction values
static RingBuffer<int>windSpdHstry(bufSize); // Circular buffer to store wind speed values
LOG_DEBUG(GwLog::LOG, "Display page WindPlot"); LOG_DEBUG(GwLog::LOG, "Display page WindPlot");
unsigned long start = millis(); unsigned long start = millis();
// Data initialization // Data initialization
if (windDirHstry.getSize() == 0) { if (windDirHstry.getCurrentSize() == 0) {
if (!windDirHstry.begin(bufSize)) { /* if (!windDirHstry.begin(bufSize)) {
logger->logDebug(GwLog::ERROR, "Failed to initialize wind direction history buffer"); logger->logDebug(GwLog::ERROR, "Failed to initialize wind direction history buffer");
return; return;
} } */
simWnd = 0; simWnd = 0;
simTWS = 0; simTWS = 0;
twdValue = 0; twdValue = 0;
@ -363,7 +364,6 @@ public:
dataName[i] = dataName[i].substring(0, 6); // String length limit for value name dataName[i] = dataName[i].substring(0, 6); // String length limit for value name
calibrationData.calibrateInstance(dataName[i], bvalue, logger); // Check if boat data value is to be calibrated calibrationData.calibrateInstance(dataName[i], bvalue, logger); // Check if boat data value is to be calibrated
dataValue[i] = bvalue->value; // Value as double in SI unit dataValue[i] = bvalue->value; // Value as double in SI unit
calibrationData.calibrateInstance(dataName[i], bvalue, logger); // Check if boat data value is to be calibrated
dataValid[i] = bvalue->valid; dataValid[i] = bvalue->valid;
dataSValue[i] = formatValue(bvalue, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places dataSValue[i] = formatValue(bvalue, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places
dataUnit[i] = formatValue(bvalue, *commonData).unit; dataUnit[i] = formatValue(bvalue, *commonData).unit;
@ -396,7 +396,7 @@ public:
// Identify buffer sizes and buffer position to print on the chart // Identify buffer sizes and buffer position to print on the chart
intvBufSize = cHeight * dataIntv; intvBufSize = cHeight * dataIntv;
count = windDirHstry.getSize(); count = windDirHstry.getCurrentSize();
numWndValues = min(count, intvBufSize); numWndValues = min(count, intvBufSize);
newDate++; newDate++;
if (dataIntv != oldDataIntv) { if (dataIntv != oldDataIntv) {
@ -522,7 +522,6 @@ public:
if (i == (cHeight - 1)) { // Reaching chart area top end () if (i == (cHeight - 1)) { // Reaching chart area top end ()
linesToShow -= min(40, cHeight); // free top 40 lines of chart for new values linesToShow -= min(40, cHeight); // free top 40 lines of chart for new values
bufStart = max(0, count - (linesToShow * dataIntv)); // next start value in buffer to show bufStart = max(0, count - (linesToShow * dataIntv)); // next start value in buffer to show
// windDirHstry.mvStart(); // virtually delete 40 values from buffer
if ((windDirHstry.getMin(numWndValues) > wndCenter) || (windDirHstry.getMax(numWndValues) < wndCenter)) { if ((windDirHstry.getMin(numWndValues) > wndCenter) || (windDirHstry.getMax(numWndValues) < wndCenter)) {
// Check if all wind value are left or right of center value -> optimize chart range // Check if all wind value are left or right of center value -> optimize chart range
int mid = windDirHstry.getMid(numWndValues); int mid = windDirHstry.getMid(numWndValues);