diff --git a/.vscode/settings.json b/.vscode/settings.json index cad7657..2ef566a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,8 @@ { - "cmake.configureOnOpen": false + "cmake.configureOnOpen": false, + "files.associations": { + "stdexcept": "cpp", + "limits": "cpp", + "functional": "cpp" + } } \ No newline at end of file diff --git a/lib/obp60task/OBPRingBuffer.h b/lib/obp60task/OBPRingBuffer.h new file mode 100644 index 0000000..d373b0c --- /dev/null +++ b/lib/obp60task/OBPRingBuffer.h @@ -0,0 +1,52 @@ +#pragma once +#include +#include +#include +#include + +template +class RingBuffer { +private: + std::vector 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 + + // 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 values of buffer + T getMax() const; // Get the highest value in buffer + T getMax(size_t amount) const; // Get maximum value of the last values of buffer + T getMid() const; // Get mid value between and value in buffer + T getMid(size_t amount) const; // Get mid value between and value of the last values of buffer + T getRng(T center, size_t amount) const; // Get maximum difference of last 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 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 getAllValues() const; // Operator[] for convenient access (same as get()) + +}; + +#include "OBPRingBuffer.tpp" \ No newline at end of file diff --git a/lib/obp60task/OBPRingBuffer.tpp b/lib/obp60task/OBPRingBuffer.tpp new file mode 100644 index 0000000..b966bec --- /dev/null +++ b/lib/obp60task/OBPRingBuffer.tpp @@ -0,0 +1,356 @@ +#include "OBPRingBuffer.h" + +template +RingBuffer::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::lowest(); + MAX_VAL = std::numeric_limits::max(); + buffer.resize(size, MIN_VAL); + + // return true; +} + +// Add a new value to the buffer +template +void RingBuffer::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 +T RingBuffer::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 +T RingBuffer::operator[](size_t index) const +{ + return get(index); +} + +// Get the first (oldest) value in the buffer +template +T RingBuffer::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 +T RingBuffer::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 +T RingBuffer::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 values of buffer +template +T RingBuffer::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 +T RingBuffer::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 values of buffer +template +T RingBuffer::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 and value in the buffer +template +T RingBuffer::getMid() const +{ + if (isEmpty()) { + return MIN_VAL; + // throw std::runtime_error("Buffer is empty"); + } + + return (getMin() + getMax()) / static_cast(2); +} + +// Get mid value between and value of the last values of buffer +template +T RingBuffer::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(2); +} + +// ******************* works for wind direction only -> move out of here ******************************* +// Get maximum difference of last of buffer values to center value +template +T RingBuffer::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 +T RingBuffer::getMedian() const +{ + if (isEmpty()) { + return MIN_VAL; + // throw std::runtime_error("Buffer is empty"); + } + + // Create a temporary vector with current valid elements + std::vector 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 values of buffer +template +T RingBuffer::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 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 +size_t RingBuffer::getCapacity() const +{ + return capacity; +} + +// Get the current number of elements in the buffer +template +size_t RingBuffer::getCurrentSize() const +{ + return count; +} + +// Check if buffer is empty +template +bool RingBuffer::isEmpty() const +{ + return count == 0; +} + +// Check if buffer is full +template +bool RingBuffer::isFull() const +{ + return is_Full; +} + +// Get lowest possible value for buffer; used for initialized buffer data +template +T RingBuffer::getMinVal() +{ + return MIN_VAL; +} + +// Get highest possible value for buffer +template +T RingBuffer::getMaxVal() +{ + return MAX_VAL; +} + +// Clear buffer +template +void RingBuffer::clear() +{ + head = 0; + first = 0; + last = 0; + count = 0; + is_Full = false; +} + +// Get all current values as a vector +template +std::vector RingBuffer::getAllValues() const +{ + std::vector result; + result.reserve(count); + + for (size_t i = 0; i < count; i++) { + result.push_back(get(i)); + } + + return result; +} \ No newline at end of file diff --git a/lib/obp60task/OBPSensorTask.cpp b/lib/obp60task/OBPSensorTask.cpp index 500389e..c2f1ac8 100644 --- a/lib/obp60task/OBPSensorTask.cpp +++ b/lib/obp60task/OBPSensorTask.cpp @@ -17,6 +17,7 @@ #include "ObpNmea0183.h" // Check NMEA0183 sentence for uncorrect content #include "OBP60Extensions.h" // Lib for hardware extensions #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 // Internal ESP32 RTC clock diff --git a/lib/obp60task/PageWindPlot.cpp b/lib/obp60task/PageWindPlot.cpp index b1b17dd..80365aa 100644 --- a/lib/obp60task/PageWindPlot.cpp +++ b/lib/obp60task/PageWindPlot.cpp @@ -3,11 +3,12 @@ #include "BoatDataCalibration.h" #include "OBP60Extensions.h" #include "Pagedata.h" +#include "OBPRingBuffer.h" #include // **************************************************************** -class wndHistory { +class OldwndHistory { // provides a circular buffer to store wind history values private: int SIZE; @@ -273,9 +274,6 @@ public: GwConfigHandler* config = commonData->config; 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; const int numCfgValues = 9; String dataName[numCfgValues]; @@ -325,15 +323,18 @@ public: int distVals; // helper to check wndCenter crossing int distMid; // helper to check wndCenter crossing + static RingBuffer windDirHstry(bufSize); // Circular buffer to store wind direction values + static RingBufferwindSpdHstry(bufSize); // Circular buffer to store wind speed values + LOG_DEBUG(GwLog::LOG, "Display page WindPlot"); unsigned long start = millis(); // Data initialization - if (windDirHstry.getSize() == 0) { - if (!windDirHstry.begin(bufSize)) { + if (windDirHstry.getCurrentSize() == 0) { + /* if (!windDirHstry.begin(bufSize)) { logger->logDebug(GwLog::ERROR, "Failed to initialize wind direction history buffer"); return; - } + } */ simWnd = 0; simTWS = 0; twdValue = 0; @@ -363,7 +364,6 @@ public: 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 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; dataSValue[i] = formatValue(bvalue, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places dataUnit[i] = formatValue(bvalue, *commonData).unit; @@ -396,7 +396,7 @@ public: // Identify buffer sizes and buffer position to print on the chart intvBufSize = cHeight * dataIntv; - count = windDirHstry.getSize(); + count = windDirHstry.getCurrentSize(); numWndValues = min(count, intvBufSize); newDate++; if (dataIntv != oldDataIntv) { @@ -522,7 +522,6 @@ public: if (i == (cHeight - 1)) { // Reaching chart area top end () 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 - // windDirHstry.mvStart(); // virtually delete 40 values from buffer if ((windDirHstry.getMin(numWndValues) > wndCenter) || (windDirHstry.getMax(numWndValues) < wndCenter)) { // Check if all wind value are left or right of center value -> optimize chart range int mid = windDirHstry.getMid(numWndValues);