From 34af16092b635efc552660129dfbfc05a803ea1d Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Sat, 14 Feb 2026 02:40:23 +0100 Subject: [PATCH 1/7] Calculate AWD conditionless; restructured true wind calculation for clearer decision tree --- lib/obp60task/OBPDataOperations.cpp | 198 +++++++++++++++------------- lib/obp60task/OBPDataOperations.h | 20 +-- lib/obp60task/obp60task.cpp | 6 +- 3 files changed, 119 insertions(+), 105 deletions(-) diff --git a/lib/obp60task/OBPDataOperations.cpp b/lib/obp60task/OBPDataOperations.cpp index c968e11..4230a0b 100644 --- a/lib/obp60task/OBPDataOperations.cpp +++ b/lib/obp60task/OBPDataOperations.cpp @@ -1,5 +1,4 @@ #include "OBPDataOperations.h" -//#include "BoatDataCalibration.h" // Functions lib for data instance calibration // --- Class CalibrationData --------------- CalibrationData::CalibrationData(GwLog* log) @@ -155,8 +154,7 @@ bool CalibrationData::calibrateInstance(GwApi::BoatValue* boatDataValue) if (format == "formatWind") { // instance is of type angle dataValue = (dataValue * slope) + offset; - // dataValue = WindUtils::toPI(dataValue); - dataValue = WindUtils::to2PI(dataValue); // we should call for format of [-180..180], but pages cannot display negative values properly yet + dataValue = WindUtils::to2PI(dataValue); // wind angle data is stored in [0..2PI] format } else if (format == "formatCourse") { // instance is of type direction dataValue = (dataValue * slope) + offset; @@ -169,7 +167,6 @@ bool CalibrationData::calibrateInstance(GwApi::BoatValue* boatDataValue) dataValue = (dataValue * slope) + offset; } - boatDataValue->value = dataValue; // update boat data value with calibrated value calibrationMap[instance].value = dataValue; // store the calibrated value in the list calibrationMap[instance].isCalibrated = true; @@ -247,23 +244,15 @@ void HstryBuf::add(double value) void HstryBuf::handle(bool useSimuData, CommonData& common) { - // GwApi::BoatValue* tmpBVal; std::unique_ptr tmpBVal; // Temp variable to get formatted and converted data value from OBP60Formatter - // create temporary boat value for calibration purposes and retrieval of simulation value - // tmpBVal = new GwApi::BoatValue(boatDataName.c_str()); - tmpBVal = std::unique_ptr(new GwApi::BoatValue(boatDataName)); - tmpBVal->setFormat(boatValue->getFormat()); - tmpBVal->value = boatValue->value; - tmpBVal->valid = boatValue->valid; - if (boatValue->valid) { - // Calibrate boat value before adding it to history buffer - //calibrationData.calibrateInstance(tmpBVal.get(), logger); - //add(tmpBVal->value); add(boatValue->value); - } else if (useSimuData) { // add simulated value to history buffer + tmpBVal = std::unique_ptr(new GwApi::BoatValue(boatDataName)); // create temporary boat value for retrieval of simulation value + tmpBVal->setFormat(boatValue->getFormat()); + tmpBVal->value = boatValue->value; + tmpBVal->valid = boatValue->valid; double simSIValue = formatValue(tmpBVal.get(), common).value; // simulated value is generated at ; here: retreive SI value add(simSIValue); } else { @@ -396,56 +385,75 @@ void WindUtils::addPolar(const double* phi1, const double* r1, toPol(&x1, &y1, phi, r); } -void WindUtils::calcTwdSA(const double* AWA, const double* AWS, - const double* CTW, const double* STW, const double* HDT, - double* TWD, double* TWS, double* TWA, double* AWD) +void WindUtils::calcTwdSA(const double* AWA, const double* AWS, const double* AWD, const double* CTW, const double* STW, const double* HDT, + double* TWA, double* TWS, double* TWD) { - *AWD = *AWA + *HDT; - *AWD = to2PI(*AWD); double stw = -*STW; addPolar(AWD, AWS, CTW, &stw, TWD, TWS); - // Normalize TWD to [0..360°] (2PI) and TWA to [-180..180] (PI) + // Normalize to [0..2PI] *TWD = to2PI(*TWD); - *TWA = toPI(*TWD - *HDT); + *TWA = to2PI(*TWD - *HDT); // for internal storage we normalize wind angle to [0..2PI], as well } -double WindUtils::calcHDT(const double* hdmVal, const double* varVal, const double* cogVal, const double* sogVal) +// calculate HDT from either HDM + VAR, HDM only, or COG +bool WindUtils::calcHDT(const double* hdmVal, const double* varVal, const double* cogVal, const double* sogVal, double* hdtVal) { - double hdt; - double minSogVal = 0.1; // SOG below this value (m/s) is assumed to be data noise from GPS sensor - - if (*hdmVal != DBL_MAX) { - hdt = *hdmVal + (*varVal != DBL_MAX ? *varVal : 0.0); // Use corrected HDM if HDT is not available (or just HDM if VAR is not available) - hdt = to2PI(hdt); - } else if (*cogVal != DBL_MAX && *sogVal >= minSogVal) { - hdt = *cogVal; // Use COG as fallback if HDT and HDM are not available, and SOG is not data noise - } else { - hdt = DBL_MAX; // Cannot calculate HDT without valid HDM or HDM+VAR or COG - } - - return hdt; -} - -bool WindUtils::calcWinds(const double* awaVal, const double* awsVal, - const double* cogVal, const double* stwVal, const double* sogVal, const double* hdtVal, - const double* hdmVal, const double* varVal, double* twdVal, double* twsVal, double* twaVal, double* awdVal) -{ - double stw, hdt, ctw; - double twd, tws, twa, awd; double minSogVal = 0.1; // SOG below this value (m/s) is assumed to be data noise from GPS sensor if (*hdtVal != DBL_MAX) { - hdt = *hdtVal; // Use HDT if available + // HDT already available-> nothing to do + return true; + } + + if (*hdmVal != DBL_MAX) { + *hdtVal = *hdmVal + (*varVal != DBL_MAX ? *varVal : 0.0); // Use corrected HDM if HDT is not available (or just HDM if VAR is not available) + *hdtVal = to2PI(*hdtVal); + } else if (*cogVal != DBL_MAX && *sogVal >= minSogVal) { + *hdtVal = *cogVal; // Use COG as fallback if HDT and HDM are not available, and SOG is not data noise } else { - hdt = calcHDT(hdmVal, varVal, cogVal, sogVal); + *hdtVal = DBL_MAX; // Cannot calculate HDT without valid HDM or HDM+VAR or COG + return false; + } + //LOG_DEBUG(GwLog::DEBUG, "WindUtils:calcHDT: HDT: %.1f, HDM %.1f, VAR %.1f, COG %.1f, SOG %.1f", *hdtVal * RAD_TO_DEG, *hdmVal * RAD_TO_DEG, *varVal * RAD_TO_DEG, + // *cogVal * RAD_TO_DEG, *sogVal * 3.6 / 1.852); + + return true; +} + +// calculate wind direction (either TWD or AWD) from wind angle (TWA or AWA) and HDT +bool WindUtils::calcWD(const double* waVal, const double* hdtVal, double* wdVal) +{ + if (*waVal != DBL_MAX && *hdtVal != DBL_MAX) { + *wdVal = *waVal + *hdtVal; + *wdVal = to2PI(*wdVal); + } else { + *wdVal = DBL_MAX; // Cannot calculate wind direction WD without valid wind angle WA and HDT + return false; + } + + return true; +} + +// calculate full set of true winds (TWA, TWS, TWD) apparent winds and more data +bool WindUtils::calcTrueWinds(const double* awaVal, const double* awsVal, const double* awd, + const double* cogVal, const double* stwVal, const double* sogVal, const double* hdtVal, + double* twaVal, double* twsVal, double* twdVal) +{ + double stw, ctw; + double twd, tws, twa; + double minSogVal = 0.1; // SOG below this value (m/s) is assumed to be data noise from GPS sensor + + if ((*awaVal == DBL_MAX) || (*awsVal == DBL_MAX)) { + // Cannot calculate true winds at all without valid AWA, AWS + return false; } if (*cogVal != DBL_MAX && *sogVal >= minSogVal) { // if SOG is data noise, we don't trust COG ctw = *cogVal; // Use COG for CTW if available } else { - ctw = hdt; // 2nd approximation for CTW; hdt must exist if we reach this part of the code + ctw = *hdtVal; // 2nd approximation for CTW; HDT must exist if we reach this part of code } if (*stwVal != DBL_MAX) { @@ -456,28 +464,21 @@ bool WindUtils::calcWinds(const double* awaVal, const double* awsVal, // If STW and SOG are not available, we cannot calculate true wind return false; } - // LOG_DEBUG(GwLog::DEBUG, "WindUtils:calcWinds: HDT: %.1f, CTW %.1f, STW %.1f", hdt, ctw, stw); + //LOG_DEBUG(GwLog::DEBUG, "WindUtils:calcTrueWinds: HDT: %.1f, CTW %.1f, STW %.1f", *hdtVal * RAD_TO_DEG, ctw * RAD_TO_DEG, stw * 3.6 / 1.852); - if ((*awaVal == DBL_MAX) || (*awsVal == DBL_MAX)) { - // Cannot calculate true wind without valid AWA, AWS; other checks are done earlier - return false; - } else { - calcTwdSA(awaVal, awsVal, &ctw, &stw, &hdt, &twd, &tws, &twa, &awd); - *twdVal = twd; - *twsVal = tws; - *twaVal = twa; - *awdVal = awd; + calcTwdSA(awaVal, awsVal, awd, &ctw, &stw, hdtVal, &twa, &tws, &twd); + *twaVal = twa; + *twsVal = tws; + *twdVal = twd; - return true; - } + return true; } // Calculate true wind data and add to obp60task boat data list -bool WindUtils::addWinds() +bool WindUtils::handleWinds(bool calcWinds) { - double twd, tws, twa, awd, hdt; + double twd, tws, twa, awd; bool twCalculated = false; - bool awdCalculated = false; double awaVal = awaBVal->valid ? awaBVal->value : DBL_MAX; double awsVal = awsBVal->valid ? awsBVal->value : DBL_MAX; @@ -487,49 +488,62 @@ bool WindUtils::addWinds() double hdtVal = hdtBVal->valid ? hdtBVal->value : DBL_MAX; double hdmVal = hdmBVal->valid ? hdmBVal->value : DBL_MAX; double varVal = varBVal->valid ? varBVal->value : DBL_MAX; - //LOG_DEBUG(GwLog::DEBUG, "WindUtils:addWinds: AWA %.1f, AWS %.1f, COG %.1f, STW %.1f, SOG %.2f, HDT %.1f, HDM %.1f, VAR %.1f", awaBVal->value * RAD_TO_DEG, awsBVal->value * 3.6 / 1.852, - // cogBVal->value * RAD_TO_DEG, stwBVal->value * 3.6 / 1.852, sogBVal->value * 3.6 / 1.852, hdtBVal->value * RAD_TO_DEG, hdmBVal->value * RAD_TO_DEG, varBVal->value * RAD_TO_DEG); + double twaVal = twaBVal->valid ? twaBVal->value : DBL_MAX; + double twsVal = twsBVal->valid ? twsBVal->value : DBL_MAX; + //LOG_DEBUG(GwLog::DEBUG, "WindUtils:handleWinds: AWA %.1f, AWS %.1f, AWD %.1f, COG %.1f, STW %.1f, SOG %.2f, HDT %.1f, HDM %.1f, VAR %.1f", awaVal * RAD_TO_DEG, awsVal * 3.6 / 1.852, + // awd * RAD_TO_DEG, cogVal * RAD_TO_DEG, stwVal * 3.6 / 1.852, sogVal * 3.6 / 1.852, hdtVal * RAD_TO_DEG, hdmVal * RAD_TO_DEG, varVal * RAD_TO_DEG); - // Check if TWD can be calculated from TWA and HDT/HDM - if (twaBVal->valid) { - if (!twdBVal->valid) { - if (hdtVal != DBL_MAX) { - hdt = hdtVal; // Use HDT if available - } else { - hdt = calcHDT(&hdmVal, &varVal, &cogVal, &sogVal); - } - twd = twaBVal->value + hdt; - twd = to2PI(twd); + if (calcHDT(&hdmVal, &varVal, &cogVal, &sogVal, &hdtVal)) { + hdtBVal->value = hdtVal; + hdtBVal->valid = true; + } else { + // determination of HDT was not possible due to missing prerequisite data + hdtBVal->valid = false; + return false; + } + + // calculate AWD if not existing and if possible + if (!awdBVal->valid) { + if (calcWD(&awaVal, &hdtVal, &awd)) { + awdBVal->value = awd; + awdBVal->valid = true; + } else { + awdBVal->valid = false; + return false; + } + } + + // calculate TWD if not existing and if possible + if (!twdBVal->valid) { + // calculate TWD if it does not exist yet and TWA is available + if (calcWD(&twaVal, &hdtVal, &twd)) { twdBVal->value = twd; twdBVal->valid = true; + } else { + twdBVal->valid = false; } + } - } else { - // Calculate true winds and AWD; if true winds exist, use at least AWD calculation - twCalculated = calcWinds(&awaVal, &awsVal, &cogVal, &stwVal, &sogVal, &hdtVal, &hdmVal, &varVal, &twd, &tws, &twa, &awd); - + if (calcWinds && (!twaBVal->valid || !twsBVal->valid || !twdBVal->valid)) { + // calculate true winds if user setting and at least one of three true wind values does not exist + twCalculated = calcTrueWinds(&awaVal, &awsVal, &awd, &cogVal, &stwVal, &sogVal, &hdtVal, &twa, &tws, &twd); if (twCalculated) { // Replace values only, if successfully calculated and not already available - if (!twdBVal->valid) { - twdBVal->value = twd; - twdBVal->valid = true; + if (!twaBVal->valid) { + twaBVal->value = twa; + twaBVal->valid = true; } if (!twsBVal->valid) { twsBVal->value = tws; twsBVal->valid = true; } - if (!twaBVal->valid) { - //twaBVal->value = twa; - twaBVal->value = to2PI(twa); // convert to [0..360], because pages cannot display negative values properly yet - twaBVal->valid = true; - } - if (!awdBVal->valid) { - awdBVal->value = awd; - awdBVal->valid = true; + if (!twdBVal->valid) { + twdBVal->value = twd; + twdBVal->valid = true; } } } - // LOG_DEBUG(GwLog::DEBUG, "WindUtils:addWinds: twCalculated %d, TWD %.1f, TWA %.1f, TWS %.2f kn, AWD: %.1f", twCalculated, twdBVal->value * RAD_TO_DEG, - // twaBVal->value * RAD_TO_DEG, twsBVal->value * 3.6 / 1.852, awdBVal->value * RAD_TO_DEG); + //LOG_DEBUG(GwLog::DEBUG, "WindUtils:handleWinds: twCalculated %d, TWD %.1f, TWA %.1f, TWS %.2f kn, AWD: %.1f", twCalculated, twdBVal->value * RAD_TO_DEG, + // twaBVal->value * RAD_TO_DEG, twsBVal->value * 3.6 / 1.852, awdBVal->value * RAD_TO_DEG); return twCalculated; } diff --git a/lib/obp60task/OBPDataOperations.h b/lib/obp60task/OBPDataOperations.h index 9c5b783..d4e41e8 100644 --- a/lib/obp60task/OBPDataOperations.h +++ b/lib/obp60task/OBPDataOperations.h @@ -19,7 +19,7 @@ private: } tCalibrationData; std::unordered_map calibrationMap; // list of calibration data instances - std::unordered_map lastValue; // array for last smoothed values of boat data values + std::unordered_map lastValue; // array for last smoothed value of boat data values GwLog* logger; static constexpr int8_t MAX_CALIBRATION_DATA = 4; // maximum number of calibration data instances @@ -50,6 +50,7 @@ public: void handle(bool useSimuData, CommonData& common); }; +// Manage history buffer for supported boat data; used by boat data charts class HstryBuffers { private: std::map> hstryBuffers; @@ -97,7 +98,8 @@ public: class WindUtils { private: GwApi::BoatValue *twaBVal, *twsBVal, *twdBVal; - GwApi::BoatValue *awaBVal, *awsBVal, *awdBVal, *cogBVal, *stwBVal, *sogBVal, *hdtBVal, *hdmBVal, *varBVal; + GwApi::BoatValue *awaBVal, *awsBVal, *awdBVal; + GwApi::BoatValue *cogBVal, *stwBVal, *sogBVal, *hdtBVal, *hdmBVal, *varBVal; static constexpr double DBL_MAX = std::numeric_limits::max(); GwLog* logger; @@ -128,12 +130,12 @@ public: void addPolar(const double* phi1, const double* r1, const double* phi2, const double* r2, double* phi, double* r); - void calcTwdSA(const double* AWA, const double* AWS, - const double* CTW, const double* STW, const double* HDT, - double* TWD, double* TWS, double* TWA, double* AWD); - static double calcHDT(const double* hdmVal, const double* varVal, const double* cogVal, const double* sogVal); - bool calcWinds(const double* awaVal, const double* awsVal, + void calcTwdSA(const double* AWA, const double* AWS, const double* AWD, const double* CTW, const double* STW, const double* HDT, + double* TWA, double* TWS, double* TWD); + bool calcHDT(const double* hdmVal, const double* varVal, const double* cogVal, const double* sogVal, double* hdtVal); + bool calcWD(const double* waVal, const double* hdtVal, double* wdVal); + bool calcTrueWinds(const double* awaVal, const double* awsVal, const double* awd, const double* cogVal, const double* stwVal, const double* sogVal, const double* hdtVal, - const double* hdmVal, const double* varVal, double* twdVal, double* twsVal, double* twaVal, double* awdVal); - bool addWinds(); + double* twdVal, double* twsVal, double* twaVal); + bool handleWinds(bool calcWinds); }; \ No newline at end of file diff --git a/lib/obp60task/obp60task.cpp b/lib/obp60task/obp60task.cpp index cc825fa..f20bfe6 100644 --- a/lib/obp60task/obp60task.cpp +++ b/lib/obp60task/obp60task.cpp @@ -811,11 +811,9 @@ void OBP60Task(GwApi *api){ api->getBoatDataValues(boatValues.numValues,boatValues.allBoatValues); api->getStatus(commonData.status); - if (calcTrueWnds) { - trueWind.addWinds(); // calculate true wind data from apparent wind values - } + trueWind.handleWinds(calcTrueWnds); // calculate true wind data from apparent wind values calibrationDataList.handleCalibration(&boatValues); // Process calibration for all boat data in - hstryBufferList.handleHstryBufs(useSimuData, commonData); // Handle history buffers for certain boat data for windplot page and other usage + hstryBufferList.handleHstryBufs(useSimuData, commonData); // Handle history buffers for certain boat data for charts and other usage // Clear display // getdisplay().fillRect(0, 0, getdisplay().width(), getdisplay().height(), commonData.bgcolor); From f21c8da2d2728eace76f8b3ac2c236f9168696f2 Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Sun, 15 Feb 2026 21:50:20 +0100 Subject: [PATCH 2/7] Clear old code fragments from history buffer class --- lib/obp60task/OBPDataOperations.cpp | 39 ++++++++++------------------- lib/obp60task/OBPDataOperations.h | 4 +-- lib/obp60task/obp60task.cpp | 3 ++- 3 files changed, 17 insertions(+), 29 deletions(-) diff --git a/lib/obp60task/OBPDataOperations.cpp b/lib/obp60task/OBPDataOperations.cpp index 4230a0b..278ef0e 100644 --- a/lib/obp60task/OBPDataOperations.cpp +++ b/lib/obp60task/OBPDataOperations.cpp @@ -249,7 +249,7 @@ void HstryBuf::handle(bool useSimuData, CommonData& common) if (boatValue->valid) { add(boatValue->value); } else if (useSimuData) { // add simulated value to history buffer - tmpBVal = std::unique_ptr(new GwApi::BoatValue(boatDataName)); // create temporary boat value for retrieval of simulation value + tmpBVal = std::unique_ptr(new GwApi::BoatValue(boatDataName)); // create temporary boat value for retrieval of simulation value tmpBVal->setFormat(boatValue->getFormat()); tmpBVal->value = boatValue->value; tmpBVal->valid = boatValue->valid; @@ -265,20 +265,7 @@ void HstryBuf::handle(bool useSimuData, CommonData& common) HstryBuffers::HstryBuffers(int size, BoatValueList* boatValues, GwLog* log) : size(size) , boatValueList(boatValues) - , logger(log) -{ - - // collect boat values for true wind calculation - // should all have been already created at true wind object initialization - // potentially to be moved to history buffer handling - awaBVal = boatValueList->findValueOrCreate("AWA"); - hdtBVal = boatValueList->findValueOrCreate("HDT"); - hdmBVal = boatValueList->findValueOrCreate("HDM"); - varBVal = boatValueList->findValueOrCreate("VAR"); - cogBVal = boatValueList->findValueOrCreate("COG"); - sogBVal = boatValueList->findValueOrCreate("SOG"); - awdBVal = boatValueList->findValueOrCreate("AWD"); -} + , logger(log) { }; // Create history buffer for boat data type void HstryBuffers::addBuffer(const String& name) @@ -290,8 +277,6 @@ void HstryBuffers::addBuffer(const String& name) return; } - hstryBuffers[name] = std::unique_ptr(new HstryBuf(name, size, boatValueList, logger)); - // Initialize metadata for buffer String valueFormat = bufferParams[name].format; // Data format of boat data type // String valueFormat = boatValueList->findValueOrCreate(name)->getFormat().c_str(); // Unfortunately, format is not yet available during system initialization @@ -300,6 +285,7 @@ void HstryBuffers::addBuffer(const String& name) double bufferMinVal = bufferParams[name].bufferMinVal; // Min value for this history buffer double bufferMaxVal = bufferParams[name].bufferMaxVal; // Max value for this history buffer + hstryBuffers[name] = std::unique_ptr(new HstryBuf(name, size, boatValueList, logger)); hstryBuffers[name]->init(valueFormat, hstryUpdFreq, mltplr, bufferMinVal, bufferMaxVal); LOG_DEBUG(GwLog::DEBUG, "HstryBuffers: new buffer added: name: %s, format: %s, multiplier: %d, min value: %.2f, max value: %.2f", name, valueFormat, mltplr, bufferMinVal, bufferMaxVal); } @@ -318,8 +304,9 @@ RingBuffer* HstryBuffers::getBuffer(const String& name) auto it = hstryBuffers.find(name); if (it != hstryBuffers.end()) { return &it->second->hstryBuf; + } else { + return nullptr; } - return nullptr; } // --- End Class HstryBuffers --------------- @@ -415,8 +402,8 @@ bool WindUtils::calcHDT(const double* hdmVal, const double* varVal, const double *hdtVal = DBL_MAX; // Cannot calculate HDT without valid HDM or HDM+VAR or COG return false; } - //LOG_DEBUG(GwLog::DEBUG, "WindUtils:calcHDT: HDT: %.1f, HDM %.1f, VAR %.1f, COG %.1f, SOG %.1f", *hdtVal * RAD_TO_DEG, *hdmVal * RAD_TO_DEG, *varVal * RAD_TO_DEG, - // *cogVal * RAD_TO_DEG, *sogVal * 3.6 / 1.852); + // LOG_DEBUG(GwLog::DEBUG, "WindUtils:calcHDT: HDT: %.1f, HDM %.1f, VAR %.1f, COG %.1f, SOG %.1f", *hdtVal * RAD_TO_DEG, *hdmVal * RAD_TO_DEG, *varVal * RAD_TO_DEG, + // *cogVal * RAD_TO_DEG, *sogVal * 3.6 / 1.852); return true; } @@ -464,7 +451,7 @@ bool WindUtils::calcTrueWinds(const double* awaVal, const double* awsVal, const // If STW and SOG are not available, we cannot calculate true wind return false; } - //LOG_DEBUG(GwLog::DEBUG, "WindUtils:calcTrueWinds: HDT: %.1f, CTW %.1f, STW %.1f", *hdtVal * RAD_TO_DEG, ctw * RAD_TO_DEG, stw * 3.6 / 1.852); + // LOG_DEBUG(GwLog::DEBUG, "WindUtils:calcTrueWinds: HDT: %.1f, CTW %.1f, STW %.1f", *hdtVal * RAD_TO_DEG, ctw * RAD_TO_DEG, stw * 3.6 / 1.852); calcTwdSA(awaVal, awsVal, awd, &ctw, &stw, hdtVal, &twa, &tws, &twd); *twaVal = twa; @@ -490,8 +477,8 @@ bool WindUtils::handleWinds(bool calcWinds) double varVal = varBVal->valid ? varBVal->value : DBL_MAX; double twaVal = twaBVal->valid ? twaBVal->value : DBL_MAX; double twsVal = twsBVal->valid ? twsBVal->value : DBL_MAX; - //LOG_DEBUG(GwLog::DEBUG, "WindUtils:handleWinds: AWA %.1f, AWS %.1f, AWD %.1f, COG %.1f, STW %.1f, SOG %.2f, HDT %.1f, HDM %.1f, VAR %.1f", awaVal * RAD_TO_DEG, awsVal * 3.6 / 1.852, - // awd * RAD_TO_DEG, cogVal * RAD_TO_DEG, stwVal * 3.6 / 1.852, sogVal * 3.6 / 1.852, hdtVal * RAD_TO_DEG, hdmVal * RAD_TO_DEG, varVal * RAD_TO_DEG); + // LOG_DEBUG(GwLog::DEBUG, "WindUtils:handleWinds: AWA %.1f, AWS %.1f, AWD %.1f, COG %.1f, STW %.1f, SOG %.2f, HDT %.1f, HDM %.1f, VAR %.1f", awaVal * RAD_TO_DEG, awsVal * 3.6 / 1.852, + // awd * RAD_TO_DEG, cogVal * RAD_TO_DEG, stwVal * 3.6 / 1.852, sogVal * 3.6 / 1.852, hdtVal * RAD_TO_DEG, hdmVal * RAD_TO_DEG, varVal * RAD_TO_DEG); if (calcHDT(&hdmVal, &varVal, &cogVal, &sogVal, &hdtVal)) { hdtBVal->value = hdtVal; @@ -512,7 +499,7 @@ bool WindUtils::handleWinds(bool calcWinds) return false; } } - + // calculate TWD if not existing and if possible if (!twdBVal->valid) { // calculate TWD if it does not exist yet and TWA is available @@ -542,8 +529,8 @@ bool WindUtils::handleWinds(bool calcWinds) } } } - //LOG_DEBUG(GwLog::DEBUG, "WindUtils:handleWinds: twCalculated %d, TWD %.1f, TWA %.1f, TWS %.2f kn, AWD: %.1f", twCalculated, twdBVal->value * RAD_TO_DEG, - // twaBVal->value * RAD_TO_DEG, twsBVal->value * 3.6 / 1.852, awdBVal->value * RAD_TO_DEG); + // LOG_DEBUG(GwLog::DEBUG, "WindUtils:handleWinds: twCalculated %d, TWD %.1f, TWA %.1f, TWS %.2f kn, AWD: %.1f", twCalculated, twdBVal->value * RAD_TO_DEG, + // twaBVal->value * RAD_TO_DEG, twsBVal->value * 3.6 / 1.852, awdBVal->value * RAD_TO_DEG); return twCalculated; } diff --git a/lib/obp60task/OBPDataOperations.h b/lib/obp60task/OBPDataOperations.h index d4e41e8..5631483 100644 --- a/lib/obp60task/OBPDataOperations.h +++ b/lib/obp60task/OBPDataOperations.h @@ -32,6 +32,7 @@ public: bool smoothInstance(GwApi::BoatValue* boatDataValue); // Smooth single boat data value }; +// Class for a single history buffer of boat values class HstryBuf { private: RingBuffer hstryBuf; // Circular buffer to store history values @@ -50,14 +51,13 @@ public: void handle(bool useSimuData, CommonData& common); }; -// Manage history buffer for supported boat data; used by boat data charts +// Manage list of history buffers for supported boat data; used by boat data charts class HstryBuffers { private: std::map> hstryBuffers; int size; // size of all history buffers BoatValueList* boatValueList; GwLog* logger; - GwApi::BoatValue *awaBVal, *hdtBVal, *hdmBVal, *varBVal, *cogBVal, *sogBVal, *awdBVal; // boat values for true wind calculation struct HistoryParams { int hstryUpdFreq; // update frequency of history buffer (documentation only) diff --git a/lib/obp60task/obp60task.cpp b/lib/obp60task/obp60task.cpp index 5db9e58..ec95b6d 100644 --- a/lib/obp60task/obp60task.cpp +++ b/lib/obp60task/obp60task.cpp @@ -479,7 +479,8 @@ void OBP60Task(GwApi *api){ pages[i].parameters.values.push_back(value); } - // Read the specified boat data types of relevant pages and create a history buffer for each type + // Read the specified boat data types of relevant pages and create a history buffer for each type for later use in charts + // applies only for pages that uses charts if (pages[i].parameters.pageName == "OneValue" || pages[i].parameters.pageName == "TwoValues" || pages[i].parameters.pageName == "WindPlot") { for (auto pVal : pages[i].parameters.values) { hstryBufferList.addBuffer(pVal->getName()); From 79aeeed65c0664068fd45045c025992a36782af5 Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Fri, 20 Feb 2026 23:59:59 +0100 Subject: [PATCH 3/7] Show correct time line on horizontal charts --- lib/obp60task/OBPcharts.cpp | 35 +++++++++++++++++---------------- lib/obp60task/PageOneValue.cpp | 2 -- lib/obp60task/PageTwoValues.cpp | 2 -- lib/obp60task/PageWindPlot.cpp | 3 +++ 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/lib/obp60task/OBPcharts.cpp b/lib/obp60task/OBPcharts.cpp index 8ae38ec..c393b7f 100644 --- a/lib/obp60task/OBPcharts.cpp +++ b/lib/obp60task/OBPcharts.cpp @@ -434,46 +434,47 @@ Pos Chart::setCurrentChartPoint(const int i, const char direction, const double // chart time axis label + lines void Chart::drawChrtTimeAxis(const char chrtDir, const int8_t chrtSz, const int8_t chrtIntv) { - float axSlots, intv, i; + int axSlots, intv, i, timeRng; char sTime[6]; - int timeRng = chrtIntv * 4; // chart time interval: [1] 4 min., [2] 8 min., [3] 12 min., [4] 16 min., [8] 32 min. + int tOffset; getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setTextColor(fgColor); - axSlots = 5; // number of axis labels - intv = timAxis / (axSlots - 1); // minutes per chart axis interval (interval is 1 less than axSlots) - i = timeRng; // Chart axis label start at -32, -16, -12, ... minutes + intv = 60; // print axis label each 60 pixels = seconds + axSlots = timAxis / intv; // number of axis labels; value will be truncated to full number + i = axSlots * chrtIntv * -1; // current minute label if (chrtDir == HORIZONTAL) { getdisplay().fillRect(0, cRoot.y, dWidth, 2, fgColor); - for (float j = 0; j < timAxis - 1; j += intv) { // fill time axis with values but keep area free on right hand side for value label + LOG_DEBUG(GwLog::DEBUG, "Chart::drawChrtTimeAxis: intv: %d, axSlots: %d, timAxis: %d, chrtIntv: %d", intv, axSlots, timAxis, chrtIntv); + for (int j = intv; j < timAxis - 1; j += intv) { // fill time axis with values but keep area free on right hand side for value label // draw text with appropriate offset - int tOffset = j == 0 ? 13 : -4; - snprintf(sTime, sizeof(sTime), "-%.0f", i); + tOffset = j == 0 ? 13 : -4; + snprintf(sTime, sizeof(sTime), "%d", i); drawTextCenter(cRoot.x + j + tOffset, cRoot.y - 8, sTime); getdisplay().drawLine(cRoot.x + j, cRoot.y, cRoot.x + j, cRoot.y + 5, fgColor); // draw short vertical time mark - i -= chrtIntv; + i += chrtIntv; } } else { // vertical chart for (float j = intv; j < timAxis - 1; j += intv) { // don't print time label at upper and lower end of time axis - i -= chrtIntv; // we start not at top chart position - snprintf(sTime, sizeof(sTime), "-%.0f", i); + snprintf(sTime, sizeof(sTime), "%d", i); getdisplay().drawLine(cRoot.x, cRoot.y + j, cRoot.x + valAxis, cRoot.y + j, fgColor); // Grid line if (chrtSz == FULL_SIZE) { // full size chart getdisplay().fillRect(0, cRoot.y + j - 9, 32, 15, bgColor); // clear small area to remove potential chart lines getdisplay().setCursor((4 - strlen(sTime)) * 7, cRoot.y + j + 3); // time value; print left screen; value right-formated - getdisplay().printf("%s", sTime); // Range value + getdisplay().printf("%s", sTime); // time value } else if (chrtSz == HALF_SIZE_RIGHT) { // half size chart; right side drawTextCenter(dWidth / 2, cRoot.y + j, sTime); // time value; print mid screen } + i += chrtIntv; } } } @@ -491,11 +492,11 @@ void Chart::drawChrtValAxis(const char chrtDir, const int8_t chrtSz, bool prntNa if (chrtSz == FULL_SIZE) { + // print buffer data name on left hand side of time axis (max. size 5 characters) font = &Ubuntu_Bold12pt8b; - - // print buffer data name on right hand side of time axis (max. size 5 characters) getdisplay().setFont(font); - drawTextRalign(cRoot.x + timAxis, cRoot.y - 3, dbName.substring(0, 5)); + getdisplay().fillRect(cRoot.x + timAxis - 57, cRoot.y + 2, 58, 20, bgColor); // clear small area to remove potential chart lines + drawTextRalign(cRoot.x + timAxis, cRoot.y + 19, dbName.substring(0, 5)); if (chrtDataFmt == WIND) { prntHorizChartThreeValueAxisLabel(font); @@ -509,11 +510,11 @@ void Chart::drawChrtValAxis(const char chrtDir, const int8_t chrtSz, bool prntNa } else { // half size chart -> just print edge values + middle chart line font = &Ubuntu_Bold10pt8b; - if (prntName) { // print buffer data name on right hand side of time axis (max. size 5 characters) getdisplay().setFont(font); - drawTextRalign(cRoot.x + timAxis, cRoot.y - 3, dbName.substring(0, 5)); + getdisplay().fillRect(cRoot.x + timAxis - 57, cRoot.y + 2, 58, 20, bgColor); // clear small area to remove potential chart lines + drawTextRalign(cRoot.x + timAxis, cRoot.y + 16, dbName.substring(0, 5)); } prntHorizChartThreeValueAxisLabel(font); diff --git a/lib/obp60task/PageOneValue.cpp b/lib/obp60task/PageOneValue.cpp index 78cc6e6..fd9509a 100644 --- a/lib/obp60task/PageOneValue.cpp +++ b/lib/obp60task/PageOneValue.cpp @@ -209,8 +209,6 @@ public: dataIntv = 3; } else if (dataIntv == 3) { dataIntv = 4; - } else if (dataIntv == 4) { - dataIntv = 8; } else { dataIntv = 1; } diff --git a/lib/obp60task/PageTwoValues.cpp b/lib/obp60task/PageTwoValues.cpp index 5990656..eaf25d3 100644 --- a/lib/obp60task/PageTwoValues.cpp +++ b/lib/obp60task/PageTwoValues.cpp @@ -211,8 +211,6 @@ public: dataIntv = 3; } else if (dataIntv == 3) { dataIntv = 4; - } else if (dataIntv == 4) { - dataIntv = 8; } else { dataIntv = 1; } diff --git a/lib/obp60task/PageWindPlot.cpp b/lib/obp60task/PageWindPlot.cpp index da948c2..9e6e879 100644 --- a/lib/obp60task/PageWindPlot.cpp +++ b/lib/obp60task/PageWindPlot.cpp @@ -231,6 +231,9 @@ public: } else if (chrtMode == SPEED) { if (wsChart) { + if (dataIntv == 8) { + dataIntv = 1; // horizontal charts show max. 4 x 7 min. only; no factor 8 multiplier + } wsChart->showChrt(HORIZONTAL, FULL_SIZE, dataIntv, PRNT_NAME, PRNT_VALUE, *wsBVal); } From a44572675767d7844e562992d342036e5dbba89e Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Thu, 26 Mar 2026 21:57:17 +0100 Subject: [PATCH 4/7] Convert class to accept flexible data types --- lib/obp60task/OBPSensorTask.cpp | 4 +- lib/obp60task/movingAvg.cpp | 67 ------------------------- lib/obp60task/movingAvg.h | 26 +++++++--- lib/obp60task/movingAvg.tpp | 88 +++++++++++++++++++++++++++++++++ 4 files changed, 108 insertions(+), 77 deletions(-) delete mode 100644 lib/obp60task/movingAvg.cpp create mode 100644 lib/obp60task/movingAvg.tpp diff --git a/lib/obp60task/OBPSensorTask.cpp b/lib/obp60task/OBPSensorTask.cpp index 1424907..5d355aa 100644 --- a/lib/obp60task/OBPSensorTask.cpp +++ b/lib/obp60task/OBPSensorTask.cpp @@ -66,8 +66,8 @@ void sensorTask(void *param){ const int avgsize = 300; constexpr int arrayBatV{avgsize}; constexpr int arrayBatC{avgsize}; - movingAvg batV(arrayBatV); - movingAvg batC(arrayBatC); + movingAvg batV(arrayBatV); + movingAvg batC(arrayBatC); batV.begin(); batC.begin(); diff --git a/lib/obp60task/movingAvg.cpp b/lib/obp60task/movingAvg.cpp deleted file mode 100644 index b9a46ab..0000000 --- a/lib/obp60task/movingAvg.cpp +++ /dev/null @@ -1,67 +0,0 @@ -// Arduino Moving Average Library -// https://github.com/JChristensen/movingAvg -// Copyright (C) 2018 by Jack Christensen and licensed under -// GNU GPL v3.0, https://www.gnu.org/licenses/gpl.html - -#include - -// initialize - allocate the interval array -void movingAvg::begin() -{ - m_readings = new int[m_interval]; -} - -// add a new reading and return the new moving average -int movingAvg::reading(int newReading) -{ - // add each new data point to the sum until the m_readings array is filled - if (m_nbrReadings < m_interval) { - ++m_nbrReadings; - m_sum += newReading; - } - // once the array is filled, subtract the oldest data point and add the new one - else { - m_sum = m_sum - m_readings[m_next] + newReading; - } - - m_readings[m_next] = newReading; - if (++m_next >= m_interval) m_next = 0; - return (m_sum + m_nbrReadings / 2) / m_nbrReadings; -} - -// just return the current moving average -int movingAvg::getAvg() -{ - return (m_sum + m_nbrReadings / 2) / m_nbrReadings; -} - -// return the average for a subset of the data, the most recent nPoints readings. -// for invalid values of nPoints, return zero. -int movingAvg::getAvg(int nPoints) -{ - if (nPoints < 1 || nPoints > m_interval || nPoints > m_nbrReadings) { - return 0; - } - else { - long sum{0}; - int i = m_next; - for (int n=0; n class movingAvg { public: movingAvg(int interval) - : m_interval{interval}, m_nbrReadings{0}, m_sum{0}, m_next{0} {} + : m_interval{interval}, m_nbrReadings{0}, m_sum{0}, m_next{0}, m_readings{nullptr} {} + ~movingAvg() { delete[] m_readings; } void begin(); - int reading(int newReading); - int getAvg(); - int getAvg(int nPoints); - int getCount() {return m_nbrReadings;} + T reading(T newReading); + T getAvg(); + T getAvg(int nPoints); + int getCount() { return m_nbrReadings; } void reset(); - int* getReadings() {return m_readings;} + T* getReadings() { return m_readings; } private: int m_interval; // number of data points for the moving average int m_nbrReadings; // number of readings - long m_sum; // sum of the m_readings array + // Sum type adapts to T: long for integers, T for floating point + using SumType = typename std::conditional::value, T, long>::type; + SumType m_sum; int m_next; // index to the next reading - int* m_readings; // pointer to the dynamically allocated interval array + T* m_readings; // pointer to the dynamically allocated interval array }; + +// Include the implementation to satisfy template instantiation requirements +#include "movingAvg.tpp" + #endif diff --git a/lib/obp60task/movingAvg.tpp b/lib/obp60task/movingAvg.tpp new file mode 100644 index 0000000..4468ee0 --- /dev/null +++ b/lib/obp60task/movingAvg.tpp @@ -0,0 +1,88 @@ +// Arduino Moving Average Library +// https://github.com/JChristensen/movingAvg +// Copyright (C) 2018 by Jack Christensen and licensed under +// GNU GPL v3.0, https://www.gnu.org/licenses/gpl.html + +// Extended to template class for handling of multiple data types + +//template +//movingAvg::movingAvg(int interval) +// : m_interval{interval}, m_nbrReadings{0}, m_sum{0}, m_next{0}, m_readings{nullptr} +//{} + +// initialize - allocate the interval array +template +void movingAvg::begin() +{ + m_readings = new T[m_interval]; +} + +// add a new reading and return the new moving average +template +T movingAvg::reading(T newReading) +{ + // add each new data point to the sum until the m_readings array is filled + if (m_nbrReadings < m_interval) { + ++m_nbrReadings; + m_sum += newReading; + } + // once the array is filled, subtract the oldest data point and add the new one + else { + m_sum = m_sum - m_readings[m_next] + newReading; + } + + m_readings[m_next] = newReading; + if (++m_next >= m_interval) m_next = 0; + return getAvg(); +} + +// just return the current moving average +template +T movingAvg::getAvg() +{ + if (m_nbrReadings == 0) return 0; + + // Apply rounding for integers only + if (std::is_floating_point::value) { + return m_sum / m_nbrReadings; + } else { + return (m_sum + (SumType)m_nbrReadings / 2) / m_nbrReadings; + } +} + +// return the average for a subset of the data, the most recent nPoints readings. +// for invalid values of nPoints, return zero. +template +T movingAvg::getAvg(int nPoints) +{ + if (nPoints < 1 || nPoints > m_interval || nPoints > m_nbrReadings) { + return 0; + } + + SumType sum{0}; + int i = m_next; + for (int n=0; n::value) { + return sum / nPoints; + } else { + return (sum + (SumType)nPoints / 2) / nPoints; // + } +} + +// start the moving average over again +template +void movingAvg::reset() +{ + m_nbrReadings = 0; + m_sum = 0; + m_next = 0; +} \ No newline at end of file From 1f0e3cb6eb117e0e314a0125570fdd5a5178009b Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Thu, 26 Mar 2026 21:58:31 +0100 Subject: [PATCH 5/7] Delete unnecessary include of in some Pages --- lib/obp60task/PageBattery2.cpp | 1 - lib/obp60task/PageGenerator.cpp | 1 - lib/obp60task/PageSolar.cpp | 1 - lib/obp60task/PageVoltage.cpp | 1 - 4 files changed, 4 deletions(-) diff --git a/lib/obp60task/PageBattery2.cpp b/lib/obp60task/PageBattery2.cpp index 05ae52e..b691c30 100644 --- a/lib/obp60task/PageBattery2.cpp +++ b/lib/obp60task/PageBattery2.cpp @@ -2,7 +2,6 @@ #include "Pagedata.h" #include "OBP60Extensions.h" -#include "movingAvg.h" // Lib for moving average building class PageBattery2 : public Page { diff --git a/lib/obp60task/PageGenerator.cpp b/lib/obp60task/PageGenerator.cpp index 05550cb..9f0b93e 100644 --- a/lib/obp60task/PageGenerator.cpp +++ b/lib/obp60task/PageGenerator.cpp @@ -2,7 +2,6 @@ #include "Pagedata.h" #include "OBP60Extensions.h" -#include "movingAvg.h" // Lib for moving average building class PageGenerator : public Page { diff --git a/lib/obp60task/PageSolar.cpp b/lib/obp60task/PageSolar.cpp index e06b95e..4fd20a5 100644 --- a/lib/obp60task/PageSolar.cpp +++ b/lib/obp60task/PageSolar.cpp @@ -2,7 +2,6 @@ #include "Pagedata.h" #include "OBP60Extensions.h" -#include "movingAvg.h" // Lib for moving average building class PageSolar : public Page { diff --git a/lib/obp60task/PageVoltage.cpp b/lib/obp60task/PageVoltage.cpp index adf3d28..de467ba 100644 --- a/lib/obp60task/PageVoltage.cpp +++ b/lib/obp60task/PageVoltage.cpp @@ -2,7 +2,6 @@ #include "Pagedata.h" #include "OBP60Extensions.h" -#include "movingAvg.h" // Lib for moving average building class PageVoltage : public Page { From 3deb80b2c65300a4422cbb6c6db1137cc8d04d02 Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Thu, 26 Mar 2026 22:08:00 +0100 Subject: [PATCH 6/7] Add smoothing option for any chart plot --- lib/obp60task/OBPcharts.cpp | 31 +++++++++++++++++++++++++++++-- lib/obp60task/OBPcharts.h | 5 +++-- lib/obp60task/PageWindPlot.cpp | 2 +- lib/obp60task/config_obp40.json | 11 +++++++++++ lib/obp60task/config_obp60.json | 11 +++++++++++ 5 files changed, 55 insertions(+), 5 deletions(-) diff --git a/lib/obp60task/OBPcharts.cpp b/lib/obp60task/OBPcharts.cpp index 071007a..d384db8 100644 --- a/lib/obp60task/OBPcharts.cpp +++ b/lib/obp60task/OBPcharts.cpp @@ -42,6 +42,11 @@ Chart::Chart(RingBuffer& dataBuf, double dfltRng, CommonData& common, dbMAX_VAL = dataBuf.getMaxVal(); bufSize = dataBuf.getCapacity(); + smoothCharts = common.config->getBool(common.config->smoothCharts); + if (smoothCharts) { + chrtAvg.begin(); + } + // Initialize chart data format; shorter version of standard format indicator if (dbFormat == "formatCourse" || dbFormat == "formatWind" || dbFormat == "formatRot") { chrtDataFmt = WIND; // Chart is showing data of course / wind format @@ -337,6 +342,23 @@ void Chart::drawChartLines(const char direction, const int8_t chrtIntv, const do double chrtVal; // Current data value Pos point, prevPoint; // current and previous chart point + if (smoothCharts) { + // prime moving average filter to ensure the first plotted point is already averaged + + chrtAvg.reset(); + + // Feed the filter the 10 values preceding bufStart + for (int p = 10; p > 0; p--) { + // Calculate index with wrapping: (start - offset + size) % size + int primeIdx = (bufStart - (p * chrtIntv)); + double primeVal = dataBuf.get(primeIdx); + + if (primeVal != dbMAX_VAL) { + chrtAvg.reading(primeVal); + } + } + } + for (int i = 0; i < (numBufVals / chrtIntv); i++) { chrtVal = dataBuf.get(bufStart + (i * chrtIntv)); // show the latest wind values in buffer; keep 1st value constant in a rolling buffer @@ -345,6 +367,11 @@ void Chart::drawChartLines(const char direction, const int8_t chrtIntv, const do chrtPrevVal = dbMAX_VAL; } else { + if (smoothCharts) { + // if chart lines shall be smoothed, apply moving average filter of the last 10 values + chrtVal = chrtAvg.reading(chrtVal); + } + point = setCurrentChartPoint(i, direction, chrtVal, chrtScale); // if (i >= (numBufVals / chrtIntv) - 5) // log chart data of 1 line (adjust for test purposes) @@ -399,7 +426,7 @@ void Chart::drawChartLines(const char direction, const int8_t chrtIntv, const do if (chrtDataFmt == WIND) { // degree of course or wind recalcRngMid = true; - LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: chart end: timAxis: %d, i: %d, bufStart: %d, numBufVals: %d, recalcRngCntr: %d", timAxis, i, bufStart, numBufVals, recalcRngMid); + // LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: chart end: timAxis: %d, i: %d, bufStart: %d, numBufVals: %d, recalcRngCntr: %d", timAxis, i, bufStart, numBufVals, recalcRngMid); } break; } @@ -454,7 +481,7 @@ void Chart::drawChrtTimeAxis(const char chrtDir, const int8_t chrtSz, const int8 if (chrtDir == HORIZONTAL) { getdisplay().fillRect(0, cRoot.y, dWidth, 2, fgColor); - LOG_DEBUG(GwLog::DEBUG, "Chart::drawChrtTimeAxis: intv: %d, axSlots: %d, timAxis: %d, chrtIntv: %d", intv, axSlots, timAxis, chrtIntv); + // LOG_DEBUG(GwLog::DEBUG, "Chart::drawChrtTimeAxis: intv: %d, axSlots: %d, timAxis: %d, chrtIntv: %d", intv, axSlots, timAxis, chrtIntv); for (int j = intv; j < timAxis - 1; j += intv) { // fill time axis with values but keep area free on right hand side for value label // draw text with appropriate offset diff --git a/lib/obp60task/OBPcharts.h b/lib/obp60task/OBPcharts.h index fbdcddd..8978fd3 100644 --- a/lib/obp60task/OBPcharts.h +++ b/lib/obp60task/OBPcharts.h @@ -2,6 +2,7 @@ #pragma once #include "Pagedata.h" #include "OBP60Extensions.h" +#include "movingAvg.h" struct Pos { int x; @@ -44,12 +45,11 @@ protected: static constexpr bool NO_SIMUDATA = true; // switch off simulation feature of function RingBuffer& dataBuf; // Buffer to display - //char chrtDir; // Chart timeline direction: 'H' = horizontal, 'V' = vertical - //int8_t chrtSz; // Chart size: [0] = full size, [1] = half size left/top, [2] half size right/bottom 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 + bool smoothCharts; // flag to indicate if charts shall be printed with smoothed gradient String tempFormat; // user defined format for temperature double zeroValue; // "0" SI value for temperature @@ -84,6 +84,7 @@ protected: bool bufDataValid = false; // Flag to indicate if buffer data is valid int oldChrtIntv = 0; // remember recent user selection of data interval + movingAvg chrtAvg{7}; // Store average of the last 7 chart values if chart gradient shall be smoothed double chrtPrevVal; // Last data value in chart area int x, y; // x and y coordinates for drawing int prevX, prevY; // Last x and y coordinates for drawing diff --git a/lib/obp60task/PageWindPlot.cpp b/lib/obp60task/PageWindPlot.cpp index 48b8d78..8f6554c 100644 --- a/lib/obp60task/PageWindPlot.cpp +++ b/lib/obp60task/PageWindPlot.cpp @@ -37,7 +37,7 @@ private: bool oldShowTruW = false; // remember recent user selection of wind data type int8_t dataIntv = 1; // Update interval for wind history chart: - // (1)|(2)|(3)|(4)|(8) x 240 seconds for 4, 8, 12, 16, 32 min. history chart + // (1)|(2)|(3)|(4)|(8) seconds for up to 32 min. history chart bool useSimuData; // bool holdValues; String flashLED; diff --git a/lib/obp60task/config_obp40.json b/lib/obp60task/config_obp40.json index 7d2662d..0926692 100644 --- a/lib/obp60task/config_obp40.json +++ b/lib/obp60task/config_obp40.json @@ -230,6 +230,17 @@ "obp40": "true" } }, + { + "name": "smoothCharts", + "label": "Smooth chart lines", + "type": "boolean", + "default": "false", + "description": "Print chart lines with a smoothed gradient", + "category": "OBP40 Settings", + "capabilities": { + "obp40": "true" + } + }, { "name": "lengthFormat", "label": "Length Format", diff --git a/lib/obp60task/config_obp60.json b/lib/obp60task/config_obp60.json index 305688f..a51f1f5 100644 --- a/lib/obp60task/config_obp60.json +++ b/lib/obp60task/config_obp60.json @@ -230,6 +230,17 @@ "obp60": "true" } }, + { + "name": "smoothCharts", + "label": "Smooth chart lines", + "type": "boolean", + "default": "false", + "description": "Print chart lines with a smoothed gradient", + "category": "OBP60 Settings", + "capabilities": { + "obp40": "true" + } + }, { "name": "lengthFormat", "label": "Length Format", From 530c1014217acb35d73b3ac5ac2c67ae0a52ac57 Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Thu, 26 Mar 2026 22:27:08 +0100 Subject: [PATCH 7/7] Adjust default ranges for charts --- lib/obp60task/OBPcharts.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/obp60task/OBPcharts.cpp b/lib/obp60task/OBPcharts.cpp index d384db8..eda15e9 100644 --- a/lib/obp60task/OBPcharts.cpp +++ b/lib/obp60task/OBPcharts.cpp @@ -6,9 +6,9 @@ std::map Chart::dfltChrtDta = { { "formatWind", { 60.0 * DEG_TO_RAD, 10.0 * DEG_TO_RAD } }, // default course range 60 degrees { "formatCourse", { 60.0 * DEG_TO_RAD, 10.0 * DEG_TO_RAD } }, // default course range 60 degrees - { "formatKnots", { 7.71, 2.56 } }, // default speed range in m/s - { "formatDepth", { 15.0, 5.0 } }, // default depth range in m - { "kelvinToC", { 30.0, 5.0 } } // default temp range in °C/K + { "formatKnots", { 2.57, 2.57 } }, // default speed range in m/s + { "formatDepth", { 10.0, 5.0 } }, // default depth range in m + { "kelvinToC", { 20.0, 5.0 } } // default temp range in °C/K }; // --- Class Chart ---------------