diff --git a/lib/obp60task/OBPDataOperations.cpp b/lib/obp60task/OBPDataOperations.cpp index 1d84a70..44bf277 100644 --- a/lib/obp60task/OBPDataOperations.cpp +++ b/lib/obp60task/OBPDataOperations.cpp @@ -107,7 +107,6 @@ void CalibrationData::readConfig(GwConfigHandler* config) LOG_DEBUG(GwLog::LOG, "Calibration data type added: %s, offset: %f, slope: %f, smoothing: %f", instance.c_str(), calibrationMap[instance].offset, calibrationMap[instance].slope, calibrationMap[instance].smooth); } - // LOG_DEBUG(GwLog::LOG, "All calibration data read"); } // Handle calibrationMap and calibrate all boat data values @@ -208,15 +207,14 @@ bool CalibrationData::smoothInstance(GwApi::BoatValue* boatDataValue) calibrationMap[instance].value = dataValue; // store the smoothed value in the list calibrationMap[instance].isCalibrated = true; - // LOG_DEBUG(GwLog::DEBUG, "BoatDataCalibration: %s: smooth: %f, oldValue: %f, result: %f", instance.c_str(), smoothFactor, oldValue, calibrationMap[instance].value); - return true; } // --- End Class CalibrationData --------------- // --- Class HstryBuf --------------- -HstryBuf::HstryBuf(const String& name, int size, BoatValueList* boatValues, GwLog* log) +HstryBuf::HstryBuf(const String& name, const int size, BoatValueList* boatValues, const bool smooth, GwLog* log) : logger(log) + , smoothing(smooth) , boatDataName(name) { hstryBuf.resize(size); @@ -232,23 +230,42 @@ void HstryBuf::init(const String& format, int updFreq, double mltplr, double min if (!boatValue->valid) { boatValue->value = std::numeric_limits::max(); // mark current value invalid } + + isTypeAngle = format == "formatCourse" || format == "formatWind" || format == "formatRot"; + // initialize correct variant of buffer for averaging history data + if (smoothing) { + if (isTypeAngle) { + chrtAvgAngle.begin(); + } else { + chrtAvg.begin(); + } + } } -void HstryBuf::add(double value) +void HstryBuf::add(const double value) { + double bufVal = value; + if (value >= hstryMin && value <= hstryMax) { - hstryBuf.add(value); + + if (smoothing) { + if (isTypeAngle) { + bufVal = chrtAvgAngle.reading(value); + } else { + bufVal = chrtAvg.reading(value); + } + } + + hstryBuf.add(bufVal); // LOG_DEBUG(GwLog::DEBUG, "HstryBuf::add: name: %s, value: %.3f, value buffer: %.3f", hstryBuf.getName(), value, hstryBuf.getLast()); } } -void HstryBuf::handle(bool useSimuData, CommonData& common) +void HstryBuf::handle(const bool useSimuData, CommonData& common) { if ((millis() - bufUpdateTime) >= hstryBuf.getUpdFreq()) { bufUpdateTime = millis(); - // LOG_DEBUG(GwLog::DEBUG, "HstryBuf::handle: name: %s, frequency: %d, format: %s, value: %.3f", hstryBuf.getName(), hstryBuf.getUpdFreq(), - // boatValue->getFormat().c_str(), boatValue->value); if (boatValue->valid) { add(boatValue->value); @@ -262,20 +279,20 @@ void HstryBuf::handle(bool useSimuData, CommonData& common) double simSIValue = formatValue(tmpBVal.get(), common).value; // simulated value is generated at ; here: retreive SI value add(simSIValue); } else { - // here we will add invalid (DBL_MAX) value; this will mark periods of missing data in buffer together with a timestamp + // TODO: add invalid (DBL_MAX) value; this will mark periods of missing data in buffer together with a timestamp } } } // --- End Class HstryBuf --------------- // --- Class HstryBuffers --------------- -HstryBuffers::HstryBuffers(int size, BoatValueList* boatValues, GwLog* log) +HstryBuffers::HstryBuffers(const int size, BoatValueList* boatValues, GwLog* log) : size(size) , boatValueList(boatValues) , logger(log) { }; // Create history buffer for boat data type -void HstryBuffers::addBuffer(const String& name) +void HstryBuffers::addBuffer(const String& name, const bool smooth) { if (HstryBuffers::getBuffer(name) != nullptr) { // buffer for this data type already exists return; @@ -288,8 +305,9 @@ void HstryBuffers::addBuffer(const String& name) } // create buffer only; initialization with metadata can only be done later after boat data have been updated first time - hstryBuffers[name] = std::unique_ptr(new HstryBuf(name, size, boatValueList, logger)); - LOG_DEBUG(GwLog::DEBUG, "HstryBuffers: new buffer added: name: %s", name); + hstryBuffers[name] = std::unique_ptr(new HstryBuf(name, size, boatValueList, smooth, logger)); + + LOG_DEBUG(GwLog::LOG, "HstryBuffers: new buffer added: name: %s", name); } // Handle all registered history buffers @@ -301,7 +319,7 @@ void HstryBuffers::handleHstryBufs(bool useSimuData, CommonData& common) if (!buf->hasMetaData()) { // meta data initialization for buffer has not been done before String valueFormat = boatValueList->findValueOrCreate(buf->boatDataName)->getFormat().c_str(); - LOG_DEBUG(GwLog::DEBUG, "HstryBuffers: value name: %s, format: %s", boatValueList->findValueOrCreate(buf->boatDataName)->getName().c_str(), valueFormat); + // LOG_DEBUG(GwLog::DEBUG, "HstryBuffers: value name: %s, format: %s", boatValueList->findValueOrCreate(buf->boatDataName)->getName().c_str(), valueFormat); if (!valueFormat.isEmpty()) { String lookupKey = buf->boatDataName; @@ -321,7 +339,7 @@ void HstryBuffers::handleHstryBufs(bool useSimuData, CommonData& common) hstryBuffers[buf->boatDataName]->init(valueFormat, hstryUpdFreq, mltplr, bufferMinVal, bufferMaxVal); buf->metaDataDefined = true; - LOG_DEBUG(GwLog::DEBUG, "HstryBuffers::handleBufs: metadata added: name: %s, format: %s, frequency: %d, multiplier: %f, min value: %.2f, max value: %.2f", buf->boatDataName, valueFormat, hstryUpdFreq, + LOG_DEBUG(GwLog::LOG, "HstryBuffers::handleBufs: metadata added: name: %s, format: %s, frequency: %d, multiplier: %f, min value: %.2f, max value: %.2f", buf->boatDataName, valueFormat, hstryUpdFreq, mltplr, bufferMinVal, bufferMaxVal); } } @@ -433,8 +451,6 @@ 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); return true; } @@ -482,7 +498,6 @@ 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); calcTwdSA(awaVal, awsVal, awd, &ctw, &stw, hdtVal, &twa, &tws, &twd); *twaVal = twa; @@ -501,7 +516,7 @@ void WindUtils::setMaxWs(GwApi::BoatValue* wsMaxValue, const double* wsVal) maxWs = *wsVal; } - if ( maxWs > 0.0 && maxWs >= wsMaxValue->value) { + if (maxWs > 0.0 && maxWs >= wsMaxValue->value) { wsMaxValue->value = maxWs; // overwrite core gateway value each second again with own user task value if that value is larger wsMaxValue->valid = true; } @@ -532,24 +547,18 @@ bool WindUtils::handleWinds(bool calcWinds) return false; } - // calculate AWD if not existing and if possible + // calculate AWD if it does not exist yet and AWA is available if (!awdBVal->valid) { if (calcWD(&awaVal, &hdtVal, &awd)) { awdBVal->value = awd; awdBVal->valid = true; } else { awdBVal->valid = false; - return false; } } - if (!calcWinds) { // don't calculate true winds if not set in configuration - return twCalculated; - } - - // calculate TWD if not existing and if possible + // calculate TWD if it does not exist yet and TWA is available 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; @@ -558,6 +567,10 @@ bool WindUtils::handleWinds(bool calcWinds) } } + if (!calcWinds) { // don't calculate true winds from apparent winds if not set in configuration + return twCalculated; + } + if (!twaBVal->valid || !twsBVal->valid || !twdBVal->valid) { // calculate true winds at least one of three true wind values does not exist twCalculated = calcTrueWinds(&awaVal, &awsVal, &awd, &cogVal, &stwVal, &sogVal, &hdtVal, &twa, &tws, &twd); @@ -576,8 +589,6 @@ 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); return twCalculated; } diff --git a/lib/obp60task/OBPDataOperations.h b/lib/obp60task/OBPDataOperations.h index 558a03b..3f4d209 100644 --- a/lib/obp60task/OBPDataOperations.h +++ b/lib/obp60task/OBPDataOperations.h @@ -3,6 +3,7 @@ #include "OBPRingBuffer.h" #include "Pagedata.h" #include "obp60task.h" +#include "movingAvg.h" #include #include @@ -36,10 +37,14 @@ public: class HstryBuf { private: RingBuffer hstryBuf; // Circular buffer to store history values + movingAvg chrtAvg {6}; // Store average of the last 6 chart values if chart gradient shall be smoothed + movingAvgAngle chrtAvgAngle {6}; // Store average of the last 6 chart values (angle data) if chart gradient shall be smoothed String boatDataName; double hstryMin; double hstryMax; bool metaDataDefined = false; + bool smoothing = false; + bool isTypeAngle = false; unsigned long bufUpdateTime; GwApi::BoatValue* boatValue; GwLog* logger; @@ -47,7 +52,7 @@ private: friend class HstryBuffers; public: - HstryBuf(const String& name, int size, BoatValueList* boatValues, GwLog* log); + HstryBuf(const String& name, const int size, BoatValueList* boatValues, const bool smooth, GwLog* log); bool hasMetaData() const { return metaDataDefined; }; void init(const String& format, int updFreq, double mltplr, double minVal, double maxVal); void add(double value); @@ -101,7 +106,7 @@ private: public: HstryBuffers(int size, BoatValueList* boatValues, GwLog* log); - void addBuffer(const String& name); + void addBuffer(const String& name, const bool smooth); void handleHstryBufs(bool useSimuData, CommonData& common); RingBuffer* getBuffer(const String& name); }; @@ -115,6 +120,12 @@ private: static constexpr double DBL_MAX = std::numeric_limits::max(); GwLog* logger; + // specify missing data for boat value type AWD; AWD is not available in core gateway and need to be specified here + void defineAWD() { + awdBVal->setFormat("formatCourse"); + awdBVal->valid = false; + } + public: WindUtils(BoatValueList* boatValues, GwLog* log) : logger(log) @@ -125,19 +136,22 @@ public: twdBVal = boatValues->findValueOrCreate("TWD"); awaBVal = boatValues->findValueOrCreate("AWA"); awsBVal = boatValues->findValueOrCreate("AWS"); - awdBVal = boatValues->findValueOrCreate("AWD"); cogBVal = boatValues->findValueOrCreate("COG"); stwBVal = boatValues->findValueOrCreate("STW"); sogBVal = boatValues->findValueOrCreate("SOG"); hdtBVal = boatValues->findValueOrCreate("HDT"); hdmBVal = boatValues->findValueOrCreate("HDM"); varBVal = boatValues->findValueOrCreate("VAR"); + + awdBVal = boatValues->findValueOrCreate("AWD"); + defineAWD(); }; static double to2PI(double a); static double toPI(double a); static double to360(double a); static double to180(double a); + void toCart(const double* phi, const double* r, double* x, double* y); void toPol(const double* x, const double* y, double* phi, double* r); void addPolar(const double* phi1, const double* r1, diff --git a/lib/obp60task/OBPRingBuffer.h b/lib/obp60task/OBPRingBuffer.h index 332dddc..d2bb015 100644 --- a/lib/obp60task/OBPRingBuffer.h +++ b/lib/obp60task/OBPRingBuffer.h @@ -80,6 +80,8 @@ public: 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 getCircularMid() const; // Get mid value of circle (degree) values of buffer + double getCircularMid(size_t amount) const; // Get mid value of circle (degree) values 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) diff --git a/lib/obp60task/OBPRingBuffer.tpp b/lib/obp60task/OBPRingBuffer.tpp index 1b88498..5609584 100644 --- a/lib/obp60task/OBPRingBuffer.tpp +++ b/lib/obp60task/OBPRingBuffer.tpp @@ -1,24 +1,7 @@ -#include "OBPRingBuffer.h" #include #include #include -template -void RingBuffer::initCommon() -{ - NUMLIMIT_LOW = std::numeric_limits::lowest(); - NUMLIMIT_HIGH = std::numeric_limits::max(); - dataName = ""; - dataFmt = ""; - updFreq = -1; - mltplr = 1; - BUFMIN_VAL = static_cast(NUMLIMIT_LOW); - BUFMAX_VAL = static_cast(NUMLIMIT_HIGH); - lowest = BUFMIN_VAL; - highest = BUFMAX_VAL; - bufLocker = xSemaphoreCreateMutex(); -} - template RingBuffer::RingBuffer() : capacity(0) @@ -47,6 +30,22 @@ RingBuffer::RingBuffer(size_t size) buffer.resize(size, NUMLIMIT_HIGH); // NUMLIMIT_HIGH indicate invalid values } +template +void RingBuffer::initCommon() +{ + NUMLIMIT_LOW = std::numeric_limits::lowest(); + NUMLIMIT_HIGH = std::numeric_limits::max(); + dataName = ""; + dataFmt = ""; + updFreq = -1; + mltplr = 1; + BUFMIN_VAL = static_cast(NUMLIMIT_LOW); + BUFMAX_VAL = static_cast(NUMLIMIT_HIGH); + lowest = BUFMIN_VAL; + highest = BUFMAX_VAL; + bufLocker = xSemaphoreCreateMutex(); +} + // Specify meta data of buffer content template void RingBuffer::setMetaData(String name, String format, int updateFrequency, double multiplier, double minValue, double maxValue) @@ -59,7 +58,7 @@ void RingBuffer::setMetaData(String name, String format, int updateFrequency, BUFMIN_VAL = static_cast(NUMLIMIT_LOW) / mltplr; // lowest possible buffer value; converted to external view BUFMAX_VAL = static_cast(NUMLIMIT_HIGH) / mltplr; // highest possible buffer value; converted to external view lowest = std::max(BUFMIN_VAL, minValue); // low value range, set by user - highest = std::min(std::nextafter(BUFMAX_VAL, -std::numeric_limits::infinity()), maxValue); // high value range, set by user; max. is 1 tick smaller than BUFMAX_VAL + highest = std::min(std::nextafter(BUFMAX_VAL, -std::numeric_limits::infinity()), maxValue); // high value range, set by user; maximum is 1 tick lower than BUFMAX_VAL } // Specify format of buffer content @@ -285,7 +284,7 @@ double RingBuffer::getMedian(size_t amount) const amount = count; // Create a temporary vector with current valid elements - std::vector temp; + std::vector temp; temp.reserve(amount); for (size_t i = 0; i < amount; i++) { @@ -295,16 +294,84 @@ double RingBuffer::getMedian(size_t amount) const // Sort to find median std::sort(temp.begin(), temp.end()); + if (temp[0] == BUFMAX_VAL) { // 1st element of sorted vector is already BUFMAX_VAL -> only invalid entries in buffer, so we return invalid value + return BUFMAX_VAL; + } + if (amount % 2 == 1) { // Odd number of elements - return static_cast(temp[amount / 2]); + return temp[amount / 2]; } else { // Even number of elements - return average of middle two - // Note: For integer types, this truncates. For floating point, it's exact. - return static_cast((temp[amount / 2 - 1] + temp[amount / 2]) / 2); + return (temp[amount / 2 - 1] + temp[amount / 2]) / 2; } } +// Get mid value of circle (degree) values of buffer +template +double RingBuffer::getCircularMid() const +{ + return getCircularMid(getCurrentSize()); +} + +// Get mid value of circle (degree) values of the last values of buffer +template +double RingBuffer::getCircularMid(size_t amount) const +{ + if (isEmpty() || amount <= 0) { + return BUFMAX_VAL; + } + if (amount > count) + amount = count; + + std::vector a; + // Create a temporary vector with current valid elements + std::vector temp; + temp.reserve(amount); + + for (size_t i = 0; i < amount; i++) { + temp.push_back(get(count - 1 - i)); + } + + // Sort to find largest gap + std::sort(temp.begin(), temp.end()); + + if (temp[0] == BUFMAX_VAL) { // 1st element of sorted vector is already BUFMAX_VAL -> only invalid entries in buffer, so we return invalid value + return BUFMAX_VAL; + } + + // Find the largest gap + double largestGap = BUFMIN_VAL; + std::size_t gapIndex = 0; + + for (std::size_t i = 0; i < temp.size(); ++i) + { + std::size_t next = (i + 1) % temp.size(); + + double gap; + if (next == 0) + gap = (temp[0] + M_TWOPI) - temp[i]; + else + gap = temp[next] - temp[i]; + + if (gap > largestGap) + { + largestGap = gap; + gapIndex = i; + } + } + + double start = temp[(gapIndex + 1) % temp.size()]; // Start of occupied arc = first angle after largest gap + double arcWidth = M_TWOPI - largestGap; // Width of occupied arc + arcWidth = start + arcWidth / 2.0; + arcWidth = fmod(arcWidth, M_TWOPI); + if (arcWidth < 0.0) { + arcWidth += M_TWOPI; + } + + return arcWidth; // Midpoint of occupied arc +} + // Get the buffer capacity (maximum size) template size_t RingBuffer::getCapacity() const diff --git a/lib/obp60task/OBPcharts.cpp b/lib/obp60task/OBPcharts.cpp index 81b5749..ae9a7b6 100644 --- a/lib/obp60task/OBPcharts.cpp +++ b/lib/obp60task/OBPcharts.cpp @@ -28,11 +28,6 @@ Chart::Chart(RingBuffer& dataBuf, CommonData& common, bool useSimuData dHeight = getdisplay().height(); #endif - smoothCharts = commonData->config->getBool(commonData->config->smoothCharts); - if (smoothCharts) { - chrtAvg.begin(); - } - init(); }; @@ -51,7 +46,7 @@ bool Chart::init() dbMIN_VAL = dataBuf.getMinVal(); dbMAX_VAL = dataBuf.getMaxVal(); bufSize = dataBuf.getCapacity(); - LOG_DEBUG(GwLog::DEBUG, "Chart Init: dbMIN_VAL: %.2f, dbMAX_VAL: %.2fd, bufSize: %d", dbMIN_VAL, dbMAX_VAL, bufSize); + // LOG_DEBUG(GwLog::DEBUG, "Chart Init: dbMIN_VAL: %.2f, dbMAX_VAL: %.2fd, bufSize: %d", dbMIN_VAL, dbMAX_VAL, bufSize); // Initialize chart data format; shorter version of standard format indicator if (dbFormat == "formatCourse" || dbFormat == "formatWind") { @@ -102,9 +97,10 @@ bool Chart::init() chrtMid = (chrtMin + chrtMax) / 2; chrtRng = dfltRng; recalcRngMid = true; // initialize and chart borders on first chart display call + allLeft = true; + allRight = true; - if (dbFormat.isEmpty()) { - // data buffer may not exist yet, because boat data object is not available yet + if (dbFormat.isEmpty()) { // data buffer may not exist yet, because boat data object is not available yet initValid = false; // chart object will get invalid data during initialization } else { initValid = true; @@ -122,8 +118,7 @@ bool Chart::init() // ; print data name on horizontal half chart [true|false] // : print current boat data value [true|false] // : current boat data value; used only for test on valid data -// void Chart::showChrt(ChrtDirection chrtDire, ChrtSize chrtSze, const int8_t chrtIntv, bool prntName, bool showCurrValue, GwApi::BoatValue currValue) -void Chart::showChrt(const char chrtDir, const int8_t chrtSz, const int8_t chrtIntv, bool prntName, bool showCurrValue, GwApi::BoatValue currValue) +void Chart::showChrt(const ChrtDir chrtDir, ChrtSize chrtSz, const int8_t chrtIntv, bool prntName, bool showCurrValue, GwApi::BoatValue currValue) { if (!setChartDimensions(chrtDir, chrtSz)) { return; // wrong chart dimension parameters @@ -146,31 +141,30 @@ void Chart::showChrt(const char chrtDir, const int8_t chrtSz, const int8_t chrtI } // define dimensions and start points for chart -// bool Chart::setChartDimensions(const ChrtDirection direction, const ChrtSize size) -bool Chart::setChartDimensions(const char direction, const int8_t size) +bool Chart::setChartDimensions(const ChrtDir chrtDir, const ChrtSize chrtSz) { - if ((direction != HORIZONTAL && direction != VERTICAL) || (size < 0 || size > 3)) { + if ((chrtDir != HORIZONTAL && chrtDir != VERTICAL) || (chrtSz < 0 || chrtSz > 3)) { LOG_DEBUG(GwLog::ERROR, "obp60:setChartDimensions %s: wrong parameters", dataBuf.getName()); return false; } - if (direction == HORIZONTAL) { + if (chrtDir == HORIZONTAL) { // horizontal chart timeline direction timAxis = dWidth - 1; - switch (size) { - case 0: + switch (chrtSz) { + case ChrtSize::FULL_SIZE: valAxis = dHeight - top - bottom; cRoot = { 0, top - 1 }; break; - case 1: + case HALF_SIZE_LEFT_TOP: valAxis = (dHeight - top - bottom) / 2 - hGap; cRoot = { 0, top - 1 }; break; - case 2: + case HALF_SIZE_RIGHT_BOTTOM: valAxis = (dHeight - top - bottom) / 2 - hGap; cRoot = { 0, top + (valAxis + hGap) + hGap - 1 }; break; - case 3: + case TWO_THIRD_TOP: valAxis = (dHeight - top - bottom) * 0.667 - hGap; cRoot = { 0, top - 1 }; break; @@ -179,34 +173,34 @@ bool Chart::setChartDimensions(const char direction, const int8_t size) cRoot = { 0, top - 1 }; } - } else if (direction == VERTICAL) { + } else { // vertical chart timeline direction timAxis = dHeight - top - bottom; - switch (size) { - case 0: + switch (chrtSz) { + case FULL_SIZE: valAxis = dWidth - 1; cRoot = { 0, top - 1 }; break; - case 1: + case HALF_SIZE_LEFT_TOP: valAxis = dWidth / 2 - vGap; cRoot = { 0, top - 1 }; break; - case 2: + case HALF_SIZE_RIGHT_BOTTOM: valAxis = dWidth / 2 - vGap; - cRoot = { dWidth / 2 + vGap - 1, top - 1 }; + cRoot = { dWidth / 2 + vGap, top - 1 }; break; default: // same as case 0; should never happen valAxis = dWidth - 1; cRoot = { 0, top - 1 }; } } - // LOG_DEBUG(GwLog::DEBUG, "obp60:setChartDimensions %s: direction: %c, size: %d, dWidth: %d, dHeight: %d, timAxis: %d, valAxis: %d, cRoot{%d, %d}, top: %d, bottom: %d, hGap: %d, vGap: %d", - // dataBuf.getName(), direction, size, dWidth, dHeight, timAxis, valAxis, cRoot.x, cRoot.y, top, bottom, hGap, vGap); + // LOG_DEBUG(GwLog::DEBUG, "obp60:setChartDimensions %s: chrtDir: %c, size: %d, dWidth: %d, dHeight: %d, timAxis: %d, valAxis: %d, cRoot{%d, %d}, top: %d, bottom: %d, hGap: %d, vGap: %d", + // dataBuf.getName(), chrtDir, size, dWidth, dHeight, timAxis, valAxis, cRoot.x, cRoot.y, top, bottom, hGap, vGap); return true; } // draw chart -void Chart::drawChrt(const char chrtDir, const int8_t chrtIntv, GwApi::BoatValue& currValue) +void Chart::drawChrt(const ChrtDir chrtDir, const int8_t chrtIntv, GwApi::BoatValue& currValue) { double chrtScale; // Scale for data values in pixels per value @@ -215,7 +209,7 @@ void Chart::drawChrt(const char chrtDir, const int8_t chrtIntv, GwApi::BoatValue // LOG_DEBUG(GwLog::DEBUG, "Chart:drawChart: min: %.1f, mid: %.1f, max: %.1f, rng: %.1f", chrtMin, chrtMid, chrtMax, chrtRng); calcChrtBorders(chrtMin, chrtMid, chrtMax, chrtRng); chrtScale = double(valAxis) / chrtRng; // Chart scale: pixels per value step - LOG_DEBUG(GwLog::DEBUG, "Chart:drawChart: min: %.1f, mid: %.1f, max: %.1f, rng: %.1f, data valid: %d", chrtMin, chrtMid, chrtMax, chrtRng, currValue.valid); + // LOG_DEBUG(GwLog::DEBUG, "Chart:drawChart: min: %.1f, mid: %.1f, max: %.1f, rng: %.1f, data valid: %d", chrtMin, chrtMid, chrtMax, chrtRng, currValue.valid); // Do we have valid buffer data? if (dataBuf.getMax() == dbMAX_VAL) { // only values in buffer -> no valid wind data available @@ -230,7 +224,6 @@ void Chart::drawChrt(const char chrtDir, const int8_t chrtIntv, GwApi::BoatValue numNoData++; bufDataValid = true; - // if (numNoData > THRESHOLD_NO_DATA) { // If more than 4 invalid values in a row, flag for invalid data if (numNoData > THRESHOLD_NO_DATA * (dataBuf.getUpdFreq() / 1000)) { // If more than invalid values in a row, flag for invalid data bufDataValid = false; return; @@ -266,6 +259,7 @@ void Chart::calcChrtBorders(double& rngMin, double& rngMid, double& rngMax, doub { if (chrtDataFmt == WIND || chrtDataFmt == ROTATION) { + // calculate rngMid if (chrtDataFmt == ROTATION) { // if chart data is of type 'rotation', we want to have always to be '0' rngMid = 0; @@ -280,22 +274,12 @@ void Chart::calcChrtBorders(double& rngMin, double& rngMid, double& rngMax, doub if (recalcRngMid) { // Set rngMid - rngMid = dataBuf.getMid(numBufVals); + rngMid = dataBuf.getCircularMid(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 = std::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); - } + rngMid = std::round(rngMid / rngStep) * rngStep; // round new center value to next value } recalcRngMid = false; // Reset flag for determination @@ -306,16 +290,11 @@ void Chart::calcChrtBorders(double& rngMin, double& rngMid, double& rngMax, doub // check and adjust range between left, mid, and right chart limit double halfRng = rng / 2.0; // we calculate with range between and edges - double tmpRng = getAngleRng(rngMid, numBufVals); + double tmpRng = getCircularRng(rngMid, numBufVals); tmpRng = (tmpRng == dbMAX_VAL ? 0 : std::ceil(tmpRng / rngStep) * rngStep); - - // LOG_DEBUG(GwLog::DEBUG, "calcChrtBorders: tmpRng: %.1f°, halfRng: %.1f°", tmpRng * RAD_TO_DEG, halfRng * RAD_TO_DEG); - if (tmpRng > halfRng) { // expand chart range to new value halfRng = tmpRng; - } - - else if (tmpRng + rngStep < halfRng) { // Contract chart range for higher resolution if possible + } else if (tmpRng + rngStep < halfRng) { // Contract chart range for higher resolution if possible halfRng = std::max(dfltRng / 2.0, tmpRng); } @@ -324,7 +303,6 @@ void Chart::calcChrtBorders(double& rngMin, double& rngMid, double& rngMax, doub rngMax = WindUtils::to2PI(rngMax); rng = halfRng * 2.0; - // LOG_DEBUG(GwLog::DEBUG, "calcChrtBorders: rngMin: %.1f°, rngMid: %.1f°, rngMax: %.1f°, tmpRng: %.1f°, rng: %.1f°, rngStep: %.1f°", rngMin * RAD_TO_DEG, rngMid * RAD_TO_DEG, rngMax * RAD_TO_DEG, // tmpRng * RAD_TO_DEG, rng * RAD_TO_DEG, rngStep * RAD_TO_DEG); @@ -364,35 +342,17 @@ void Chart::calcChrtBorders(double& rngMin, double& rngMid, double& rngMax, doub rngMid = (rngMin + rngMax) / 2.0; rng = rngMax - rngMin; - - LOG_DEBUG(GwLog::DEBUG, "calcChrtRange-end: currMinVal: %.1f, currMaxVal: %.1f, rngMin: %.1f, rngMid: %.1f, rngMax: %.1f, rng: %.1f, rngStep: %.1f, zeroValue: %.1f, dbMIN_VAL: %.1f", - currMinVal, currMaxVal, rngMin, rngMid, rngMax, rng, rngStep, zeroValue, dbMIN_VAL); + // LOG_DEBUG(GwLog::DEBUG, "calcChrtRange-end: currMinVal: %.1f, currMaxVal: %.1f, rngMin: %.1f, rngMid: %.1f, rngMax: %.1f, rng: %.1f, rngStep: %.1f, zeroValue: %.1f, dbMIN_VAL: %.1f", + // currMinVal, currMaxVal, rngMin, rngMid, rngMax, rng, rngStep, zeroValue, dbMIN_VAL); } } // Draw chart graph -void Chart::drawChartLines(const char direction, const int8_t chrtIntv, const double chrtScale) +void Chart::drawChartLines(const ChrtDir chrtDir, const int8_t chrtIntv, const double chrtScale) { 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 @@ -401,19 +361,12 @@ 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) - LOG_DEBUG(GwLog::DEBUG, "PageWindPlot Chart: i: %d, chrtVal: %.2f, chrtMin: %.2f, {x,y} {%d,%d}", i, chrtVal, chrtMin, x, y); + point = setChartPoint(i, chrtDir, chrtVal, chrtScale); + // if (i >= (numBufVals / chrtIntv) - 5) // log chart data of x lines (adjust for test purposes) + // LOG_DEBUG(GwLog::DEBUG, "PageWindPlot Chart: i: %d, chrtVal: %.2f, chrtMin: %.2f, {x,y} {%d,%d}", i, chrtVal, chrtMin, point.x, point.y); if ((i == 0) || (chrtPrevVal == dbMAX_VAL)) { - // just a dot for 1st chart point or after some invalid values - prevPoint = point; + prevPoint = point; // just a dot for 1st chart point or after some invalid values } else if (chrtDataFmt == WIND || chrtDataFmt == ROTATION) { // cross borders check for degree values; shift values to [-PI..0..PI]; when crossing borders, range is 2x PI degrees @@ -427,7 +380,7 @@ void Chart::drawChartLines(const char direction, const int8_t chrtIntv, const do // LOG_DEBUG(GwLog::DEBUG, "PageWindPlot Chart: crossedBorders: %d, chrtVal: %.2f, chrtPrevVal: %.2f", crossedBorders, chrtVal, chrtPrevVal); bool wrappingFromHighToLow = normCurrVal < normPrevVal; // Determine which edge we're crossing - if (direction == HORIZONTAL) { + if (chrtDir == HORIZONTAL) { int ySplit = wrappingFromHighToLow ? (cRoot.y + valAxis) : cRoot.y; drawBoldLine(prevPoint.x, prevPoint.y, point.x, ySplit); prevPoint.y = wrappingFromHighToLow ? cRoot.y : (cRoot.y + valAxis); @@ -438,10 +391,19 @@ void Chart::drawChartLines(const char direction, const int8_t chrtIntv, const do prevPoint.x = wrappingFromHighToLow ? cRoot.x : (cRoot.x + valAxis); } } + + // test position of chart point against chart middle; ignore first values which we don't show next time anyway + if (i > MIN_FREE_VALUES) { + double d = WindUtils::toPI(chrtVal - chrtMid); + if (d > 0) // point is right of mid point + allLeft = false; + if (d < 0) // point is left of mid point + allRight = false; + } } if (chrtDataFmt == DEPTH) { - if (direction == HORIZONTAL) { // horizontal chart + if (chrtDir == HORIZONTAL) { // horizontal chart drawBoldLine(point.x, point.y, point.x, cRoot.y + valAxis); } else { // vertical chart drawBoldLine(point.x, point.y, cRoot.x + valAxis, point.y); @@ -458,23 +420,28 @@ void Chart::drawChartLines(const char direction, const int8_t chrtIntv, const do if (i >= timAxis - 1) { oldChrtIntv = 0; // force reset of buffer start and number of values to show in next display loop - 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); + if (chrtDataFmt == WIND) { + if (allLeft || allRight || chrtRng == M_TWOPI) { // check if we should recalculate chart axis middle value + recalcRngMid = true; + } + // LOG_DEBUG(GwLog::DEBUG, "OBPcharts: WIND chart end: timAxis: %d, i: %d, bufStart: %d, numBufVals: %d, recalcRngCntr: %d, allLeft: %d, allRight: %d", timAxis, i, bufStart, + // numBufVals, recalcRngMid, allLeft, allRight); + allLeft = true; // reset marker for all left/right chart points + allRight = true; } break; } - taskYIELD(); // we run for 50-150ms; be polite to other tasks with same priority + taskYIELD(); // we run for 50-150ms, so we want to be polite to other tasks with same priority } } // Set current chart point to draw -Pos Chart::setCurrentChartPoint(const int i, const char direction, const double chrtVal, const double chrtScale) +Pos Chart::setChartPoint(const int i, const ChrtDir chrtDir, const double chrtVal, const double chrtScale) { Pos currentPoint; - if (direction == HORIZONTAL) { + if (chrtDir == HORIZONTAL) { currentPoint.x = cRoot.x + i; // Position in chart area if (chrtDataFmt == WIND || chrtDataFmt == ROTATION) { // degree type value @@ -499,7 +466,7 @@ 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) +void Chart::drawChrtTimeAxis(const ChrtDir chrtDir, const ChrtSize chrtSz, const int8_t chrtIntv) { int axSlots, intv, i, timeRng; char sTime[6]; @@ -533,13 +500,13 @@ void Chart::drawChrtTimeAxis(const char chrtDir, const int8_t chrtSz, const int8 for (float j = intv; j < timAxis - 1; j += intv) { // don't print time label at upper and lower end of time axis snprintf(sTime, sizeof(sTime), "%d", i); - getdisplay().drawLine(cRoot.x, cRoot.y + j, cRoot.x + valAxis, cRoot.y + j, fgColor); // Grid line + getdisplay().drawLine(cRoot.x, cRoot.y + j, cRoot.x + valAxis - 2, 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); // time value - } else if (chrtSz == HALF_SIZE_RIGHT) { // half size chart; right side + } else if (chrtSz == HALF_SIZE_RIGHT_BOTTOM) { // half size chart; right side drawTextCenter(dWidth / 2, cRoot.y + j, sTime); // time value; print mid screen } i -= chrtIntv; @@ -548,11 +515,11 @@ void Chart::drawChrtTimeAxis(const char chrtDir, const int8_t chrtSz, const int8 } // chart value axis labels + lines -void Chart::drawChrtValAxis(const char chrtDir, const int8_t chrtSz, bool prntName) +void Chart::drawChrtValAxis(const ChrtDir chrtDir, const ChrtSize chrtSz, const bool prntName) { const GFXfont* font; - constexpr bool NO_LABEL = false; - constexpr bool LABEL = true; + // constexpr bool NO_LABEL = false; + // constexpr bool LABEL = true; getdisplay().setTextColor(fgColor); @@ -560,13 +527,14 @@ 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; - getdisplay().setFont(font); - getdisplay().fillRect(cRoot.x + timAxis - 57, cRoot.y + 2, 58, 20, bgColor); // clear small area to remove potential chart lines - String name = xdrDelete(dbName); // Value name - drawTextRalign(cRoot.x + timAxis - 1, cRoot.y + 19, name.substring(0, 5)); - + if (prntName) { + // print buffer data name on left hand side of time axis (max. size 5 characters) + font = &Ubuntu_Bold12pt8b; + getdisplay().setFont(font); + getdisplay().fillRect(cRoot.x + timAxis - 57, cRoot.y + 2, 58, 20, bgColor); // clear small area to remove potential chart lines + String name = xdrDelete(dbName); // Value name + drawTextRalign(cRoot.x + timAxis - 1, cRoot.y + 19, name.substring(0, 5)); + } if (chrtDataFmt == WIND) { prntHorizChartThreeValueAxisLabel(font); return; @@ -594,9 +562,13 @@ void Chart::drawChrtValAxis(const char chrtDir, const int8_t chrtSz, bool prntNa } else { // vertical chart - if (chrtSz == FULL_SIZE) { - font = &Ubuntu_Bold12pt8b; - getdisplay().setFont(font); // use larger font + if (prntName) { + if (chrtSz == FULL_SIZE) { + font = &Ubuntu_Bold12pt8b; + } else { + font = &Ubuntu_Bold10pt8b; + } + getdisplay().setFont(font); String name = xdrDelete(dbName); // Value name drawTextRalign(cRoot.x + (valAxis * 0.42), cRoot.y - 2, name.substring(0, 6)); // print buffer data name (max. size 6 characters) } @@ -607,10 +579,10 @@ void Chart::drawChrtValAxis(const char chrtDir, const int8_t chrtSz, bool prntNa } // Print current data value -void Chart::prntCurrValue(const char direction, GwApi::BoatValue& currValue) +void Chart::prntCurrValue(const ChrtDir chrtDir, GwApi::BoatValue& currValue) { - const int xPosVal = (direction == HORIZONTAL) ? cRoot.x + (timAxis / 2) - 74 : cRoot.x + 31; - const int yPosVal = (direction == HORIZONTAL) ? cRoot.y + valAxis : cRoot.y + timAxis; + const int xPosVal = (chrtDir == HORIZONTAL) ? cRoot.x + (timAxis / 2) - 74 : cRoot.x + 31; + const int yPosVal = (chrtDir == HORIZONTAL) ? cRoot.y + valAxis : cRoot.y + timAxis; FormattedData frmtDbData = formatValue(&currValue, *commonData, NO_SIMUDATA); String sdbValue = frmtDbData.svalue; // value as formatted string @@ -638,13 +610,13 @@ void Chart::prntCurrValue(const char direction, GwApi::BoatValue& currValue) } // print message for no valid data availabletemplate -void Chart::prntNoValidData(const char direction) +void Chart::prntNoValidData(const ChrtDir chrtDir) { Pos p; getdisplay().setFont(&Ubuntu_Bold10pt8b); - if (direction == HORIZONTAL) { + if (chrtDir == HORIZONTAL) { p.x = cRoot.x + (timAxis / 2); p.y = cRoot.y + (valAxis / 2) - 10; } else { @@ -659,7 +631,7 @@ void Chart::prntNoValidData(const char direction) } // Get maximum difference of last of dataBuf ringbuffer values to center chart; for angle data only -double Chart::getAngleRng(const double center, size_t amount) +double Chart::getCircularRng(const double center, size_t amount) { size_t count = dataBuf.getCurrentSize(); diff --git a/lib/obp60task/OBPcharts.h b/lib/obp60task/OBPcharts.h index a628336..8c276f4 100644 --- a/lib/obp60task/OBPcharts.h +++ b/lib/obp60task/OBPcharts.h @@ -20,24 +20,28 @@ class GwLog; class Chart { public: - /* enum class ChrtDirection { - HORIZONTALE, - VERTICALE - }; + enum ChrtDir { + HORIZONTAL, + VERTICAL + }; - enum class ChrtSize { - FULL_SIZEE, - HALF_SIZE_LEFTE, - HALF_SIZE_RIGHTE, - TWO_THIRD_TOPE - }; */ + enum ChrtSize { + FULL_SIZE, + HALF_SIZE_LEFT_TOP, + HALF_SIZE_RIGHT_BOTTOM, + TWO_THIRD_TOP + }; + + static constexpr bool PRNT_NAME = true; + static constexpr bool NO_PRNT_NAME = false; + static constexpr bool PRNT_VALUE = true; + static constexpr bool NO_PRNT_VALUE = false; Chart(RingBuffer& dataBuf, CommonData& common, bool useSimuData); // Chart object of data chart ~Chart(); bool init(); // initialize chart object parameters bool isValid() { return initValid; }; // Checks if chart object has been fully initialized - void showChrt(const char chrtDir, const int8_t chrtSz, const int8_t chrtIntv, bool prntName, bool showCurrValue, GwApi::BoatValue currValue); // Perform all actions to draw chart - // void showChrt(ChrtDirection chrtDir, ChrtSize chrtSz, const int8_t chrtIntv, bool prntName, bool showCurrValue, GwApi::BoatValue currValue); // Perform all actions to draw chart + void showChrt(const ChrtDir chrtDir, const ChrtSize chrtSz, const int8_t chrtIntv, bool prntName, bool showCurrValue, GwApi::BoatValue currValue); // Perform all actions to draw chart protected: CommonData* commonData; @@ -54,13 +58,6 @@ protected: OTHER }; - static constexpr char HORIZONTAL = 'H'; - static constexpr char VERTICAL = 'V'; - static constexpr int8_t FULL_SIZE = 0; - static constexpr int8_t HALF_SIZE_LEFT = 1; - static constexpr int8_t HALF_SIZE_RIGHT = 2; - static constexpr int8_t TWO_THIRD_TOP = 3; - static constexpr int8_t MIN_FREE_VALUES = 60; // free 60 values when chart line reaches chart end static constexpr int8_t THRESHOLD_NO_DATA = 3; // max. seconds of invalid values in a row static constexpr int8_t VALAXIS_SLOTS = 5; // no. of value axis labels @@ -90,7 +87,8 @@ protected: 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 recalcRngMid = false; // Flag for re-calculation of mid value of chart for wind data types + bool recalcRngMid; // Flag for re-calculation of mid value of chart for wind data types + bool allLeft, allRight; // indicate whether all chart points are left or right of middle value (only for wind data type) String dbName, dbFormat; // Name and format of data buffer ChrtDataFormat chrtDataFmt; // Data format of chart boat data type @@ -108,9 +106,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 // Default ranges for various boat data types @@ -124,18 +120,17 @@ protected: { "formatXdr:P:P", { 4000.0, 1000.0 } } // default pressure range in Pascal (hPa * 100); XDR (bar) format is represented in gateway in the same way }; - bool setChartDimensions(const char direction, const int8_t size); // define dimensions and start points for chart - // bool setChartDimensions(const ChrtDirection direction, const ChrtSize size); // define dimensions and start points for chart - void drawChrt(const char chrtDir, const int8_t chrtIntv, GwApi::BoatValue& currValue); // Draw chart line + bool setChartDimensions(const ChrtDir direction, const ChrtSize chrtSz); // define dimensions and start points for chart + void drawChrt(const ChrtDir chrtDir, const int8_t chrtIntv, GwApi::BoatValue& currValue); // Draw chart line void getBufferStartNSize(const int8_t chrtIntv); // Identify buffer size and buffer start position for chart void calcChrtBorders(double& rngMin, double& rngMid, double& rngMax, double& rng); // Calculate chart points for value axis and return range between and - void drawChartLines(const char direction, const int8_t chrtIntv, const double chrtScale); // Draw chart graph - Pos setCurrentChartPoint(const int i, const char direction, const double chrtVal, const double chrtScale); // Set current chart point to draw - void drawChrtTimeAxis(const char chrtDir, const int8_t chrtSz, const int8_t chrtIntv); // Draw time axis of chart, value and lines - void drawChrtValAxis(const char chrtDir, const int8_t chrtSz, bool prntLabel); // Draw value axis of chart, value and lines - void prntCurrValue(const char chrtDir, GwApi::BoatValue& currValue); // Add current boat data value to chart - void prntNoValidData(const char chrtDir); // print message for no valid data available - double getAngleRng(const double center, size_t amount); // Calculate range between chart center and edges + void drawChartLines(const ChrtDir direction, const int8_t chrtIntv, const double chrtScale); // Draw chart graph + Pos setChartPoint(const int i, const ChrtDir chrtDir, const double chrtVal, const double chrtScale); // Set current chart point to draw + void drawChrtTimeAxis(const ChrtDir chrtDir, const ChrtSize chrtSz, const int8_t chrtIntv); // Draw time axis of chart, value and lines + void drawChrtValAxis(const ChrtDir chrtDir, const ChrtSize chrtSz, const bool prntLabel); // Draw value axis of chart, value and lines + void prntCurrValue(const ChrtDir chrtDir, GwApi::BoatValue& currValue); // Add current boat data value to chart + void prntNoValidData(const ChrtDir chrtDir); // print message for no valid data available + double getCircularRng(const double center, size_t amount); // Calculate range between chart center and edges void prntVerticChartThreeValueAxisLabel(const GFXfont* font); // print value axis label with only three values: top, mid, and bottom for vertical chart void prntHorizChartThreeValueAxisLabel(const GFXfont* font); // print value axis label with only three values: top, mid, and bottom for horizontal chart void prntHorizChartMultiValueAxisLabel(const GFXfont* font); // print value axis label with multiple axis lines for horizontal chart diff --git a/lib/obp60task/PageOneValue.cpp b/lib/obp60task/PageOneValue.cpp index 294ad86..fe47a94 100644 --- a/lib/obp60task/PageOneValue.cpp +++ b/lib/obp60task/PageOneValue.cpp @@ -19,17 +19,6 @@ private: HALF }; - static constexpr char HORIZONTAL = 'H'; - static constexpr char VERTICAL = 'V'; - static constexpr int8_t FULL_SIZE = 0; - static constexpr int8_t HALF_SIZE_TOP = 1; - static constexpr int8_t HALF_SIZE_BOTTOM = 2; - - static constexpr bool PRNT_NAME = true; - static constexpr bool NO_PRNT_NAME = false; - static constexpr bool PRNT_VALUE = true; - static constexpr bool NO_PRNT_VALUE = false; - int width; // Screen width int height; // Screen height @@ -286,13 +275,13 @@ public: } else if (pageMode == CHART) { // show only data chart if (dataChart) { - dataChart->showChrt(HORIZONTAL, FULL_SIZE, dataIntv, PRNT_NAME, PRNT_VALUE, *bValue1); + dataChart->showChrt(Chart::HORIZONTAL, Chart::FULL_SIZE, dataIntv, Chart::PRNT_NAME, Chart::PRNT_VALUE, *bValue1); } } else if (pageMode == BOTH) { // show data value and chart showData(bValue1, HALF); if (dataChart) { - dataChart->showChrt(HORIZONTAL, HALF_SIZE_BOTTOM, dataIntv, NO_PRNT_NAME, NO_PRNT_VALUE, *bValue1); + dataChart->showChrt(Chart::HORIZONTAL, Chart::HALF_SIZE_RIGHT_BOTTOM, dataIntv, Chart::NO_PRNT_NAME, Chart::NO_PRNT_VALUE, *bValue1); } } diff --git a/lib/obp60task/PageTwoValues.cpp b/lib/obp60task/PageTwoValues.cpp index 847f12b..bc580e0 100644 --- a/lib/obp60task/PageTwoValues.cpp +++ b/lib/obp60task/PageTwoValues.cpp @@ -20,17 +20,6 @@ private: HALF }; - static constexpr char HORIZONTAL = 'H'; - static constexpr char VERTICAL = 'V'; - static constexpr int8_t FULL_SIZE = 0; - static constexpr int8_t HALF_SIZE_TOP = 1; - static constexpr int8_t HALF_SIZE_BOTTOM = 2; - - static constexpr bool PRNT_NAME = true; - static constexpr bool NO_PRNT_NAME = false; - static constexpr bool PRNT_VALUE = true; - static constexpr bool NO_PRNT_VALUE = false; - static constexpr int YOFFSET = 130; // y offset for display of 2nd boat value int width; // Screen width @@ -296,28 +285,28 @@ public: } else if (pageMode == VAL1_CHART) { // show data value 1 and chart showData({ bValue[0] }, HALF); if (dataChart[0]) { - dataChart[0]->showChrt(HORIZONTAL, HALF_SIZE_BOTTOM, dataIntv, NO_PRNT_NAME, NO_PRNT_VALUE, *bValue[0]); + dataChart[0]->showChrt(Chart::HORIZONTAL, Chart::HALF_SIZE_RIGHT_BOTTOM, dataIntv, Chart::NO_PRNT_NAME, Chart::NO_PRNT_VALUE, *bValue[0]); } } else if (pageMode == VAL2_CHART) { // show data value 2 and chart showData({ bValue[1] }, HALF); if (dataChart[1]) { - dataChart[1]->showChrt(HORIZONTAL, HALF_SIZE_BOTTOM, dataIntv, NO_PRNT_NAME, NO_PRNT_VALUE, *bValue[1]); + dataChart[1]->showChrt(Chart::HORIZONTAL, Chart::HALF_SIZE_RIGHT_BOTTOM, dataIntv, Chart::NO_PRNT_NAME, Chart::NO_PRNT_VALUE, *bValue[1]); } } else if (pageMode == CHARTS) { // show both data charts if (dataChart[0]) { if (dataChart[1]) { - dataChart[0]->showChrt(HORIZONTAL, HALF_SIZE_TOP, dataIntv, PRNT_NAME, PRNT_VALUE, *bValue[0]); + dataChart[0]->showChrt(Chart::HORIZONTAL, Chart::HALF_SIZE_LEFT_TOP, dataIntv, Chart::PRNT_NAME, Chart::PRNT_VALUE, *bValue[0]); } else { - dataChart[0]->showChrt(HORIZONTAL, FULL_SIZE, dataIntv, PRNT_NAME, PRNT_VALUE, *bValue[0]); + dataChart[0]->showChrt(Chart::HORIZONTAL, Chart::FULL_SIZE, dataIntv, Chart::PRNT_NAME, Chart::PRNT_VALUE, *bValue[0]); } } if (dataChart[1]) { if (dataChart[0]) { - dataChart[1]->showChrt(HORIZONTAL, HALF_SIZE_BOTTOM, dataIntv, PRNT_NAME, PRNT_VALUE, *bValue[1]); + dataChart[1]->showChrt(Chart::HORIZONTAL, Chart::HALF_SIZE_RIGHT_BOTTOM, dataIntv, Chart::PRNT_NAME, Chart::PRNT_VALUE, *bValue[1]); } else { - dataChart[1]->showChrt(HORIZONTAL, FULL_SIZE, dataIntv, PRNT_NAME, PRNT_VALUE, *bValue[1]); + dataChart[1]->showChrt(Chart::HORIZONTAL, Chart::FULL_SIZE, dataIntv, Chart::PRNT_NAME, Chart::PRNT_VALUE, *bValue[1]); } } } diff --git a/lib/obp60task/PageWeather.cpp b/lib/obp60task/PageWeather.cpp index 89156b4..c33a88c 100644 --- a/lib/obp60task/PageWeather.cpp +++ b/lib/obp60task/PageWeather.cpp @@ -13,22 +13,6 @@ private: VAL_CHART, CHART }; - /* enum DisplayMode { - FULL, - HALF - }; */ - - static constexpr char HORIZONTAL = 'H'; - static constexpr char VERTICAL = 'V'; - static constexpr int8_t FULL_SIZE = 0; - static constexpr int8_t HALF_SIZE_TOP = 1; - static constexpr int8_t HALF_SIZE_BOTTOM = 2; - static constexpr int8_t TWO_THIRD_TOP = 3; - - static constexpr bool PRNT_NAME = true; - static constexpr bool NO_PRNT_NAME = false; - static constexpr bool PRNT_VALUE = true; - static constexpr bool NO_PRNT_VALUE = false; static constexpr int XOFFSET = 133; // x offset for display of boat values @@ -125,7 +109,6 @@ public: height = getdisplay().height(); // Screen height // Get config data - // lengthformat = commonData->config->getString(commonData->config->lengthFormat); useSimuData = commonData->config->getBool(commonData->config->useSimuData); holdValues = commonData->config->getBool(commonData->config->holdvalues); flashLED = commonData->config->getString(commonData->config->flashLED); @@ -218,9 +201,6 @@ public: String bValFormat = bValue->getFormat(); // Value format dataHstryBuf[i] = pageData.hstryBuffers->getBuffer(bValName); -// if (dataHstryBuf[i]->getFormat() == "") { // data format might have been unknown at time of buffer creation -// dataHstryBuf[i]->setFormat(bValFormat); // in that case, we specify it here, because we need it for printing of buffer data -// } if (dataHstryBuf[i]) { dataChart[i].reset(new Chart(*dataHstryBuf[i], *commonData, useSimuData)); @@ -235,8 +215,6 @@ public: int displayPage(PageData& pageData) { - // using CD = Chart::ChrtDirection; - LOG_DEBUG(GwLog::LOG, "Display PageWeather"); // Get latest boat values for page @@ -272,12 +250,12 @@ public: if (pageMode == VAL_CHART) { if (dataChart[0]) { - dataChart[0]->showChrt(HORIZONTAL, TWO_THIRD_TOP, dataIntv, PRNT_NAME, PRNT_VALUE, *bValue[0]); + dataChart[0]->showChrt(Chart::HORIZONTAL, Chart::TWO_THIRD_TOP, dataIntv, Chart::PRNT_NAME, Chart::PRNT_VALUE, *bValue[0]); } showData(bValue); } else if (pageMode == CHART && dataChart[0]) { // show only data chart, but that has to exist - dataChart[0]->showChrt(HORIZONTAL, FULL_SIZE, dataIntv, PRNT_NAME, PRNT_VALUE, *bValue[0]); + dataChart[0]->showChrt(Chart::HORIZONTAL, Chart::FULL_SIZE, dataIntv, Chart::PRNT_NAME, Chart::PRNT_VALUE, *bValue[0]); } return PAGE_UPDATE; diff --git a/lib/obp60task/PageWindPlot.cpp b/lib/obp60task/PageWindPlot.cpp index 9a77d2b..a9b455d 100644 --- a/lib/obp60task/PageWindPlot.cpp +++ b/lib/obp60task/PageWindPlot.cpp @@ -11,28 +11,17 @@ class PageWindPlot : public Page { private: GwLog* logger; - enum ChartMode { + enum PageMode { DIRECTION, SPEED, BOTH }; - static constexpr char HORIZONTAL = 'H'; - static constexpr char VERTICAL = 'V'; - static constexpr int8_t FULL_SIZE = 0; - static constexpr int8_t HALF_SIZE_LEFT = 1; - static constexpr int8_t HALF_SIZE_RIGHT = 2; - - static constexpr bool PRNT_NAME = true; - static constexpr bool NO_PRNT_NAME = false; - static constexpr bool PRNT_VALUE = true; - static constexpr bool NO_PRNT_VALUE = false; - int width; // Screen width int height; // Screen height bool keylock = false; // Keylock - ChartMode chrtMode = DIRECTION; + PageMode chrtMode = DIRECTION; bool showTruW = true; // Show true wind or apparent wind in chart area bool oldShowTruW = false; // remember recent user selection of wind data type @@ -226,7 +215,7 @@ public: if (chrtMode == DIRECTION) { if (wdChart) { - wdChart->showChrt(VERTICAL, FULL_SIZE, dataIntv, PRNT_NAME, PRNT_VALUE, *wdBVal); + wdChart->showChrt(Chart::VERTICAL, Chart::FULL_SIZE, dataIntv, Chart::PRNT_NAME, Chart::PRNT_VALUE, *wdBVal); } } else if (chrtMode == SPEED) { @@ -234,15 +223,15 @@ public: 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); + wsChart->showChrt(Chart::HORIZONTAL, Chart::FULL_SIZE, dataIntv, Chart::PRNT_NAME, Chart::PRNT_VALUE, *wsBVal); } } else if (chrtMode == BOTH) { if (wdChart) { - wdChart->showChrt(VERTICAL, HALF_SIZE_LEFT, dataIntv, PRNT_NAME, PRNT_VALUE, *wdBVal); + wdChart->showChrt(Chart::VERTICAL, Chart::HALF_SIZE_LEFT_TOP, dataIntv, Chart::NO_PRNT_NAME, Chart::PRNT_VALUE, *wdBVal); } if (wsChart) { - wsChart->showChrt(VERTICAL, HALF_SIZE_RIGHT, dataIntv, PRNT_NAME, PRNT_VALUE, *wsBVal); + wsChart->showChrt(Chart::VERTICAL, Chart::HALF_SIZE_RIGHT_BOTTOM, dataIntv, Chart::NO_PRNT_NAME, Chart::PRNT_VALUE, *wsBVal); } } diff --git a/lib/obp60task/movingAvg.h b/lib/obp60task/movingAvg.h index 1d905d4..9e9451a 100644 --- a/lib/obp60task/movingAvg.h +++ b/lib/obp60task/movingAvg.h @@ -22,6 +22,7 @@ class movingAvg int getCount() { return m_nbrReadings; } void reset(); T* getReadings() { return m_readings; } + T to2PI(T a); private: int m_interval; // number of data points for the moving average @@ -33,6 +34,32 @@ class movingAvg T* m_readings; // pointer to the dynamically allocated interval array }; +// moving average for angle type of data (wind, course, rotation) +// makes sense for data types double and float; angle in radians [0..2pi] +template +class movingAvgAngle +{ + public: + movingAvgAngle(int interval) + : m_interval{interval}, m_nbrReadings{0}, m_sumSin{0}, m_sumCos{0}, m_next{0}, m_buffer{nullptr} {} + ~movingAvgAngle() { delete[] m_buffer; } + void begin(); + T reading(T newReading); + T getAvg(); + T getAvg(int nPoints); + int getCount() { return m_nbrReadings; } + void reset(); + T* getReadings() { return m_buffer; } + T to2PI(T a); + + private: + int m_interval; // number of data points for the moving average + int m_nbrReadings; // number of readings + double m_sumSin, m_sumCos; // sum for angle values should always be double for precision reasons, regardless of class type + int m_next; // index to the next reading + T* m_buffer; // pointer to the dynamically allocated interval array +}; + // Include the implementation to satisfy template instantiation requirements #include "movingAvg.tpp" diff --git a/lib/obp60task/movingAvg.tpp b/lib/obp60task/movingAvg.tpp index 4468ee0..f46473a 100644 --- a/lib/obp60task/movingAvg.tpp +++ b/lib/obp60task/movingAvg.tpp @@ -5,11 +5,6 @@ // 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() @@ -85,4 +80,104 @@ void movingAvg::reset() m_nbrReadings = 0; m_sum = 0; m_next = 0; -} \ No newline at end of file +} +// --- End Class movingAvg --------------- + +// --- Class MovingAvgAngle --------------- +template +void movingAvgAngle::begin() +{ + m_buffer = new T[m_interval]; +} + +// add a new reading and return the new moving average +template +T movingAvgAngle::reading(T newReading) +{ + double s = std::sin(newReading); + double c = std::cos(newReading); + + // add each new data point to the sum until the m_readings array is filled + if (m_nbrReadings < m_interval) { + ++m_nbrReadings; + m_sumSin += s; + m_sumCos += c; + + } else { + // array is filled; subtract the oldest data point and add the new one + m_sumSin = m_sumSin - sin(m_buffer[m_next]) + s; + m_sumCos = m_sumCos - cos(m_buffer[m_next]) + c; + } + m_buffer[m_next] = newReading; + + if (++m_next >= m_interval) + m_next = 0; + + return getAvg(); +} + +// return the current moving average +template +T movingAvgAngle::getAvg() +{ + if (m_nbrReadings == 0) + return 0; + + // check size of vector; if near 0, set it to 0 + const double len = m_sumSin * m_sumSin + m_sumCos * m_sumCos; + if (len < 1e-24) + return 0.0; // average direction undefined + + return static_cast(to2PI(std::atan2(m_sumSin, m_sumCos))); +} + +// return the current moving average for a subset of the data, the most recent nPoints readings. +// for invalid values of nPoints, return zero. +template +T movingAvgAngle::getAvg(int nPoints) +{ + if (nPoints < 1 || nPoints > m_interval || nPoints > m_nbrReadings) + return 0; + + double sumSin = 0.0; + double sumCos = 0.0; + + int i = m_next; + for (int n = 0; n < nPoints; ++n) { + if (i == 0) + i = m_interval - 1; + else + --i; + + sumSin += std::sin(m_buffer[i]); + sumCos += std::cos(m_buffer[i]); + } + + // check size of vector; if near 0, set it to 0 + const double len = m_sumSin * m_sumSin + m_sumCos * m_sumCos; + if (len < 1e-24) + return 0.0; // average direction undefined + + return static_cast(to2PI(std::atan2(sumSin, sumCos))); +} + +// start the moving average over again +template +void movingAvgAngle::reset() +{ + m_nbrReadings = 0; + m_sumSin = 0; + m_sumCos = 0; + m_next = 0; +} + +template +T movingAvgAngle::to2PI(T a) +{ + a = fmod(a, M_TWOPI); + if (a < 0.0) { + a += M_TWOPI; + } + return a; +} +// --- End class MovingAvgAngle --------------- diff --git a/lib/obp60task/obp60task.cpp b/lib/obp60task/obp60task.cpp index 3a900db..6a24c3b 100644 --- a/lib/obp60task/obp60task.cpp +++ b/lib/obp60task/obp60task.cpp @@ -462,6 +462,13 @@ void OBP60Task(GwApi *api){ WindUtils trueWind(&boatValues, logger); // Create helper object for true wind calculation CalibrationData calibrationDataList(logger); // all boat data types which are supposed to be calibrated + // Read user settings from config file + bool calcTrueWnds = api->getConfig()->getBool(api->getConfig()->calcTrueWnds, false); + bool smoothCharts = api->getConfig()->getBool(api->getConfig()->smoothCharts, false); + bool useSimuData = api->getConfig()->getBool(api->getConfig()->useSimuData, false); + // Read user calibration data settings from config file + calibrationDataList.readConfig(config); + //fill the page data from config numPages=config->getInt(config->visiblePages,1); if (numPages < 1) numPages=1; @@ -507,7 +514,7 @@ void OBP60Task(GwApi *api){ if (pages[i].parameters.pageName == "OneValue" || pages[i].parameters.pageName == "TwoValues" || pages[i].parameters.pageName == "WindPlot" || pages[i].parameters.pageName == "Weather") { for (auto pVal : pages[i].parameters.values) { - hstryBufferList.addBuffer(pVal->getName()); + hstryBufferList.addBuffer(pVal->getName(), smoothCharts); } } // Add list of history buffers to page parameters @@ -517,12 +524,6 @@ void OBP60Task(GwApi *api){ // add out of band system page (always available) Page *syspage = allPages.pages[0]->creator(commonData); - // Read user settings from config file - bool calcTrueWnds = api->getConfig()->getBool(api->getConfig()->calcTrueWnds, false); - bool useSimuData = api->getConfig()->getBool(api->getConfig()->useSimuData, false); - // Read user calibration data settings from config file - calibrationDataList.readConfig(config); - // Display screenshot handler for HTTP request // http://192.168.15.1/api/user/OBP60Task/screenshot api->registerRequestHandler("screenshot", [api, &pageNumber, pages](AsyncWebServerRequest *request) { @@ -848,12 +849,12 @@ void OBP60Task(GwApi *api){ api->getBoatDataValues(boatValues.numValues,boatValues.allBoatValues); api->getStatus(commonData.status); - // ulong startHndl = millis(); + // ulong startHandl = millis(); trueWind.handleWinds(calcTrueWnds); // calculate true wind data from apparent wind values - trueWind.setMaxWs(); // maintain MaxTWS value in any case; invalid TWS value is considered automatically + trueWind.setMaxWs(); // maintain MaxTWS value in any case; invalid TWS value is considered automatically; MaxAWS is provided by core gateway if AWS is available calibrationDataList.handleCalibration(&boatValues); // Process calibration for all boat data in hstryBufferList.handleHstryBufs(useSimuData, commonData); // Handle history buffers for certain boat data for charts and other usage - // LOG_DEBUG(GwLog::DEBUG, "obp60task: data handling: %d ms", millis() - startHndl); + // LOG_DEBUG(GwLog::DEBUG, "obp60task: data handling: %d ms", millis() - startHandl); // Clear display // getdisplay().fillRect(0, 0, getdisplay().width(), getdisplay().height(), commonData.bgcolor);