diff --git a/lib/obp60task/OBPDataOperations.cpp b/lib/obp60task/OBPDataOperations.cpp index 73908ac..c68ef53 100644 --- a/lib/obp60task/OBPDataOperations.cpp +++ b/lib/obp60task/OBPDataOperations.cpp @@ -1,15 +1,19 @@ #include "OBPDataOperations.h" +#include "BoatDataCalibration.h" // Functions lib for data instance calibration +#include // --- Class HstryBuf --------------- + // Init history buffers for selected boat data void HstryBuf::init(BoatValueList* boatValues, GwLog *log) { logger = log; int hstryUpdFreq = 1000; // Update frequency for history buffers in ms - int hstryMinVal = 0; // Minimum value for these history buffers - twdHstryMax = 6283; // Max value for wind direction (TWD, AWD) in rad [0...2*PI], shifted by 1000 for 3 decimals - twsHstryMax = 65000; // Max value for wind speed (TWS, AWS) in m/s [0..65], shifted by 1000 for 3 decimals + int mltplr = 1000; // Multiplier which transforms original value into buffer type format + double hstryMinVal = 0; // Minimum value for these history buffers + twdHstryMax = 2 * M_PI; // Max value for wind direction (TWD, AWD) in rad [0...2*PI] + twsHstryMax = 65; // Max value for wind speed (TWS, AWS) in m/s [0..65] (limit due to type capacity of buffer - shifted by ) awdHstryMax = twdHstryMax; awsHstryMax = twsHstryMax; twdHstryMin = hstryMinVal; @@ -19,10 +23,12 @@ void HstryBuf::init(BoatValueList* boatValues, GwLog *log) { const double DBL_MAX = std::numeric_limits::max(); // Initialize history buffers with meta data - hstryBufList.twdHstry->setMetaData("TWD", "formatCourse", hstryUpdFreq, hstryMinVal, twdHstryMax); - hstryBufList.twsHstry->setMetaData("TWS", "formatKnots", hstryUpdFreq, hstryMinVal, twsHstryMax); - hstryBufList.awdHstry->setMetaData("AWD", "formatCourse", hstryUpdFreq, hstryMinVal, twdHstryMax); - hstryBufList.awsHstry->setMetaData("AWS", "formatKnots", hstryUpdFreq, hstryMinVal, twsHstryMax); + mltplr = 10000; // Store 4 decimals for course data + hstryBufList.twdHstry->setMetaData("TWD", "formatCourse", hstryUpdFreq, mltplr, hstryMinVal, twdHstryMax); + hstryBufList.awdHstry->setMetaData("AWD", "formatCourse", hstryUpdFreq, mltplr, hstryMinVal, twdHstryMax); + mltplr = 1000; // Store 3 decimals for windspeed data + hstryBufList.twsHstry->setMetaData("TWS", "formatKnots", hstryUpdFreq, mltplr, hstryMinVal, twsHstryMax); + hstryBufList.awsHstry->setMetaData("AWS", "formatKnots", hstryUpdFreq, mltplr, hstryMinVal, twsHstryMax); // create boat values for history data types, if they don't exist yet twdBVal = boatValues->findValueOrCreate(hstryBufList.twdHstry->getName()); @@ -49,30 +55,32 @@ void HstryBuf::init(BoatValueList* boatValues, GwLog *log) { //void HstryBuf::handleHstryBuf(GwApi* api, BoatValueList* boatValues, bool useSimuData) { void HstryBuf::handleHstryBuf(bool useSimuData) { - static int16_t twd = 20; //initial value only relevant if we use simulation data - static uint16_t tws = 20; //initial value only relevant if we use simulation data - static double awd, aws, hdt = 20; //initial value only relevant if we use simulation data + static double twd, tws, awd, aws, hdt = 20; //initial value only relevant if we use simulation data GwApi::BoatValue *calBVal; // temp variable just for data calibration -> we don't want to calibrate the original data here LOG_DEBUG(GwLog::DEBUG,"obp60task handleHstryBuf: TWD_isValid? %d, twdBVal: %.1f, twaBVal: %.1f, twsBVal: %.1f", twdBVal->valid, twdBVal->value * RAD_TO_DEG, twaBVal->value * RAD_TO_DEG, twsBVal->value * 3.6 / 1.852); if (twdBVal->valid) { +// if (!useSimuData) { calBVal = new GwApi::BoatValue("TWD"); // temporary solution for calibration of history buffer values calBVal->setFormat(twdBVal->getFormat()); calBVal->value = twdBVal->value; calBVal->valid = twdBVal->valid; calibrationData.calibrateInstance(calBVal, logger); // Check if boat data value is to be calibrated - twd = static_cast(std::round(calBVal->value * 1000.0)); + twd = calBVal->value; if (twd >= twdHstryMin && twd <= twdHstryMax) { hstryBufList.twdHstry->add(twd); + LOG_DEBUG(GwLog::DEBUG,"obp60task handleHstryBuf: calBVal.value %.2f, twd: %.2f, twdHstryMin: %.1f, twdHstryMax: %.2f", calBVal->value, twd, twdHstryMin, twdHstryMax); } delete calBVal; calBVal = nullptr; } else if (useSimuData) { +// } else { twd += random(-20, 20); - twd = WindUtils::to360(twd); - hstryBufList.twdHstry->add(static_cast(DegToRad(twd) * 1000.0)); + twd += static_cast(random(-349, 349) / 1000.0); // add up to +/- 20 degree in RAD + twd = WindUtils::to2PI(twd); + hstryBufList.twdHstry->add(twd); } if (twsBVal->valid) { @@ -81,15 +89,16 @@ void HstryBuf::handleHstryBuf(bool useSimuData) { calBVal->value = twsBVal->value; calBVal->valid = twsBVal->valid; calibrationData.calibrateInstance(calBVal, logger); // Check if boat data value is to be calibrated - tws = static_cast(std::round(calBVal->value * 1000)); + tws = calBVal->value; if (tws >= twsHstryMin && tws <= twsHstryMax) { hstryBufList.twsHstry->add(tws); } delete calBVal; calBVal = nullptr; } else if (useSimuData) { - tws += random(-5000, 5000); // TWS value in m/s; expands to 3 decimals - tws = constrain(tws, 0, 25000); // Limit TWS to [0..25] m/s + // tws += random(-5000, 5000); // TWS value in m/s; expands to 3 decimals + tws += static_cast(random(-5000, 5000) / 1000.0); // add up to +/- 5 m/s TWS speed + tws = constrain(tws, 0, 40); // Limit TWS to [0..40] m/s hstryBufList.twsHstry->add(tws); } @@ -109,16 +118,16 @@ void HstryBuf::handleHstryBuf(bool useSimuData) { calibrationData.calibrateInstance(calBVal, logger); // Check if boat data value is to be calibrated awdBVal->value = calBVal->value; awdBVal->valid = true; - awd = std::round(calBVal->value * 1000.0); + awd = calBVal->value; if (awd >= awdHstryMin && awd <= awdHstryMax) { - hstryBufList.awdHstry->add(static_cast(awd)); + hstryBufList.awdHstry->add(awd); } delete calBVal; calBVal = nullptr; } else if (useSimuData) { - awd += random(-20, 20); - awd = WindUtils::to360(awd); - hstryBufList.awdHstry->add(static_cast(DegToRad(awd) * 1000.0)); + awd += static_cast(random(-349, 349) / 1000.0); // add up to +/- 20 degree in RAD + awd = WindUtils::to2PI(awd); + hstryBufList.awdHstry->add(awd); } if (awsBVal->valid) { @@ -127,26 +136,28 @@ void HstryBuf::handleHstryBuf(bool useSimuData) { calBVal->value = awsBVal->value; calBVal->valid = awsBVal->valid; calibrationData.calibrateInstance(calBVal, logger); // Check if boat data value is to be calibrated - aws = std::round(calBVal->value * 1000); + aws = calBVal->value; if (aws >= awsHstryMin && aws <= awsHstryMax) { - hstryBufList.awsHstry->add(static_cast(aws)); + hstryBufList.awsHstry->add(aws); } delete calBVal; calBVal = nullptr; } else if (useSimuData) { - aws += random(-5000, 5000); // TWS value in m/s; expands to 1 decimal - aws = constrain(aws, 0, 25000); // Limit TWS to [0..25] m/s + aws += static_cast(random(-5000, 5000) / 1000.0); // add up to +/- 5 m/s TWS speed + aws = constrain(aws, 0, 40); // Limit TWS to [0..40] m/s hstryBufList.awsHstry->add(aws); } + LOG_DEBUG(GwLog::DEBUG,"obp60task handleHstryBuf-End: Buffer twdHstry: %.3f, twsHstry: %.3f, awdHstry: %.3f, awsHstry: %.3f", hstryBufList.twdHstry->getLast(), hstryBufList.twsHstry->getLast(), + hstryBufList.awdHstry->getLast(),hstryBufList.awsHstry->getLast()); } // --- Class HstryBuf --------------- // --- Class WindUtils -------------- double WindUtils::to2PI(double a) { - a = fmod(a, 2 * M_PI); + a = fmod(a, M_TWOPI); if (a < 0.0) { - a += 2 * M_PI; + a += M_TWOPI; } return a; } @@ -162,18 +173,18 @@ double WindUtils::toPI(double a) double WindUtils::to360(double a) { - a = fmod(a, 360); + a = fmod(a, 360.0); if (a < 0.0) { - a += 360; + a += 360.0; } return a; } double WindUtils::to180(double a) { - a += 180; + a += 180.0; a = to360(a); - a -= 180; + a -= 180.0; return a; } @@ -263,7 +274,7 @@ bool WindUtils::calcTrueWind(const double* awaVal, const double* awsVal, // If STW and SOG are not available, we cannot calculate true wind return false; } - // Serial.println("\ncalcTrueWind: HDT: " + String(hdt) + ", CTW: " + String(ctw) + ", STW: " + String(stw)); +// Serial.println("\ncalcTrueWind: HDT: " + String(hdt) + ", CTW: " + String(ctw) + ", STW: " + String(stw)); if ((*awaVal == DBL_MAX) || (*awsVal == DBL_MAX)) { // Cannot calculate true wind without valid AWA, AWS; other checks are done earlier diff --git a/lib/obp60task/OBPDataOperations.h b/lib/obp60task/OBPDataOperations.h index fc520f9..5ee604b 100644 --- a/lib/obp60task/OBPDataOperations.h +++ b/lib/obp60task/OBPDataOperations.h @@ -2,14 +2,12 @@ #pragma once #include #include "OBPRingBuffer.h" -#include "BoatDataCalibration.h" // Functions lib for data instance calibration #include "obp60task.h" -#include typedef struct { - RingBuffer* twdHstry; + RingBuffer* twdHstry; RingBuffer* twsHstry; - RingBuffer* awdHstry; + RingBuffer* awdHstry; RingBuffer* awsHstry; } tBoatHstryData; // Holds pointers to all history buffers for boat data @@ -17,18 +15,18 @@ class HstryBuf { private: GwLog *logger; - RingBuffer twdHstry; // Circular buffer to store true wind direction values + RingBuffer twdHstry; // Circular buffer to store true wind direction values RingBuffer twsHstry; // Circular buffer to store true wind speed values (TWS) - RingBuffer awdHstry; // Circular buffer to store apparant wind direction values + RingBuffer awdHstry; // Circular buffer to store apparant wind direction values RingBuffer awsHstry; // Circular buffer to store apparant xwind speed values (AWS) - int16_t twdHstryMin; // Min value for wind direction (TWD) in history buffer - int16_t twdHstryMax; // Max value for wind direction (TWD) in history buffer - uint16_t twsHstryMin; - uint16_t twsHstryMax; - int16_t awdHstryMin; - int16_t awdHstryMax; - uint16_t awsHstryMin; - uint16_t awsHstryMax; + double twdHstryMin; // Min value for wind direction (TWD) in history buffer + double twdHstryMax; // Max value for wind direction (TWD) in history buffer + double twsHstryMin; + double twsHstryMax; + double awdHstryMin; + double awdHstryMax; + double awsHstryMin; + double awsHstryMax; // boat values for buffers and for true wind calculation GwApi::BoatValue *twdBVal, *twsBVal, *twaBVal, *awdBVal, *awsBVal; @@ -72,6 +70,7 @@ public: hdmBVal = boatValues->findValueOrCreate("HDM"); varBVal = boatValues->findValueOrCreate("VAR"); }; + static double to2PI(double a); static double toPI(double a); static double to360(double a); diff --git a/lib/obp60task/OBPRingBuffer.h b/lib/obp60task/OBPRingBuffer.h index 4d13da6..15ad5c1 100644 --- a/lib/obp60task/OBPRingBuffer.h +++ b/lib/obp60task/OBPRingBuffer.h @@ -1,10 +1,6 @@ #pragma once +//#include "FreeRTOS.h" #include "GwSynchronized.h" -#include "WString.h" -#include "esp_heap_caps.h" -#include -#include -#include #include template @@ -41,7 +37,6 @@ bool operator!=(const PSRAMAllocator&, const PSRAMAllocator&) { return fal template class RingBuffer { private: - // std::vector buffer; // THE buffer vector std::vector> buffer; // THE buffer vector, allocated in PSRAM size_t capacity; size_t head; // Points to the next insertion position @@ -51,49 +46,52 @@ private: bool is_Full; // Indicates that all buffer elements are used and ringing is in use T MIN_VAL; // lowest possible value of buffer of type T MAX_VAL; // highest possible value of buffer of type -> indicates invalid value in buffer + double dblMIN_VAL, dblMAX_VAL; // MIN_VAL, MAX_VAL in double format mutable SemaphoreHandle_t bufLocker; // metadata for buffer String dataName; // Name of boat data in buffer String dataFmt; // Format of boat data in buffer int updFreq; // Update frequency in milliseconds - T smallest; // Value range of buffer: smallest value; needs to be => MIN_VAL - T largest; // Value range of buffer: biggest value; needs to be < MAX_VAL, since MAX_VAL indicates invalid entries + double mltplr; // Multiplier which transforms original value into buffer type format + double smallest; // Value range of buffer: smallest value; needs to be => MIN_VAL + double largest; // Value range of buffer: biggest value; needs to be < MAX_VAL, since MAX_VAL indicates invalid entries void initCommon(); public: RingBuffer(); RingBuffer(size_t size); - void setMetaData(String name, String format, int updateFrequency, T minValue, T maxValue); // Set meta data for buffer - bool getMetaData(String& name, String& format, int& updateFrequency, T& minValue, T& maxValue); // Get meta data of buffer + void setMetaData(String name, String format, int updateFrequency, double multiplier, double minValue, double maxValue); // Set meta data for buffer + bool getMetaData(String& name, String& format, int& updateFrequency, double& multiplier, double& minValue, double& maxValue); // Get meta data of buffer bool getMetaData(String& name, String& format); String getName() const; // Get buffer name String getFormat() const; // Get buffer data format - 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 getMedian() const; // Get the median value in buffer - T getMedian(size_t amount) const; // Get the median value of the last values of buffer + void add(const double& value); // Add a new value to buffer + double get(size_t index) const; // Get value at specific position (0-based index from oldest to newest) + double getFirst() const; // Get the first (oldest) value in buffer + double getLast() const; // Get the last (newest) value in buffer + double getMin() const; // Get the lowest value in buffer + double getMin(size_t amount) const; // Get minimum value of the last values of buffer + double getMax() const; // Get the highest value in buffer + double getMax(size_t amount) const; // Get maximum value of the last values of buffer + double getMid() const; // Get mid value between and value in buffer + double getMid(size_t amount) const; // Get mid value between and value of the last values of buffer + double getMedian() const; // Get the median value in buffer + double 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 size_t getFirstIdx() const; // Get the index of oldest value in buffer size_t getLastIdx() const; // Get the index of newest value in buffer bool isEmpty() const; // Check if buffer is empty bool isFull() const; // Check if buffer is full - T getMinVal() const; // Get lowest possible value for buffer - T getMaxVal() const; // Get highest possible value for buffer; used for unset/invalid buffer data + double getMinVal() const; // Get lowest possible value for buffer + double getMaxVal() const; // Get highest possible value for buffer; used for unset/invalid buffer data void clear(); // Clear buffer void resize(size_t size); // Delete buffer and set new size - T operator[](size_t index) const; // Operator[] for convenient access (same as get()) - std::vector getAllValues() const; // Get all current values as a vector + double operator[](size_t index) const; // Operator[] for convenient access (same as get()) + std::vector getAllValues() const; // Get all current values in native buffer format as a vector + std::vector getAllValues(size_t amount) const; // Get last values in native buffer format as a vector }; #include "OBPRingBuffer.tpp" \ No newline at end of file diff --git a/lib/obp60task/OBPRingBuffer.tpp b/lib/obp60task/OBPRingBuffer.tpp index 9174568..281e89d 100644 --- a/lib/obp60task/OBPRingBuffer.tpp +++ b/lib/obp60task/OBPRingBuffer.tpp @@ -1,14 +1,20 @@ #include "OBPRingBuffer.h" +#include +#include template -void RingBuffer::initCommon() { +void RingBuffer::initCommon() +{ MIN_VAL = std::numeric_limits::lowest(); MAX_VAL = std::numeric_limits::max(); + dblMIN_VAL = static_cast(MIN_VAL); + dblMAX_VAL = static_cast(MAX_VAL); dataName = ""; dataFmt = ""; updFreq = -1; - smallest = MIN_VAL; - largest = MAX_VAL; + mltplr = 1; + smallest = dblMIN_VAL; + largest = dblMAX_VAL; bufLocker = xSemaphoreCreateMutex(); } @@ -42,19 +48,20 @@ RingBuffer::RingBuffer(size_t size) // Specify meta data of buffer content template -void RingBuffer::setMetaData(String name, String format, int updateFrequency, T minValue, T maxValue) +void RingBuffer::setMetaData(String name, String format, int updateFrequency, double multiplier, double minValue, double maxValue) { GWSYNCHRONIZED(&bufLocker); dataName = name; dataFmt = format; updFreq = updateFrequency; - smallest = std::max(MIN_VAL, minValue); - largest = std::min(MAX_VAL, maxValue); + mltplr = multiplier; + smallest = std::max(dblMIN_VAL, minValue); + largest = std::min(dblMAX_VAL, maxValue); } // Get meta data of buffer content template -bool RingBuffer::getMetaData(String& name, String& format, int& updateFrequency, T& minValue, T& maxValue) +bool RingBuffer::getMetaData(String& name, String& format, int& updateFrequency, double& multiplier, double& minValue, double& maxValue) { if (dataName == "" || dataFmt == "" || updFreq == -1) { return false; // Meta data not set @@ -64,6 +71,7 @@ bool RingBuffer::getMetaData(String& name, String& format, int& updateFrequen name = dataName; format = dataFmt; updateFrequency = updFreq; + multiplier = mltplr; minValue = smallest; maxValue = largest; return true; @@ -99,13 +107,13 @@ String RingBuffer::getFormat() const // Add a new value to buffer template -void RingBuffer::add(const T& value) +void RingBuffer::add(const double& value) { GWSYNCHRONIZED(&bufLocker); if (value < smallest || value > largest) { buffer[head] = MAX_VAL; // Store MAX_VAL if value is out of range } else { - buffer[head] = value; + buffer[head] = static_cast(std::round(value * mltplr)); } last = head; @@ -117,63 +125,63 @@ void RingBuffer::add(const T& value) is_Full = true; } } - + // Serial.printf("Ringbuffer: value %.3f, multiplier: %.1f, buffer: %d\n", value, mltplr, buffer[head]); head = (head + 1) % capacity; } // Get value at specific position (0-based index from oldest to newest) template -T RingBuffer::get(size_t index) const +double RingBuffer::get(size_t index) const { GWSYNCHRONIZED(&bufLocker); if (isEmpty() || index < 0 || index >= count) { - return MAX_VAL; + return dblMAX_VAL; } size_t realIndex = (first + index) % capacity; - return buffer[realIndex]; + return static_cast(buffer[realIndex] / mltplr); } // Operator[] for convenient access (same as get()) template -T RingBuffer::operator[](size_t index) const +double RingBuffer::operator[](size_t index) const { return get(index); } // Get the first (oldest) value in the buffer template -T RingBuffer::getFirst() const +double RingBuffer::getFirst() const { if (isEmpty()) { - return MAX_VAL; + return dblMAX_VAL; } return get(0); } // Get the last (newest) value in the buffer template -T RingBuffer::getLast() const +double RingBuffer::getLast() const { if (isEmpty()) { - return MAX_VAL; + return dblMAX_VAL; } return get(count - 1); } // Get the lowest value in the buffer template -T RingBuffer::getMin() const +double RingBuffer::getMin() const { if (isEmpty()) { - return MAX_VAL; + return dblMAX_VAL; } - T minVal = MAX_VAL; - T value; + double minVal = dblMAX_VAL; + double value; for (size_t i = 0; i < count; i++) { value = get(i); - if (value < minVal && value != MAX_VAL) { + if (value < minVal && value != dblMAX_VAL) { minVal = value; } } @@ -182,19 +190,19 @@ T RingBuffer::getMin() const // Get minimum value of the last values of buffer template -T RingBuffer::getMin(size_t amount) const +double RingBuffer::getMin(size_t amount) const { if (isEmpty() || amount <= 0) { - return MAX_VAL; + return dblMAX_VAL; } if (amount > count) amount = count; - T minVal = MAX_VAL; - T value; + double minVal = dblMAX_VAL; + double value; for (size_t i = 0; i < amount; i++) { value = get(count - 1 - i); - if (value < minVal && value != MAX_VAL) { + if (value < minVal && value != dblMAX_VAL) { minVal = value; } } @@ -203,75 +211,81 @@ T RingBuffer::getMin(size_t amount) const // Get the highest value in the buffer template -T RingBuffer::getMax() const +double RingBuffer::getMax() const { if (isEmpty()) { - return MAX_VAL; + return dblMAX_VAL; } - T maxVal = MIN_VAL; - T value; + double maxVal = dblMIN_VAL; + double value; for (size_t i = 0; i < count; i++) { value = get(i); - if (value > maxVal && value != MAX_VAL) { + if (value > maxVal && value != dblMAX_VAL) { maxVal = value; } } + if (maxVal == dblMIN_VAL) { // no change of initial value -> buffer has only invalid values (MAX_VAL) + maxVal = dblMAX_VAL; + } return maxVal; } // Get maximum value of the last values of buffer template -T RingBuffer::getMax(size_t amount) const +double RingBuffer::getMax(size_t amount) const { if (isEmpty() || amount <= 0) { - return MAX_VAL; + return dblMAX_VAL; } if (amount > count) amount = count; - T maxVal = MIN_VAL; - T value; + double maxVal = dblMIN_VAL; + double value; for (size_t i = 0; i < amount; i++) { value = get(count - 1 - i); - if (value > maxVal && value != MAX_VAL) { + if (value > maxVal && value != dblMAX_VAL) { maxVal = value; } } + if (maxVal == dblMIN_VAL) { // no change of initial value -> buffer has only invalid values (MAX_VAL) + maxVal = dblMAX_VAL; + } return maxVal; } // Get mid value between and value in the buffer template -T RingBuffer::getMid() const +double RingBuffer::getMid() const { if (isEmpty()) { - return MAX_VAL; + return dblMAX_VAL; } - return (getMin() + getMax()) / static_cast(2); + return (getMin() + getMax()) / 2; } // Get mid value between and value of the last values of buffer template -T RingBuffer::getMid(size_t amount) const +double RingBuffer::getMid(size_t amount) const { if (isEmpty() || amount <= 0) { - return MAX_VAL; + return dblMAX_VAL; } if (amount > count) amount = count; - return (getMin(amount) + getMax(amount)) / static_cast(2); + return (getMin(amount) + getMax(amount)) / 2; } // Get the median value in the buffer template -T RingBuffer::getMedian() const +double RingBuffer::getMedian() const { if (isEmpty()) { - return MAX_VAL; + return dblMAX_VAL; } // Create a temporary vector with current valid elements @@ -287,20 +301,20 @@ T RingBuffer::getMedian() const if (count % 2 == 1) { // Odd number of elements - return temp[count / 2]; + return static_cast(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; + return static_cast((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 +double RingBuffer::getMedian(size_t amount) const { if (isEmpty() || amount <= 0) { - return MAX_VAL; + return dblMAX_VAL; } if (amount > count) amount = count; @@ -310,7 +324,7 @@ T RingBuffer::getMedian(size_t amount) const temp.reserve(amount); for (size_t i = 0; i < amount; i++) { - temp.push_back(get(i)); + temp.push_back(get(count - 1 - i)); } // Sort to find median @@ -318,11 +332,11 @@ T RingBuffer::getMedian(size_t amount) const if (amount % 2 == 1) { // Odd number of elements - return temp[amount / 2]; + return static_cast(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; + return static_cast((temp[amount / 2 - 1] + temp[amount / 2]) / 2); } } @@ -370,16 +384,16 @@ bool RingBuffer::isFull() const // Get lowest possible value for buffer template -T RingBuffer::getMinVal() const +double RingBuffer::getMinVal() const { - return MIN_VAL; + return dblMIN_VAL; } // Get highest possible value for buffer; used for unset/invalid buffer data template -T RingBuffer::getMaxVal() const +double RingBuffer::getMaxVal() const { - return MAX_VAL; + return dblMAX_VAL; } // Clear buffer @@ -411,16 +425,37 @@ void RingBuffer::resize(size_t newSize) buffer.resize(newSize, MAX_VAL); } -// Get all current values as a vector +// Get all current values in native buffer format as a vector template -std::vector RingBuffer::getAllValues() const +std::vector RingBuffer::getAllValues() const { - std::vector result; + std::vector result; result.reserve(count); for (size_t i = 0; i < count; i++) { result.push_back(get(i)); } + return result; +} + +// Get last values in native buffer format as a vector +template +std::vector RingBuffer::getAllValues(size_t amount) const +{ + std::vector result; + + if (isEmpty() || amount <= 0) { + return result; + } + if (amount > count) + amount = count; + + result.reserve(amount); + + for (size_t i = 0; i < amount; i++) { + result.push_back(get(count - 1 - i)); + } + return result; } \ No newline at end of file diff --git a/lib/obp60task/OBPcharts.cpp b/lib/obp60task/OBPcharts.cpp index 907712b..96954dc 100644 --- a/lib/obp60task/OBPcharts.cpp +++ b/lib/obp60task/OBPcharts.cpp @@ -5,7 +5,7 @@ // --- Class Chart --------------- template -Chart::Chart(RingBuffer& dataBuf, int8_t chrtDir, int8_t chrtSz, int dfltRng, CommonData& common, bool useSimuData) +Chart::Chart(RingBuffer& dataBuf, int8_t chrtDir, int8_t chrtSz, double dfltRng, CommonData& common, bool useSimuData) : dataBuf(dataBuf) , chrtDir(chrtDir) , chrtSz(chrtSz) @@ -17,28 +17,28 @@ Chart::Chart(RingBuffer& dataBuf, int8_t chrtDir, int8_t chrtSz, int dfltR fgColor = commonData->fgcolor; bgColor = commonData->bgcolor; - LOG_DEBUG(GwLog::DEBUG, "Chart create: dataBuf: %p", (void*)&dataBuf); + LOG_DEBUG(GwLog::DEBUG, "Chart Init: dataBuf: %p", (void*)&dataBuf); dWidth = getdisplay().width(); dHeight = getdisplay().height(); if (chrtDir == 0) { // horizontal chart timeline direction - timAxis = dWidth - xOffset; + timAxis = dWidth; switch (chrtSz) { case 0: valAxis = dHeight - top - bottom; - cStart = { xOffset, top }; + cStart = { 0, top }; break; case 1: valAxis = (dHeight - top - bottom) / 2 - gap; - cStart = { xOffset, top }; + cStart = { 0, top }; break; case 2: valAxis = (dHeight - top - bottom) / 2 - gap; - cStart = { xOffset, top + (valAxis + gap) + gap }; + cStart = { 0, top + (valAxis + gap) + gap }; break; default: - LOG_DEBUG(GwLog::DEBUG, "displayChart: wrong parameter"); + LOG_DEBUG(GwLog::ERROR, "displayChart: wrong init parameter"); return; } } else if (chrtDir == 1) { @@ -46,31 +46,52 @@ Chart::Chart(RingBuffer& dataBuf, int8_t chrtDir, int8_t chrtSz, int dfltR timAxis = dHeight - top - bottom; switch (chrtSz) { case 0: - valAxis = dWidth - xOffset; - cStart = { xOffset, top }; + valAxis = dWidth; + cStart = { 0, top }; break; case 1: - valAxis = dWidth / 2 - gap; + valAxis = dWidth / 2 - gap - 1; cStart = { 0, top }; break; case 2: - valAxis = dWidth / 2 - gap; + valAxis = dWidth / 2 - gap - 1; cStart = { dWidth / 2 + gap, top }; break; default: - LOG_DEBUG(GwLog::DEBUG, "displayChart: wrong parameter"); + LOG_DEBUG(GwLog::ERROR, "displayChart: wrong init parameter"); return; } } else { - LOG_DEBUG(GwLog::DEBUG, "displayChart: wrong parameter"); + LOG_DEBUG(GwLog::ERROR, "displayChart: wrong init parameter"); return; } - // xCenter = timAxis / 2; dataBuf.getMetaData(dbName, dbFormat); + dbMIN_VAL = dataBuf.getMinVal(); dbMAX_VAL = dataBuf.getMaxVal(); bufSize = dataBuf.getCapacity(); - LOG_DEBUG(GwLog::DEBUG, "Chart create: dWidth: %d, dHeight: %d, timAxis: %d, valAxis: %d, cStart {x,y}: %d, %d, dbname: %s", dWidth, dHeight, timAxis, valAxis, cStart.x, cStart.y, dbName); + + if (dbFormat == "formatCourse" || dbFormat == "FormatWind" || dbFormat == "FormatRot") { + + if (dbFormat == "FormatRot") { + chrtDataFmt = 2; // Chart is showing data of rotational format + } else { + chrtDataFmt = 1; // Chart is showing data of course / wind format + } + rngStep = M_TWOPI / 360.0 * 10.0; // +/-10 degrees on each end of chrtMid; we are calculating with SI values + + } else { + chrtDataFmt = 0; // Chart is showing any other data format than + rngStep = 5.0; // +/- 10 for all other values (eg. m/s, m, K, mBar) + } + + chrtMin = 0; + chrtMax = 0; + chrtMid = dbMAX_VAL; + chrtRng = dfltRng; + recalcRngCntr = true; // initialize on first screen call + + LOG_DEBUG(GwLog::DEBUG, "Chart Init: dWidth: %d, dHeight: %d, timAxis: %d, valAxis: %d, cStart {x,y}: %d, %d, dbname: %s, rngStep: %.4f", dWidth, dHeight, timAxis, valAxis, cStart.x, cStart.y, dbName, rngStep); }; template @@ -78,114 +99,25 @@ Chart::~Chart() { } -// chart time axis label + lines +// Perform all actions to draw chart +// Parameters are chart time interval, and the current boat data value to be printed template -void Chart::drawChrtTimeAxis(int8_t chrtIntv) +void Chart::showChrt(int8_t chrtIntv, GwApi::BoatValue currValue) { - int timeRng; - float slots, intv, i; - char sTime[6]; - - getdisplay().setTextColor(fgColor); - if (chrtDir == 0) { // horizontal chart - getdisplay().fillRect(0, top, dWidth, 2, fgColor); - getdisplay().setFont(&Ubuntu_Bold8pt8b); - - timeRng = chrtIntv * 4; // Chart time interval: [1] 4 min., [2] 8 min., [3] 12 min., [4] 16 min., [8] 32 min. - slots = (timAxis - xOffset) / 75.0; // number of axis labels - intv = timeRng / slots; // minutes per chart axis interval - i = timeRng; // Chart axis label start at -32, -16, -12, ... minutes - - for (int j = 0; j < timAxis - 30; j += 75) { - LOG_DEBUG(GwLog::DEBUG, "ChartHdr: timAxis: %d, {x,y}: {%d,%d}, i: %.1f, j: %d, chrtIntv: %d, intv: %.1f, slots: %.1f", timAxis, cStart.x, cStart.y, i, j, chrtIntv, intv, slots); - if (chrtIntv < 3) { - snprintf(sTime, size_t(sTime), "-%.1f", i); - drawTextCenter(cStart.x + j - 8, cStart.y - 8, sTime); // time interval - } else { - snprintf(sTime, size_t(sTime), "-%.0f", std::round(i)); - drawTextCenter(cStart.x + j - 4, cStart.y - 8, sTime); // time interval - } - getdisplay().drawLine(cStart.x + j, cStart.y, cStart.x + j, cStart.y + 5, fgColor); - i -= intv; - } - /* getdisplay().setFont(&Ubuntu_Bold8pt8b); - getdisplay().setCursor(timAxis - 8, cStart.y - 2); - getdisplay().print("min"); */ - - } else { // chrtDir == 1; vertical chart - getdisplay().setFont(&Ubuntu_Bold8pt8b); - timeRng = chrtIntv * 4; // Chart time interval: [1] 4 min., [2] 8 min., [3] 12 min., [4] 16 min., [8] 32 min. - slots = timAxis / 75.0; // number of axis labels - intv = timeRng / slots; // minutes per chart axis interval - i = 0; // Chart axis label start at -32, -16, -12, ... minutes - - for (int j = 0; j < (timAxis - 75); j += 75) { // don't print time label at lower end - LOG_DEBUG(GwLog::DEBUG, "ChartHdr: timAxis: %d, {x,y}: {%d,%d}, i: %.1f, j: %d, chrtIntv: %d, intv: %.1f, slots: %.1f", timAxis, cStart.x, cStart.y, i, j, chrtIntv, intv, slots); - if (chrtIntv < 3) { // print 1 decimal if time range is single digit (4 or 8 minutes) - snprintf(sTime, size_t(sTime), "%.1f", i * -1); - } else { - snprintf(sTime, size_t(sTime), "%.0f", std::round(i) * -1); - } - drawTextCenter(dWidth / 2, cStart.y + j, sTime); // time value - getdisplay().drawLine(cStart.x, cStart.y + j, cStart.x + valAxis, cStart.y + j, fgColor); // Grid line - i += intv; - } - } -} - -// chart value axis labels + lines -template -void Chart::drawChrtValAxis() -{ - float slots; - int i, intv; - char sVal[6]; - - getdisplay().setFont(&Ubuntu_Bold10pt8b); - if (chrtDir == 0) { // horizontal chart - slots = valAxis / 60.0; // number of axis labels - intv = static_cast(round(chrtRng / slots)); - i = intv; - for (int j = 60; j < valAxis - 30; j += 60) { - LOG_DEBUG(GwLog::DEBUG, "ChartGrd: chrtRng: %d, intv: %d, slots: %.1f, valAxis: %d, i: %d, j: %d", chrtRng, intv, slots, valAxis, i, j); - getdisplay().fillRect(cStart.x - xOffset, cStart.y + j - 9, cStart.x - xOffset + 28, 12, bgColor); // Clear small area to remove potential chart lines - String sVal = String(static_cast(round(i))); - getdisplay().setCursor((3 - sVal.length()) * 9, cStart.y + j + 4); // value right-formated - getdisplay().printf("%s", sVal); // Range value - i += intv; - getdisplay().drawLine(cStart.x + 2, cStart.y + j, cStart.x + timAxis, cStart.y + j, fgColor); - } - getdisplay().setFont(&Ubuntu_Bold12pt8b); - drawTextRalign(cStart.x + timAxis, cStart.y - 3, dataBuf.getName()); // buffer data name - - } else { // chrtDir == 1; vertical chart - getdisplay().fillRect(cStart.x, top, valAxis, 2, fgColor); // top chart line - getdisplay().setCursor(cStart.x, cStart.y - 2); - snprintf(sVal, sizeof(sVal), "%d", dataBuf.getMin(numBufVals) / 1000); - getdisplay().printf("%s", sVal); // Range low end - snprintf(sVal, sizeof(sVal), "%.0f", round(chrtRng / 2)); - drawTextCenter(cStart.x + (valAxis / 2), cStart.y - 10, sVal); // Range mid end - snprintf(sVal, sizeof(sVal), "%.0f", round(chrtRng)); - drawTextRalign(cStart.x + valAxis - 1, cStart.y - 2, sVal); // Range high end - for (int j = 0; j <= valAxis; j += (valAxis / 2)) { - getdisplay().drawLine(cStart.x + j - 1, cStart.y, cStart.x + j - 1, cStart.y + timAxis, fgColor); - } - getdisplay().setFont(&Ubuntu_Bold12pt8b); - drawTextCenter(cStart.x + (valAxis / 4) + 4, cStart.y - 11, dataBuf.getName()); // buffer data name - LOG_DEBUG(GwLog::DEBUG, "ChartGrd: chrtRng: %d, intv: %d, slots: %.1f, valAxis: %d, i: %d, sVal.length: %d", chrtRng, intv, slots, valAxis, i, sizeof(sVal)); - } + drawChrtTimeAxis(chrtIntv); + drawChrt(chrtIntv, currValue); + drawChrtValAxis(); } // draw chart template -void Chart::drawChrt(int8_t chrtIntv, GwApi::BoatValue currValue) +void Chart::drawChrt(int8_t chrtIntv, GwApi::BoatValue& currValue) { - float chrtScl; // Scale for data values in pixels per value - int chrtVal; // Current data value - static int chrtPrevVal; // Last data value in chart area + double chrtVal; // Current data value + double chrtScl; // Scale for data values in pixels per value + static double chrtPrevVal; // Last data value in chart area bool bufDataValid = false; // Flag to indicate if buffer data is valid static int numNoData; // Counter for multiple invalid data values in a row - // GwApi::BoatValue currValue; // temporary boat value to display current data buffer value int x, y; // x and y coordinates for drawing static int prevX, prevY; // Last x and y coordinates for drawing @@ -194,13 +126,15 @@ void Chart::drawChrt(int8_t chrtIntv, GwApi::BoatValue currValue) count = dataBuf.getCurrentSize(); currIdx = dataBuf.getLastIdx(); numAddedBufVals = (currIdx - lastAddedIdx + bufSize) % bufSize; // Number of values added to buffer since last display + if (chrtIntv != oldChrtIntv || count == 1) { // new data interval selected by user; this is only x * 230 values instead of 240 seconds (4 minutes) per interval step - intvBufSize = timAxis * chrtIntv; - numBufVals = min(count, (timAxis - 60) * chrtIntv); + // intvBufSize = timAxis * chrtIntv; // obsolete + numBufVals = min(count, (timAxis - 60) * chrtIntv); // keep free or release 60 values on chart for plotting of new values bufStart = max(0, count - numBufVals); lastAddedIdx = currIdx; oldChrtIntv = chrtIntv; + } else { numBufVals = numBufVals + numAddedBufVals; lastAddedIdx = currIdx; @@ -209,19 +143,16 @@ void Chart::drawChrt(int8_t chrtIntv, GwApi::BoatValue currValue) } } - calcChrtRng(); - chrtScl = float(valAxis) / float(chrtRng); // Chart scale: pixels per value step + calcChrtBorders(chrtMid, chrtMin, chrtMax, chrtRng); + chrtScl = double(valAxis) / chrtRng; // Chart scale: pixels per value step // Do we have valid buffer data? - if (dataBuf.getMax() == dbMAX_VAL) { - // only values in buffer -> no valid wind data available + if (dataBuf.getMax() == dbMAX_VAL) { // only values in buffer -> no valid wind data available bufDataValid = false; - } else if (!currValue.valid && !useSimuData) { - // currently no valid boat data available and no simulation mode + } else if (!currValue.valid && !useSimuData) { // currently no valid boat data available and no simulation mode numNoData++; bufDataValid = true; - if (numNoData > 3) { - // If more than 4 invalid values in a row, send message + if (numNoData > 3) { // If more than 4 invalid values in a row, send message bufDataValid = false; } } else { @@ -233,135 +164,451 @@ void Chart::drawChrt(int8_t chrtIntv, GwApi::BoatValue currValue) //*********************************************************************** if (bufDataValid) { for (int i = 0; i < (numBufVals / chrtIntv); i++) { - chrtVal = static_cast(dataBuf.get(bufStart + (i * chrtIntv))); // show the latest wind values in buffer; keep 1st value constant in a rolling buffer + chrtVal = dataBuf.get(bufStart + (i * chrtIntv)); // show the latest wind values in buffer; keep 1st value constant in a rolling buffer if (chrtVal == dbMAX_VAL) { chrtPrevVal = dbMAX_VAL; } else { - chrtVal = static_cast((chrtVal / 1000.0) + 0.5); // Convert to real value and round + if (chrtDir == 0) { // horizontal chart x = cStart.x + i; // Position in chart area - y = cStart.y + (chrtVal * chrtScl); // value + if (chrtDataFmt == 0) { + y = cStart.y + static_cast(((chrtVal - chrtMin) * chrtScl) + 0.5); // calculate chart point and round + } else { // degree type value + y = cStart.y + static_cast((WindUtils::to2PI(chrtVal - chrtMin) * chrtScl) + 0.5); // calculate chart point and round + } } else { // vertical chart - x = cStart.x + (chrtVal * chrtScl); // value y = cStart.y + timAxis - i; // Position in chart area + if (chrtDataFmt == 0) { + x = cStart.x + static_cast(((chrtVal - chrtMin) * chrtScl) + 0.5); // calculate chart point and round + } else { // degree type value + x = cStart.x + static_cast((WindUtils::to2PI(chrtVal - chrtMin) * chrtScl) + 0.5); // calculate chart point and round + } } - if (i >= (numBufVals / chrtIntv) - 10) // log chart data of 1 line (adjust for test purposes) - LOG_DEBUG(GwLog::DEBUG, "PageWindPlot Chart: i: %d, chrtVal: %d, {x,y} {%d,%d}", i, chrtVal, x, y); + + if (i >= (numBufVals / chrtIntv) - 4) // log chart data of 1 line (adjust for test purposes) + LOG_DEBUG(GwLog::DEBUG, "PageWindPlot Chart: i: %d, chrtVal: %.4f, {x,y} {%d,%d}", i, chrtVal, x, y); if ((i == 0) || (chrtPrevVal == dbMAX_VAL)) { // just a dot for 1st chart point or after some invalid values prevX = x; prevY = y; + + } else if (chrtDataFmt != 0) { // cross borders check for degree values; shift values to [-PI..0..PI]; when crossing borders, range is 2x PI degrees + + // Normalize both values relative to chrtMin (shift range to start at 0) + double normCurr = WindUtils::to2PI(chrtVal - chrtMin); + double normPrev = WindUtils::to2PI(chrtPrevVal - chrtMin); + // Check if pixel positions are far apart (crossing chart boundary); happens when one value is near chrtMax and the other near chrtMin + bool crossedBorders = std::abs(normCurr - normPrev) > (chrtRng / 2.0); + + if (crossedBorders) { // If current value crosses chart borders compared to previous value, split line + LOG_DEBUG(GwLog::DEBUG, "PageWindPlot Chart: crossedBorders: %d, chrtVal: %.2f, chrtPrevVal: %.2f", crossedBorders, chrtVal, chrtPrevVal); + bool wrappingFromHighToLow = normCurr < normPrev; // Determine which edge we're crossing + int xSplit = wrappingFromHighToLow ? (cStart.x + valAxis) : cStart.x; + getdisplay().drawLine(prevX, prevY, xSplit, y, fgColor); + getdisplay().drawLine(prevX, prevY - 1, ((xSplit != prevX) ? xSplit : xSplit - 1), ((xSplit != prevX) ? y - 1 : y), fgColor); + prevX = wrappingFromHighToLow ? cStart.x : (cStart.x + valAxis); + } } + // Draw line with 2 pixels width + make sure vertical lines are drawn correctly if (chrtDir == 0 || x == prevX) { // vertical line getdisplay().drawLine(prevX, prevY, x, y, fgColor); getdisplay().drawLine(prevX - 1, prevY, x - 1, y, fgColor); +// getdisplay().drawLine(prevX + 1, prevY, x - 1, y, fgColor); } else if (chrtDir == 1 || x != prevX) { // line with some horizontal trend -> normal state getdisplay().drawLine(prevX, prevY, x, y, fgColor); getdisplay().drawLine(prevX, prevY - 1, x, y - 1, fgColor); +// getdisplay().drawLine(prevX, prevY + 1, x, y - 1, fgColor); } chrtPrevVal = chrtVal; prevX = x; prevY = y; } + // Reaching chart area bottom end if (i >= timAxis - 1) { oldChrtIntv = 0; // force reset of buffer start and number of values to show in next display loop + + if (chrtDataFmt == 1) { // degree of course or wind + recalcRngCntr = true; + LOG_DEBUG(GwLog::DEBUG, "PageWindPlot FreeTop: timAxis: %d, i: %d, bufStart: %d, numBufVals: %d, recalcRngCntr: %d", timAxis, i, bufStart, numBufVals, recalcRngCntr); + } break; } } - // drawChrtValAxis(); - // uses BoatValue temp variable to format latest buffer value // doesn't work unfortunately when 'simulation data' is active, because OBP60Formatter generates own simulation value in that case - uint16_t lastVal = dataBuf.getLast(); - currValue.value = lastVal / 1000.0; - currValue.valid = (static_cast(lastVal) != dbMAX_VAL); - LOG_DEBUG(GwLog::DEBUG, "Chart drawChrt: lastVal: %d, currValue-value: %.1f, Valid: %d, Name: %s, Address: %p", lastVal, currValue.value, - currValue.valid, currValue.getName(), (void*)&currValue); - prntCurrValue(&currValue, { x, y }); + currValue.value = dataBuf.getLast(); + currValue.valid = currValue.value != dbMAX_VAL; + Chart::prntCurrValue(currValue, { x, y }); + LOG_DEBUG(GwLog::DEBUG, "Chart drawChrt: currValue-value: %.1f, Valid: %d, Name: %s, Address: %p", currValue.value, currValue.valid, currValue.getName(), (void*)&currValue); } else { // No valid data available - LOG_DEBUG(GwLog::LOG, "PageWindPlot: No valid data available"); getdisplay().setFont(&Ubuntu_Bold10pt8b); + int pX, pY; - if (chrtDir == 0) { - pX = dWidth / 2; + if (chrtDir == 0) { // horizontal chart + pX = cStart.x + (timAxis / 2); pY = cStart.y + (valAxis / 2) - 10; - } else { - pX = valAxis / 2; + } else { // vertical chart + pX = cStart.x + (valAxis / 2); pY = cStart.y + (timAxis / 2) - 10; } - getdisplay().fillRect(pX - 33, pY - 10, 66, 24, commonData->bgcolor); // Clear area for message + + getdisplay().fillRect(pX - 33, pY - 10, 66, 24, bgColor); // Clear area for message drawTextCenter(pX, pY, "No data"); + LOG_DEBUG(GwLog::LOG, "PageWindPlot: No valid data available"); + } +} + +// Get maximum difference of last of dataBuf ringbuffer values to center chart +template +double Chart::getRng(double center, size_t amount) +{ + size_t count = dataBuf.getCurrentSize(); + + if (dataBuf.isEmpty() || amount <= 0) { + return dbMAX_VAL; + } + if (amount > count) + amount = count; + + double value = 0; + double range = 0; + double maxRng = dbMIN_VAL; + + // Start from the newest value (last) and go backwards x times + for (size_t i = 0; i < amount; i++) { + value = dataBuf.get(count - 1 - i); + + if (value == dbMAX_VAL) { + continue; // ignore invalid values + } + + range = abs(fmod((value - center + (M_TWOPI + M_PI)), M_TWOPI) - M_PI); + if (range > maxRng) + maxRng = range; } - drawChrtValAxis(); + if (maxRng > M_PI) { + maxRng = M_PI; + } + + return (maxRng != dbMIN_VAL ? maxRng : dbMAX_VAL); // Return range from to +} + +// check and adjust chart range and set range borders and range middle +template +void Chart::calcChrtBorders(double& rngMid, double& rngMin, double& rngMax, double& rng) +{ + if (chrtDataFmt == 0) { + // Chart data is of any type but 'degree' + + double oldRngMin = rngMin; + double oldRngMax = rngMax; + + // Chart starts at lowest range value, but at least '0' or includes even negative values + double currMinVal = dataBuf.getMin(numBufVals); + LOG_DEBUG(GwLog::DEBUG, "calcChrtRange0a: currMinVal: %.1f, currMaxVal: %.1f, rngMin: %.1f, rngMid: %.1f, rngMax: %.1f, rng: %.1f, rngStep: %.1f, oldRngMin: %.1f, oldRngMax: %.1f, dfltRng: %.1f, numBufVals: %d", + currMinVal, dataBuf.getMax(numBufVals), rngMin, rngMid, rngMax, rng, rngStep, oldRngMin, oldRngMax, dfltRng, numBufVals); + + if (currMinVal != dbMAX_VAL) { // current min value is valid + if (currMinVal > 0 && dbMIN_VAL == 0) { // Chart range starts at least at '0' or includes negative values + rngMin = 0; + } else if (currMinVal < oldRngMin || (oldRngMin < 0 && (currMinVal > (oldRngMin + rngStep)))) { // decrease rngMin if required or increase if lowest value is higher than old rngMin + rngMin = std::floor(currMinVal / rngStep) * rngStep; + } + } // otherwise keep rngMin unchanged + + double currMaxVal = dataBuf.getMax(numBufVals); + if (currMaxVal != dbMAX_VAL) { // current max value is valid + if ((currMaxVal > oldRngMax) || (currMaxVal < (oldRngMax - rngStep))) { // increase rngMax if required or decrease if lowest value is lower than old rngMax + rngMax = std::ceil(currMaxVal / rngStep) * rngStep; + rngMax = std::max(rngMax, rngMin + dfltRng); // keep at least default chart range + } + } // otherwise keep rngMax unchanged + + rngMid = (rngMin + rngMax) / 2.0; + rng = rngMax - rngMin; + LOG_DEBUG(GwLog::DEBUG, "calcChrtRange1a: currMinVal: %.1f, currMaxVal: %.1f, rngMin: %.1f, rngMid: %.1f, rngMax: %.1f, rng: %.1f, rngStep: %.1f, oldRngMin: %.1f, oldRngMax: %.1f, dfltRng: %.1f, numBufVals: %d", + currMinVal, currMaxVal, rngMin, rngMid, rngMax, rng, rngStep, oldRngMin, oldRngMax, dfltRng, numBufVals); + } else { + + if (chrtDataFmt == 1) { + // Chart data is of type 'course' or 'wind' + + if ((count == 1 && rngMid == 0) || rngMid == dbMAX_VAL) { + recalcRngCntr = true; // initialize + } + + // Set rngMid + if (recalcRngCntr) { + rngMid = dataBuf.getMid(numBufVals); + if (rngMid == dbMAX_VAL) { + rngMid = 0; + } else { + rngMid = std::round(rngMid / rngStep) * rngStep; // Set new center value; round to next value + + // Check if range between 'min' and 'max' is > 180° or crosses '0' + rngMin = dataBuf.getMin(numBufVals); + rngMax = dataBuf.getMax(numBufVals); + rng = (rngMax >= rngMin ? rngMax - rngMin : M_TWOPI - rngMin + rngMax); + rng = max(rng, dfltRng); // keep at least default chart range + if (rng > M_PI) { // If wind range > 180°, adjust wndCenter to smaller wind range end + rngMid = WindUtils::to2PI(rngMid + M_PI); + } + } + recalcRngCntr = false; // Reset flag for determination + LOG_DEBUG(GwLog::DEBUG, "calcChrtRange1b: rngMid: %.1f°, rngMin: %.1f°, rngMax: %.1f°, rng: %.1f°, rngStep: %.1f°", rngMid * RAD_TO_DEG, rngMin * RAD_TO_DEG, rngMax * RAD_TO_DEG, + rng * RAD_TO_DEG, rngStep * RAD_TO_DEG); + } + + } else if (chrtDataFmt == 2) { + // Chart data is of type 'rotation'; then we want to have always to be '0' + rngMid = 0; + } + + // check and adjust range between left, center, and right chart limit + double halfRng = rng / 2.0; // we calculate with range between and edges + double diffRng = getRng(rngMid, numBufVals); + // LOG_DEBUG(GwLog::DEBUG, "calcChrtRange2: diffRng: %.1f°, halfRng: %.1f°", diffRng * RAD_TO_DEG, halfRng * RAD_TO_DEG); + diffRng = (diffRng == dbMAX_VAL ? 0 : std::ceil(diffRng / rngStep) * rngStep); + // LOG_DEBUG(GwLog::DEBUG, "calcChrtRange2: diffRng: %.1f°, halfRng: %.1f°", diffRng * RAD_TO_DEG, halfRng * RAD_TO_DEG); + + if (diffRng > halfRng) { + halfRng = diffRng; // round to next value + } else if (diffRng + rngStep < halfRng) { // Reduce chart range for higher resolution if possible + halfRng = max(dfltRng / 2.0, diffRng); + } + + rngMin = WindUtils::to2PI(rngMid - halfRng); + rngMax = (halfRng < M_PI ? rngMid + halfRng : rngMid + halfRng - (M_TWOPI / 360)); // if chart range is 360°, then make 1° smaller than + rngMax = WindUtils::to2PI(rngMax); + // LOG_DEBUG(GwLog::DEBUG, "calcChrtRange2: diffRng: %.1f°, halfRng: %.1f°", diffRng * RAD_TO_DEG, halfRng * RAD_TO_DEG); + + rng = halfRng * 2.0; + LOG_DEBUG(GwLog::DEBUG, "calcChrtRange2b: rngMid: %.1f°, rngMin: %.1f°, rngMax: %.1f°, diffRng: %.1f°, rng: %.1f°, rngStep: %.1f°", rngMid * RAD_TO_DEG, rngMin * RAD_TO_DEG, rngMax * RAD_TO_DEG, + diffRng * RAD_TO_DEG, rng * RAD_TO_DEG, rngStep * RAD_TO_DEG); + } +} + +// chart time axis label + lines +template +void Chart::drawChrtTimeAxis(int8_t chrtIntv) +{ + int timeRng; + float slots, intv, i; + char sTime[6]; + getdisplay().setFont(&Ubuntu_Bold8pt8b); + getdisplay().setTextColor(fgColor); + + if (chrtDir == 0) { // horizontal chart + getdisplay().fillRect(0, top, dWidth, 2, fgColor); + + timeRng = chrtIntv * 4; // Chart time interval: [1] 4 min., [2] 8 min., [3] 12 min., [4] 16 min., [8] 32 min. + slots = timAxis / 80.0; // number of axis labels + intv = timeRng / slots; // minutes per chart axis interval + i = timeRng; // Chart axis label start at -32, -16, -12, ... minutes + + for (int j = 0; j < timAxis - 30; j += 80) { // fill time axis with values but keep area free on right hand side for value label + // LOG_DEBUG(GwLog::DEBUG, "ChartTimeAxis: timAxis: %d, {x,y}: {%d,%d}, i: %.1f, j: %d, chrtIntv: %d, intv: %.1f, slots: %.1f", timAxis, cStart.x, cStart.y, i, j, chrtIntv, intv, slots); + + // Format time label based on interval + if (chrtIntv < 3) { + snprintf(sTime, sizeof(sTime), "-%.1f", i); + } else { + snprintf(sTime, sizeof(sTime), "-%.0f", std::round(i)); + } + + // draw text with appropriate offset + // int tOffset = (j == 0) ? 13 : (chrtIntv < 3 ? -4 : -4); + int tOffset = j == 0 ? 13 : -4; + drawTextCenter(cStart.x + j + tOffset, cStart.y - 8, sTime); + getdisplay().drawLine(cStart.x + j, cStart.y, cStart.x + j, cStart.y + 5, fgColor); // draw short vertical time mark + + i -= intv; + } + + } else { // chrtDir == 1; vertical chart + timeRng = chrtIntv * 4; // chart time interval: [1] 4 min., [2] 8 min., [3] 12 min., [4] 16 min., [8] 32 min. + slots = timAxis / 75.0; // number of axis labels + intv = timeRng / slots; // minutes per chart axis interval + i = -intv; // chart axis label start at -32, -16, -12, ... minutes + + for (int j = 75; j < (timAxis - 75); j += 75) { // don't print time label at upper and lower end of time axis + if (chrtIntv < 3) { // print 1 decimal if time range is single digit (4 or 8 minutes) + snprintf(sTime, sizeof(sTime), "%.1f", i); + } else { + snprintf(sTime, sizeof(sTime), "%.0f", std::floor(i)); + } + + getdisplay().drawLine(cStart.x, cStart.y + j, cStart.x + valAxis, cStart.y + j, fgColor); // Grid line + + if (chrtSz == 0) { // full size chart + getdisplay().fillRect(0, cStart.y + j - 9, 32, 15, bgColor); // clear small area to remove potential chart lines + getdisplay().setCursor((4 - strlen(sTime)) * 7, cStart.y + j + 3); // time value; print left screen; value right-formated + getdisplay().printf("%s", sTime); // Range value + } else if (chrtSz == 2) { // half size chart; right side + drawTextCenter(dWidth / 2, cStart.y + j, sTime); // time value; print mid screen + } + + i -= intv; + } + } +} + +// chart value axis labels + lines +template +void Chart::drawChrtValAxis() +{ + double slots; + int i, intv; + double cVal, cchrtRng, crngMin; + char sVal[6]; + std::unique_ptr tmpBVal; // Temp variable to get formatted and converted data value from OBP60Formatter + tmpBVal = std::unique_ptr(new GwApi::BoatValue(dataBuf.getName())); + tmpBVal->setFormat(dataBuf.getFormat()); + tmpBVal->valid = true; + + if (chrtDir == 0) { // horizontal chart + slots = valAxis / 60.0; // number of axis labels + tmpBVal->value = chrtRng; + cchrtRng = formatValue(tmpBVal.get(), *commonData).cvalue; // value (converted) + intv = static_cast(round(cchrtRng / slots)); + i = intv; + + getdisplay().setFont(&Ubuntu_Bold10pt8b); + + for (int j = 60; j < valAxis - 30; j += 60) { + LOG_DEBUG(GwLog::DEBUG, "ChartValAxis: chrtRng: %.2f, cchrtRng: %.2f, intv: %d, slots: %.1f, valAxis: %d, i: %d, j: %d", chrtRng, cchrtRng, intv, slots, valAxis, i, j); + getdisplay().drawLine(cStart.x, cStart.y + j, cStart.x + timAxis, cStart.y + j, fgColor); + + getdisplay().fillRect(cStart.x, cStart.y + j - 9, cStart.x + 32, 18, bgColor); // Clear small area to remove potential chart lines + String sVal = String(i); + getdisplay().setCursor((3 - sVal.length()) * 8, cStart.y + j + 6); // value right-formated + getdisplay().printf("%s", sVal); // Range value + + i += intv; + } + + getdisplay().setFont(&Ubuntu_Bold12pt8b); + drawTextRalign(cStart.x + timAxis, cStart.y - 3, dbName); // buffer data name + + } else { // chrtDir == 1; vertical chart + getdisplay().setFont(&Ubuntu_Bold10pt8b); + + getdisplay().fillRect(cStart.x, top, valAxis, 2, fgColor); // top chart line + getdisplay().setCursor(cStart.x, cStart.y - 2); + tmpBVal->value = chrtMin; + cVal = formatValue(tmpBVal.get(), *commonData).cvalue; // value (converted) + snprintf(sVal, sizeof(sVal), "%.0f", round(cVal)); + getdisplay().printf("%s", sVal); // Range low end + + tmpBVal->value = chrtMid; + cVal = formatValue(tmpBVal.get(), *commonData).cvalue; // value (converted) + snprintf(sVal, sizeof(sVal), "%.0f", round(cVal)); + drawTextCenter(cStart.x + (valAxis / 2), cStart.y - 10, sVal); // Range mid end + + tmpBVal->value = chrtMax; + cVal = formatValue(tmpBVal.get(), *commonData).cvalue; // value (converted) + snprintf(sVal, sizeof(sVal), "%.0f", round(cVal)); + drawTextRalign(cStart.x + valAxis - 1, cStart.y - 2, sVal); // Range high end + + for (int j = 0; j <= valAxis + 2; j += ((valAxis + 2) / 2)) { + getdisplay().drawLine(cStart.x + j, cStart.y, cStart.x + j, cStart.y + timAxis, fgColor); + } + + if (chrtSz == 0) { + getdisplay().setFont(&Ubuntu_Bold12pt8b); + drawTextCenter(cStart.x + (valAxis / 4) + 5, cStart.y - 11, dbName); // buffer data name + } + LOG_DEBUG(GwLog::DEBUG, "ChartGrd: chrtRng: %.2f, intv: %d, slots: %.1f, valAxis: %d, i: %d", chrtRng, intv, slots, valAxis, i); + } } // Print current data value template -void Chart::prntCurrValue(GwApi::BoatValue* currValue, const Pos chrtPos) +void Chart::prntCurrValue(GwApi::BoatValue& currValue, const Pos chrtPos) { int currentZone; static int lastZone = 0; static bool flipVal = false; int xPosVal; static const int yPosVal = (chrtDir == 0) ? cStart.y + valAxis - 5 : cStart.y + timAxis - 5; + xPosVal = cStart.x + 1; - // flexible move of location for latest boat data value, in case chart data is printed at the current location - /* xPosVal = flipVal ? 8 : valAxis - 135; - currentZone = (chrtPos.y >= yPosVal - 32) && (chrtPos.y <= yPosVal + 6) && (chrtPos.x >= xPosVal - 4) && (chrtPos.x <= xPosVal + 146) ? 1 : 0; // Define current zone for data value - if (currentZone != lastZone) { - // Only flip when x moves to a different zone - if ((chrtPos.y >= yPosVal - 32) && (chrtPos.y <= yPosVal + 6) && (chrtPos.x >= xPosVal - 3) && (chrtPos.x <= xPosVal + 146)) { - flipVal = !flipVal; - xPosVal = flipVal ? 8 : valAxis - 135; - } - } - lastZone = currentZone; */ - - xPosVal = (chrtDir == 0) ? cStart.x + timAxis - 117 : cStart.x + valAxis - 117; - FormattedData frmtDbData = formatValue(currValue, *commonData); + FormattedData frmtDbData = formatValue(&currValue, *commonData); double testdbValue = frmtDbData.value; String sdbValue = frmtDbData.svalue; // value (string) String dbUnit = frmtDbData.unit; // Unit of value - LOG_DEBUG(GwLog::DEBUG, "Chart CurrValue: dbValue: %.2f, sdbValue: %s, fmrtDbValue: %.2f, dbFormat: %s, dbUnit: %s, Valid: %d, Name: %s, Address: %p", currValue->value, sdbValue, - testdbValue, currValue->getFormat(), dbUnit, currValue->valid, currValue->getName(), (void*)currValue); - getdisplay().fillRect(xPosVal - 3, yPosVal - 34, 118, 40, bgColor); // Clear area for TWS value + LOG_DEBUG(GwLog::DEBUG, "Chart CurrValue: dbValue: %.2f, sdbValue: %s, fmrtDbValue: %.2f, dbFormat: %s, dbUnit: %s, Valid: %d, Name: %s, Address: %p", currValue.value, sdbValue, + testdbValue, currValue.getFormat(), dbUnit, currValue.valid, currValue.getName(), currValue); + + getdisplay().fillRect(xPosVal, yPosVal - 34, 121, 40, bgColor); // Clear area for TWS value getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b); - getdisplay().setCursor(xPosVal, yPosVal); - if (useSimuData) - getdisplay().printf("%2.1f", currValue->value); // Value - else + getdisplay().setCursor(xPosVal + 1, yPosVal); + if (useSimuData) { + getdisplay().printf("%2.1f", currValue.value); // Value + } else { getdisplay().print(sdbValue); // Value - // getdisplay().setFont(&Ubuntu_Bold12pt8b); - // getdisplay().setCursor(xPosVal + 76, yPosVal - 14); - // getdisplay().print(dbName); // Name + } + + getdisplay().setFont(&Ubuntu_Bold10pt8b); + getdisplay().setCursor(xPosVal + 76, yPosVal - 17); + getdisplay().print(dbName); // Name + getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setCursor(xPosVal + 76, yPosVal + 1); getdisplay().print(dbUnit); // Unit } -// check and adjust chart range +// Identify Min and Max values of range for course data and select them considering smallest gap +// E.g., Min=30°, Max=270° will be converted to smaller range of Min=270° and Max=30° +// obsolete; creates random results by purpose with large data arrays when data is equally distributed template -void Chart::calcChrtRng() +void Chart::getAngleMinMax(const std::vector& angles, double& rngMin, double& rngMax) { - int diffRng; - - diffRng = dataBuf.getMax(numBufVals) / 1000; - if (diffRng > chrtRng) { - chrtRng = int((diffRng + (diffRng >= 0 ? 9 : -1)) / 10) * 10; // Round up to next 10 value - } else if (diffRng + 10 < chrtRng) { // Reduce chart range for higher resolution if possible - chrtRng = max(dfltRng, int((diffRng + (diffRng >= 0 ? 9 : -1)) / 10) * 10); + if (angles.empty()) { + rngMin = 0; + rngMax = 0; + return; } - LOG_DEBUG(GwLog::DEBUG, "Chart Range: diffRng: %d, chrtRng: %d, Min: %.0f, Max: %.0f", diffRng, chrtRng, dataBuf.getMin(numBufVals) / 1000, dataBuf.getMax(numBufVals) / 1000); + + if (angles.size() == 1) { + rngMin = angles[0]; + rngMax = angles[0]; + return; + } + + // Sort angles + std::vector sorted = angles; + std::sort(sorted.begin(), sorted.end()); + + // Find the largest gap between consecutive angles + double maxGap = 0.0; + int maxGapIndex = 0; + for (size_t i = 0; i < sorted.size(); i++) { + double next = sorted[(i + 1) % sorted.size()]; + double curr = sorted[i]; + + // Calculate gap (wrapping around at 360°/2*Pi) + double gap = (i == sorted.size() - 1) ? (M_TWOPI - curr + next) : (next - curr); + + if (gap > maxGap) { + maxGap = gap; + maxGapIndex = i; + } + } + + // The range is on the opposite side of the largest gap + // Min is after the gap, max is before it + rngMin = sorted[(maxGapIndex + 1) % sorted.size()]; + rngMax = sorted[maxGapIndex]; } // Explicitly instantiate class with required data types to avoid linker errors template class Chart; -template class Chart; // --- Class Chart --------------- diff --git a/lib/obp60task/OBPcharts.h b/lib/obp60task/OBPcharts.h index 2494213..c33f52e 100644 --- a/lib/obp60task/OBPcharts.h +++ b/lib/obp60task/OBPcharts.h @@ -1,7 +1,5 @@ // Function lib for display of boat data in various chart formats #pragma once -#include -#include #include "Pagedata.h" struct Pos { @@ -20,7 +18,7 @@ protected: RingBuffer &dataBuf; // Buffer to display int8_t chrtDir; // Chart timeline direction: [0] = horizontal, [1] = vertical int8_t chrtSz; // Chart size: [0] = full size, [1] = half size left/top, [2] half size right/bottom - int dfltRng; // Default range of chart, e.g. 30 = [0..30] + double dfltRng; // Default range of chart, e.g. 30 = [0..30] uint16_t fgColor; // color code for any screen writing uint16_t bgColor; // color code for screen background bool useSimuData; // flag to indicate if simulation data is active @@ -34,12 +32,18 @@ protected: int dHeight; // Display height int timAxis, valAxis; // size of time and value chart axis Pos cStart; // start point of chart area - int chrtRng; // Range of buffer values from min to max value + double chrtRng; // Range of buffer values from min to max value + double chrtMin; // Range low end value + double chrtMax; // Range high end value + double chrtMid; // Range mid value + double rngStep; // Defines the step of adjustment (e.g. 10 m/s) for value axis range + bool recalcRngCntr = false; // Flag for re-calculation of mid value of chart for wind data types String dbName, dbFormat; // Name and format of data buffer - int16_t dbMAX_VAL; // Highest possible value of buffer of type -> indicates invalid value in buffer + int chrtDataFmt; // Data format of chart: [0] size values; [1] degree of course or wind; [2] rotational degrees + double dbMIN_VAL; // Lowest possible value of buffer of type + double dbMAX_VAL; // Highest possible value of buffer of type ; indicates invalid value in buffer size_t bufSize; // History buffer size: 1.920 values for 32 min. history chart - int intvBufSize; // Buffer size used for currently selected time interval int count; // current size of buffer int numBufVals; // number of wind values available for current interval selection int bufStart; // 1st data value in buffer to show @@ -49,14 +53,17 @@ protected: size_t lastAddedIdx = 0; // Last index of TWD history buffer when new data was added int oldChrtIntv = 0; // remember recent user selection of data interval - void calcChrtRng(); - void drawChrtValAxis(); + void drawChrt(int8_t chrtIntv, GwApi::BoatValue& currValue); // Draw chart line + double getRng(double center, size_t amount); // Calculate range between chart center and edges + void calcChrtBorders(double& rngMid, double& rngMin, double& rngMax, double& rng); // Calculate chart points for value axis and return range between and + void drawChrtTimeAxis(int8_t chrtIntv); // Draw time axis of chart, value and lines + void drawChrtValAxis(); // Draw value axis of chart, value and lines + void prntCurrValue(GwApi::BoatValue& currValue, Pos chrtPos); // Add current boat data value to chart + void getAngleMinMax(const std::vector& angles, double& rngMin, double& rngMax); // Identify Min and Max for course data with smallest gap public: - Chart(RingBuffer& dataBuf, int8_t chrtDir, int8_t chrtSz, int dfltRng, CommonData& common, bool useSimuData); + Chart(RingBuffer& dataBuf, int8_t chrtDir, int8_t chrtSz, double dfltRng, CommonData& common, bool useSimuData); // Chart object of data chart ~Chart(); - void drawChrtTimeAxis(int8_t chrtIntv); - void drawChrt(int8_t chrtIntv, GwApi::BoatValue currValue); - void prntCurrValue(GwApi::BoatValue* currValue, Pos chrtPos); + void showChrt(int8_t chrtIntv, GwApi::BoatValue currValue); // Perform all actions to draw chart }; \ No newline at end of file diff --git a/lib/obp60task/PageWindPlot.cpp b/lib/obp60task/PageWindPlot.cpp index eb2d079..4a115dc 100644 --- a/lib/obp60task/PageWindPlot.cpp +++ b/lib/obp60task/PageWindPlot.cpp @@ -184,22 +184,24 @@ public: GwConfigHandler* config = commonData->config; GwLog* logger = commonData->logger; - static RingBuffer* wdHstry; // Wind direction data buffer + static RingBuffer* wdHstry; // Wind direction data buffer static RingBuffer* wsHstry; // Wind speed data buffer static String wdName, wdFormat; // Wind direction name and format static String wsName, wsFormat; // Wind speed name and format static int16_t wdMAX_VAL; // Max. value of wd history buffer, indicating invalid values - static std::unique_ptr> twsFlChart; // chart object for wind speed chart - static std::unique_ptr> twdHfChart; // chart object for wind direction chart - static std::unique_ptr> twsHfChart; // chart object for wind speed chart - float wsValue; // Wind speed value in chart area - String wsUnit; // Wind speed unit in chart area + static std::unique_ptr> twdFlChart; // chart object for wind direction chart, full size + static std::unique_ptr> twsFlChart; // chart object for wind speed chart, full size + static std::unique_ptr> twdHfChart; // chart object for wind direction chart, half size + static std::unique_ptr> twsHfChart; // chart object for wind speed chart, half size +// float wsValue; // Wind speed value in chart area +// String wsUnit; // Wind speed unit in chart area + static GwApi::BoatValue* wdBVal = new GwApi::BoatValue("TWD"); // temp BoatValue for wind direction unit identification; required by OBP60Formater static GwApi::BoatValue* wsBVal = new GwApi::BoatValue("TWS"); // temp BoatValue for wind speed unit identification; required by OBP60Formater - // current boat data values; TWD/AWD only for validation test - const int numBoatData = 2; - GwApi::BoatValue* bvalue; - bool BDataValid[numBoatData]; + // current boat data values + const int numBoatData = 4; + GwApi::BoatValue* bvalue[numBoatData]; + // bool BDataValid[numBoatData]; static bool isInitialized = false; // Flag to indicate that page is initialized static bool wndDataValid = false; // Flag to indicate if wind data is valid @@ -224,7 +226,10 @@ public: static int wndCenter; // chart wind center value position static int wndLeft; // chart wind left value position static int wndRight; // chart wind right value position - static int chrtRng; // Range of wind values from mid wind value to min/max wind value in degrees + static int chrtRng; // Range of wind values from min to max wind value in degrees + double dfltRngWd; // default range for course chart from min to max value in degrees + double dfltRngWs; // defautl range for wind speed chart from min to max value in m/s + int diffRng; // Difference between mid and current wind value static const int dfltRng = 60; // Default range for chart int midWndDir; // New value for wndCenter after chart start / shift @@ -246,7 +251,7 @@ public: numNoData = 0; bufStart = 0; oldDataIntv = 0; - wsValue = 0; + // wsValue = 0; numAddedBufVals, currIdx, lastIdx = 0; wndCenter = INT_MAX; midWndDir = 0; @@ -256,10 +261,10 @@ public: isInitialized = true; // Set flag to indicate that page is now initialized } - // read boat data values; TWD/AWS only for validation test + // read boat data values for (int i = 0; i < numBoatData; i++) { - bvalue = pageData.values[i]; - BDataValid[i] = bvalue->valid; + bvalue[i] = pageData.values[i]; + // BDataValid[i] = bvalue->valid; } // Optical warning by limit violation (unused) @@ -280,13 +285,17 @@ public: wsHstry->getMetaData(wsName, wsFormat); wdMAX_VAL = wdHstry->getMaxVal(); bufSize = wdHstry->getCapacity(); - wsBVal->setFormat(wsHstry->getFormat()); + wdBVal->setFormat(wdFormat); + wsBVal->setFormat(wsFormat); lastAddedIdx = wdHstry->getLastIdx(); - LOG_DEBUG(GwLog::DEBUG, "PageWindPlot twsChart: *wsHstry: %p", wsHstry); - twsFlChart = std::unique_ptr>(new Chart(*wsHstry, 0, 0, 15, *commonData, useSimuData)); - twdHfChart = std::unique_ptr>(new Chart(*wdHstry, 1, 1, 15, *commonData, useSimuData)); - twsHfChart = std::unique_ptr>(new Chart(*wsHstry, 1, 2, 15, *commonData, useSimuData)); + dfltRngWd = 60.0 * DEG_TO_RAD; // default range for course chart: 60° + dfltRngWs = 7.5; // default range for wind speed chart: 7.5 m/s + LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: *wdHstry: %p, *wsHstry: %p", wdHstry, wsHstry); + twdFlChart = std::unique_ptr>(new Chart(*wdHstry, 1, 0, dfltRngWd, *commonData, useSimuData)); + twsFlChart = std::unique_ptr>(new Chart(*wsHstry, 0, 0, dfltRngWs, *commonData, useSimuData)); + twdHfChart = std::unique_ptr>(new Chart(*wdHstry, 1, 1, dfltRngWd, *commonData, useSimuData)); + twsHfChart = std::unique_ptr>(new Chart(*wsHstry, 1, 2, dfltRngWs, *commonData, useSimuData)); oldShowTruW = showTruW; } @@ -295,7 +304,7 @@ public: getdisplay().setPartialWindow(0, 0, width, height); // Set partial update getdisplay().setTextColor(commonData->fgcolor); - if (chrtMode == 'D') { +/* if (chrtMode == 'D') { // Identify buffer size and buffer start position for chart count = wdHstry->getCurrentSize(); currIdx = wdHstry->getLastIdx(); @@ -478,7 +487,7 @@ public: } else { getdisplay().printf("%4.1f", wsValue); // Value, round to 1 decimal } - } */ + } */ /* getdisplay().setFont(&Ubuntu_Bold12pt8b); getdisplay().setCursor(xPosTws + 82, yPosTws - 14); getdisplay().print(wsName); // Name @@ -512,19 +521,30 @@ public: } getdisplay().printf("%3d", chrtLbl); // Wind value label } +*/ + if (chrtMode == 'D') { + wdBVal->value = wdHstry->getLast(); + wdBVal->valid = wdBVal->value != wdHstry->getMaxVal(); +// twdFlChart->showChrt(dataIntv, *wdBVal); + twdFlChart->showChrt(dataIntv, *bvalue[0]); + } else if (chrtMode == 'S') { -// wsValue = wsHstry->getLast(); - twsFlChart->drawChrtTimeAxis(dataIntv); - LOG_DEBUG(GwLog::DEBUG, "PageWindPlot chart: wsBVal.name: %s, format: %s, wsBVal.value: %.1f, address: %p", wsBVal->getName(), wsBVal->getFormat(), wsBVal->value, wsBVal); - twsFlChart->drawChrt(dataIntv, *wsBVal); + wsBVal->value = wsHstry->getLast(); + wsBVal->valid = wsBVal->value != wsHstry->getMaxVal(); +// twsFlChart->showChrt(dataIntv, *wsBVal); + twsFlChart->showChrt(dataIntv, *bvalue[1]); } else if (chrtMode == 'B') { -// wsValue = wsHstry->getLast(); - twdHfChart->drawChrtTimeAxis(dataIntv); - twsHfChart->drawChrtTimeAxis(dataIntv); - LOG_DEBUG(GwLog::DEBUG, "PageWindPlot chart: wsBVal.name: %s, format: %s, wsBVal.value: %.1f, address: %p", wsBVal->getName(), wsBVal->getFormat(), wsBVal->value, wsBVal); - twdHfChart->drawChrt(dataIntv, *wsBVal); - twsHfChart->drawChrt(dataIntv, *wsBVal); + wdBVal->value = wdHstry->getLast(); + wdBVal->valid = wdBVal->value != wdHstry->getMaxVal(); + wsBVal->value = wsHstry->getLast(); + wsBVal->valid = wsBVal->value != wsHstry->getMaxVal(); + LOG_DEBUG(GwLog::DEBUG, "PageWindPlot showChrt: wsBVal.name: %s, format: %s, wsBVal.value: %.1f, valid: %d, address: %p", wsBVal->getName(), wsBVal->getFormat(), wsBVal->value, + wsBVal->valid, wsBVal); +// twdHfChart->showChrt(dataIntv, *wdBVal); +// twsHfChart->showChrt(dataIntv, *wsBVal); + twdHfChart->showChrt(dataIntv, *bvalue[0]); + twsHfChart->showChrt(dataIntv, *bvalue[1]); } LOG_DEBUG(GwLog::DEBUG, "PageWindPlot time: %ld", millis() - timer); @@ -546,7 +566,7 @@ PageDescription registerPageWindPlot( "WindPlot", // Page name createPage, // Action 0, // Number of bus values depends on selection in Web configuration - { "TWD", "AWD" }, // Bus values we need in the page + { "TWD", "TWS", "AWD", "AWS" }, // Bus values we need in the page true // Show display header on/off ); diff --git a/lib/obp60task/obp60task.cpp b/lib/obp60task/obp60task.cpp index b1c0e01..75796ec 100644 --- a/lib/obp60task/obp60task.cpp +++ b/lib/obp60task/obp60task.cpp @@ -806,7 +806,7 @@ void OBP60Task(GwApi *api){ if (calcTrueWnds) { trueWind.addTrueWind(api, &boatValues, logger); } - // Handle history buffers for TWD, TWS for wind plot page and other usage + // Handle history buffers for certain boat data for windplot page and other usage hstryBufList.handleHstryBuf(useSimuData); // Clear display