From b3e2dea45b528c62b921222ab53dc00650f09e8c Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Fri, 12 Sep 2025 18:42:49 +0200 Subject: [PATCH 01/10] Code part for more chart plots --- lib/obp60task/OBPDataOperations.h | 1 + lib/obp60task/OBPcharts.cpp | 75 ++++++ lib/obp60task/OBPcharts.h | 103 ++++++++ lib/obp60task/PageWindPlot.cpp | 418 +++++++++++++++--------------- 4 files changed, 393 insertions(+), 204 deletions(-) create mode 100644 lib/obp60task/OBPcharts.cpp create mode 100644 lib/obp60task/OBPcharts.h diff --git a/lib/obp60task/OBPDataOperations.h b/lib/obp60task/OBPDataOperations.h index c93c1fe..b819e15 100644 --- a/lib/obp60task/OBPDataOperations.h +++ b/lib/obp60task/OBPDataOperations.h @@ -1,3 +1,4 @@ +// Function lib for history buffer handling, true wind calculation, and other operations on boat data #pragma once #include #include "OBPRingBuffer.h" diff --git a/lib/obp60task/OBPcharts.cpp b/lib/obp60task/OBPcharts.cpp new file mode 100644 index 0000000..44c782e --- /dev/null +++ b/lib/obp60task/OBPcharts.cpp @@ -0,0 +1,75 @@ +// Function lib for display of boat data in various chart formats +#include "OBPcharts.h" + +// --- Class Chart --------------- +void Chart::drawChrtHdr() { + // chart header label + lines + int i; + getdisplay().fillRect(0, top, cWidth, 2, commonData->fgcolor); + + // horizontal chart labels + getdisplay().drawLine(cStart.x, cStart.y, cWidth, cStart.y); + getdisplay().fillRect(cStart.x, cStart.y, cWidth, 2, commonData->fgcolor); + + getdisplay().setFont(&Ubuntu_Bold10pt8b); + getdisplay().setCursor(cStart.x, cStart.y + 12); + getdisplay().print(dbName); // Wind data name + + getdisplay().setFont(&Ubuntu_Bold12pt8b); + if (chrtSze == 0) { + i = -1 * (chrtIntv / 8 - 2); + } else { + i = -1 * (chrtIntv / 4 - 2); + } + for (int j = 50; j <= (cWidth - 50); j += 50 ) { + getdisplay().setCursor(cStart.x + j - 16, cStart.y + 12); + getdisplay().print(i++); // time interval + // i++; + getdisplay().drawLine(cStart.x + j - 30, cStart.y, cStart.x - 30, cHeight + top); + } +} + +void Chart::drawChrtGrd(const int chrtRng) { + // chart Y axis labels + lines + int i; + + getdisplay().setFont(&Ubuntu_Bold8pt8b); + if (chrtDir == 0) { + i = -1 * (chrtRng / 4 - 2); + for (int j = cStart.x; j <= (cHeight - (cHeight / 4)); j += cHeight / 4 ) { + getdisplay().drawLine(0, cStart.y, cWidth, cStart.y); + getdisplay().setCursor(0, cStart.y + 12); + if (i < 10) + getdisplay().printf("!!%1d", i); // Range value + else if (i < 100) + getdisplay().printf("!%2d", i); // Range value + else + getdisplay().printf("%3d", i); // Range value + i += (chrtRng / 4); + } + } else { + i = -1 * (chrtRng / 8 - 2); + for (int j = cStart.x; j <= (cHeight - (cHeight / 8)); j += cHeight / 8 ) { + getdisplay().drawLine(cStart.x + j - 30, cStart.y, cStart.x - 30, cHeight + top); + getdisplay().setCursor(0, cStart.y + 12); + if (i < 10) + getdisplay().printf("!!%1d", i); // Range value + else if (i < 100) + getdisplay().printf("!%2d", i); // Range value + else + getdisplay().printf("%3d", i); // Range value + i += (chrtRng / 4); + } + } +} + +bool Chart::drawChrt(int8_t chrtIntv, int dfltRng) { +// hstryBuf = buffer to display +// bValue = present value to display additionally to chart +// chrtDir = chart direction: [0] = vertical, [1] = horizontal +// chrtSze = chart size: [0] = full size, [1] = half size left half/top, [2] half size right half/bottom +// chrtIntv = chart time interval: [1] 4 min., [2] 8 min., [3] 12 min., [4] 16 min., [5] 32 min. +// dfltRng = default range of chart, e.g. 30 = [0..30] + +} +// --- Class Chart --------------- diff --git a/lib/obp60task/OBPcharts.h b/lib/obp60task/OBPcharts.h new file mode 100644 index 0000000..813c2da --- /dev/null +++ b/lib/obp60task/OBPcharts.h @@ -0,0 +1,103 @@ +// Function lib for display of boat data in various chart formats +#pragma once +#include "OBP60Extensions.h" +#include "Pagedata.h" +// #include "OBPDataOperations.h" +// #include "OBPRingBuffer.h" + +struct Point { + int x; + int y; +}; + +class Chart { +protected: + RingBuffer* dataBuf; // Buffer to display + GwApi::BoatValue* bValue; // Present value to display additionally to chart + int8_t chrtDir; // Chart direction: [0] = vertical, [1] = horizontal + int8_t chrtSze; // Chart size: [0] = full size, [1] = half size left/top, [2] half size right/bottom + int8_t chrtIntv; // Chart time interval: [4] 4 min., [8] 8 min., [12] 12 min., [16] 16 min., [32] 32 min. + int dfltRng; // Default range of chart, e.g. 30 = [0..30] + + int top = 48; // display top header lines + int bottom = 22; // display bottom lines + int gap = 4; // gap between 2 charts; actual gap is 2x + int cWidth; + int cHeight; + Point cStart; // start point for chart area + int cLines; // number of chart lines + int xCenter; // x center point of chart + + String dbName, dbFormat; + int16_t dbMAX_VAL; + size_t bufSize; + GwApi::BoatValue* bValue; + +public: + Chart(RingBuffer* dataBuf, GwApi::BoatValue* bValue, int8_t chrtDir, int8_t chrtSz, int8_t chrtIntv, int dfltRng, GwLog* logger) + : dataBuf(dataBuf) + , bValue(bValue) + , chrtDir(chrtDir) + , chrtSze(chrtSze) + , chrtIntv(chrtIntv) + , dfltRng(dfltRng) + { + cWidth = getdisplay().width(); + cHeight = getdisplay().height(); + cHeight = cHeight - top - bottom; + if (chrtDir == 0) { + // vertical chart + switch (chrtSze) { + case 0: + // default is already set + break; + case 1: + cWidth = cWidth; + cHeight = cHeight / 2 - gap; + cStart = { 30, cHeight + top }; + break; + case 2: + cWidth = cWidth; + cHeight = cHeight / 2 - gap; + cStart = { cWidth + gap, top }; + break; + default: + LOG_DEBUG(GwLog::DEBUG, "displayChart: wrong parameter"); + return; + } + } else if (chrtDir == 1) { + // horizontal chart + switch (chrtSze) { + case 0: + cStart = { 0, cHeight - bottom }; + break; + case 1: + cWidth = cWidth / 2 - gap; + cHeight = cHeight; + cStart = { 0, cHeight - bottom }; + break; + case 2: + cWidth = cWidth / 2 - gap; + cHeight = cHeight; + cStart = { cWidth + gap, cHeight - bottom }; + break; + default: + LOG_DEBUG(GwLog::DEBUG, "displayChart: wrong parameter"); + return; + } + } else { + LOG_DEBUG(GwLog::DEBUG, "displayChart: wrong parameter"); + return; + } + xCenter = cWidth / 2; + cLines = cHeight - 22; + + dataBuf->getMetaData(dbName, dbFormat); + dbMAX_VAL = dataBuf->getMaxVal(); + bufSize = dataBuf->getCapacity(); + bValue->setFormat(dataBuf->getFormat()); + }; + void drawChrtHdr(); + void drawChrtGrd(const int chrtRng); + bool drawChrt(int8_t chrtIntv, int dfltRng); +}; \ No newline at end of file diff --git a/lib/obp60task/PageWindPlot.cpp b/lib/obp60task/PageWindPlot.cpp index 4bc79f7..b3f55ea 100644 --- a/lib/obp60task/PageWindPlot.cpp +++ b/lib/obp60task/PageWindPlot.cpp @@ -1,10 +1,11 @@ #if defined BOARD_OBP60S3 || defined BOARD_OBP40S3 -#include "Pagedata.h" -#include "OBP60Extensions.h" -#include "OBPRingBuffer.h" -#include "OBPDataOperations.h" #include "BoatDataCalibration.h" +#include "OBP60Extensions.h" +#include "OBPDataOperations.h" +#include "OBPRingBuffer.h" +#include "OBPcharts.h" +#include "Pagedata.h" #include static const double radToDeg = 180.0 / M_PI; // Conversion factor from radians to degrees @@ -59,7 +60,7 @@ int getRng(const RingBuffer& windDirHstry, int center, size_t amount) value = windDirHstry.get(count - 1 - i); if (value == MAX_VAL) { - continue; // ignore invalid values + continue; // ignore invalid values } value = value / 1000.0 * radToDeg; @@ -99,13 +100,12 @@ public: // holdValues = common.config->getBool(common.config->holdvalues); flashLED = common.config->getString(common.config->flashLED); backlightMode = common.config->getString(common.config->backlight); - } virtual void setupKeys() { Page::setupKeys(); - // commonData->keydata[0].label = "MODE"; + commonData->keydata[0].label = "MODE"; #if defined BOARD_OBP60S3 commonData->keydata[1].label = "SRC"; commonData->keydata[4].label = "INTV"; @@ -161,17 +161,18 @@ public: return key; } - virtual void displayNew(PageData &pageData){ + virtual void displayNew(PageData& pageData) + { #ifdef BOARD_OBP40S3 String wndSrc; // Wind source true/apparant wind - preselection for OBP40 wndSrc = commonData->config->getString("page" + String(pageData.pageNumber) + "wndsrc"); - if (wndSrc =="True wind") { + if (wndSrc == "True wind") { showTruW = true; } else { showTruW = false; // Wind source is apparant wind } - commonData->logger->logDebug(GwLog::LOG,"New PageWindPlot: wind source=%s", wndSrc); + commonData->logger->logDebug(GwLog::LOG, "New PageWindPlot: wind source=%s", wndSrc); #endif oldShowTruW = !showTruW; // makes wind source being initialized at initial page call } @@ -280,225 +281,234 @@ public: oldShowTruW = showTruW; } - // Identify buffer size and buffer start position for chart - count = wdHstry->getCurrentSize(); - currIdx = wdHstry->getLastIdx(); - numAddedBufVals = (currIdx - lastAddedIdx + bufSize) % bufSize; // Number of values added to buffer since last display - if (dataIntv != oldDataIntv || count == 1) { - // new data interval selected by user - intvBufSize = cHeight * dataIntv; - numWndVals = min(count, (cHeight - 60) * dataIntv); - bufStart = max(0, count - numWndVals); - lastAddedIdx = currIdx; - oldDataIntv = dataIntv; - } else { - numWndVals = numWndVals + numAddedBufVals; - lastAddedIdx = currIdx; - if (count == bufSize) { - bufStart = max(0, bufStart - numAddedBufVals); + if (chrtMode == 'D') { + // Identify buffer size and buffer start position for chart + count = wdHstry->getCurrentSize(); + currIdx = wdHstry->getLastIdx(); + numAddedBufVals = (currIdx - lastAddedIdx + bufSize) % bufSize; // Number of values added to buffer since last display + if (dataIntv != oldDataIntv || count == 1) { + // new data interval selected by user + intvBufSize = cHeight * dataIntv; + numWndVals = min(count, (cHeight - 60) * dataIntv); + bufStart = max(0, count - numWndVals); + lastAddedIdx = currIdx; + oldDataIntv = dataIntv; + } else { + numWndVals = numWndVals + numAddedBufVals; + lastAddedIdx = currIdx; + if (count == bufSize) { + bufStart = max(0, bufStart - numAddedBufVals); + } } - } - LOG_DEBUG(GwLog::DEBUG, "PageWindPlot Dataset: count: %d, xWD: %.1f, xWS: %.2f, xWD_valid? %d, intvBufSize: %d, numWndVals: %d, bufStart: %d, numAddedBufVals: %d, lastIdx: %d, wind source: %s", - count, wdHstry->getLast() / 1000.0 * radToDeg, wsHstry->getLast() / 1000.0 * 1.94384, BDataValid[0], intvBufSize, numWndVals, bufStart, numAddedBufVals, wdHstry->getLastIdx(), - showTruW ? "True" : "App"); + LOG_DEBUG(GwLog::DEBUG, "PageWindPlot Dataset: count: %d, xWD: %.1f, xWS: %.2f, xWD_valid? %d, intvBufSize: %d, numWndVals: %d, bufStart: %d, numAddedBufVals: %d, lastIdx: %d, wind source: %s", + count, wdHstry->getLast() / 1000.0 * radToDeg, wsHstry->getLast() / 1000.0 * 1.94384, BDataValid[0], intvBufSize, numWndVals, bufStart, numAddedBufVals, wdHstry->getLastIdx(), + showTruW ? "True" : "App"); - // Set wndCenter from 1st real buffer value - if (wndCenter == INT_MAX || (wndCenter == 0 && count == 1)) { - wndCenter = getCntr(*wdHstry, numWndVals); - LOG_DEBUG(GwLog::DEBUG, "PageWindPlot Range Init: count: %d, xWD: %.1f, wndCenter: %d, diffRng: %d, chrtRng: %d, Min: %.0f, Max: %.0f", count, wdHstry->getLast() / 1000.0 * radToDeg, - wndCenter, diffRng, chrtRng, wdHstry->getMin(numWndVals) / 1000.0 * radToDeg, wdHstry->getMax(numWndVals) / 1000.0 * radToDeg); - } else { - // check and adjust range between left, center, and right chart limit - diffRng = getRng(*wdHstry, wndCenter, numWndVals); - diffRng = (diffRng == wdMAX_VAL ? 0 : diffRng); - if (diffRng > chrtRng) { - chrtRng = int((diffRng + (diffRng >= 0 ? 9 : -1)) / 10) * 10; // Round up to next 10 degree value - } else if (diffRng + 10 < chrtRng) { // Reduce chart range for higher resolution if possible - chrtRng = max(dfltRng, int((diffRng + (diffRng >= 0 ? 9 : -1)) / 10) * 10); - LOG_DEBUG(GwLog::DEBUG, "PageWindPlot Range adjust: wndCenter: %d, diffRng: %d, chrtRng: %d, Min: %.0f, Max: %.0f", wndCenter, diffRng, chrtRng, - wdHstry->getMin(numWndVals) / 1000.0 * radToDeg, wdHstry->getMax(numWndVals) / 1000.0 * radToDeg); + // Set wndCenter from 1st real buffer value + if (wndCenter == INT_MAX || (wndCenter == 0 && count == 1)) { + wndCenter = getCntr(*wdHstry, numWndVals); + LOG_DEBUG(GwLog::DEBUG, "PageWindPlot Range Init: count: %d, xWD: %.1f, wndCenter: %d, diffRng: %d, chrtRng: %d, Min: %.0f, Max: %.0f", count, wdHstry->getLast() / 1000.0 * radToDeg, + wndCenter, diffRng, chrtRng, wdHstry->getMin(numWndVals) / 1000.0 * radToDeg, wdHstry->getMax(numWndVals) / 1000.0 * radToDeg); + } else { + // check and adjust range between left, center, and right chart limit + diffRng = getRng(*wdHstry, wndCenter, numWndVals); + diffRng = (diffRng == wdMAX_VAL ? 0 : diffRng); + if (diffRng > chrtRng) { + chrtRng = int((diffRng + (diffRng >= 0 ? 9 : -1)) / 10) * 10; // Round up to next 10 degree value + } else if (diffRng + 10 < chrtRng) { // Reduce chart range for higher resolution if possible + chrtRng = max(dfltRng, int((diffRng + (diffRng >= 0 ? 9 : -1)) / 10) * 10); + LOG_DEBUG(GwLog::DEBUG, "PageWindPlot Range adjust: wndCenter: %d, diffRng: %d, chrtRng: %d, Min: %.0f, Max: %.0f", wndCenter, diffRng, chrtRng, + wdHstry->getMin(numWndVals) / 1000.0 * radToDeg, wdHstry->getMax(numWndVals) / 1000.0 * radToDeg); + } } - } - chrtScl = float(width) / float(chrtRng) / 2.0; // Chart scale: pixels per degree - wndLeft = wndCenter - chrtRng; - if (wndLeft < 0) - wndLeft += 360; - wndRight = (chrtRng < 180 ? wndCenter + chrtRng : wndCenter + chrtRng - 1); - if (wndRight >= 360) - wndRight -= 360; + chrtScl = float(width) / float(chrtRng) / 2.0; // Chart scale: pixels per degree + wndLeft = wndCenter - chrtRng; + if (wndLeft < 0) + wndLeft += 360; + wndRight = (chrtRng < 180 ? wndCenter + chrtRng : wndCenter + chrtRng - 1); + if (wndRight >= 360) + wndRight -= 360; - // Draw page - //*********************************************************************** + // Draw page + //*********************************************************************** - // Set display in partial refresh mode - getdisplay().setPartialWindow(0, 0, width, height); // Set partial update - getdisplay().setTextColor(commonData->fgcolor); + // Set display in partial refresh mode + getdisplay().setPartialWindow(0, 0, width, height); // Set partial update + getdisplay().setTextColor(commonData->fgcolor); - // chart lines - getdisplay().fillRect(0, yOffset, width, 2, commonData->fgcolor); - getdisplay().fillRect(xCenter, yOffset, 1, cHeight, commonData->fgcolor); + // chart lines + getdisplay().fillRect(0, yOffset, width, 2, commonData->fgcolor); + getdisplay().fillRect(xCenter, yOffset, 1, cHeight, commonData->fgcolor); - // chart labels - char sWndLbl[4]; // char buffer for Wind angle label - getdisplay().setFont(&Ubuntu_Bold12pt8b); - getdisplay().setCursor(xCenter - 88, yOffset - 3); - getdisplay().print(wdName); // Wind data name - snprintf(sWndLbl, 4, "%03d", (wndCenter < 0) ? (wndCenter + 360) : wndCenter); - drawTextCenter(xCenter, yOffset - 11, sWndLbl); - getdisplay().drawCircle(xCenter + 25, yOffset - 17, 2, commonData->fgcolor); // symbol - getdisplay().drawCircle(xCenter + 25, yOffset - 17, 3, commonData->fgcolor); // symbol - getdisplay().setCursor(1, yOffset - 3); - snprintf(sWndLbl, 4, "%03d", (wndLeft < 0) ? (wndLeft + 360) : wndLeft); - getdisplay().print(sWndLbl); // Wind left value - getdisplay().drawCircle(46, yOffset - 17, 2, commonData->fgcolor); // symbol - getdisplay().drawCircle(46, yOffset - 17, 3, commonData->fgcolor); // symbol - getdisplay().setCursor(width - 50, yOffset - 3); - snprintf(sWndLbl, 4, "%03d", (wndRight < 0) ? (wndRight + 360) : wndRight); - getdisplay().print(sWndLbl); // Wind right value - getdisplay().drawCircle(width - 5, yOffset - 17, 2, commonData->fgcolor); // symbol - getdisplay().drawCircle(width - 5, yOffset - 17, 3, commonData->fgcolor); // symbol + // chart labels + char sWndLbl[4]; // char buffer for Wind angle label + getdisplay().setFont(&Ubuntu_Bold12pt8b); + getdisplay().setCursor(xCenter - 88, yOffset - 3); + getdisplay().print(wdName); // Wind data name + snprintf(sWndLbl, 4, "%03d", (wndCenter < 0) ? (wndCenter + 360) : wndCenter); + drawTextCenter(xCenter, yOffset - 11, sWndLbl); + getdisplay().drawCircle(xCenter + 25, yOffset - 17, 2, commonData->fgcolor); // symbol + getdisplay().drawCircle(xCenter + 25, yOffset - 17, 3, commonData->fgcolor); // symbol + getdisplay().setCursor(1, yOffset - 3); + snprintf(sWndLbl, 4, "%03d", (wndLeft < 0) ? (wndLeft + 360) : wndLeft); + getdisplay().print(sWndLbl); // Wind left value + getdisplay().drawCircle(46, yOffset - 17, 2, commonData->fgcolor); // symbol + getdisplay().drawCircle(46, yOffset - 17, 3, commonData->fgcolor); // symbol + getdisplay().setCursor(width - 50, yOffset - 3); + snprintf(sWndLbl, 4, "%03d", (wndRight < 0) ? (wndRight + 360) : wndRight); + getdisplay().print(sWndLbl); // Wind right value + getdisplay().drawCircle(width - 5, yOffset - 17, 2, commonData->fgcolor); // symbol + getdisplay().drawCircle(width - 5, yOffset - 17, 3, commonData->fgcolor); // symbol - if (wdHstry->getMax() == wdMAX_VAL) { - // only values in buffer -> no valid wind data available - wndDataValid = false; - } else if (!BDataValid[0] && !useSimuData) { - // currently no valid xWD data available and no simulation mode - numNoData++; - wndDataValid = true; - if (numNoData > 3) { - // If more than 4 invalid values in a row, send message + if (wdHstry->getMax() == wdMAX_VAL) { + // only values in buffer -> no valid wind data available wndDataValid = false; + } else if (!BDataValid[0] && !useSimuData) { + // currently no valid xWD data available and no simulation mode + numNoData++; + wndDataValid = true; + if (numNoData > 3) { + // If more than 4 invalid values in a row, send message + wndDataValid = false; + } + } else { + numNoData = 0; // reset data error counter + wndDataValid = true; // At least some wind data available } - } else { - numNoData = 0; // reset data error counter - wndDataValid = true; // At least some wind data available - } - // Draw wind values in chart - //*********************************************************************** - if (wndDataValid) { - for (int i = 0; i < (numWndVals / dataIntv); i++) { - chrtVal = static_cast(wdHstry->get(bufStart + (i * dataIntv))); // show the latest wind values in buffer; keep 1st value constant in a rolling buffer - if (chrtVal == wdMAX_VAL) { - chrtPrevVal = wdMAX_VAL; - } else { - chrtVal = static_cast((chrtVal / 1000.0 * radToDeg) + 0.5); // Convert to degrees and round - x = ((chrtVal - wndLeft + 360) % 360) * chrtScl; - y = yOffset + cHeight - i; // Position in chart area + // Draw wind values in chart + //*********************************************************************** + if (wndDataValid) { + for (int i = 0; i < (numWndVals / dataIntv); i++) { + chrtVal = static_cast(wdHstry->get(bufStart + (i * dataIntv))); // show the latest wind values in buffer; keep 1st value constant in a rolling buffer + if (chrtVal == wdMAX_VAL) { + chrtPrevVal = wdMAX_VAL; + } else { + chrtVal = static_cast((chrtVal / 1000.0 * radToDeg) + 0.5); // Convert to degrees and round + x = ((chrtVal - wndLeft + 360) % 360) * chrtScl; + y = yOffset + cHeight - i; // Position in chart area - if (i >= (numWndVals / dataIntv) - 1) // log chart data of 1 line (adjust for test purposes) - LOG_DEBUG(GwLog::DEBUG, "PageWindPlot Chart: i: %d, chrtVal: %d, bufStart: %d, count: %d, linesToShow: %d", i, chrtVal, bufStart, count, (numWndVals / dataIntv)); + if (i >= (numWndVals / dataIntv) - 1) // log chart data of 1 line (adjust for test purposes) + LOG_DEBUG(GwLog::DEBUG, "PageWindPlot Chart: i: %d, chrtVal: %d, bufStart: %d, count: %d, linesToShow: %d", i, chrtVal, bufStart, count, (numWndVals / dataIntv)); - if ((i == 0) || (chrtPrevVal == wdMAX_VAL)) { - // just a dot for 1st chart point or after some invalid values + if ((i == 0) || (chrtPrevVal == wdMAX_VAL)) { + // just a dot for 1st chart point or after some invalid values + prevX = x; + prevY = y; + } else { + // cross borders check; shift values to [-180..0..180]; when crossing borders, range is 2x 180 degrees + int wndLeftDlt = -180 - ((wndLeft >= 180) ? (wndLeft - 360) : wndLeft); + int chrtVal180 = ((chrtVal + wndLeftDlt + 180) % 360 + 360) % 360 - 180; + int chrtPrevVal180 = ((chrtPrevVal + wndLeftDlt + 180) % 360 + 360) % 360 - 180; + if (((chrtPrevVal180 >= -180) && (chrtPrevVal180 < -90) && (chrtVal180 > 90)) || ((chrtPrevVal180 <= 179) && (chrtPrevVal180 > 90) && chrtVal180 <= -90)) { + // If current value crosses chart borders compared to previous value, split line + int xSplit = (((chrtPrevVal180 > 0 ? wndRight : wndLeft) - wndLeft + 360) % 360) * chrtScl; + getdisplay().drawLine(prevX, prevY, xSplit, y, commonData->fgcolor); + getdisplay().drawLine(prevX, prevY - 1, ((xSplit != prevX) ? xSplit : xSplit - 1), ((xSplit != prevX) ? y - 1 : y), commonData->fgcolor); + prevX = (((chrtVal180 > 0 ? wndRight : wndLeft) - wndLeft + 360) % 360) * chrtScl; + } + } + + // Draw line with 2 pixels width + make sure vertical line are drawn correctly + getdisplay().drawLine(prevX, prevY, x, y, commonData->fgcolor); + getdisplay().drawLine(prevX, prevY - 1, ((x != prevX) ? x : x - 1), ((x != prevX) ? y - 1 : y), commonData->fgcolor); + chrtPrevVal = chrtVal; prevX = x; prevY = y; - } else { - // cross borders check; shift values to [-180..0..180]; when crossing borders, range is 2x 180 degrees - int wndLeftDlt = -180 - ((wndLeft >= 180) ? (wndLeft - 360) : wndLeft); - int chrtVal180 = ((chrtVal + wndLeftDlt + 180) % 360 + 360) % 360 - 180; - int chrtPrevVal180 = ((chrtPrevVal + wndLeftDlt + 180) % 360 + 360) % 360 - 180; - if (((chrtPrevVal180 >= -180) && (chrtPrevVal180 < -90) && (chrtVal180 > 90)) || ((chrtPrevVal180 <= 179) && (chrtPrevVal180 > 90) && chrtVal180 <= -90)) { - // If current value crosses chart borders compared to previous value, split line - int xSplit = (((chrtPrevVal180 > 0 ? wndRight : wndLeft) - wndLeft + 360) % 360) * chrtScl; - getdisplay().drawLine(prevX, prevY, xSplit, y, commonData->fgcolor); - getdisplay().drawLine(prevX, prevY - 1, ((xSplit != prevX) ? xSplit : xSplit - 1), ((xSplit != prevX) ? y - 1 : y), commonData->fgcolor); - prevX = (((chrtVal180 > 0 ? wndRight : wndLeft) - wndLeft + 360) % 360) * chrtScl; + } + // Reaching chart area top end + if (i >= (cHeight - 1)) { + oldDataIntv = 0; // force reset of buffer start and number of values to show in next display loop + + int minWndDir = wdHstry->getMin(numWndVals) / 1000.0 * radToDeg; + int maxWndDir = wdHstry->getMax(numWndVals) / 1000.0 * radToDeg; + LOG_DEBUG(GwLog::DEBUG, "PageWindPlot FreeTop: Minimum: %d, Maximum: %d, OldwndCenter: %d", minWndDir, maxWndDir, wndCenter); + // if (((minWndDir - wndCenter >= 0) && (minWndDir - wndCenter < 180)) || ((maxWndDir - wndCenter <= 0) && (maxWndDir - wndCenter >=180))) { + if ((wndRight > wndCenter && (minWndDir >= wndCenter && minWndDir <= wndRight)) || (wndRight <= wndCenter && (minWndDir >= wndCenter || minWndDir <= wndRight)) || (wndLeft < wndCenter && (maxWndDir <= wndCenter && maxWndDir >= wndLeft)) || (wndLeft >= wndCenter && (maxWndDir <= wndCenter || maxWndDir >= wndLeft))) { + // Check if all wind value are left or right of center value -> optimize chart center + wndCenter = getCntr(*wdHstry, numWndVals); } + LOG_DEBUG(GwLog::DEBUG, "PageWindPlot FreeTop: cHeight: %d, bufStart: %d, numWndVals: %d, wndCenter: %d", cHeight, bufStart, numWndVals, wndCenter); + break; } - - // Draw line with 2 pixels width + make sure vertical line are drawn correctly - getdisplay().drawLine(prevX, prevY, x, y, commonData->fgcolor); - getdisplay().drawLine(prevX, prevY - 1, ((x != prevX) ? x : x - 1), ((x != prevX) ? y - 1 : y), commonData->fgcolor); - chrtPrevVal = chrtVal; - prevX = x; - prevY = y; } - // Reaching chart area top end - if (i >= (cHeight - 1)) { - oldDataIntv = 0; // force reset of buffer start and number of values to show in next display loop - int minWndDir = wdHstry->getMin(numWndVals) / 1000.0 * radToDeg; - int maxWndDir = wdHstry->getMax(numWndVals) / 1000.0 * radToDeg; - LOG_DEBUG(GwLog::DEBUG, "PageWindPlot FreeTop: Minimum: %d, Maximum: %d, OldwndCenter: %d", minWndDir, maxWndDir, wndCenter); - // if (((minWndDir - wndCenter >= 0) && (minWndDir - wndCenter < 180)) || ((maxWndDir - wndCenter <= 0) && (maxWndDir - wndCenter >=180))) { - if ((wndRight > wndCenter && (minWndDir >= wndCenter && minWndDir <= wndRight)) || (wndRight <= wndCenter && (minWndDir >= wndCenter || minWndDir <= wndRight)) || (wndLeft < wndCenter && (maxWndDir <= wndCenter && maxWndDir >= wndLeft)) || (wndLeft >= wndCenter && (maxWndDir <= wndCenter || maxWndDir >= wndLeft))) { - // Check if all wind value are left or right of center value -> optimize chart center - wndCenter = getCntr(*wdHstry, numWndVals); + // Print wind speed value + int currentZone; + static int lastZone = 0; + static bool flipTws = false; + int xPosTws; + static const int yPosTws = yOffset + 40; + + xPosTws = flipTws ? 20 : width - 145; + currentZone = (y >= yPosTws - 38) && (y <= yPosTws + 6) && (x >= xPosTws - 4) && (x <= xPosTws + 146) ? 1 : 0; // Define current zone for TWS value + if (currentZone != lastZone) { + // Only flip when x moves to a different zone + if ((y >= yPosTws - 38) && (y <= yPosTws + 6) && (x >= xPosTws - 4) && (x <= xPosTws + 146)) { + flipTws = !flipTws; + xPosTws = flipTws ? 20 : width - 145; } - LOG_DEBUG(GwLog::DEBUG, "PageWindPlot FreeTop: cHeight: %d, bufStart: %d, numWndVals: %d, wndCenter: %d", cHeight, bufStart, numWndVals, wndCenter); - break; } - } + lastZone = currentZone; - // Print wind speed value - int currentZone; - static int lastZone = 0; - static bool flipTws = false; - int xPosTws; - static const int yPosTws = yOffset + 40; + wsValue = wsHstry->getLast(); + wsBVal->value = wsValue / 1000.0; // temp variable to retreive data unit from OBP60Formater + wsBVal->valid = (static_cast(wsValue) != wsHstry->getMinVal()); + String swsValue = formatValue(wsBVal, *commonData).svalue; // value (string) + wsUnit = formatValue(wsBVal, *commonData).unit; // Unit of value + getdisplay().fillRect(xPosTws - 4, yPosTws - 38, 142, 44, commonData->bgcolor); // Clear area for TWS value + getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b); + getdisplay().setCursor(xPosTws, yPosTws); + getdisplay().print(swsValue); // Value + /* if (!wsBVal->valid) { + getdisplay().print("--.-"); + } else { + wsValue = wsValue / 1000.0 * 1.94384; // Wind speed value in knots + if (wsValue < 10.0) { + getdisplay().printf("!%3.1f", wsValue); // Value, round to 1 decimal + } else { + getdisplay().printf("%4.1f", wsValue); // Value, round to 1 decimal + } + } */ + getdisplay().setFont(&Ubuntu_Bold12pt8b); + getdisplay().setCursor(xPosTws + 82, yPosTws - 14); + getdisplay().print(wsName); // Name + getdisplay().setFont(&Ubuntu_Bold8pt8b); + getdisplay().setCursor(xPosTws + 82, yPosTws + 1); + getdisplay().print(wsUnit); // Unit - xPosTws = flipTws ? 20 : width - 145; - currentZone = (y >= yPosTws - 38) && (y <= yPosTws + 6) && (x >= xPosTws - 4) && (x <= xPosTws + 146) ? 1 : 0; // Define current zone for TWS value - if (currentZone != lastZone) { - // Only flip when x moves to a different zone - if ((y >= yPosTws - 38) && (y <= yPosTws + 6) && (x >= xPosTws - 4) && (x <= xPosTws + 146)) { - flipTws = !flipTws; - xPosTws = flipTws ? 20 : width - 145; - } - } - lastZone = currentZone; - - wsValue = wsHstry->getLast(); - wsBVal->value = wsValue / 1000.0; // temp variable to retreive data unit from OBP60Formater - wsBVal->valid = (static_cast(wsValue) != wsHstry->getMinVal()); - String swsValue = formatValue(wsBVal, *commonData).svalue; // value (string) - wsUnit = formatValue(wsBVal, *commonData).unit; // Unit of value - getdisplay().fillRect(xPosTws - 4, yPosTws - 38, 142, 44, commonData->bgcolor); // Clear area for TWS value - getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b); - getdisplay().setCursor(xPosTws, yPosTws); - getdisplay().print(swsValue); // Value -/* if (!wsBVal->valid) { - getdisplay().print("--.-"); } else { - wsValue = wsValue / 1000.0 * 1.94384; // Wind speed value in knots - if (wsValue < 10.0) { - getdisplay().printf("!%3.1f", wsValue); // Value, round to 1 decimal - } else { - getdisplay().printf("%4.1f", wsValue); // Value, round to 1 decimal - } - } */ - getdisplay().setFont(&Ubuntu_Bold12pt8b); - getdisplay().setCursor(xPosTws + 82, yPosTws - 14); - getdisplay().print(wsName); // Name + // No valid data available + LOG_DEBUG(GwLog::LOG, "PageWindPlot: No valid data available"); + getdisplay().setFont(&Ubuntu_Bold10pt8b); + getdisplay().fillRect(xCenter - 33, height / 2 - 20, 66, 24, commonData->bgcolor); // Clear area for message + drawTextCenter(xCenter, height / 2 - 10, "No data"); + } + + // chart Y axis labels; print at last to overwrite potential chart lines in label area + int yPos; + int chrtLbl; getdisplay().setFont(&Ubuntu_Bold8pt8b); - getdisplay().setCursor(xPosTws + 82, yPosTws + 1); - getdisplay().print(wsUnit); // Unit - - } else { - // No valid data available - LOG_DEBUG(GwLog::LOG, "PageWindPlot: No valid data available"); - getdisplay().setFont(&Ubuntu_Bold10pt8b); - getdisplay().fillRect(xCenter - 33, height / 2 - 20, 66, 24, commonData->bgcolor); // Clear area for message - drawTextCenter(xCenter, height / 2 - 10, "No data"); - } - - // chart Y axis labels; print at last to overwrite potential chart lines in label area - int yPos; - int chrtLbl; - getdisplay().setFont(&Ubuntu_Bold8pt8b); - for (int i = 1; i <= 3; i++) { - yPos = yOffset + (i * 60); - getdisplay().fillRect(0, yPos, width, 1, commonData->fgcolor); - getdisplay().fillRect(0, yPos - 8, 24, 16, commonData->bgcolor); // Clear small area to remove potential chart lines - getdisplay().setCursor(1, yPos + 4); - if (count >= intvBufSize) { - // Calculate minute value for label - chrtLbl = ((i - 1 + (prevY < yOffset + 30)) * dataIntv) * -1; // change label if last data point is more than 30 lines (= seconds) from chart line - } else { - int j = 3 - i; - chrtLbl = (int((((numWndVals / dataIntv) - 50) * dataIntv / 60) + 1) - (j * dataIntv)) * -1; // 50 lines left below last chart line + for (int i = 1; i <= 3; i++) { + yPos = yOffset + (i * 60); + getdisplay().fillRect(0, yPos, width, 1, commonData->fgcolor); + getdisplay().fillRect(0, yPos - 8, 24, 16, commonData->bgcolor); // Clear small area to remove potential chart lines + getdisplay().setCursor(1, yPos + 4); + if (count >= intvBufSize) { + // Calculate minute value for label + chrtLbl = ((i - 1 + (prevY < yOffset + 30)) * dataIntv) * -1; // change label if last data point is more than 30 lines (= seconds) from chart line + } else { + int j = 3 - i; + chrtLbl = (int((((numWndVals / dataIntv) - 50) * dataIntv / 60) + 1) - (j * dataIntv)) * -1; // 50 lines left below last chart line + } + getdisplay().printf("%3d", chrtLbl); // Wind value label } - getdisplay().printf("%3d", chrtLbl); // Wind value label + } else if (chrtMode == 'S') { + wsValue = wsHstry->getLast(); + Chart twsChart(wsHstry, wsBVal, 0, 0, dataIntv, dfltRng, logger); + twsChart.drawChrtHdr(); + twsChart.drawChrtGrd(40); + + } else if (chrtMode == 'B') { } LOG_DEBUG(GwLog::DEBUG, "PageWindPlot time: %ld", millis() - timer); From bcc24ee99d52803d181f4d029712431c082a12f7 Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Fri, 17 Oct 2025 00:42:13 +0200 Subject: [PATCH 02/10] OBPcharts principle working --- lib/obp60task/OBPcharts.cpp | 412 ++++++++++++++++++++++++++++----- lib/obp60task/OBPcharts.h | 127 ++++------ lib/obp60task/PageWindPlot.cpp | 49 ++-- 3 files changed, 426 insertions(+), 162 deletions(-) diff --git a/lib/obp60task/OBPcharts.cpp b/lib/obp60task/OBPcharts.cpp index 44c782e..907712b 100644 --- a/lib/obp60task/OBPcharts.cpp +++ b/lib/obp60task/OBPcharts.cpp @@ -1,75 +1,367 @@ // Function lib for display of boat data in various chart formats #include "OBPcharts.h" +#include "OBP60Extensions.h" +#include "OBPRingBuffer.h" // --- Class Chart --------------- -void Chart::drawChrtHdr() { - // chart header label + lines - int i; - getdisplay().fillRect(0, top, cWidth, 2, commonData->fgcolor); +template +Chart::Chart(RingBuffer& dataBuf, int8_t chrtDir, int8_t chrtSz, int dfltRng, CommonData& common, bool useSimuData) + : dataBuf(dataBuf) + , chrtDir(chrtDir) + , chrtSz(chrtSz) + , dfltRng(dfltRng) + , commonData(&common) + , useSimuData(useSimuData) +{ + logger = commonData->logger; + fgColor = commonData->fgcolor; + bgColor = commonData->bgcolor; - // horizontal chart labels - getdisplay().drawLine(cStart.x, cStart.y, cWidth, cStart.y); - getdisplay().fillRect(cStart.x, cStart.y, cWidth, 2, commonData->fgcolor); + LOG_DEBUG(GwLog::DEBUG, "Chart create: dataBuf: %p", (void*)&dataBuf); + dWidth = getdisplay().width(); + dHeight = getdisplay().height(); - getdisplay().setFont(&Ubuntu_Bold10pt8b); - getdisplay().setCursor(cStart.x, cStart.y + 12); - getdisplay().print(dbName); // Wind data name + if (chrtDir == 0) { + // horizontal chart timeline direction + timAxis = dWidth - xOffset; + switch (chrtSz) { + case 0: + valAxis = dHeight - top - bottom; + cStart = { xOffset, top }; + break; + case 1: + valAxis = (dHeight - top - bottom) / 2 - gap; + cStart = { xOffset, top }; + break; + case 2: + valAxis = (dHeight - top - bottom) / 2 - gap; + cStart = { xOffset, top + (valAxis + gap) + gap }; + break; + default: + LOG_DEBUG(GwLog::DEBUG, "displayChart: wrong parameter"); + return; + } + } else if (chrtDir == 1) { + // vertical chart timeline direction + timAxis = dHeight - top - bottom; + switch (chrtSz) { + case 0: + valAxis = dWidth - xOffset; + cStart = { xOffset, top }; + break; + case 1: + valAxis = dWidth / 2 - gap; + cStart = { 0, top }; + break; + case 2: + valAxis = dWidth / 2 - gap; + cStart = { dWidth / 2 + gap, top }; + break; + default: + LOG_DEBUG(GwLog::DEBUG, "displayChart: wrong parameter"); + return; + } + } else { + LOG_DEBUG(GwLog::DEBUG, "displayChart: wrong parameter"); + return; + } + // xCenter = timAxis / 2; - getdisplay().setFont(&Ubuntu_Bold12pt8b); - if (chrtSze == 0) { - i = -1 * (chrtIntv / 8 - 2); - } else { - i = -1 * (chrtIntv / 4 - 2); - } - for (int j = 50; j <= (cWidth - 50); j += 50 ) { - getdisplay().setCursor(cStart.x + j - 16, cStart.y + 12); - getdisplay().print(i++); // time interval - // i++; - getdisplay().drawLine(cStart.x + j - 30, cStart.y, cStart.x - 30, cHeight + top); - } + dataBuf.getMetaData(dbName, dbFormat); + dbMAX_VAL = dataBuf.getMaxVal(); + bufSize = dataBuf.getCapacity(); + LOG_DEBUG(GwLog::DEBUG, "Chart create: dWidth: %d, dHeight: %d, timAxis: %d, valAxis: %d, cStart {x,y}: %d, %d, dbname: %s", dWidth, dHeight, timAxis, valAxis, cStart.x, cStart.y, dbName); +}; + +template +Chart::~Chart() +{ } -void Chart::drawChrtGrd(const int chrtRng) { - // chart Y axis labels + lines - int i; +// chart time axis label + lines +template +void Chart::drawChrtTimeAxis(int8_t chrtIntv) +{ + int timeRng; + float slots, intv, i; + char sTime[6]; - getdisplay().setFont(&Ubuntu_Bold8pt8b); - if (chrtDir == 0) { - i = -1 * (chrtRng / 4 - 2); - for (int j = cStart.x; j <= (cHeight - (cHeight / 4)); j += cHeight / 4 ) { - getdisplay().drawLine(0, cStart.y, cWidth, cStart.y); - getdisplay().setCursor(0, cStart.y + 12); - if (i < 10) - getdisplay().printf("!!%1d", i); // Range value - else if (i < 100) - getdisplay().printf("!%2d", i); // Range value - else - getdisplay().printf("%3d", i); // Range value - i += (chrtRng / 4); - } - } else { - i = -1 * (chrtRng / 8 - 2); - for (int j = cStart.x; j <= (cHeight - (cHeight / 8)); j += cHeight / 8 ) { - getdisplay().drawLine(cStart.x + j - 30, cStart.y, cStart.x - 30, cHeight + top); - getdisplay().setCursor(0, cStart.y + 12); - if (i < 10) - getdisplay().printf("!!%1d", i); // Range value - else if (i < 100) - getdisplay().printf("!%2d", i); // Range value - else - getdisplay().printf("%3d", i); // Range value - i += (chrtRng / 4); - } - } + getdisplay().setTextColor(fgColor); + if (chrtDir == 0) { // horizontal chart + getdisplay().fillRect(0, top, dWidth, 2, fgColor); + getdisplay().setFont(&Ubuntu_Bold8pt8b); + + timeRng = chrtIntv * 4; // Chart time interval: [1] 4 min., [2] 8 min., [3] 12 min., [4] 16 min., [8] 32 min. + slots = (timAxis - xOffset) / 75.0; // number of axis labels + intv = timeRng / slots; // minutes per chart axis interval + i = timeRng; // Chart axis label start at -32, -16, -12, ... minutes + + for (int j = 0; j < timAxis - 30; j += 75) { + LOG_DEBUG(GwLog::DEBUG, "ChartHdr: timAxis: %d, {x,y}: {%d,%d}, i: %.1f, j: %d, chrtIntv: %d, intv: %.1f, slots: %.1f", timAxis, cStart.x, cStart.y, i, j, chrtIntv, intv, slots); + if (chrtIntv < 3) { + snprintf(sTime, size_t(sTime), "-%.1f", i); + drawTextCenter(cStart.x + j - 8, cStart.y - 8, sTime); // time interval + } else { + snprintf(sTime, size_t(sTime), "-%.0f", std::round(i)); + drawTextCenter(cStart.x + j - 4, cStart.y - 8, sTime); // time interval + } + getdisplay().drawLine(cStart.x + j, cStart.y, cStart.x + j, cStart.y + 5, fgColor); + i -= intv; + } + /* getdisplay().setFont(&Ubuntu_Bold8pt8b); + getdisplay().setCursor(timAxis - 8, cStart.y - 2); + getdisplay().print("min"); */ + + } else { // chrtDir == 1; vertical chart + getdisplay().setFont(&Ubuntu_Bold8pt8b); + timeRng = chrtIntv * 4; // Chart time interval: [1] 4 min., [2] 8 min., [3] 12 min., [4] 16 min., [8] 32 min. + slots = timAxis / 75.0; // number of axis labels + intv = timeRng / slots; // minutes per chart axis interval + i = 0; // Chart axis label start at -32, -16, -12, ... minutes + + for (int j = 0; j < (timAxis - 75); j += 75) { // don't print time label at lower end + LOG_DEBUG(GwLog::DEBUG, "ChartHdr: timAxis: %d, {x,y}: {%d,%d}, i: %.1f, j: %d, chrtIntv: %d, intv: %.1f, slots: %.1f", timAxis, cStart.x, cStart.y, i, j, chrtIntv, intv, slots); + if (chrtIntv < 3) { // print 1 decimal if time range is single digit (4 or 8 minutes) + snprintf(sTime, size_t(sTime), "%.1f", i * -1); + } else { + snprintf(sTime, size_t(sTime), "%.0f", std::round(i) * -1); + } + drawTextCenter(dWidth / 2, cStart.y + j, sTime); // time value + getdisplay().drawLine(cStart.x, cStart.y + j, cStart.x + valAxis, cStart.y + j, fgColor); // Grid line + i += intv; + } + } } -bool Chart::drawChrt(int8_t chrtIntv, int dfltRng) { -// hstryBuf = buffer to display -// bValue = present value to display additionally to chart -// chrtDir = chart direction: [0] = vertical, [1] = horizontal -// chrtSze = chart size: [0] = full size, [1] = half size left half/top, [2] half size right half/bottom -// chrtIntv = chart time interval: [1] 4 min., [2] 8 min., [3] 12 min., [4] 16 min., [5] 32 min. -// dfltRng = default range of chart, e.g. 30 = [0..30] +// chart value axis labels + lines +template +void Chart::drawChrtValAxis() +{ + float slots; + int i, intv; + char sVal[6]; + getdisplay().setFont(&Ubuntu_Bold10pt8b); + if (chrtDir == 0) { // horizontal chart + slots = valAxis / 60.0; // number of axis labels + intv = static_cast(round(chrtRng / slots)); + i = intv; + for (int j = 60; j < valAxis - 30; j += 60) { + LOG_DEBUG(GwLog::DEBUG, "ChartGrd: chrtRng: %d, intv: %d, slots: %.1f, valAxis: %d, i: %d, j: %d", chrtRng, intv, slots, valAxis, i, j); + getdisplay().fillRect(cStart.x - xOffset, cStart.y + j - 9, cStart.x - xOffset + 28, 12, bgColor); // Clear small area to remove potential chart lines + String sVal = String(static_cast(round(i))); + getdisplay().setCursor((3 - sVal.length()) * 9, cStart.y + j + 4); // value right-formated + getdisplay().printf("%s", sVal); // Range value + i += intv; + getdisplay().drawLine(cStart.x + 2, cStart.y + j, cStart.x + timAxis, cStart.y + j, fgColor); + } + getdisplay().setFont(&Ubuntu_Bold12pt8b); + drawTextRalign(cStart.x + timAxis, cStart.y - 3, dataBuf.getName()); // buffer data name + + } else { // chrtDir == 1; vertical chart + getdisplay().fillRect(cStart.x, top, valAxis, 2, fgColor); // top chart line + getdisplay().setCursor(cStart.x, cStart.y - 2); + snprintf(sVal, sizeof(sVal), "%d", dataBuf.getMin(numBufVals) / 1000); + getdisplay().printf("%s", sVal); // Range low end + snprintf(sVal, sizeof(sVal), "%.0f", round(chrtRng / 2)); + drawTextCenter(cStart.x + (valAxis / 2), cStart.y - 10, sVal); // Range mid end + snprintf(sVal, sizeof(sVal), "%.0f", round(chrtRng)); + drawTextRalign(cStart.x + valAxis - 1, cStart.y - 2, sVal); // Range high end + for (int j = 0; j <= valAxis; j += (valAxis / 2)) { + getdisplay().drawLine(cStart.x + j - 1, cStart.y, cStart.x + j - 1, cStart.y + timAxis, fgColor); + } + getdisplay().setFont(&Ubuntu_Bold12pt8b); + drawTextCenter(cStart.x + (valAxis / 4) + 4, cStart.y - 11, dataBuf.getName()); // buffer data name + LOG_DEBUG(GwLog::DEBUG, "ChartGrd: chrtRng: %d, intv: %d, slots: %.1f, valAxis: %d, i: %d, sVal.length: %d", chrtRng, intv, slots, valAxis, i, sizeof(sVal)); + } } + +// draw chart +template +void Chart::drawChrt(int8_t chrtIntv, GwApi::BoatValue currValue) +{ + float chrtScl; // Scale for data values in pixels per value + int chrtVal; // Current data value + static int chrtPrevVal; // Last data value in chart area + bool bufDataValid = false; // Flag to indicate if buffer data is valid + static int numNoData; // Counter for multiple invalid data values in a row + // GwApi::BoatValue currValue; // temporary boat value to display current data buffer value + + int x, y; // x and y coordinates for drawing + static int prevX, prevY; // Last x and y coordinates for drawing + + // Identify buffer size and buffer start position for chart + count = dataBuf.getCurrentSize(); + currIdx = dataBuf.getLastIdx(); + numAddedBufVals = (currIdx - lastAddedIdx + bufSize) % bufSize; // Number of values added to buffer since last display + if (chrtIntv != oldChrtIntv || count == 1) { + // new data interval selected by user; this is only x * 230 values instead of 240 seconds (4 minutes) per interval step + intvBufSize = timAxis * chrtIntv; + numBufVals = min(count, (timAxis - 60) * chrtIntv); + bufStart = max(0, count - numBufVals); + lastAddedIdx = currIdx; + oldChrtIntv = chrtIntv; + } else { + numBufVals = numBufVals + numAddedBufVals; + lastAddedIdx = currIdx; + if (count == bufSize) { + bufStart = max(0, bufStart - numAddedBufVals); + } + } + + calcChrtRng(); + chrtScl = float(valAxis) / float(chrtRng); // Chart scale: pixels per value step + + // Do we have valid buffer data? + if (dataBuf.getMax() == dbMAX_VAL) { + // only values in buffer -> no valid wind data available + bufDataValid = false; + } else if (!currValue.valid && !useSimuData) { + // currently no valid boat data available and no simulation mode + numNoData++; + bufDataValid = true; + if (numNoData > 3) { + // If more than 4 invalid values in a row, send message + bufDataValid = false; + } + } else { + numNoData = 0; // reset data error counter + bufDataValid = true; // At least some wind data available + } + + // Draw wind values in chart + //*********************************************************************** + if (bufDataValid) { + for (int i = 0; i < (numBufVals / chrtIntv); i++) { + chrtVal = static_cast(dataBuf.get(bufStart + (i * chrtIntv))); // show the latest wind values in buffer; keep 1st value constant in a rolling buffer + if (chrtVal == dbMAX_VAL) { + chrtPrevVal = dbMAX_VAL; + } else { + chrtVal = static_cast((chrtVal / 1000.0) + 0.5); // Convert to real value and round + if (chrtDir == 0) { // horizontal chart + x = cStart.x + i; // Position in chart area + y = cStart.y + (chrtVal * chrtScl); // value + } else { // vertical chart + x = cStart.x + (chrtVal * chrtScl); // value + y = cStart.y + timAxis - i; // Position in chart area + } + if (i >= (numBufVals / chrtIntv) - 10) // log chart data of 1 line (adjust for test purposes) + LOG_DEBUG(GwLog::DEBUG, "PageWindPlot Chart: i: %d, chrtVal: %d, {x,y} {%d,%d}", i, chrtVal, x, y); + + if ((i == 0) || (chrtPrevVal == dbMAX_VAL)) { + // just a dot for 1st chart point or after some invalid values + prevX = x; + prevY = y; + } + // Draw line with 2 pixels width + make sure vertical lines are drawn correctly + if (chrtDir == 0 || x == prevX) { // vertical line + getdisplay().drawLine(prevX, prevY, x, y, fgColor); + getdisplay().drawLine(prevX - 1, prevY, x - 1, y, fgColor); + } else if (chrtDir == 1 || x != prevX) { // line with some horizontal trend -> normal state + getdisplay().drawLine(prevX, prevY, x, y, fgColor); + getdisplay().drawLine(prevX, prevY - 1, x, y - 1, fgColor); + } + chrtPrevVal = chrtVal; + prevX = x; + prevY = y; + } + // Reaching chart area bottom end + if (i >= timAxis - 1) { + oldChrtIntv = 0; // force reset of buffer start and number of values to show in next display loop + break; + } + } + + // drawChrtValAxis(); + + // uses BoatValue temp variable to format latest buffer value + // doesn't work unfortunately when 'simulation data' is active, because OBP60Formatter generates own simulation value in that case + uint16_t lastVal = dataBuf.getLast(); + currValue.value = lastVal / 1000.0; + currValue.valid = (static_cast(lastVal) != dbMAX_VAL); + LOG_DEBUG(GwLog::DEBUG, "Chart drawChrt: lastVal: %d, currValue-value: %.1f, Valid: %d, Name: %s, Address: %p", lastVal, currValue.value, + currValue.valid, currValue.getName(), (void*)&currValue); + prntCurrValue(&currValue, { x, y }); + + } else { + // No valid data available + LOG_DEBUG(GwLog::LOG, "PageWindPlot: No valid data available"); + getdisplay().setFont(&Ubuntu_Bold10pt8b); + int pX, pY; + if (chrtDir == 0) { + pX = dWidth / 2; + pY = cStart.y + (valAxis / 2) - 10; + } else { + pX = valAxis / 2; + pY = cStart.y + (timAxis / 2) - 10; + } + getdisplay().fillRect(pX - 33, pY - 10, 66, 24, commonData->bgcolor); // Clear area for message + drawTextCenter(pX, pY, "No data"); + } + + drawChrtValAxis(); +} + +// Print current data value +template +void Chart::prntCurrValue(GwApi::BoatValue* currValue, const Pos chrtPos) +{ + int currentZone; + static int lastZone = 0; + static bool flipVal = false; + int xPosVal; + static const int yPosVal = (chrtDir == 0) ? cStart.y + valAxis - 5 : cStart.y + timAxis - 5; + + // flexible move of location for latest boat data value, in case chart data is printed at the current location + /* xPosVal = flipVal ? 8 : valAxis - 135; + currentZone = (chrtPos.y >= yPosVal - 32) && (chrtPos.y <= yPosVal + 6) && (chrtPos.x >= xPosVal - 4) && (chrtPos.x <= xPosVal + 146) ? 1 : 0; // Define current zone for data value + if (currentZone != lastZone) { + // Only flip when x moves to a different zone + if ((chrtPos.y >= yPosVal - 32) && (chrtPos.y <= yPosVal + 6) && (chrtPos.x >= xPosVal - 3) && (chrtPos.x <= xPosVal + 146)) { + flipVal = !flipVal; + xPosVal = flipVal ? 8 : valAxis - 135; + } + } + lastZone = currentZone; */ + + xPosVal = (chrtDir == 0) ? cStart.x + timAxis - 117 : cStart.x + valAxis - 117; + FormattedData frmtDbData = formatValue(currValue, *commonData); + double testdbValue = frmtDbData.value; + String sdbValue = frmtDbData.svalue; // value (string) + String dbUnit = frmtDbData.unit; // Unit of value + LOG_DEBUG(GwLog::DEBUG, "Chart CurrValue: dbValue: %.2f, sdbValue: %s, fmrtDbValue: %.2f, dbFormat: %s, dbUnit: %s, Valid: %d, Name: %s, Address: %p", currValue->value, sdbValue, + testdbValue, currValue->getFormat(), dbUnit, currValue->valid, currValue->getName(), (void*)currValue); + getdisplay().fillRect(xPosVal - 3, yPosVal - 34, 118, 40, bgColor); // Clear area for TWS value + getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b); + getdisplay().setCursor(xPosVal, yPosVal); + if (useSimuData) + getdisplay().printf("%2.1f", currValue->value); // Value + else + getdisplay().print(sdbValue); // Value + // getdisplay().setFont(&Ubuntu_Bold12pt8b); + // getdisplay().setCursor(xPosVal + 76, yPosVal - 14); + // getdisplay().print(dbName); // Name + getdisplay().setFont(&Ubuntu_Bold8pt8b); + getdisplay().setCursor(xPosVal + 76, yPosVal + 1); + getdisplay().print(dbUnit); // Unit +} + +// check and adjust chart range +template +void Chart::calcChrtRng() +{ + int diffRng; + + diffRng = dataBuf.getMax(numBufVals) / 1000; + if (diffRng > chrtRng) { + chrtRng = int((diffRng + (diffRng >= 0 ? 9 : -1)) / 10) * 10; // Round up to next 10 value + } else if (diffRng + 10 < chrtRng) { // Reduce chart range for higher resolution if possible + chrtRng = max(dfltRng, int((diffRng + (diffRng >= 0 ? 9 : -1)) / 10) * 10); + } + LOG_DEBUG(GwLog::DEBUG, "Chart Range: diffRng: %d, chrtRng: %d, Min: %.0f, Max: %.0f", diffRng, chrtRng, dataBuf.getMin(numBufVals) / 1000, dataBuf.getMax(numBufVals) / 1000); +} + +// Explicitly instantiate class with required data types to avoid linker errors +template class Chart; +template class Chart; // --- Class Chart --------------- diff --git a/lib/obp60task/OBPcharts.h b/lib/obp60task/OBPcharts.h index 813c2da..2494213 100644 --- a/lib/obp60task/OBPcharts.h +++ b/lib/obp60task/OBPcharts.h @@ -1,103 +1,62 @@ // Function lib for display of boat data in various chart formats #pragma once -#include "OBP60Extensions.h" +#include +#include #include "Pagedata.h" -// #include "OBPDataOperations.h" -// #include "OBPRingBuffer.h" -struct Point { +struct Pos { int x; int y; }; +template class RingBuffer; +class GwLog; +template class Chart { protected: - RingBuffer* dataBuf; // Buffer to display - GwApi::BoatValue* bValue; // Present value to display additionally to chart - int8_t chrtDir; // Chart direction: [0] = vertical, [1] = horizontal - int8_t chrtSze; // Chart size: [0] = full size, [1] = half size left/top, [2] half size right/bottom - int8_t chrtIntv; // Chart time interval: [4] 4 min., [8] 8 min., [12] 12 min., [16] 16 min., [32] 32 min. + CommonData *commonData; + GwLog *logger; + + RingBuffer &dataBuf; // Buffer to display + int8_t chrtDir; // Chart timeline direction: [0] = horizontal, [1] = vertical + int8_t chrtSz; // Chart size: [0] = full size, [1] = half size left/top, [2] half size right/bottom int dfltRng; // Default range of chart, e.g. 30 = [0..30] + 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 int top = 48; // display top header lines int bottom = 22; // display bottom lines - int gap = 4; // gap between 2 charts; actual gap is 2x - int cWidth; - int cHeight; - Point cStart; // start point for chart area - int cLines; // number of chart lines - int xCenter; // x center point of chart + int gap = 20; // gap between 2 charts; actual gap is 2x + int xOffset = 33; // offset for horizontal axis (time/value), because of space for left vertical axis labeling + int yOffset = 10; // offset for vertical axis (time/value), because of space for top horizontal axis labeling + int dWidth; // Display width + int dHeight; // Display height + int timAxis, valAxis; // size of time and value chart axis + Pos cStart; // start point of chart area + int chrtRng; // Range of buffer values from min to max value - String dbName, dbFormat; - int16_t dbMAX_VAL; - size_t bufSize; - GwApi::BoatValue* bValue; + String dbName, dbFormat; // Name and format of data buffer + int16_t dbMAX_VAL; // Highest possible value of buffer of type -> indicates invalid value in buffer + size_t bufSize; // History buffer size: 1.920 values for 32 min. history chart + int intvBufSize; // Buffer size used for currently selected time interval + int count; // current size of buffer + int numBufVals; // number of wind values available for current interval selection + int bufStart; // 1st data value in buffer to show + int numAddedBufVals; // Number of values added to buffer since last display + size_t currIdx; // Current index in TWD history buffer + size_t lastIdx; // Last index of TWD history buffer + size_t lastAddedIdx = 0; // Last index of TWD history buffer when new data was added + int oldChrtIntv = 0; // remember recent user selection of data interval + + void calcChrtRng(); + void drawChrtValAxis(); public: - Chart(RingBuffer* dataBuf, GwApi::BoatValue* bValue, int8_t chrtDir, int8_t chrtSz, int8_t chrtIntv, int dfltRng, GwLog* logger) - : dataBuf(dataBuf) - , bValue(bValue) - , chrtDir(chrtDir) - , chrtSze(chrtSze) - , chrtIntv(chrtIntv) - , dfltRng(dfltRng) - { - cWidth = getdisplay().width(); - cHeight = getdisplay().height(); - cHeight = cHeight - top - bottom; - if (chrtDir == 0) { - // vertical chart - switch (chrtSze) { - case 0: - // default is already set - break; - case 1: - cWidth = cWidth; - cHeight = cHeight / 2 - gap; - cStart = { 30, cHeight + top }; - break; - case 2: - cWidth = cWidth; - cHeight = cHeight / 2 - gap; - cStart = { cWidth + gap, top }; - break; - default: - LOG_DEBUG(GwLog::DEBUG, "displayChart: wrong parameter"); - return; - } - } else if (chrtDir == 1) { - // horizontal chart - switch (chrtSze) { - case 0: - cStart = { 0, cHeight - bottom }; - break; - case 1: - cWidth = cWidth / 2 - gap; - cHeight = cHeight; - cStart = { 0, cHeight - bottom }; - break; - case 2: - cWidth = cWidth / 2 - gap; - cHeight = cHeight; - cStart = { cWidth + gap, cHeight - bottom }; - break; - default: - LOG_DEBUG(GwLog::DEBUG, "displayChart: wrong parameter"); - return; - } - } else { - LOG_DEBUG(GwLog::DEBUG, "displayChart: wrong parameter"); - return; - } - xCenter = cWidth / 2; - cLines = cHeight - 22; + Chart(RingBuffer& dataBuf, int8_t chrtDir, int8_t chrtSz, int dfltRng, CommonData& common, bool useSimuData); + ~Chart(); + void drawChrtTimeAxis(int8_t chrtIntv); + void drawChrt(int8_t chrtIntv, GwApi::BoatValue currValue); + void prntCurrValue(GwApi::BoatValue* currValue, Pos chrtPos); - dataBuf->getMetaData(dbName, dbFormat); - dbMAX_VAL = dataBuf->getMaxVal(); - bufSize = dataBuf->getCapacity(); - bValue->setFormat(dataBuf->getFormat()); - }; - void drawChrtHdr(); - void drawChrtGrd(const int chrtRng); - bool drawChrt(int8_t chrtIntv, int dfltRng); }; \ No newline at end of file diff --git a/lib/obp60task/PageWindPlot.cpp b/lib/obp60task/PageWindPlot.cpp index 5a92554..eb2d079 100644 --- a/lib/obp60task/PageWindPlot.cpp +++ b/lib/obp60task/PageWindPlot.cpp @@ -1,11 +1,11 @@ #if defined BOARD_OBP60S3 || defined BOARD_OBP40S3 +#include "Pagedata.h" #include "BoatDataCalibration.h" #include "OBP60Extensions.h" #include "OBPDataOperations.h" #include "OBPRingBuffer.h" #include "OBPcharts.h" -#include "Pagedata.h" #include static const double radToDeg = 180.0 / M_PI; // Conversion factor from radians to degrees @@ -117,7 +117,7 @@ public: // Key functions virtual int handleKey(int key) { - // Set chart mode TWD | TWS -> to be implemented + // Set chart mode TWD | TWS if (key == 1) { if (chrtMode == 'D') { chrtMode = 'S'; @@ -189,6 +189,9 @@ public: static String wdName, wdFormat; // Wind direction name and format static String wsName, wsFormat; // Wind speed name and format static int16_t wdMAX_VAL; // Max. value of wd history buffer, indicating invalid values + static std::unique_ptr> twsFlChart; // chart object for wind speed chart + static std::unique_ptr> twdHfChart; // chart object for wind direction chart + static std::unique_ptr> twsHfChart; // chart object for wind speed chart float wsValue; // Wind speed value in chart area String wsUnit; // Wind speed unit in chart area static GwApi::BoatValue* wsBVal = new GwApi::BoatValue("TWS"); // temp BoatValue for wind speed unit identification; required by OBP60Formater @@ -253,7 +256,7 @@ public: isInitialized = true; // Set flag to indicate that page is now initialized } - // read boat data values; TWD only for validation test, TWS for display of current value + // read boat data values; TWD/AWS only for validation test for (int i = 0; i < numBoatData; i++) { bvalue = pageData.values[i]; BDataValid[i] = bvalue->valid; @@ -280,9 +283,18 @@ public: wsBVal->setFormat(wsHstry->getFormat()); lastAddedIdx = wdHstry->getLastIdx(); + LOG_DEBUG(GwLog::DEBUG, "PageWindPlot twsChart: *wsHstry: %p", wsHstry); + twsFlChart = std::unique_ptr>(new Chart(*wsHstry, 0, 0, 15, *commonData, useSimuData)); + twdHfChart = std::unique_ptr>(new Chart(*wdHstry, 1, 1, 15, *commonData, useSimuData)); + twsHfChart = std::unique_ptr>(new Chart(*wsHstry, 1, 2, 15, *commonData, useSimuData)); + oldShowTruW = showTruW; } + // Set display in partial refresh mode + getdisplay().setPartialWindow(0, 0, width, height); // Set partial update + getdisplay().setTextColor(commonData->fgcolor); + if (chrtMode == 'D') { // Identify buffer size and buffer start position for chart count = wdHstry->getCurrentSize(); @@ -302,8 +314,7 @@ public: bufStart = max(0, bufStart - numAddedBufVals); } } - // LOG_DEBUG(GwLog::DEBUG,"PSRAM Size: %d kByte; free: %d Byte", ESP.getPsramSize()/1024, ESP.getFreePsram()); - LOG_DEBUG(GwLog::DEBUG, "PageWindPlot Dataset: count: %d, xWD: %.1f, xWS: %.2f, xWD_valid? %d, intvBufSize: %d, numWndVals: %d, bufStart: %d, numAddedBufVals: %d, lastIdx: %d, wind source: %s", + LOG_DEBUG(GwLog::DEBUG, "PageWindPlot Dataset: count: %d, xWD: %.1f, xWS: %.2f, xWD_valid? %d, intvBufSize: %d, numWndVals: %d, bufStart: %d, numAddedBufVals: %d, lastIdx: %d, wind source: %s", count, wdHstry->getLast() / 1000.0 * radToDeg, wsHstry->getLast() / 1000.0 * 1.94384, BDataValid[0], intvBufSize, numWndVals, bufStart, numAddedBufVals, wdHstry->getLastIdx(), showTruW ? "True" : "App"); @@ -335,10 +346,6 @@ public: // Draw page //*********************************************************************** - // Set display in partial refresh mode - getdisplay().setPartialWindow(0, 0, width, height); // Set partial update - getdisplay().setTextColor(commonData->fgcolor); - // chart lines getdisplay().fillRect(0, yOffset, width, 2, commonData->fgcolor); getdisplay().fillRect(xCenter, yOffset, 1, cHeight, commonData->fgcolor); @@ -348,17 +355,17 @@ public: getdisplay().setFont(&Ubuntu_Bold12pt8b); getdisplay().setCursor(xCenter - 88, yOffset - 3); getdisplay().print(wdName); // Wind data name - snprintf(sWndLbl, 4, "%03d", (wndCenter < 0) ? (wndCenter + 360) : wndCenter); + snprintf(sWndLbl, size_t(sWndLbl), "%03d", (wndCenter < 0) ? (wndCenter + 360) : wndCenter); drawTextCenter(xCenter, yOffset - 11, sWndLbl); getdisplay().drawCircle(xCenter + 25, yOffset - 17, 2, commonData->fgcolor); // symbol getdisplay().drawCircle(xCenter + 25, yOffset - 17, 3, commonData->fgcolor); // symbol getdisplay().setCursor(1, yOffset - 3); - snprintf(sWndLbl, 4, "%03d", (wndLeft < 0) ? (wndLeft + 360) : wndLeft); + snprintf(sWndLbl, size_t(sWndLbl), "%03d", (wndLeft < 0) ? (wndLeft + 360) : wndLeft); getdisplay().print(sWndLbl); // Wind left value getdisplay().drawCircle(46, yOffset - 17, 2, commonData->fgcolor); // symbol getdisplay().drawCircle(46, yOffset - 17, 3, commonData->fgcolor); // symbol getdisplay().setCursor(width - 50, yOffset - 3); - snprintf(sWndLbl, 4, "%03d", (wndRight < 0) ? (wndRight + 360) : wndRight); + snprintf(sWndLbl, size_t(sWndLbl), "%03d", (wndRight < 0) ? (wndRight + 360) : wndRight); getdisplay().print(sWndLbl); // Wind right value getdisplay().drawCircle(width - 5, yOffset - 17, 2, commonData->fgcolor); // symbol getdisplay().drawCircle(width - 5, yOffset - 17, 3, commonData->fgcolor); // symbol @@ -505,14 +512,20 @@ public: } getdisplay().printf("%3d", chrtLbl); // Wind value label } -/* } else if (chrtMode == 'S') { - wsValue = wsHstry->getLast(); - Chart twsChart(wsHstry, wsBVal, 0, 0, dataIntv, dfltRng, logger); - twsChart.drawChrtHdr(); - twsChart.drawChrtGrd(40); + } else if (chrtMode == 'S') { +// wsValue = wsHstry->getLast(); + twsFlChart->drawChrtTimeAxis(dataIntv); + LOG_DEBUG(GwLog::DEBUG, "PageWindPlot chart: wsBVal.name: %s, format: %s, wsBVal.value: %.1f, address: %p", wsBVal->getName(), wsBVal->getFormat(), wsBVal->value, wsBVal); + twsFlChart->drawChrt(dataIntv, *wsBVal); } else if (chrtMode == 'B') { - } */ +// wsValue = wsHstry->getLast(); + twdHfChart->drawChrtTimeAxis(dataIntv); + twsHfChart->drawChrtTimeAxis(dataIntv); + LOG_DEBUG(GwLog::DEBUG, "PageWindPlot chart: wsBVal.name: %s, format: %s, wsBVal.value: %.1f, address: %p", wsBVal->getName(), wsBVal->getFormat(), wsBVal->value, wsBVal); + twdHfChart->drawChrt(dataIntv, *wsBVal); + twsHfChart->drawChrt(dataIntv, *wsBVal); + } LOG_DEBUG(GwLog::DEBUG, "PageWindPlot time: %ld", millis() - timer); return PAGE_UPDATE; From dd5f05922afe0b98c0892e27cb3f375381958cff Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Sat, 22 Nov 2025 01:32:50 +0100 Subject: [PATCH 03/10] Added to OBP60Formatter to return numerical converted value --- lib/obp60task/OBP60Formatter.cpp | 31 +++++++++++++++++++++++++++++++ lib/obp60task/Pagedata.h | 7 ++++--- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/lib/obp60task/OBP60Formatter.cpp b/lib/obp60task/OBP60Formatter.cpp index cfdcc96..e4e73e0 100644 --- a/lib/obp60task/OBP60Formatter.cpp +++ b/lib/obp60task/OBP60Formatter.cpp @@ -55,6 +55,8 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){ static int dayoffset = 0; double rawvalue = 0; + result.cvalue = value->value; + // Load configuration values String stimeZone = commondata.config->getString(commondata.config->timeZone); // [UTC -14.00...+12.00] double timeZone = stimeZone.toDouble(); @@ -149,6 +151,7 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){ val = modf(val*3600.0/60.0, &intmin); modf(val*60.0,&intsec); snprintf(buffer, bsize, "%02.0f:%02.0f:%02.0f", inthr, intmin, intsec); + result.cvalue = timeInSeconds; } else{ static long sec; @@ -158,6 +161,7 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){ } sec = sec % 60; snprintf(buffer, bsize, "11:36:%02i", int(sec)); + result.cvalue = sec; lasttime = millis(); } if(timeZone == 0){ @@ -178,6 +182,7 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){ snprintf(buffer, bsize, "%3.0f", rawvalue); } result.unit = ""; + result.cvalue = rawvalue; } //######################################################## else if (value->getFormat() == "formatCourse" || value->getFormat() == "formatWind"){ @@ -195,6 +200,7 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){ // Format 3 numbers with prefix zero snprintf(buffer,bsize,"%03.0f",course); result.unit = "Deg"; + result.cvalue = course; } //######################################################## else if (value->getFormat() == "formatKnots" && (value->getName() == "SOG" || value->getName() == "STW")){ @@ -228,6 +234,7 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){ else { snprintf(buffer, bsize, fmt_dec_100, speed); } + result.cvalue = speed; } //######################################################## else if (value->getFormat() == "formatKnots" && (value->getName() == "AWS" || value->getName() == "TWS" || value->getName() == "MaxAws" || value->getName() == "MaxTws")){ @@ -308,6 +315,7 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){ snprintf(buffer, bsize, fmt_dec_100, speed); } } + result.cvalue = speed; } //######################################################## else if (value->getFormat() == "formatRot"){ @@ -334,6 +342,7 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){ if (rotation <= -10 || rotation >= 10){ snprintf(buffer, bsize, "%3.0f", rotation); } + result.cvalue = rotation; } //######################################################## else if (value->getFormat() == "formatDop"){ @@ -359,6 +368,7 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){ else { snprintf(buffer, bsize, fmt_dec_100, dop); } + result.cvalue = dop; } //######################################################## else if (value->getFormat() == "formatLatitude"){ @@ -383,6 +393,7 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){ rawvalue = 35.0 + float(random(0, 10)) / 10000.0; snprintf(buffer, bsize, " 51\" %2.4f' N", rawvalue); } + result.cvalue = rawvalue; } //######################################################## else if (value->getFormat() == "formatLongitude"){ @@ -407,6 +418,7 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){ rawvalue = 6.0 + float(random(0, 10)) / 100000.0; snprintf(buffer, bsize, " 15\" %2.4f'", rawvalue); } + result.cvalue = rawvalue; } //######################################################## else if (value->getFormat() == "formatDepth"){ @@ -435,6 +447,7 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){ else { snprintf(buffer, bsize, fmt_dec_100, depth); } + result.cvalue = depth; } //######################################################## else if (value->getFormat() == "formatXte"){ @@ -467,6 +480,7 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){ if(xte >= 100){ snprintf(buffer,bsize,"%3.0f",xte); } + result.cvalue = xte; } //######################################################## else if (value->getFormat() == "kelvinToC"){ @@ -499,6 +513,7 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){ else { snprintf(buffer, bsize, fmt_dec_100, temp); } + result.cvalue = temp; } //######################################################## else if (value->getFormat() == "mtr2nm"){ @@ -531,6 +546,7 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){ else { snprintf(buffer, bsize, fmt_dec_100, distance); } + result.cvalue = distance; } //######################################################## // Special XDR formats @@ -549,6 +565,7 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){ } snprintf(buffer, bsize, "%4.0f", pressure); result.unit = "hPa"; + result.cvalue = pressure; } //######################################################## else if (value->getFormat() == "formatXdr:P:B"){ @@ -564,6 +581,7 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){ } snprintf(buffer, bsize, "%4.0f", pressure); result.unit = "mBar"; + result.cvalue = pressure; } //######################################################## else if (value->getFormat() == "formatXdr:U:V"){ @@ -583,6 +601,7 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){ snprintf(buffer, bsize, fmt_dec_10, voltage); } result.unit = "V"; + result.cvalue = voltage; } //######################################################## else if (value->getFormat() == "formatXdr:I:A"){ @@ -605,6 +624,7 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){ snprintf(buffer, bsize, fmt_dec_100, current); } result.unit = "A"; + result.cvalue = current; } //######################################################## else if (value->getFormat() == "formatXdr:C:K"){ @@ -627,6 +647,7 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){ snprintf(buffer, bsize, fmt_dec_100, temperature); } result.unit = "Deg C"; + result.cvalue = temperature; } //######################################################## else if (value->getFormat() == "formatXdr:C:C"){ @@ -649,6 +670,7 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){ snprintf(buffer, bsize, fmt_dec_100, temperature); } result.unit = "Deg C"; + result.cvalue = temperature; } //######################################################## else if (value->getFormat() == "formatXdr:H:P"){ @@ -671,6 +693,7 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){ snprintf(buffer, bsize, fmt_dec_100, humidity); } result.unit = "%"; + result.cvalue = humidity; } //######################################################## else if (value->getFormat() == "formatXdr:V:P"){ @@ -693,6 +716,7 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){ snprintf(buffer, bsize, fmt_dec_100, volume); } result.unit = "%"; + result.cvalue = volume; } //######################################################## else if (value->getFormat() == "formatXdr:V:M"){ @@ -715,6 +739,7 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){ snprintf(buffer, bsize, fmt_dec_100, volume); } result.unit = "l"; + result.cvalue = volume; } //######################################################## else if (value->getFormat() == "formatXdr:R:I"){ @@ -737,6 +762,7 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){ snprintf(buffer, bsize, fmt_dec_100, flow); } result.unit = "l/min"; + result.cvalue = flow; } //######################################################## else if (value->getFormat() == "formatXdr:G:"){ @@ -759,6 +785,7 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){ snprintf(buffer, bsize, fmt_dec_100, generic); } result.unit = ""; + result.cvalue = generic; } //######################################################## else if (value->getFormat() == "formatXdr:A:P"){ @@ -781,6 +808,7 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){ snprintf(buffer, bsize, fmt_dec_100, dplace); } result.unit = "%"; + result.cvalue = dplace; } //######################################################## else if (value->getFormat() == "formatXdr:A:D"){ @@ -801,6 +829,7 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){ snprintf(buffer,bsize,"%3.0f",angle); } result.unit = "Deg"; + result.cvalue = angle; } //######################################################## else if (value->getFormat() == "formatXdr:T:R"){ @@ -823,6 +852,7 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){ snprintf(buffer, bsize, fmt_dec_100, rpm); } result.unit = "rpm"; + result.cvalue = rpm; } //######################################################## // Default format @@ -838,6 +868,7 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){ snprintf(buffer, bsize, fmt_dec_100, value->value); } result.unit = ""; + result.cvalue = value->value; } buffer[bsize] = 0; result.value = rawvalue; // Return value is only necessary in case of simulation of graphic pointer diff --git a/lib/obp60task/Pagedata.h b/lib/obp60task/Pagedata.h index 3f56a7c..9c515b4 100644 --- a/lib/obp60task/Pagedata.h +++ b/lib/obp60task/Pagedata.h @@ -195,9 +195,10 @@ String formatLongitude(double lon); // Structure for formatted boat values typedef struct{ - double value; - String svalue; - String unit; + double value; // SI value of boat data value + double cvalue; // value converted to target unit + String svalue; // value converted to target unit and formatted + String unit; // target value unit } FormattedData; // Formatter for boat values From 489ee7ed09a0ebaee6d4b27a967d75d8b1ae7b66 Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Sat, 22 Nov 2025 02:33:58 +0100 Subject: [PATCH 04/10] Lots of fixes and enhancements for OBPcharts; ringbuffer now returns values - internally still 2-byte storage; charts operate now with SI values; added flexible multiplier to history buffer; included data calibration for history data --- lib/obp60task/OBPDataOperations.cpp | 75 ++-- lib/obp60task/OBPDataOperations.h | 27 +- lib/obp60task/OBPRingBuffer.h | 50 ++- lib/obp60task/OBPRingBuffer.tpp | 155 ++++--- lib/obp60task/OBPcharts.cpp | 609 +++++++++++++++++++--------- lib/obp60task/OBPcharts.h | 31 +- lib/obp60task/PageWindPlot.cpp | 86 ++-- lib/obp60task/obp60task.cpp | 2 +- 8 files changed, 676 insertions(+), 359 deletions(-) diff --git a/lib/obp60task/OBPDataOperations.cpp b/lib/obp60task/OBPDataOperations.cpp index 73908ac..c68ef53 100644 --- a/lib/obp60task/OBPDataOperations.cpp +++ b/lib/obp60task/OBPDataOperations.cpp @@ -1,15 +1,19 @@ #include "OBPDataOperations.h" +#include "BoatDataCalibration.h" // Functions lib for data instance calibration +#include // --- Class HstryBuf --------------- + // Init history buffers for selected boat data void HstryBuf::init(BoatValueList* boatValues, GwLog *log) { logger = log; int hstryUpdFreq = 1000; // Update frequency for history buffers in ms - int hstryMinVal = 0; // Minimum value for these history buffers - twdHstryMax = 6283; // Max value for wind direction (TWD, AWD) in rad [0...2*PI], shifted by 1000 for 3 decimals - twsHstryMax = 65000; // Max value for wind speed (TWS, AWS) in m/s [0..65], shifted by 1000 for 3 decimals + int mltplr = 1000; // Multiplier which transforms original value into buffer type format + double hstryMinVal = 0; // Minimum value for these history buffers + twdHstryMax = 2 * M_PI; // Max value for wind direction (TWD, AWD) in rad [0...2*PI] + twsHstryMax = 65; // Max value for wind speed (TWS, AWS) in m/s [0..65] (limit due to type capacity of buffer - shifted by ) awdHstryMax = twdHstryMax; awsHstryMax = twsHstryMax; twdHstryMin = hstryMinVal; @@ -19,10 +23,12 @@ void HstryBuf::init(BoatValueList* boatValues, GwLog *log) { const double DBL_MAX = std::numeric_limits::max(); // Initialize history buffers with meta data - hstryBufList.twdHstry->setMetaData("TWD", "formatCourse", hstryUpdFreq, hstryMinVal, twdHstryMax); - hstryBufList.twsHstry->setMetaData("TWS", "formatKnots", hstryUpdFreq, hstryMinVal, twsHstryMax); - hstryBufList.awdHstry->setMetaData("AWD", "formatCourse", hstryUpdFreq, hstryMinVal, twdHstryMax); - hstryBufList.awsHstry->setMetaData("AWS", "formatKnots", hstryUpdFreq, hstryMinVal, twsHstryMax); + mltplr = 10000; // Store 4 decimals for course data + hstryBufList.twdHstry->setMetaData("TWD", "formatCourse", hstryUpdFreq, mltplr, hstryMinVal, twdHstryMax); + hstryBufList.awdHstry->setMetaData("AWD", "formatCourse", hstryUpdFreq, mltplr, hstryMinVal, twdHstryMax); + mltplr = 1000; // Store 3 decimals for windspeed data + hstryBufList.twsHstry->setMetaData("TWS", "formatKnots", hstryUpdFreq, mltplr, hstryMinVal, twsHstryMax); + hstryBufList.awsHstry->setMetaData("AWS", "formatKnots", hstryUpdFreq, mltplr, hstryMinVal, twsHstryMax); // create boat values for history data types, if they don't exist yet twdBVal = boatValues->findValueOrCreate(hstryBufList.twdHstry->getName()); @@ -49,30 +55,32 @@ void HstryBuf::init(BoatValueList* boatValues, GwLog *log) { //void HstryBuf::handleHstryBuf(GwApi* api, BoatValueList* boatValues, bool useSimuData) { void HstryBuf::handleHstryBuf(bool useSimuData) { - static int16_t twd = 20; //initial value only relevant if we use simulation data - static uint16_t tws = 20; //initial value only relevant if we use simulation data - static double awd, aws, hdt = 20; //initial value only relevant if we use simulation data + static double twd, tws, awd, aws, hdt = 20; //initial value only relevant if we use simulation data GwApi::BoatValue *calBVal; // temp variable just for data calibration -> we don't want to calibrate the original data here LOG_DEBUG(GwLog::DEBUG,"obp60task handleHstryBuf: TWD_isValid? %d, twdBVal: %.1f, twaBVal: %.1f, twsBVal: %.1f", twdBVal->valid, twdBVal->value * RAD_TO_DEG, twaBVal->value * RAD_TO_DEG, twsBVal->value * 3.6 / 1.852); if (twdBVal->valid) { +// if (!useSimuData) { calBVal = new GwApi::BoatValue("TWD"); // temporary solution for calibration of history buffer values calBVal->setFormat(twdBVal->getFormat()); calBVal->value = twdBVal->value; calBVal->valid = twdBVal->valid; calibrationData.calibrateInstance(calBVal, logger); // Check if boat data value is to be calibrated - twd = static_cast(std::round(calBVal->value * 1000.0)); + twd = calBVal->value; if (twd >= twdHstryMin && twd <= twdHstryMax) { hstryBufList.twdHstry->add(twd); + LOG_DEBUG(GwLog::DEBUG,"obp60task handleHstryBuf: calBVal.value %.2f, twd: %.2f, twdHstryMin: %.1f, twdHstryMax: %.2f", calBVal->value, twd, twdHstryMin, twdHstryMax); } delete calBVal; calBVal = nullptr; } else if (useSimuData) { +// } else { twd += random(-20, 20); - twd = WindUtils::to360(twd); - hstryBufList.twdHstry->add(static_cast(DegToRad(twd) * 1000.0)); + twd += static_cast(random(-349, 349) / 1000.0); // add up to +/- 20 degree in RAD + twd = WindUtils::to2PI(twd); + hstryBufList.twdHstry->add(twd); } if (twsBVal->valid) { @@ -81,15 +89,16 @@ void HstryBuf::handleHstryBuf(bool useSimuData) { calBVal->value = twsBVal->value; calBVal->valid = twsBVal->valid; calibrationData.calibrateInstance(calBVal, logger); // Check if boat data value is to be calibrated - tws = static_cast(std::round(calBVal->value * 1000)); + tws = calBVal->value; if (tws >= twsHstryMin && tws <= twsHstryMax) { hstryBufList.twsHstry->add(tws); } delete calBVal; calBVal = nullptr; } else if (useSimuData) { - tws += random(-5000, 5000); // TWS value in m/s; expands to 3 decimals - tws = constrain(tws, 0, 25000); // Limit TWS to [0..25] m/s + // tws += random(-5000, 5000); // TWS value in m/s; expands to 3 decimals + tws += static_cast(random(-5000, 5000) / 1000.0); // add up to +/- 5 m/s TWS speed + tws = constrain(tws, 0, 40); // Limit TWS to [0..40] m/s hstryBufList.twsHstry->add(tws); } @@ -109,16 +118,16 @@ void HstryBuf::handleHstryBuf(bool useSimuData) { calibrationData.calibrateInstance(calBVal, logger); // Check if boat data value is to be calibrated awdBVal->value = calBVal->value; awdBVal->valid = true; - awd = std::round(calBVal->value * 1000.0); + awd = calBVal->value; if (awd >= awdHstryMin && awd <= awdHstryMax) { - hstryBufList.awdHstry->add(static_cast(awd)); + hstryBufList.awdHstry->add(awd); } delete calBVal; calBVal = nullptr; } else if (useSimuData) { - awd += random(-20, 20); - awd = WindUtils::to360(awd); - hstryBufList.awdHstry->add(static_cast(DegToRad(awd) * 1000.0)); + awd += static_cast(random(-349, 349) / 1000.0); // add up to +/- 20 degree in RAD + awd = WindUtils::to2PI(awd); + hstryBufList.awdHstry->add(awd); } if (awsBVal->valid) { @@ -127,26 +136,28 @@ void HstryBuf::handleHstryBuf(bool useSimuData) { calBVal->value = awsBVal->value; calBVal->valid = awsBVal->valid; calibrationData.calibrateInstance(calBVal, logger); // Check if boat data value is to be calibrated - aws = std::round(calBVal->value * 1000); + aws = calBVal->value; if (aws >= awsHstryMin && aws <= awsHstryMax) { - hstryBufList.awsHstry->add(static_cast(aws)); + hstryBufList.awsHstry->add(aws); } delete calBVal; calBVal = nullptr; } else if (useSimuData) { - aws += random(-5000, 5000); // TWS value in m/s; expands to 1 decimal - aws = constrain(aws, 0, 25000); // Limit TWS to [0..25] m/s + aws += static_cast(random(-5000, 5000) / 1000.0); // add up to +/- 5 m/s TWS speed + aws = constrain(aws, 0, 40); // Limit TWS to [0..40] m/s hstryBufList.awsHstry->add(aws); } + LOG_DEBUG(GwLog::DEBUG,"obp60task handleHstryBuf-End: Buffer twdHstry: %.3f, twsHstry: %.3f, awdHstry: %.3f, awsHstry: %.3f", hstryBufList.twdHstry->getLast(), hstryBufList.twsHstry->getLast(), + hstryBufList.awdHstry->getLast(),hstryBufList.awsHstry->getLast()); } // --- Class HstryBuf --------------- // --- Class WindUtils -------------- double WindUtils::to2PI(double a) { - a = fmod(a, 2 * M_PI); + a = fmod(a, M_TWOPI); if (a < 0.0) { - a += 2 * M_PI; + a += M_TWOPI; } return a; } @@ -162,18 +173,18 @@ double WindUtils::toPI(double a) double WindUtils::to360(double a) { - a = fmod(a, 360); + a = fmod(a, 360.0); if (a < 0.0) { - a += 360; + a += 360.0; } return a; } double WindUtils::to180(double a) { - a += 180; + a += 180.0; a = to360(a); - a -= 180; + a -= 180.0; return a; } @@ -263,7 +274,7 @@ bool WindUtils::calcTrueWind(const double* awaVal, const double* awsVal, // If STW and SOG are not available, we cannot calculate true wind return false; } - // Serial.println("\ncalcTrueWind: HDT: " + String(hdt) + ", CTW: " + String(ctw) + ", STW: " + String(stw)); +// Serial.println("\ncalcTrueWind: HDT: " + String(hdt) + ", CTW: " + String(ctw) + ", STW: " + String(stw)); if ((*awaVal == DBL_MAX) || (*awsVal == DBL_MAX)) { // Cannot calculate true wind without valid AWA, AWS; other checks are done earlier diff --git a/lib/obp60task/OBPDataOperations.h b/lib/obp60task/OBPDataOperations.h index fc520f9..5ee604b 100644 --- a/lib/obp60task/OBPDataOperations.h +++ b/lib/obp60task/OBPDataOperations.h @@ -2,14 +2,12 @@ #pragma once #include #include "OBPRingBuffer.h" -#include "BoatDataCalibration.h" // Functions lib for data instance calibration #include "obp60task.h" -#include typedef struct { - RingBuffer* twdHstry; + RingBuffer* twdHstry; RingBuffer* twsHstry; - RingBuffer* awdHstry; + RingBuffer* awdHstry; RingBuffer* awsHstry; } tBoatHstryData; // Holds pointers to all history buffers for boat data @@ -17,18 +15,18 @@ class HstryBuf { private: GwLog *logger; - RingBuffer twdHstry; // Circular buffer to store true wind direction values + RingBuffer twdHstry; // Circular buffer to store true wind direction values RingBuffer twsHstry; // Circular buffer to store true wind speed values (TWS) - RingBuffer awdHstry; // Circular buffer to store apparant wind direction values + RingBuffer awdHstry; // Circular buffer to store apparant wind direction values RingBuffer awsHstry; // Circular buffer to store apparant xwind speed values (AWS) - int16_t twdHstryMin; // Min value for wind direction (TWD) in history buffer - int16_t twdHstryMax; // Max value for wind direction (TWD) in history buffer - uint16_t twsHstryMin; - uint16_t twsHstryMax; - int16_t awdHstryMin; - int16_t awdHstryMax; - uint16_t awsHstryMin; - uint16_t awsHstryMax; + double twdHstryMin; // Min value for wind direction (TWD) in history buffer + double twdHstryMax; // Max value for wind direction (TWD) in history buffer + double twsHstryMin; + double twsHstryMax; + double awdHstryMin; + double awdHstryMax; + double awsHstryMin; + double awsHstryMax; // boat values for buffers and for true wind calculation GwApi::BoatValue *twdBVal, *twsBVal, *twaBVal, *awdBVal, *awsBVal; @@ -72,6 +70,7 @@ public: hdmBVal = boatValues->findValueOrCreate("HDM"); varBVal = boatValues->findValueOrCreate("VAR"); }; + static double to2PI(double a); static double toPI(double a); static double to360(double a); diff --git a/lib/obp60task/OBPRingBuffer.h b/lib/obp60task/OBPRingBuffer.h index 4d13da6..15ad5c1 100644 --- a/lib/obp60task/OBPRingBuffer.h +++ b/lib/obp60task/OBPRingBuffer.h @@ -1,10 +1,6 @@ #pragma once +//#include "FreeRTOS.h" #include "GwSynchronized.h" -#include "WString.h" -#include "esp_heap_caps.h" -#include -#include -#include #include template @@ -41,7 +37,6 @@ bool operator!=(const PSRAMAllocator&, const PSRAMAllocator&) { return fal template class RingBuffer { private: - // std::vector buffer; // THE buffer vector std::vector> buffer; // THE buffer vector, allocated in PSRAM size_t capacity; size_t head; // Points to the next insertion position @@ -51,49 +46,52 @@ private: bool is_Full; // Indicates that all buffer elements are used and ringing is in use T MIN_VAL; // lowest possible value of buffer of type T MAX_VAL; // highest possible value of buffer of type -> indicates invalid value in buffer + double dblMIN_VAL, dblMAX_VAL; // MIN_VAL, MAX_VAL in double format mutable SemaphoreHandle_t bufLocker; // metadata for buffer String dataName; // Name of boat data in buffer String dataFmt; // Format of boat data in buffer int updFreq; // Update frequency in milliseconds - T smallest; // Value range of buffer: smallest value; needs to be => MIN_VAL - T largest; // Value range of buffer: biggest value; needs to be < MAX_VAL, since MAX_VAL indicates invalid entries + double mltplr; // Multiplier which transforms original value into buffer type format + double smallest; // Value range of buffer: smallest value; needs to be => MIN_VAL + double largest; // Value range of buffer: biggest value; needs to be < MAX_VAL, since MAX_VAL indicates invalid entries void initCommon(); public: RingBuffer(); RingBuffer(size_t size); - void setMetaData(String name, String format, int updateFrequency, T minValue, T maxValue); // Set meta data for buffer - bool getMetaData(String& name, String& format, int& updateFrequency, T& minValue, T& maxValue); // Get meta data of buffer + void setMetaData(String name, String format, int updateFrequency, double multiplier, double minValue, double maxValue); // Set meta data for buffer + bool getMetaData(String& name, String& format, int& updateFrequency, double& multiplier, double& minValue, double& maxValue); // Get meta data of buffer bool getMetaData(String& name, String& format); String getName() const; // Get buffer name String getFormat() const; // Get buffer data format - void add(const T& value); // Add a new value to buffer - T get(size_t index) const; // Get value at specific position (0-based index from oldest to newest) - T getFirst() const; // Get the first (oldest) value in buffer - T getLast() const; // Get the last (newest) value in buffer - T getMin() const; // Get the lowest value in buffer - T getMin(size_t amount) const; // Get minimum value of the last values of buffer - T getMax() const; // Get the highest value in buffer - T getMax(size_t amount) const; // Get maximum value of the last values of buffer - T getMid() const; // Get mid value between and value in buffer - T getMid(size_t amount) const; // Get mid value between and value of the last values of buffer - T getMedian() const; // Get the median value in buffer - T getMedian(size_t amount) const; // Get the median value of the last values of buffer + void add(const double& value); // Add a new value to buffer + double get(size_t index) const; // Get value at specific position (0-based index from oldest to newest) + double getFirst() const; // Get the first (oldest) value in buffer + double getLast() const; // Get the last (newest) value in buffer + double getMin() const; // Get the lowest value in buffer + double getMin(size_t amount) const; // Get minimum value of the last values of buffer + double getMax() const; // Get the highest value in buffer + double getMax(size_t amount) const; // Get maximum value of the last values of buffer + double getMid() const; // Get mid value between and value in buffer + double getMid(size_t amount) const; // Get mid value between and value of the last values of buffer + double getMedian() const; // Get the median value in buffer + double getMedian(size_t amount) const; // Get the median value of the last values of buffer size_t getCapacity() const; // Get the buffer capacity (maximum size) size_t getCurrentSize() const; // Get the current number of elements in buffer size_t getFirstIdx() const; // Get the index of oldest value in buffer size_t getLastIdx() const; // Get the index of newest value in buffer bool isEmpty() const; // Check if buffer is empty bool isFull() const; // Check if buffer is full - T getMinVal() const; // Get lowest possible value for buffer - T getMaxVal() const; // Get highest possible value for buffer; used for unset/invalid buffer data + double getMinVal() const; // Get lowest possible value for buffer + double getMaxVal() const; // Get highest possible value for buffer; used for unset/invalid buffer data void clear(); // Clear buffer void resize(size_t size); // Delete buffer and set new size - T operator[](size_t index) const; // Operator[] for convenient access (same as get()) - std::vector getAllValues() const; // Get all current values as a vector + double operator[](size_t index) const; // Operator[] for convenient access (same as get()) + std::vector getAllValues() const; // Get all current values in native buffer format as a vector + std::vector getAllValues(size_t amount) const; // Get last values in native buffer format as a vector }; #include "OBPRingBuffer.tpp" \ No newline at end of file diff --git a/lib/obp60task/OBPRingBuffer.tpp b/lib/obp60task/OBPRingBuffer.tpp index 9174568..281e89d 100644 --- a/lib/obp60task/OBPRingBuffer.tpp +++ b/lib/obp60task/OBPRingBuffer.tpp @@ -1,14 +1,20 @@ #include "OBPRingBuffer.h" +#include +#include template -void RingBuffer::initCommon() { +void RingBuffer::initCommon() +{ MIN_VAL = std::numeric_limits::lowest(); MAX_VAL = std::numeric_limits::max(); + dblMIN_VAL = static_cast(MIN_VAL); + dblMAX_VAL = static_cast(MAX_VAL); dataName = ""; dataFmt = ""; updFreq = -1; - smallest = MIN_VAL; - largest = MAX_VAL; + mltplr = 1; + smallest = dblMIN_VAL; + largest = dblMAX_VAL; bufLocker = xSemaphoreCreateMutex(); } @@ -42,19 +48,20 @@ RingBuffer::RingBuffer(size_t size) // Specify meta data of buffer content template -void RingBuffer::setMetaData(String name, String format, int updateFrequency, T minValue, T maxValue) +void RingBuffer::setMetaData(String name, String format, int updateFrequency, double multiplier, double minValue, double maxValue) { GWSYNCHRONIZED(&bufLocker); dataName = name; dataFmt = format; updFreq = updateFrequency; - smallest = std::max(MIN_VAL, minValue); - largest = std::min(MAX_VAL, maxValue); + mltplr = multiplier; + smallest = std::max(dblMIN_VAL, minValue); + largest = std::min(dblMAX_VAL, maxValue); } // Get meta data of buffer content template -bool RingBuffer::getMetaData(String& name, String& format, int& updateFrequency, T& minValue, T& maxValue) +bool RingBuffer::getMetaData(String& name, String& format, int& updateFrequency, double& multiplier, double& minValue, double& maxValue) { if (dataName == "" || dataFmt == "" || updFreq == -1) { return false; // Meta data not set @@ -64,6 +71,7 @@ bool RingBuffer::getMetaData(String& name, String& format, int& updateFrequen name = dataName; format = dataFmt; updateFrequency = updFreq; + multiplier = mltplr; minValue = smallest; maxValue = largest; return true; @@ -99,13 +107,13 @@ String RingBuffer::getFormat() const // Add a new value to buffer template -void RingBuffer::add(const T& value) +void RingBuffer::add(const double& value) { GWSYNCHRONIZED(&bufLocker); if (value < smallest || value > largest) { buffer[head] = MAX_VAL; // Store MAX_VAL if value is out of range } else { - buffer[head] = value; + buffer[head] = static_cast(std::round(value * mltplr)); } last = head; @@ -117,63 +125,63 @@ void RingBuffer::add(const T& value) is_Full = true; } } - + // Serial.printf("Ringbuffer: value %.3f, multiplier: %.1f, buffer: %d\n", value, mltplr, buffer[head]); head = (head + 1) % capacity; } // Get value at specific position (0-based index from oldest to newest) template -T RingBuffer::get(size_t index) const +double RingBuffer::get(size_t index) const { GWSYNCHRONIZED(&bufLocker); if (isEmpty() || index < 0 || index >= count) { - return MAX_VAL; + return dblMAX_VAL; } size_t realIndex = (first + index) % capacity; - return buffer[realIndex]; + return static_cast(buffer[realIndex] / mltplr); } // Operator[] for convenient access (same as get()) template -T RingBuffer::operator[](size_t index) const +double RingBuffer::operator[](size_t index) const { return get(index); } // Get the first (oldest) value in the buffer template -T RingBuffer::getFirst() const +double RingBuffer::getFirst() const { if (isEmpty()) { - return MAX_VAL; + return dblMAX_VAL; } return get(0); } // Get the last (newest) value in the buffer template -T RingBuffer::getLast() const +double RingBuffer::getLast() const { if (isEmpty()) { - return MAX_VAL; + return dblMAX_VAL; } return get(count - 1); } // Get the lowest value in the buffer template -T RingBuffer::getMin() const +double RingBuffer::getMin() const { if (isEmpty()) { - return MAX_VAL; + return dblMAX_VAL; } - T minVal = MAX_VAL; - T value; + double minVal = dblMAX_VAL; + double value; for (size_t i = 0; i < count; i++) { value = get(i); - if (value < minVal && value != MAX_VAL) { + if (value < minVal && value != dblMAX_VAL) { minVal = value; } } @@ -182,19 +190,19 @@ T RingBuffer::getMin() const // Get minimum value of the last values of buffer template -T RingBuffer::getMin(size_t amount) const +double RingBuffer::getMin(size_t amount) const { if (isEmpty() || amount <= 0) { - return MAX_VAL; + return dblMAX_VAL; } if (amount > count) amount = count; - T minVal = MAX_VAL; - T value; + double minVal = dblMAX_VAL; + double value; for (size_t i = 0; i < amount; i++) { value = get(count - 1 - i); - if (value < minVal && value != MAX_VAL) { + if (value < minVal && value != dblMAX_VAL) { minVal = value; } } @@ -203,75 +211,81 @@ T RingBuffer::getMin(size_t amount) const // Get the highest value in the buffer template -T RingBuffer::getMax() const +double RingBuffer::getMax() const { if (isEmpty()) { - return MAX_VAL; + return dblMAX_VAL; } - T maxVal = MIN_VAL; - T value; + double maxVal = dblMIN_VAL; + double value; for (size_t i = 0; i < count; i++) { value = get(i); - if (value > maxVal && value != MAX_VAL) { + if (value > maxVal && value != dblMAX_VAL) { maxVal = value; } } + if (maxVal == dblMIN_VAL) { // no change of initial value -> buffer has only invalid values (MAX_VAL) + maxVal = dblMAX_VAL; + } return maxVal; } // Get maximum value of the last values of buffer template -T RingBuffer::getMax(size_t amount) const +double RingBuffer::getMax(size_t amount) const { if (isEmpty() || amount <= 0) { - return MAX_VAL; + return dblMAX_VAL; } if (amount > count) amount = count; - T maxVal = MIN_VAL; - T value; + double maxVal = dblMIN_VAL; + double value; for (size_t i = 0; i < amount; i++) { value = get(count - 1 - i); - if (value > maxVal && value != MAX_VAL) { + if (value > maxVal && value != dblMAX_VAL) { maxVal = value; } } + if (maxVal == dblMIN_VAL) { // no change of initial value -> buffer has only invalid values (MAX_VAL) + maxVal = dblMAX_VAL; + } return maxVal; } // Get mid value between and value in the buffer template -T RingBuffer::getMid() const +double RingBuffer::getMid() const { if (isEmpty()) { - return MAX_VAL; + return dblMAX_VAL; } - return (getMin() + getMax()) / static_cast(2); + return (getMin() + getMax()) / 2; } // Get mid value between and value of the last values of buffer template -T RingBuffer::getMid(size_t amount) const +double RingBuffer::getMid(size_t amount) const { if (isEmpty() || amount <= 0) { - return MAX_VAL; + return dblMAX_VAL; } if (amount > count) amount = count; - return (getMin(amount) + getMax(amount)) / static_cast(2); + return (getMin(amount) + getMax(amount)) / 2; } // Get the median value in the buffer template -T RingBuffer::getMedian() const +double RingBuffer::getMedian() const { if (isEmpty()) { - return MAX_VAL; + return dblMAX_VAL; } // Create a temporary vector with current valid elements @@ -287,20 +301,20 @@ T RingBuffer::getMedian() const if (count % 2 == 1) { // Odd number of elements - return temp[count / 2]; + return static_cast(temp[count / 2]); } else { // Even number of elements - return average of middle two // Note: For integer types, this truncates. For floating point, it's exact. - return (temp[count / 2 - 1] + temp[count / 2]) / 2; + return static_cast((temp[count / 2 - 1] + temp[count / 2]) / 2); } } // Get the median value of the last values of buffer template -T RingBuffer::getMedian(size_t amount) const +double RingBuffer::getMedian(size_t amount) const { if (isEmpty() || amount <= 0) { - return MAX_VAL; + return dblMAX_VAL; } if (amount > count) amount = count; @@ -310,7 +324,7 @@ T RingBuffer::getMedian(size_t amount) const temp.reserve(amount); for (size_t i = 0; i < amount; i++) { - temp.push_back(get(i)); + temp.push_back(get(count - 1 - i)); } // Sort to find median @@ -318,11 +332,11 @@ T RingBuffer::getMedian(size_t amount) const if (amount % 2 == 1) { // Odd number of elements - return temp[amount / 2]; + return static_cast(temp[amount / 2]); } else { // Even number of elements - return average of middle two // Note: For integer types, this truncates. For floating point, it's exact. - return (temp[amount / 2 - 1] + temp[amount / 2]) / 2; + return static_cast((temp[amount / 2 - 1] + temp[amount / 2]) / 2); } } @@ -370,16 +384,16 @@ bool RingBuffer::isFull() const // Get lowest possible value for buffer template -T RingBuffer::getMinVal() const +double RingBuffer::getMinVal() const { - return MIN_VAL; + return dblMIN_VAL; } // Get highest possible value for buffer; used for unset/invalid buffer data template -T RingBuffer::getMaxVal() const +double RingBuffer::getMaxVal() const { - return MAX_VAL; + return dblMAX_VAL; } // Clear buffer @@ -411,16 +425,37 @@ void RingBuffer::resize(size_t newSize) buffer.resize(newSize, MAX_VAL); } -// Get all current values as a vector +// Get all current values in native buffer format as a vector template -std::vector RingBuffer::getAllValues() const +std::vector RingBuffer::getAllValues() const { - std::vector result; + std::vector result; result.reserve(count); for (size_t i = 0; i < count; i++) { result.push_back(get(i)); } + return result; +} + +// Get last values in native buffer format as a vector +template +std::vector RingBuffer::getAllValues(size_t amount) const +{ + std::vector result; + + if (isEmpty() || amount <= 0) { + return result; + } + if (amount > count) + amount = count; + + result.reserve(amount); + + for (size_t i = 0; i < amount; i++) { + result.push_back(get(count - 1 - i)); + } + return result; } \ No newline at end of file diff --git a/lib/obp60task/OBPcharts.cpp b/lib/obp60task/OBPcharts.cpp index 907712b..96954dc 100644 --- a/lib/obp60task/OBPcharts.cpp +++ b/lib/obp60task/OBPcharts.cpp @@ -5,7 +5,7 @@ // --- Class Chart --------------- template -Chart::Chart(RingBuffer& dataBuf, int8_t chrtDir, int8_t chrtSz, int dfltRng, CommonData& common, bool useSimuData) +Chart::Chart(RingBuffer& dataBuf, int8_t chrtDir, int8_t chrtSz, double dfltRng, CommonData& common, bool useSimuData) : dataBuf(dataBuf) , chrtDir(chrtDir) , chrtSz(chrtSz) @@ -17,28 +17,28 @@ Chart::Chart(RingBuffer& dataBuf, int8_t chrtDir, int8_t chrtSz, int dfltR fgColor = commonData->fgcolor; bgColor = commonData->bgcolor; - LOG_DEBUG(GwLog::DEBUG, "Chart create: dataBuf: %p", (void*)&dataBuf); + LOG_DEBUG(GwLog::DEBUG, "Chart Init: dataBuf: %p", (void*)&dataBuf); dWidth = getdisplay().width(); dHeight = getdisplay().height(); if (chrtDir == 0) { // horizontal chart timeline direction - timAxis = dWidth - xOffset; + timAxis = dWidth; switch (chrtSz) { case 0: valAxis = dHeight - top - bottom; - cStart = { xOffset, top }; + cStart = { 0, top }; break; case 1: valAxis = (dHeight - top - bottom) / 2 - gap; - cStart = { xOffset, top }; + cStart = { 0, top }; break; case 2: valAxis = (dHeight - top - bottom) / 2 - gap; - cStart = { xOffset, top + (valAxis + gap) + gap }; + cStart = { 0, top + (valAxis + gap) + gap }; break; default: - LOG_DEBUG(GwLog::DEBUG, "displayChart: wrong parameter"); + LOG_DEBUG(GwLog::ERROR, "displayChart: wrong init parameter"); return; } } else if (chrtDir == 1) { @@ -46,31 +46,52 @@ Chart::Chart(RingBuffer& dataBuf, int8_t chrtDir, int8_t chrtSz, int dfltR timAxis = dHeight - top - bottom; switch (chrtSz) { case 0: - valAxis = dWidth - xOffset; - cStart = { xOffset, top }; + valAxis = dWidth; + cStart = { 0, top }; break; case 1: - valAxis = dWidth / 2 - gap; + valAxis = dWidth / 2 - gap - 1; cStart = { 0, top }; break; case 2: - valAxis = dWidth / 2 - gap; + valAxis = dWidth / 2 - gap - 1; cStart = { dWidth / 2 + gap, top }; break; default: - LOG_DEBUG(GwLog::DEBUG, "displayChart: wrong parameter"); + LOG_DEBUG(GwLog::ERROR, "displayChart: wrong init parameter"); return; } } else { - LOG_DEBUG(GwLog::DEBUG, "displayChart: wrong parameter"); + LOG_DEBUG(GwLog::ERROR, "displayChart: wrong init parameter"); return; } - // xCenter = timAxis / 2; dataBuf.getMetaData(dbName, dbFormat); + dbMIN_VAL = dataBuf.getMinVal(); dbMAX_VAL = dataBuf.getMaxVal(); bufSize = dataBuf.getCapacity(); - LOG_DEBUG(GwLog::DEBUG, "Chart create: dWidth: %d, dHeight: %d, timAxis: %d, valAxis: %d, cStart {x,y}: %d, %d, dbname: %s", dWidth, dHeight, timAxis, valAxis, cStart.x, cStart.y, dbName); + + if (dbFormat == "formatCourse" || dbFormat == "FormatWind" || dbFormat == "FormatRot") { + + if (dbFormat == "FormatRot") { + chrtDataFmt = 2; // Chart is showing data of rotational format + } else { + chrtDataFmt = 1; // Chart is showing data of course / wind format + } + rngStep = M_TWOPI / 360.0 * 10.0; // +/-10 degrees on each end of chrtMid; we are calculating with SI values + + } else { + chrtDataFmt = 0; // Chart is showing any other data format than + rngStep = 5.0; // +/- 10 for all other values (eg. m/s, m, K, mBar) + } + + chrtMin = 0; + chrtMax = 0; + chrtMid = dbMAX_VAL; + chrtRng = dfltRng; + recalcRngCntr = true; // initialize on first screen call + + LOG_DEBUG(GwLog::DEBUG, "Chart Init: dWidth: %d, dHeight: %d, timAxis: %d, valAxis: %d, cStart {x,y}: %d, %d, dbname: %s, rngStep: %.4f", dWidth, dHeight, timAxis, valAxis, cStart.x, cStart.y, dbName, rngStep); }; template @@ -78,114 +99,25 @@ Chart::~Chart() { } -// chart time axis label + lines +// Perform all actions to draw chart +// Parameters are chart time interval, and the current boat data value to be printed template -void Chart::drawChrtTimeAxis(int8_t chrtIntv) +void Chart::showChrt(int8_t chrtIntv, GwApi::BoatValue currValue) { - int timeRng; - float slots, intv, i; - char sTime[6]; - - getdisplay().setTextColor(fgColor); - if (chrtDir == 0) { // horizontal chart - getdisplay().fillRect(0, top, dWidth, 2, fgColor); - getdisplay().setFont(&Ubuntu_Bold8pt8b); - - timeRng = chrtIntv * 4; // Chart time interval: [1] 4 min., [2] 8 min., [3] 12 min., [4] 16 min., [8] 32 min. - slots = (timAxis - xOffset) / 75.0; // number of axis labels - intv = timeRng / slots; // minutes per chart axis interval - i = timeRng; // Chart axis label start at -32, -16, -12, ... minutes - - for (int j = 0; j < timAxis - 30; j += 75) { - LOG_DEBUG(GwLog::DEBUG, "ChartHdr: timAxis: %d, {x,y}: {%d,%d}, i: %.1f, j: %d, chrtIntv: %d, intv: %.1f, slots: %.1f", timAxis, cStart.x, cStart.y, i, j, chrtIntv, intv, slots); - if (chrtIntv < 3) { - snprintf(sTime, size_t(sTime), "-%.1f", i); - drawTextCenter(cStart.x + j - 8, cStart.y - 8, sTime); // time interval - } else { - snprintf(sTime, size_t(sTime), "-%.0f", std::round(i)); - drawTextCenter(cStart.x + j - 4, cStart.y - 8, sTime); // time interval - } - getdisplay().drawLine(cStart.x + j, cStart.y, cStart.x + j, cStart.y + 5, fgColor); - i -= intv; - } - /* getdisplay().setFont(&Ubuntu_Bold8pt8b); - getdisplay().setCursor(timAxis - 8, cStart.y - 2); - getdisplay().print("min"); */ - - } else { // chrtDir == 1; vertical chart - getdisplay().setFont(&Ubuntu_Bold8pt8b); - timeRng = chrtIntv * 4; // Chart time interval: [1] 4 min., [2] 8 min., [3] 12 min., [4] 16 min., [8] 32 min. - slots = timAxis / 75.0; // number of axis labels - intv = timeRng / slots; // minutes per chart axis interval - i = 0; // Chart axis label start at -32, -16, -12, ... minutes - - for (int j = 0; j < (timAxis - 75); j += 75) { // don't print time label at lower end - LOG_DEBUG(GwLog::DEBUG, "ChartHdr: timAxis: %d, {x,y}: {%d,%d}, i: %.1f, j: %d, chrtIntv: %d, intv: %.1f, slots: %.1f", timAxis, cStart.x, cStart.y, i, j, chrtIntv, intv, slots); - if (chrtIntv < 3) { // print 1 decimal if time range is single digit (4 or 8 minutes) - snprintf(sTime, size_t(sTime), "%.1f", i * -1); - } else { - snprintf(sTime, size_t(sTime), "%.0f", std::round(i) * -1); - } - drawTextCenter(dWidth / 2, cStart.y + j, sTime); // time value - getdisplay().drawLine(cStart.x, cStart.y + j, cStart.x + valAxis, cStart.y + j, fgColor); // Grid line - i += intv; - } - } -} - -// chart value axis labels + lines -template -void Chart::drawChrtValAxis() -{ - float slots; - int i, intv; - char sVal[6]; - - getdisplay().setFont(&Ubuntu_Bold10pt8b); - if (chrtDir == 0) { // horizontal chart - slots = valAxis / 60.0; // number of axis labels - intv = static_cast(round(chrtRng / slots)); - i = intv; - for (int j = 60; j < valAxis - 30; j += 60) { - LOG_DEBUG(GwLog::DEBUG, "ChartGrd: chrtRng: %d, intv: %d, slots: %.1f, valAxis: %d, i: %d, j: %d", chrtRng, intv, slots, valAxis, i, j); - getdisplay().fillRect(cStart.x - xOffset, cStart.y + j - 9, cStart.x - xOffset + 28, 12, bgColor); // Clear small area to remove potential chart lines - String sVal = String(static_cast(round(i))); - getdisplay().setCursor((3 - sVal.length()) * 9, cStart.y + j + 4); // value right-formated - getdisplay().printf("%s", sVal); // Range value - i += intv; - getdisplay().drawLine(cStart.x + 2, cStart.y + j, cStart.x + timAxis, cStart.y + j, fgColor); - } - getdisplay().setFont(&Ubuntu_Bold12pt8b); - drawTextRalign(cStart.x + timAxis, cStart.y - 3, dataBuf.getName()); // buffer data name - - } else { // chrtDir == 1; vertical chart - getdisplay().fillRect(cStart.x, top, valAxis, 2, fgColor); // top chart line - getdisplay().setCursor(cStart.x, cStart.y - 2); - snprintf(sVal, sizeof(sVal), "%d", dataBuf.getMin(numBufVals) / 1000); - getdisplay().printf("%s", sVal); // Range low end - snprintf(sVal, sizeof(sVal), "%.0f", round(chrtRng / 2)); - drawTextCenter(cStart.x + (valAxis / 2), cStart.y - 10, sVal); // Range mid end - snprintf(sVal, sizeof(sVal), "%.0f", round(chrtRng)); - drawTextRalign(cStart.x + valAxis - 1, cStart.y - 2, sVal); // Range high end - for (int j = 0; j <= valAxis; j += (valAxis / 2)) { - getdisplay().drawLine(cStart.x + j - 1, cStart.y, cStart.x + j - 1, cStart.y + timAxis, fgColor); - } - getdisplay().setFont(&Ubuntu_Bold12pt8b); - drawTextCenter(cStart.x + (valAxis / 4) + 4, cStart.y - 11, dataBuf.getName()); // buffer data name - LOG_DEBUG(GwLog::DEBUG, "ChartGrd: chrtRng: %d, intv: %d, slots: %.1f, valAxis: %d, i: %d, sVal.length: %d", chrtRng, intv, slots, valAxis, i, sizeof(sVal)); - } + drawChrtTimeAxis(chrtIntv); + drawChrt(chrtIntv, currValue); + drawChrtValAxis(); } // draw chart template -void Chart::drawChrt(int8_t chrtIntv, GwApi::BoatValue currValue) +void Chart::drawChrt(int8_t chrtIntv, GwApi::BoatValue& currValue) { - float chrtScl; // Scale for data values in pixels per value - int chrtVal; // Current data value - static int chrtPrevVal; // Last data value in chart area + double chrtVal; // Current data value + double chrtScl; // Scale for data values in pixels per value + static double chrtPrevVal; // Last data value in chart area bool bufDataValid = false; // Flag to indicate if buffer data is valid static int numNoData; // Counter for multiple invalid data values in a row - // GwApi::BoatValue currValue; // temporary boat value to display current data buffer value int x, y; // x and y coordinates for drawing static int prevX, prevY; // Last x and y coordinates for drawing @@ -194,13 +126,15 @@ void Chart::drawChrt(int8_t chrtIntv, GwApi::BoatValue currValue) count = dataBuf.getCurrentSize(); currIdx = dataBuf.getLastIdx(); numAddedBufVals = (currIdx - lastAddedIdx + bufSize) % bufSize; // Number of values added to buffer since last display + if (chrtIntv != oldChrtIntv || count == 1) { // new data interval selected by user; this is only x * 230 values instead of 240 seconds (4 minutes) per interval step - intvBufSize = timAxis * chrtIntv; - numBufVals = min(count, (timAxis - 60) * chrtIntv); + // intvBufSize = timAxis * chrtIntv; // obsolete + numBufVals = min(count, (timAxis - 60) * chrtIntv); // keep free or release 60 values on chart for plotting of new values bufStart = max(0, count - numBufVals); lastAddedIdx = currIdx; oldChrtIntv = chrtIntv; + } else { numBufVals = numBufVals + numAddedBufVals; lastAddedIdx = currIdx; @@ -209,19 +143,16 @@ void Chart::drawChrt(int8_t chrtIntv, GwApi::BoatValue currValue) } } - calcChrtRng(); - chrtScl = float(valAxis) / float(chrtRng); // Chart scale: pixels per value step + calcChrtBorders(chrtMid, chrtMin, chrtMax, chrtRng); + chrtScl = double(valAxis) / chrtRng; // Chart scale: pixels per value step // Do we have valid buffer data? - if (dataBuf.getMax() == dbMAX_VAL) { - // only values in buffer -> no valid wind data available + if (dataBuf.getMax() == dbMAX_VAL) { // only values in buffer -> no valid wind data available bufDataValid = false; - } else if (!currValue.valid && !useSimuData) { - // currently no valid boat data available and no simulation mode + } else if (!currValue.valid && !useSimuData) { // currently no valid boat data available and no simulation mode numNoData++; bufDataValid = true; - if (numNoData > 3) { - // If more than 4 invalid values in a row, send message + if (numNoData > 3) { // If more than 4 invalid values in a row, send message bufDataValid = false; } } else { @@ -233,135 +164,451 @@ void Chart::drawChrt(int8_t chrtIntv, GwApi::BoatValue currValue) //*********************************************************************** if (bufDataValid) { for (int i = 0; i < (numBufVals / chrtIntv); i++) { - chrtVal = static_cast(dataBuf.get(bufStart + (i * chrtIntv))); // show the latest wind values in buffer; keep 1st value constant in a rolling buffer + chrtVal = dataBuf.get(bufStart + (i * chrtIntv)); // show the latest wind values in buffer; keep 1st value constant in a rolling buffer if (chrtVal == dbMAX_VAL) { chrtPrevVal = dbMAX_VAL; } else { - chrtVal = static_cast((chrtVal / 1000.0) + 0.5); // Convert to real value and round + if (chrtDir == 0) { // horizontal chart x = cStart.x + i; // Position in chart area - y = cStart.y + (chrtVal * chrtScl); // value + if (chrtDataFmt == 0) { + y = cStart.y + static_cast(((chrtVal - chrtMin) * chrtScl) + 0.5); // calculate chart point and round + } else { // degree type value + y = cStart.y + static_cast((WindUtils::to2PI(chrtVal - chrtMin) * chrtScl) + 0.5); // calculate chart point and round + } } else { // vertical chart - x = cStart.x + (chrtVal * chrtScl); // value y = cStart.y + timAxis - i; // Position in chart area + if (chrtDataFmt == 0) { + x = cStart.x + static_cast(((chrtVal - chrtMin) * chrtScl) + 0.5); // calculate chart point and round + } else { // degree type value + x = cStart.x + static_cast((WindUtils::to2PI(chrtVal - chrtMin) * chrtScl) + 0.5); // calculate chart point and round + } } - if (i >= (numBufVals / chrtIntv) - 10) // log chart data of 1 line (adjust for test purposes) - LOG_DEBUG(GwLog::DEBUG, "PageWindPlot Chart: i: %d, chrtVal: %d, {x,y} {%d,%d}", i, chrtVal, x, y); + + if (i >= (numBufVals / chrtIntv) - 4) // log chart data of 1 line (adjust for test purposes) + LOG_DEBUG(GwLog::DEBUG, "PageWindPlot Chart: i: %d, chrtVal: %.4f, {x,y} {%d,%d}", i, chrtVal, x, y); if ((i == 0) || (chrtPrevVal == dbMAX_VAL)) { // just a dot for 1st chart point or after some invalid values prevX = x; prevY = y; + + } else if (chrtDataFmt != 0) { // cross borders check for degree values; shift values to [-PI..0..PI]; when crossing borders, range is 2x PI degrees + + // Normalize both values relative to chrtMin (shift range to start at 0) + double normCurr = WindUtils::to2PI(chrtVal - chrtMin); + double normPrev = WindUtils::to2PI(chrtPrevVal - chrtMin); + // Check if pixel positions are far apart (crossing chart boundary); happens when one value is near chrtMax and the other near chrtMin + bool crossedBorders = std::abs(normCurr - normPrev) > (chrtRng / 2.0); + + if (crossedBorders) { // If current value crosses chart borders compared to previous value, split line + LOG_DEBUG(GwLog::DEBUG, "PageWindPlot Chart: crossedBorders: %d, chrtVal: %.2f, chrtPrevVal: %.2f", crossedBorders, chrtVal, chrtPrevVal); + bool wrappingFromHighToLow = normCurr < normPrev; // Determine which edge we're crossing + int xSplit = wrappingFromHighToLow ? (cStart.x + valAxis) : cStart.x; + getdisplay().drawLine(prevX, prevY, xSplit, y, fgColor); + getdisplay().drawLine(prevX, prevY - 1, ((xSplit != prevX) ? xSplit : xSplit - 1), ((xSplit != prevX) ? y - 1 : y), fgColor); + prevX = wrappingFromHighToLow ? cStart.x : (cStart.x + valAxis); + } } + // Draw line with 2 pixels width + make sure vertical lines are drawn correctly if (chrtDir == 0 || x == prevX) { // vertical line getdisplay().drawLine(prevX, prevY, x, y, fgColor); getdisplay().drawLine(prevX - 1, prevY, x - 1, y, fgColor); +// getdisplay().drawLine(prevX + 1, prevY, x - 1, y, fgColor); } else if (chrtDir == 1 || x != prevX) { // line with some horizontal trend -> normal state getdisplay().drawLine(prevX, prevY, x, y, fgColor); getdisplay().drawLine(prevX, prevY - 1, x, y - 1, fgColor); +// getdisplay().drawLine(prevX, prevY + 1, x, y - 1, fgColor); } chrtPrevVal = chrtVal; prevX = x; prevY = y; } + // Reaching chart area bottom end if (i >= timAxis - 1) { oldChrtIntv = 0; // force reset of buffer start and number of values to show in next display loop + + if (chrtDataFmt == 1) { // degree of course or wind + recalcRngCntr = true; + LOG_DEBUG(GwLog::DEBUG, "PageWindPlot FreeTop: timAxis: %d, i: %d, bufStart: %d, numBufVals: %d, recalcRngCntr: %d", timAxis, i, bufStart, numBufVals, recalcRngCntr); + } break; } } - // drawChrtValAxis(); - // uses BoatValue temp variable to format latest buffer value // doesn't work unfortunately when 'simulation data' is active, because OBP60Formatter generates own simulation value in that case - uint16_t lastVal = dataBuf.getLast(); - currValue.value = lastVal / 1000.0; - currValue.valid = (static_cast(lastVal) != dbMAX_VAL); - LOG_DEBUG(GwLog::DEBUG, "Chart drawChrt: lastVal: %d, currValue-value: %.1f, Valid: %d, Name: %s, Address: %p", lastVal, currValue.value, - currValue.valid, currValue.getName(), (void*)&currValue); - prntCurrValue(&currValue, { x, y }); + currValue.value = dataBuf.getLast(); + currValue.valid = currValue.value != dbMAX_VAL; + Chart::prntCurrValue(currValue, { x, y }); + LOG_DEBUG(GwLog::DEBUG, "Chart drawChrt: currValue-value: %.1f, Valid: %d, Name: %s, Address: %p", currValue.value, currValue.valid, currValue.getName(), (void*)&currValue); } else { // No valid data available - LOG_DEBUG(GwLog::LOG, "PageWindPlot: No valid data available"); getdisplay().setFont(&Ubuntu_Bold10pt8b); + int pX, pY; - if (chrtDir == 0) { - pX = dWidth / 2; + if (chrtDir == 0) { // horizontal chart + pX = cStart.x + (timAxis / 2); pY = cStart.y + (valAxis / 2) - 10; - } else { - pX = valAxis / 2; + } else { // vertical chart + pX = cStart.x + (valAxis / 2); pY = cStart.y + (timAxis / 2) - 10; } - getdisplay().fillRect(pX - 33, pY - 10, 66, 24, commonData->bgcolor); // Clear area for message + + getdisplay().fillRect(pX - 33, pY - 10, 66, 24, bgColor); // Clear area for message drawTextCenter(pX, pY, "No data"); + LOG_DEBUG(GwLog::LOG, "PageWindPlot: No valid data available"); + } +} + +// Get maximum difference of last of dataBuf ringbuffer values to center chart +template +double Chart::getRng(double center, size_t amount) +{ + size_t count = dataBuf.getCurrentSize(); + + if (dataBuf.isEmpty() || amount <= 0) { + return dbMAX_VAL; + } + if (amount > count) + amount = count; + + double value = 0; + double range = 0; + double maxRng = dbMIN_VAL; + + // Start from the newest value (last) and go backwards x times + for (size_t i = 0; i < amount; i++) { + value = dataBuf.get(count - 1 - i); + + if (value == dbMAX_VAL) { + continue; // ignore invalid values + } + + range = abs(fmod((value - center + (M_TWOPI + M_PI)), M_TWOPI) - M_PI); + if (range > maxRng) + maxRng = range; } - drawChrtValAxis(); + if (maxRng > M_PI) { + maxRng = M_PI; + } + + return (maxRng != dbMIN_VAL ? maxRng : dbMAX_VAL); // Return range from to +} + +// check and adjust chart range and set range borders and range middle +template +void Chart::calcChrtBorders(double& rngMid, double& rngMin, double& rngMax, double& rng) +{ + if (chrtDataFmt == 0) { + // Chart data is of any type but 'degree' + + double oldRngMin = rngMin; + double oldRngMax = rngMax; + + // Chart starts at lowest range value, but at least '0' or includes even negative values + double currMinVal = dataBuf.getMin(numBufVals); + LOG_DEBUG(GwLog::DEBUG, "calcChrtRange0a: currMinVal: %.1f, currMaxVal: %.1f, rngMin: %.1f, rngMid: %.1f, rngMax: %.1f, rng: %.1f, rngStep: %.1f, oldRngMin: %.1f, oldRngMax: %.1f, dfltRng: %.1f, numBufVals: %d", + currMinVal, dataBuf.getMax(numBufVals), rngMin, rngMid, rngMax, rng, rngStep, oldRngMin, oldRngMax, dfltRng, numBufVals); + + if (currMinVal != dbMAX_VAL) { // current min value is valid + if (currMinVal > 0 && dbMIN_VAL == 0) { // Chart range starts at least at '0' or includes negative values + rngMin = 0; + } else if (currMinVal < oldRngMin || (oldRngMin < 0 && (currMinVal > (oldRngMin + rngStep)))) { // decrease rngMin if required or increase if lowest value is higher than old rngMin + rngMin = std::floor(currMinVal / rngStep) * rngStep; + } + } // otherwise keep rngMin unchanged + + double currMaxVal = dataBuf.getMax(numBufVals); + if (currMaxVal != dbMAX_VAL) { // current max value is valid + if ((currMaxVal > oldRngMax) || (currMaxVal < (oldRngMax - rngStep))) { // increase rngMax if required or decrease if lowest value is lower than old rngMax + rngMax = std::ceil(currMaxVal / rngStep) * rngStep; + rngMax = std::max(rngMax, rngMin + dfltRng); // keep at least default chart range + } + } // otherwise keep rngMax unchanged + + rngMid = (rngMin + rngMax) / 2.0; + rng = rngMax - rngMin; + LOG_DEBUG(GwLog::DEBUG, "calcChrtRange1a: currMinVal: %.1f, currMaxVal: %.1f, rngMin: %.1f, rngMid: %.1f, rngMax: %.1f, rng: %.1f, rngStep: %.1f, oldRngMin: %.1f, oldRngMax: %.1f, dfltRng: %.1f, numBufVals: %d", + currMinVal, currMaxVal, rngMin, rngMid, rngMax, rng, rngStep, oldRngMin, oldRngMax, dfltRng, numBufVals); + } else { + + if (chrtDataFmt == 1) { + // Chart data is of type 'course' or 'wind' + + if ((count == 1 && rngMid == 0) || rngMid == dbMAX_VAL) { + recalcRngCntr = true; // initialize + } + + // Set rngMid + if (recalcRngCntr) { + rngMid = dataBuf.getMid(numBufVals); + if (rngMid == dbMAX_VAL) { + rngMid = 0; + } else { + rngMid = std::round(rngMid / rngStep) * rngStep; // Set new center value; round to next value + + // Check if range between 'min' and 'max' is > 180° or crosses '0' + rngMin = dataBuf.getMin(numBufVals); + rngMax = dataBuf.getMax(numBufVals); + rng = (rngMax >= rngMin ? rngMax - rngMin : M_TWOPI - rngMin + rngMax); + rng = max(rng, dfltRng); // keep at least default chart range + if (rng > M_PI) { // If wind range > 180°, adjust wndCenter to smaller wind range end + rngMid = WindUtils::to2PI(rngMid + M_PI); + } + } + recalcRngCntr = false; // Reset flag for determination + LOG_DEBUG(GwLog::DEBUG, "calcChrtRange1b: rngMid: %.1f°, rngMin: %.1f°, rngMax: %.1f°, rng: %.1f°, rngStep: %.1f°", rngMid * RAD_TO_DEG, rngMin * RAD_TO_DEG, rngMax * RAD_TO_DEG, + rng * RAD_TO_DEG, rngStep * RAD_TO_DEG); + } + + } else if (chrtDataFmt == 2) { + // Chart data is of type 'rotation'; then we want to have always to be '0' + rngMid = 0; + } + + // check and adjust range between left, center, and right chart limit + double halfRng = rng / 2.0; // we calculate with range between and edges + double diffRng = getRng(rngMid, numBufVals); + // LOG_DEBUG(GwLog::DEBUG, "calcChrtRange2: diffRng: %.1f°, halfRng: %.1f°", diffRng * RAD_TO_DEG, halfRng * RAD_TO_DEG); + diffRng = (diffRng == dbMAX_VAL ? 0 : std::ceil(diffRng / rngStep) * rngStep); + // LOG_DEBUG(GwLog::DEBUG, "calcChrtRange2: diffRng: %.1f°, halfRng: %.1f°", diffRng * RAD_TO_DEG, halfRng * RAD_TO_DEG); + + if (diffRng > halfRng) { + halfRng = diffRng; // round to next value + } else if (diffRng + rngStep < halfRng) { // Reduce chart range for higher resolution if possible + halfRng = max(dfltRng / 2.0, diffRng); + } + + rngMin = WindUtils::to2PI(rngMid - halfRng); + rngMax = (halfRng < M_PI ? rngMid + halfRng : rngMid + halfRng - (M_TWOPI / 360)); // if chart range is 360°, then make 1° smaller than + rngMax = WindUtils::to2PI(rngMax); + // LOG_DEBUG(GwLog::DEBUG, "calcChrtRange2: diffRng: %.1f°, halfRng: %.1f°", diffRng * RAD_TO_DEG, halfRng * RAD_TO_DEG); + + rng = halfRng * 2.0; + LOG_DEBUG(GwLog::DEBUG, "calcChrtRange2b: rngMid: %.1f°, rngMin: %.1f°, rngMax: %.1f°, diffRng: %.1f°, rng: %.1f°, rngStep: %.1f°", rngMid * RAD_TO_DEG, rngMin * RAD_TO_DEG, rngMax * RAD_TO_DEG, + diffRng * RAD_TO_DEG, rng * RAD_TO_DEG, rngStep * RAD_TO_DEG); + } +} + +// chart time axis label + lines +template +void Chart::drawChrtTimeAxis(int8_t chrtIntv) +{ + int timeRng; + float slots, intv, i; + char sTime[6]; + getdisplay().setFont(&Ubuntu_Bold8pt8b); + getdisplay().setTextColor(fgColor); + + if (chrtDir == 0) { // horizontal chart + getdisplay().fillRect(0, top, dWidth, 2, fgColor); + + timeRng = chrtIntv * 4; // Chart time interval: [1] 4 min., [2] 8 min., [3] 12 min., [4] 16 min., [8] 32 min. + slots = timAxis / 80.0; // number of axis labels + intv = timeRng / slots; // minutes per chart axis interval + i = timeRng; // Chart axis label start at -32, -16, -12, ... minutes + + for (int j = 0; j < timAxis - 30; j += 80) { // fill time axis with values but keep area free on right hand side for value label + // LOG_DEBUG(GwLog::DEBUG, "ChartTimeAxis: timAxis: %d, {x,y}: {%d,%d}, i: %.1f, j: %d, chrtIntv: %d, intv: %.1f, slots: %.1f", timAxis, cStart.x, cStart.y, i, j, chrtIntv, intv, slots); + + // Format time label based on interval + if (chrtIntv < 3) { + snprintf(sTime, sizeof(sTime), "-%.1f", i); + } else { + snprintf(sTime, sizeof(sTime), "-%.0f", std::round(i)); + } + + // draw text with appropriate offset + // int tOffset = (j == 0) ? 13 : (chrtIntv < 3 ? -4 : -4); + int tOffset = j == 0 ? 13 : -4; + drawTextCenter(cStart.x + j + tOffset, cStart.y - 8, sTime); + getdisplay().drawLine(cStart.x + j, cStart.y, cStart.x + j, cStart.y + 5, fgColor); // draw short vertical time mark + + i -= intv; + } + + } else { // chrtDir == 1; vertical chart + timeRng = chrtIntv * 4; // chart time interval: [1] 4 min., [2] 8 min., [3] 12 min., [4] 16 min., [8] 32 min. + slots = timAxis / 75.0; // number of axis labels + intv = timeRng / slots; // minutes per chart axis interval + i = -intv; // chart axis label start at -32, -16, -12, ... minutes + + for (int j = 75; j < (timAxis - 75); j += 75) { // don't print time label at upper and lower end of time axis + if (chrtIntv < 3) { // print 1 decimal if time range is single digit (4 or 8 minutes) + snprintf(sTime, sizeof(sTime), "%.1f", i); + } else { + snprintf(sTime, sizeof(sTime), "%.0f", std::floor(i)); + } + + getdisplay().drawLine(cStart.x, cStart.y + j, cStart.x + valAxis, cStart.y + j, fgColor); // Grid line + + if (chrtSz == 0) { // full size chart + getdisplay().fillRect(0, cStart.y + j - 9, 32, 15, bgColor); // clear small area to remove potential chart lines + getdisplay().setCursor((4 - strlen(sTime)) * 7, cStart.y + j + 3); // time value; print left screen; value right-formated + getdisplay().printf("%s", sTime); // Range value + } else if (chrtSz == 2) { // half size chart; right side + drawTextCenter(dWidth / 2, cStart.y + j, sTime); // time value; print mid screen + } + + i -= intv; + } + } +} + +// chart value axis labels + lines +template +void Chart::drawChrtValAxis() +{ + double slots; + int i, intv; + double cVal, cchrtRng, crngMin; + char sVal[6]; + std::unique_ptr tmpBVal; // Temp variable to get formatted and converted data value from OBP60Formatter + tmpBVal = std::unique_ptr(new GwApi::BoatValue(dataBuf.getName())); + tmpBVal->setFormat(dataBuf.getFormat()); + tmpBVal->valid = true; + + if (chrtDir == 0) { // horizontal chart + slots = valAxis / 60.0; // number of axis labels + tmpBVal->value = chrtRng; + cchrtRng = formatValue(tmpBVal.get(), *commonData).cvalue; // value (converted) + intv = static_cast(round(cchrtRng / slots)); + i = intv; + + getdisplay().setFont(&Ubuntu_Bold10pt8b); + + for (int j = 60; j < valAxis - 30; j += 60) { + LOG_DEBUG(GwLog::DEBUG, "ChartValAxis: chrtRng: %.2f, cchrtRng: %.2f, intv: %d, slots: %.1f, valAxis: %d, i: %d, j: %d", chrtRng, cchrtRng, intv, slots, valAxis, i, j); + getdisplay().drawLine(cStart.x, cStart.y + j, cStart.x + timAxis, cStart.y + j, fgColor); + + getdisplay().fillRect(cStart.x, cStart.y + j - 9, cStart.x + 32, 18, bgColor); // Clear small area to remove potential chart lines + String sVal = String(i); + getdisplay().setCursor((3 - sVal.length()) * 8, cStart.y + j + 6); // value right-formated + getdisplay().printf("%s", sVal); // Range value + + i += intv; + } + + getdisplay().setFont(&Ubuntu_Bold12pt8b); + drawTextRalign(cStart.x + timAxis, cStart.y - 3, dbName); // buffer data name + + } else { // chrtDir == 1; vertical chart + getdisplay().setFont(&Ubuntu_Bold10pt8b); + + getdisplay().fillRect(cStart.x, top, valAxis, 2, fgColor); // top chart line + getdisplay().setCursor(cStart.x, cStart.y - 2); + tmpBVal->value = chrtMin; + cVal = formatValue(tmpBVal.get(), *commonData).cvalue; // value (converted) + snprintf(sVal, sizeof(sVal), "%.0f", round(cVal)); + getdisplay().printf("%s", sVal); // Range low end + + tmpBVal->value = chrtMid; + cVal = formatValue(tmpBVal.get(), *commonData).cvalue; // value (converted) + snprintf(sVal, sizeof(sVal), "%.0f", round(cVal)); + drawTextCenter(cStart.x + (valAxis / 2), cStart.y - 10, sVal); // Range mid end + + tmpBVal->value = chrtMax; + cVal = formatValue(tmpBVal.get(), *commonData).cvalue; // value (converted) + snprintf(sVal, sizeof(sVal), "%.0f", round(cVal)); + drawTextRalign(cStart.x + valAxis - 1, cStart.y - 2, sVal); // Range high end + + for (int j = 0; j <= valAxis + 2; j += ((valAxis + 2) / 2)) { + getdisplay().drawLine(cStart.x + j, cStart.y, cStart.x + j, cStart.y + timAxis, fgColor); + } + + if (chrtSz == 0) { + getdisplay().setFont(&Ubuntu_Bold12pt8b); + drawTextCenter(cStart.x + (valAxis / 4) + 5, cStart.y - 11, dbName); // buffer data name + } + LOG_DEBUG(GwLog::DEBUG, "ChartGrd: chrtRng: %.2f, intv: %d, slots: %.1f, valAxis: %d, i: %d", chrtRng, intv, slots, valAxis, i); + } } // Print current data value template -void Chart::prntCurrValue(GwApi::BoatValue* currValue, const Pos chrtPos) +void Chart::prntCurrValue(GwApi::BoatValue& currValue, const Pos chrtPos) { int currentZone; static int lastZone = 0; static bool flipVal = false; int xPosVal; static const int yPosVal = (chrtDir == 0) ? cStart.y + valAxis - 5 : cStart.y + timAxis - 5; + xPosVal = cStart.x + 1; - // flexible move of location for latest boat data value, in case chart data is printed at the current location - /* xPosVal = flipVal ? 8 : valAxis - 135; - currentZone = (chrtPos.y >= yPosVal - 32) && (chrtPos.y <= yPosVal + 6) && (chrtPos.x >= xPosVal - 4) && (chrtPos.x <= xPosVal + 146) ? 1 : 0; // Define current zone for data value - if (currentZone != lastZone) { - // Only flip when x moves to a different zone - if ((chrtPos.y >= yPosVal - 32) && (chrtPos.y <= yPosVal + 6) && (chrtPos.x >= xPosVal - 3) && (chrtPos.x <= xPosVal + 146)) { - flipVal = !flipVal; - xPosVal = flipVal ? 8 : valAxis - 135; - } - } - lastZone = currentZone; */ - - xPosVal = (chrtDir == 0) ? cStart.x + timAxis - 117 : cStart.x + valAxis - 117; - FormattedData frmtDbData = formatValue(currValue, *commonData); + FormattedData frmtDbData = formatValue(&currValue, *commonData); double testdbValue = frmtDbData.value; String sdbValue = frmtDbData.svalue; // value (string) String dbUnit = frmtDbData.unit; // Unit of value - LOG_DEBUG(GwLog::DEBUG, "Chart CurrValue: dbValue: %.2f, sdbValue: %s, fmrtDbValue: %.2f, dbFormat: %s, dbUnit: %s, Valid: %d, Name: %s, Address: %p", currValue->value, sdbValue, - testdbValue, currValue->getFormat(), dbUnit, currValue->valid, currValue->getName(), (void*)currValue); - getdisplay().fillRect(xPosVal - 3, yPosVal - 34, 118, 40, bgColor); // Clear area for TWS value + LOG_DEBUG(GwLog::DEBUG, "Chart CurrValue: dbValue: %.2f, sdbValue: %s, fmrtDbValue: %.2f, dbFormat: %s, dbUnit: %s, Valid: %d, Name: %s, Address: %p", currValue.value, sdbValue, + testdbValue, currValue.getFormat(), dbUnit, currValue.valid, currValue.getName(), currValue); + + getdisplay().fillRect(xPosVal, yPosVal - 34, 121, 40, bgColor); // Clear area for TWS value getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b); - getdisplay().setCursor(xPosVal, yPosVal); - if (useSimuData) - getdisplay().printf("%2.1f", currValue->value); // Value - else + getdisplay().setCursor(xPosVal + 1, yPosVal); + if (useSimuData) { + getdisplay().printf("%2.1f", currValue.value); // Value + } else { getdisplay().print(sdbValue); // Value - // getdisplay().setFont(&Ubuntu_Bold12pt8b); - // getdisplay().setCursor(xPosVal + 76, yPosVal - 14); - // getdisplay().print(dbName); // Name + } + + getdisplay().setFont(&Ubuntu_Bold10pt8b); + getdisplay().setCursor(xPosVal + 76, yPosVal - 17); + getdisplay().print(dbName); // Name + getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setCursor(xPosVal + 76, yPosVal + 1); getdisplay().print(dbUnit); // Unit } -// check and adjust chart range +// Identify Min and Max values of range for course data and select them considering smallest gap +// E.g., Min=30°, Max=270° will be converted to smaller range of Min=270° and Max=30° +// obsolete; creates random results by purpose with large data arrays when data is equally distributed template -void Chart::calcChrtRng() +void Chart::getAngleMinMax(const std::vector& angles, double& rngMin, double& rngMax) { - int diffRng; - - diffRng = dataBuf.getMax(numBufVals) / 1000; - if (diffRng > chrtRng) { - chrtRng = int((diffRng + (diffRng >= 0 ? 9 : -1)) / 10) * 10; // Round up to next 10 value - } else if (diffRng + 10 < chrtRng) { // Reduce chart range for higher resolution if possible - chrtRng = max(dfltRng, int((diffRng + (diffRng >= 0 ? 9 : -1)) / 10) * 10); + if (angles.empty()) { + rngMin = 0; + rngMax = 0; + return; } - LOG_DEBUG(GwLog::DEBUG, "Chart Range: diffRng: %d, chrtRng: %d, Min: %.0f, Max: %.0f", diffRng, chrtRng, dataBuf.getMin(numBufVals) / 1000, dataBuf.getMax(numBufVals) / 1000); + + if (angles.size() == 1) { + rngMin = angles[0]; + rngMax = angles[0]; + return; + } + + // Sort angles + std::vector sorted = angles; + std::sort(sorted.begin(), sorted.end()); + + // Find the largest gap between consecutive angles + double maxGap = 0.0; + int maxGapIndex = 0; + for (size_t i = 0; i < sorted.size(); i++) { + double next = sorted[(i + 1) % sorted.size()]; + double curr = sorted[i]; + + // Calculate gap (wrapping around at 360°/2*Pi) + double gap = (i == sorted.size() - 1) ? (M_TWOPI - curr + next) : (next - curr); + + if (gap > maxGap) { + maxGap = gap; + maxGapIndex = i; + } + } + + // The range is on the opposite side of the largest gap + // Min is after the gap, max is before it + rngMin = sorted[(maxGapIndex + 1) % sorted.size()]; + rngMax = sorted[maxGapIndex]; } // Explicitly instantiate class with required data types to avoid linker errors template class Chart; -template class Chart; // --- Class Chart --------------- diff --git a/lib/obp60task/OBPcharts.h b/lib/obp60task/OBPcharts.h index 2494213..c33f52e 100644 --- a/lib/obp60task/OBPcharts.h +++ b/lib/obp60task/OBPcharts.h @@ -1,7 +1,5 @@ // Function lib for display of boat data in various chart formats #pragma once -#include -#include #include "Pagedata.h" struct Pos { @@ -20,7 +18,7 @@ protected: RingBuffer &dataBuf; // Buffer to display int8_t chrtDir; // Chart timeline direction: [0] = horizontal, [1] = vertical int8_t chrtSz; // Chart size: [0] = full size, [1] = half size left/top, [2] half size right/bottom - int dfltRng; // Default range of chart, e.g. 30 = [0..30] + double dfltRng; // Default range of chart, e.g. 30 = [0..30] uint16_t fgColor; // color code for any screen writing uint16_t bgColor; // color code for screen background bool useSimuData; // flag to indicate if simulation data is active @@ -34,12 +32,18 @@ protected: int dHeight; // Display height int timAxis, valAxis; // size of time and value chart axis Pos cStart; // start point of chart area - int chrtRng; // Range of buffer values from min to max value + double chrtRng; // Range of buffer values from min to max value + double chrtMin; // Range low end value + double chrtMax; // Range high end value + double chrtMid; // Range mid value + double rngStep; // Defines the step of adjustment (e.g. 10 m/s) for value axis range + bool recalcRngCntr = false; // Flag for re-calculation of mid value of chart for wind data types String dbName, dbFormat; // Name and format of data buffer - int16_t dbMAX_VAL; // Highest possible value of buffer of type -> indicates invalid value in buffer + int chrtDataFmt; // Data format of chart: [0] size values; [1] degree of course or wind; [2] rotational degrees + double dbMIN_VAL; // Lowest possible value of buffer of type + double dbMAX_VAL; // Highest possible value of buffer of type ; indicates invalid value in buffer size_t bufSize; // History buffer size: 1.920 values for 32 min. history chart - int intvBufSize; // Buffer size used for currently selected time interval int count; // current size of buffer int numBufVals; // number of wind values available for current interval selection int bufStart; // 1st data value in buffer to show @@ -49,14 +53,17 @@ protected: size_t lastAddedIdx = 0; // Last index of TWD history buffer when new data was added int oldChrtIntv = 0; // remember recent user selection of data interval - void calcChrtRng(); - void drawChrtValAxis(); + void drawChrt(int8_t chrtIntv, GwApi::BoatValue& currValue); // Draw chart line + double getRng(double center, size_t amount); // Calculate range between chart center and edges + void calcChrtBorders(double& rngMid, double& rngMin, double& rngMax, double& rng); // Calculate chart points for value axis and return range between and + void drawChrtTimeAxis(int8_t chrtIntv); // Draw time axis of chart, value and lines + void drawChrtValAxis(); // Draw value axis of chart, value and lines + void prntCurrValue(GwApi::BoatValue& currValue, Pos chrtPos); // Add current boat data value to chart + void getAngleMinMax(const std::vector& angles, double& rngMin, double& rngMax); // Identify Min and Max for course data with smallest gap public: - Chart(RingBuffer& dataBuf, int8_t chrtDir, int8_t chrtSz, int dfltRng, CommonData& common, bool useSimuData); + Chart(RingBuffer& dataBuf, int8_t chrtDir, int8_t chrtSz, double dfltRng, CommonData& common, bool useSimuData); // Chart object of data chart ~Chart(); - void drawChrtTimeAxis(int8_t chrtIntv); - void drawChrt(int8_t chrtIntv, GwApi::BoatValue currValue); - void prntCurrValue(GwApi::BoatValue* currValue, Pos chrtPos); + void showChrt(int8_t chrtIntv, GwApi::BoatValue currValue); // Perform all actions to draw chart }; \ No newline at end of file diff --git a/lib/obp60task/PageWindPlot.cpp b/lib/obp60task/PageWindPlot.cpp index eb2d079..4a115dc 100644 --- a/lib/obp60task/PageWindPlot.cpp +++ b/lib/obp60task/PageWindPlot.cpp @@ -184,22 +184,24 @@ public: GwConfigHandler* config = commonData->config; GwLog* logger = commonData->logger; - static RingBuffer* wdHstry; // Wind direction data buffer + static RingBuffer* wdHstry; // Wind direction data buffer static RingBuffer* wsHstry; // Wind speed data buffer static String wdName, wdFormat; // Wind direction name and format static String wsName, wsFormat; // Wind speed name and format static int16_t wdMAX_VAL; // Max. value of wd history buffer, indicating invalid values - static std::unique_ptr> twsFlChart; // chart object for wind speed chart - static std::unique_ptr> twdHfChart; // chart object for wind direction chart - static std::unique_ptr> twsHfChart; // chart object for wind speed chart - float wsValue; // Wind speed value in chart area - String wsUnit; // Wind speed unit in chart area + static std::unique_ptr> twdFlChart; // chart object for wind direction chart, full size + static std::unique_ptr> twsFlChart; // chart object for wind speed chart, full size + static std::unique_ptr> twdHfChart; // chart object for wind direction chart, half size + static std::unique_ptr> twsHfChart; // chart object for wind speed chart, half size +// float wsValue; // Wind speed value in chart area +// String wsUnit; // Wind speed unit in chart area + static GwApi::BoatValue* wdBVal = new GwApi::BoatValue("TWD"); // temp BoatValue for wind direction unit identification; required by OBP60Formater static GwApi::BoatValue* wsBVal = new GwApi::BoatValue("TWS"); // temp BoatValue for wind speed unit identification; required by OBP60Formater - // current boat data values; TWD/AWD only for validation test - const int numBoatData = 2; - GwApi::BoatValue* bvalue; - bool BDataValid[numBoatData]; + // current boat data values + const int numBoatData = 4; + GwApi::BoatValue* bvalue[numBoatData]; + // bool BDataValid[numBoatData]; static bool isInitialized = false; // Flag to indicate that page is initialized static bool wndDataValid = false; // Flag to indicate if wind data is valid @@ -224,7 +226,10 @@ public: static int wndCenter; // chart wind center value position static int wndLeft; // chart wind left value position static int wndRight; // chart wind right value position - static int chrtRng; // Range of wind values from mid wind value to min/max wind value in degrees + static int chrtRng; // Range of wind values from min to max wind value in degrees + double dfltRngWd; // default range for course chart from min to max value in degrees + double dfltRngWs; // defautl range for wind speed chart from min to max value in m/s + int diffRng; // Difference between mid and current wind value static const int dfltRng = 60; // Default range for chart int midWndDir; // New value for wndCenter after chart start / shift @@ -246,7 +251,7 @@ public: numNoData = 0; bufStart = 0; oldDataIntv = 0; - wsValue = 0; + // wsValue = 0; numAddedBufVals, currIdx, lastIdx = 0; wndCenter = INT_MAX; midWndDir = 0; @@ -256,10 +261,10 @@ public: isInitialized = true; // Set flag to indicate that page is now initialized } - // read boat data values; TWD/AWS only for validation test + // read boat data values for (int i = 0; i < numBoatData; i++) { - bvalue = pageData.values[i]; - BDataValid[i] = bvalue->valid; + bvalue[i] = pageData.values[i]; + // BDataValid[i] = bvalue->valid; } // Optical warning by limit violation (unused) @@ -280,13 +285,17 @@ public: wsHstry->getMetaData(wsName, wsFormat); wdMAX_VAL = wdHstry->getMaxVal(); bufSize = wdHstry->getCapacity(); - wsBVal->setFormat(wsHstry->getFormat()); + wdBVal->setFormat(wdFormat); + wsBVal->setFormat(wsFormat); lastAddedIdx = wdHstry->getLastIdx(); - LOG_DEBUG(GwLog::DEBUG, "PageWindPlot twsChart: *wsHstry: %p", wsHstry); - twsFlChart = std::unique_ptr>(new Chart(*wsHstry, 0, 0, 15, *commonData, useSimuData)); - twdHfChart = std::unique_ptr>(new Chart(*wdHstry, 1, 1, 15, *commonData, useSimuData)); - twsHfChart = std::unique_ptr>(new Chart(*wsHstry, 1, 2, 15, *commonData, useSimuData)); + dfltRngWd = 60.0 * DEG_TO_RAD; // default range for course chart: 60° + dfltRngWs = 7.5; // default range for wind speed chart: 7.5 m/s + LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: *wdHstry: %p, *wsHstry: %p", wdHstry, wsHstry); + twdFlChart = std::unique_ptr>(new Chart(*wdHstry, 1, 0, dfltRngWd, *commonData, useSimuData)); + twsFlChart = std::unique_ptr>(new Chart(*wsHstry, 0, 0, dfltRngWs, *commonData, useSimuData)); + twdHfChart = std::unique_ptr>(new Chart(*wdHstry, 1, 1, dfltRngWd, *commonData, useSimuData)); + twsHfChart = std::unique_ptr>(new Chart(*wsHstry, 1, 2, dfltRngWs, *commonData, useSimuData)); oldShowTruW = showTruW; } @@ -295,7 +304,7 @@ public: getdisplay().setPartialWindow(0, 0, width, height); // Set partial update getdisplay().setTextColor(commonData->fgcolor); - if (chrtMode == 'D') { +/* if (chrtMode == 'D') { // Identify buffer size and buffer start position for chart count = wdHstry->getCurrentSize(); currIdx = wdHstry->getLastIdx(); @@ -478,7 +487,7 @@ public: } else { getdisplay().printf("%4.1f", wsValue); // Value, round to 1 decimal } - } */ + } */ /* getdisplay().setFont(&Ubuntu_Bold12pt8b); getdisplay().setCursor(xPosTws + 82, yPosTws - 14); getdisplay().print(wsName); // Name @@ -512,19 +521,30 @@ public: } getdisplay().printf("%3d", chrtLbl); // Wind value label } +*/ + if (chrtMode == 'D') { + wdBVal->value = wdHstry->getLast(); + wdBVal->valid = wdBVal->value != wdHstry->getMaxVal(); +// twdFlChart->showChrt(dataIntv, *wdBVal); + twdFlChart->showChrt(dataIntv, *bvalue[0]); + } else if (chrtMode == 'S') { -// wsValue = wsHstry->getLast(); - twsFlChart->drawChrtTimeAxis(dataIntv); - LOG_DEBUG(GwLog::DEBUG, "PageWindPlot chart: wsBVal.name: %s, format: %s, wsBVal.value: %.1f, address: %p", wsBVal->getName(), wsBVal->getFormat(), wsBVal->value, wsBVal); - twsFlChart->drawChrt(dataIntv, *wsBVal); + wsBVal->value = wsHstry->getLast(); + wsBVal->valid = wsBVal->value != wsHstry->getMaxVal(); +// twsFlChart->showChrt(dataIntv, *wsBVal); + twsFlChart->showChrt(dataIntv, *bvalue[1]); } else if (chrtMode == 'B') { -// wsValue = wsHstry->getLast(); - twdHfChart->drawChrtTimeAxis(dataIntv); - twsHfChart->drawChrtTimeAxis(dataIntv); - LOG_DEBUG(GwLog::DEBUG, "PageWindPlot chart: wsBVal.name: %s, format: %s, wsBVal.value: %.1f, address: %p", wsBVal->getName(), wsBVal->getFormat(), wsBVal->value, wsBVal); - twdHfChart->drawChrt(dataIntv, *wsBVal); - twsHfChart->drawChrt(dataIntv, *wsBVal); + wdBVal->value = wdHstry->getLast(); + wdBVal->valid = wdBVal->value != wdHstry->getMaxVal(); + wsBVal->value = wsHstry->getLast(); + wsBVal->valid = wsBVal->value != wsHstry->getMaxVal(); + LOG_DEBUG(GwLog::DEBUG, "PageWindPlot showChrt: wsBVal.name: %s, format: %s, wsBVal.value: %.1f, valid: %d, address: %p", wsBVal->getName(), wsBVal->getFormat(), wsBVal->value, + wsBVal->valid, wsBVal); +// twdHfChart->showChrt(dataIntv, *wdBVal); +// twsHfChart->showChrt(dataIntv, *wsBVal); + twdHfChart->showChrt(dataIntv, *bvalue[0]); + twsHfChart->showChrt(dataIntv, *bvalue[1]); } LOG_DEBUG(GwLog::DEBUG, "PageWindPlot time: %ld", millis() - timer); @@ -546,7 +566,7 @@ PageDescription registerPageWindPlot( "WindPlot", // Page name createPage, // Action 0, // Number of bus values depends on selection in Web configuration - { "TWD", "AWD" }, // Bus values we need in the page + { "TWD", "TWS", "AWD", "AWS" }, // Bus values we need in the page true // Show display header on/off ); diff --git a/lib/obp60task/obp60task.cpp b/lib/obp60task/obp60task.cpp index b1c0e01..75796ec 100644 --- a/lib/obp60task/obp60task.cpp +++ b/lib/obp60task/obp60task.cpp @@ -806,7 +806,7 @@ void OBP60Task(GwApi *api){ if (calcTrueWnds) { trueWind.addTrueWind(api, &boatValues, logger); } - // Handle history buffers for TWD, TWS for wind plot page and other usage + // Handle history buffers for certain boat data for windplot page and other usage hstryBufList.handleHstryBuf(useSimuData); // Clear display From 942ca28ab54a717e7cef952b20b6391c4d8b899c Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Sat, 22 Nov 2025 19:59:43 +0100 Subject: [PATCH 05/10] Clean PageWindPlot to adjust to new OBPcharts setup --- lib/obp60task/PageWindPlot.cpp | 396 +++------------------------------ 1 file changed, 25 insertions(+), 371 deletions(-) diff --git a/lib/obp60task/PageWindPlot.cpp b/lib/obp60task/PageWindPlot.cpp index 4a115dc..4c5216c 100644 --- a/lib/obp60task/PageWindPlot.cpp +++ b/lib/obp60task/PageWindPlot.cpp @@ -1,83 +1,18 @@ #if defined BOARD_OBP60S3 || defined BOARD_OBP40S3 #include "Pagedata.h" -#include "BoatDataCalibration.h" #include "OBP60Extensions.h" -#include "OBPDataOperations.h" -#include "OBPRingBuffer.h" #include "OBPcharts.h" -#include - -static const double radToDeg = 180.0 / M_PI; // Conversion factor from radians to degrees - -// Get maximum difference of last of TWD ringbuffer values to center chart; returns "0" if data is not valid -int getCntr(const RingBuffer& windDirHstry, size_t amount) -{ - const int MAX_VAL = windDirHstry.getMaxVal(); - size_t count = windDirHstry.getCurrentSize(); - - if (windDirHstry.isEmpty() || amount <= 0) { - return 0; - } - if (amount > count) - amount = count; - - uint16_t midWndDir, minWndDir, maxWndDir = 0; - int wndCenter = 0; - - midWndDir = windDirHstry.getMid(amount); - if (midWndDir != MAX_VAL) { - midWndDir = midWndDir / 1000.0 * radToDeg; - wndCenter = int((midWndDir + (midWndDir >= 0 ? 5 : -5)) / 10) * 10; // Set new center value; round to nearest 10 degree value - minWndDir = windDirHstry.getMin(amount) / 1000.0 * radToDeg; - maxWndDir = windDirHstry.getMax(amount) / 1000.0 * radToDeg; - if ((maxWndDir - minWndDir) > 180 && !(minWndDir > maxWndDir)) { // if wind range is > 180 and no 0° crossover, adjust wndCenter to smaller wind range end - wndCenter = WindUtils::to360(wndCenter + 180); - } - } - - return wndCenter; -} - -// Get maximum difference of last of TWD ringbuffer values to center chart -int getRng(const RingBuffer& windDirHstry, int center, size_t amount) -{ - int minVal = windDirHstry.getMinVal(); - const int MAX_VAL = windDirHstry.getMaxVal(); - size_t count = windDirHstry.getCurrentSize(); - - if (windDirHstry.isEmpty() || amount <= 0) { - return MAX_VAL; - } - if (amount > count) - amount = count; - - int value = 0; - int rng = 0; - int maxRng = minVal; - // Start from the newest value (last) and go backwards x times - for (size_t i = 0; i < amount; i++) { - value = windDirHstry.get(count - 1 - i); - - if (value == MAX_VAL) { - continue; // ignore invalid values - } - - value = value / 1000.0 * radToDeg; - rng = abs(((value - center + 540) % 360) - 180); - if (rng > maxRng) - maxRng = rng; - } - if (maxRng > 180) { - maxRng = 180; - } - - return (maxRng != minVal ? maxRng : MAX_VAL); -} // **************************************************************** class PageWindPlot : public Page { +private: + GwLog* logger; + + int width; // Screen width + int height; // Screen height + bool keylock = false; // Keylock char chrtMode = 'D'; // Chart mode: 'D' for TWD, 'S' for TWS, 'B' for both bool showTruW = true; // Show true wind or apparant wind in chart area @@ -93,7 +28,8 @@ public: PageWindPlot(CommonData& common) { commonData = &common; - common.logger->logDebug(GwLog::LOG, "Instantiate PageWindPlot"); + logger = commonData->logger; + LOG_DEBUG(GwLog::LOG, "Instantiate PageWindPlot"); // Get config data useSimuData = common.config->getBool(common.config->useSimuData); @@ -174,97 +110,41 @@ public: } else { showTruW = false; // Wind source is apparant wind } - commonData->logger->logDebug(GwLog::LOG, "New PageWindPlot: wind source=%s", wndSrc); + LOG_DEBUG(GwLog::LOG, "New PageWindPlot; wind source=%s", wndSrc); + // commonData->logger->logDebug(GwLog::LOG, "New PageWindPlot: wind source=%s", wndSrc); #endif oldShowTruW = !showTruW; // makes wind source being initialized at initial page call + + width = getdisplay().width(); // Screen width + height = getdisplay().height(); // Screen height } int displayPage(PageData& pageData) { GwConfigHandler* config = commonData->config; - GwLog* logger = commonData->logger; static RingBuffer* wdHstry; // Wind direction data buffer static RingBuffer* wsHstry; // Wind speed data buffer static String wdName, wdFormat; // Wind direction name and format static String wsName, wsFormat; // Wind speed name and format - static int16_t wdMAX_VAL; // Max. value of wd history buffer, indicating invalid values static std::unique_ptr> twdFlChart; // chart object for wind direction chart, full size static std::unique_ptr> twsFlChart; // chart object for wind speed chart, full size static std::unique_ptr> twdHfChart; // chart object for wind direction chart, half size static std::unique_ptr> twsHfChart; // chart object for wind speed chart, half size -// float wsValue; // Wind speed value in chart area -// String wsUnit; // Wind speed unit in chart area static GwApi::BoatValue* wdBVal = new GwApi::BoatValue("TWD"); // temp BoatValue for wind direction unit identification; required by OBP60Formater - static GwApi::BoatValue* wsBVal = new GwApi::BoatValue("TWS"); // temp BoatValue for wind speed unit identification; required by OBP60Formater + static GwApi::BoatValue* wsBVal = new GwApi::BoatValue("TWS"); // temp BoatValue for wind speed unit identification; required by OBP60Formater */ + double dfltRngWd = 60.0 * DEG_TO_RAD; // default range for course chart from min to max value in RAD + double dfltRngWs = 7.5; // default range for wind speed chart from min to max value in m/s - // current boat data values const int numBoatData = 4; - GwApi::BoatValue* bvalue[numBoatData]; - // bool BDataValid[numBoatData]; - - static bool isInitialized = false; // Flag to indicate that page is initialized - static bool wndDataValid = false; // Flag to indicate if wind data is valid - static int numNoData; // Counter for multiple invalid data values in a row - - static int width; // Screen width - static int height; // Screen height - static int xCenter; // Center of screen in x direction - static const int yOffset = 48; // Offset for y coordinates of chart area - static int cHeight; // height of chart area - static int bufSize; // History buffer size: 1.920 values for 32 min. history chart - static int intvBufSize; // Buffer size used for currently selected time interval - int count; // current size of buffer - static int numWndVals; // number of wind values available for current interval selection - static int bufStart; // 1st data value in buffer to show - int numAddedBufVals; // Number of values added to buffer since last display - size_t currIdx; // Current index in TWD history buffer - static size_t lastIdx; // Last index of TWD history buffer - static size_t lastAddedIdx = 0; // Last index of TWD history buffer when new data was added - static int oldDataIntv; // remember recent user selection of data interval - - static int wndCenter; // chart wind center value position - static int wndLeft; // chart wind left value position - static int wndRight; // chart wind right value position - static int chrtRng; // Range of wind values from min to max wind value in degrees - double dfltRngWd; // default range for course chart from min to max value in degrees - double dfltRngWs; // defautl range for wind speed chart from min to max value in m/s - - int diffRng; // Difference between mid and current wind value - static const int dfltRng = 60; // Default range for chart - int midWndDir; // New value for wndCenter after chart start / shift - - int x, y; // x and y coordinates for drawing - static int prevX, prevY; // Last x and y coordinates for drawing - static float chrtScl; // Scale for wind values in pixels per degree - int chrtVal; // Current wind value - static int chrtPrevVal; // Last wind value in chart area for check if value crosses 180 degree line + GwApi::BoatValue* bvalue[numBoatData]; // current boat data values LOG_DEBUG(GwLog::LOG, "Display PageWindPlot"); - ulong timer = millis(); - - if (!isInitialized) { - width = getdisplay().width(); - height = getdisplay().height(); - xCenter = width / 2; - cHeight = height - yOffset - 22; - numNoData = 0; - bufStart = 0; - oldDataIntv = 0; - // wsValue = 0; - numAddedBufVals, currIdx, lastIdx = 0; - wndCenter = INT_MAX; - midWndDir = 0; - diffRng = dfltRng; - chrtRng = dfltRng; - - isInitialized = true; // Set flag to indicate that page is now initialized - } + ulong pageTime = millis(); // read boat data values for (int i = 0; i < numBoatData; i++) { bvalue[i] = pageData.values[i]; - // BDataValid[i] = bvalue->valid; } // Optical warning by limit violation (unused) @@ -283,14 +163,7 @@ public: } wdHstry->getMetaData(wdName, wdFormat); wsHstry->getMetaData(wsName, wsFormat); - wdMAX_VAL = wdHstry->getMaxVal(); - bufSize = wdHstry->getCapacity(); - wdBVal->setFormat(wdFormat); - wsBVal->setFormat(wsFormat); - lastAddedIdx = wdHstry->getLastIdx(); - dfltRngWd = 60.0 * DEG_TO_RAD; // default range for course chart: 60° - dfltRngWs = 7.5; // default range for wind speed chart: 7.5 m/s LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: *wdHstry: %p, *wsHstry: %p", wdHstry, wsHstry); twdFlChart = std::unique_ptr>(new Chart(*wdHstry, 1, 0, dfltRngWd, *commonData, useSimuData)); twsFlChart = std::unique_ptr>(new Chart(*wsHstry, 0, 0, dfltRngWs, *commonData, useSimuData)); @@ -300,238 +173,21 @@ public: oldShowTruW = showTruW; } + // Draw page + //*********************************************************** + // Set display in partial refresh mode getdisplay().setPartialWindow(0, 0, width, height); // Set partial update getdisplay().setTextColor(commonData->fgcolor); -/* if (chrtMode == 'D') { - // Identify buffer size and buffer start position for chart - count = wdHstry->getCurrentSize(); - currIdx = wdHstry->getLastIdx(); - numAddedBufVals = (currIdx - lastAddedIdx + bufSize) % bufSize; // Number of values added to buffer since last display - if (dataIntv != oldDataIntv || count == 1) { - // new data interval selected by user; this is only x * 230 values instead of 240 seconds (4 minutes) per interval step - intvBufSize = cHeight * dataIntv; - numWndVals = min(count, (cHeight - 60) * dataIntv); - bufStart = max(0, count - numWndVals); - lastAddedIdx = currIdx; - oldDataIntv = dataIntv; - } else { - numWndVals = numWndVals + numAddedBufVals; - lastAddedIdx = currIdx; - if (count == bufSize) { - bufStart = max(0, bufStart - numAddedBufVals); - } - } - LOG_DEBUG(GwLog::DEBUG, "PageWindPlot Dataset: count: %d, xWD: %.1f, xWS: %.2f, xWD_valid? %d, intvBufSize: %d, numWndVals: %d, bufStart: %d, numAddedBufVals: %d, lastIdx: %d, wind source: %s", - count, wdHstry->getLast() / 1000.0 * radToDeg, wsHstry->getLast() / 1000.0 * 1.94384, BDataValid[0], intvBufSize, numWndVals, bufStart, numAddedBufVals, wdHstry->getLastIdx(), - showTruW ? "True" : "App"); - - // Set wndCenter from 1st real buffer value - if (wndCenter == INT_MAX || (wndCenter == 0 && count == 1)) { - wndCenter = getCntr(*wdHstry, numWndVals); - LOG_DEBUG(GwLog::DEBUG, "PageWindPlot Range Init: count: %d, xWD: %.1f, wndCenter: %d, diffRng: %d, chrtRng: %d, Min: %.0f, Max: %.0f", count, wdHstry->getLast() / 1000.0 * radToDeg, - wndCenter, diffRng, chrtRng, wdHstry->getMin(numWndVals) / 1000.0 * radToDeg, wdHstry->getMax(numWndVals) / 1000.0 * radToDeg); - } else { - // check and adjust range between left, center, and right chart limit - diffRng = getRng(*wdHstry, wndCenter, numWndVals); - diffRng = (diffRng == wdMAX_VAL ? 0 : diffRng); - if (diffRng > chrtRng) { - chrtRng = int((diffRng + (diffRng >= 0 ? 9 : -1)) / 10) * 10; // Round up to next 10 degree value - } else if (diffRng + 10 < chrtRng) { // Reduce chart range for higher resolution if possible - chrtRng = max(dfltRng, int((diffRng + (diffRng >= 0 ? 9 : -1)) / 10) * 10); - LOG_DEBUG(GwLog::DEBUG, "PageWindPlot Range adjust: wndCenter: %d, diffRng: %d, chrtRng: %d, Min: %.0f, Max: %.0f", wndCenter, diffRng, chrtRng, - wdHstry->getMin(numWndVals) / 1000.0 * radToDeg, wdHstry->getMax(numWndVals) / 1000.0 * radToDeg); - } - } - chrtScl = float(width) / float(chrtRng) / 2.0; // Chart scale: pixels per degree - wndLeft = wndCenter - chrtRng; - if (wndLeft < 0) - wndLeft += 360; - wndRight = (chrtRng < 180 ? wndCenter + chrtRng : wndCenter + chrtRng - 1); - if (wndRight >= 360) - wndRight -= 360; - - // Draw page - //*********************************************************************** - - // chart lines - getdisplay().fillRect(0, yOffset, width, 2, commonData->fgcolor); - getdisplay().fillRect(xCenter, yOffset, 1, cHeight, commonData->fgcolor); - - // chart labels - char sWndLbl[4]; // char buffer for Wind angle label - getdisplay().setFont(&Ubuntu_Bold12pt8b); - getdisplay().setCursor(xCenter - 88, yOffset - 3); - getdisplay().print(wdName); // Wind data name - snprintf(sWndLbl, size_t(sWndLbl), "%03d", (wndCenter < 0) ? (wndCenter + 360) : wndCenter); - drawTextCenter(xCenter, yOffset - 11, sWndLbl); - getdisplay().drawCircle(xCenter + 25, yOffset - 17, 2, commonData->fgcolor); // symbol - getdisplay().drawCircle(xCenter + 25, yOffset - 17, 3, commonData->fgcolor); // symbol - getdisplay().setCursor(1, yOffset - 3); - snprintf(sWndLbl, size_t(sWndLbl), "%03d", (wndLeft < 0) ? (wndLeft + 360) : wndLeft); - getdisplay().print(sWndLbl); // Wind left value - getdisplay().drawCircle(46, yOffset - 17, 2, commonData->fgcolor); // symbol - getdisplay().drawCircle(46, yOffset - 17, 3, commonData->fgcolor); // symbol - getdisplay().setCursor(width - 50, yOffset - 3); - snprintf(sWndLbl, size_t(sWndLbl), "%03d", (wndRight < 0) ? (wndRight + 360) : wndRight); - getdisplay().print(sWndLbl); // Wind right value - getdisplay().drawCircle(width - 5, yOffset - 17, 2, commonData->fgcolor); // symbol - getdisplay().drawCircle(width - 5, yOffset - 17, 3, commonData->fgcolor); // symbol - - if (wdHstry->getMax() == wdMAX_VAL) { - // only values in buffer -> no valid wind data available - wndDataValid = false; - } else if (!BDataValid[0] && !useSimuData) { - // currently no valid xWD data available and no simulation mode - numNoData++; - wndDataValid = true; - if (numNoData > 3) { - // If more than 4 invalid values in a row, send message - wndDataValid = false; - } - } else { - numNoData = 0; // reset data error counter - wndDataValid = true; // At least some wind data available - } - // Draw wind values in chart - //*********************************************************************** - if (wndDataValid) { - for (int i = 0; i < (numWndVals / dataIntv); i++) { - chrtVal = static_cast(wdHstry->get(bufStart + (i * dataIntv))); // show the latest wind values in buffer; keep 1st value constant in a rolling buffer - if (chrtVal == wdMAX_VAL) { - chrtPrevVal = wdMAX_VAL; - } else { - chrtVal = static_cast((chrtVal / 1000.0 * radToDeg) + 0.5); // Convert to degrees and round - x = ((chrtVal - wndLeft + 360) % 360) * chrtScl; - y = yOffset + cHeight - i; // Position in chart area - - if (i >= (numWndVals / dataIntv) - 1) // log chart data of 1 line (adjust for test purposes) - LOG_DEBUG(GwLog::DEBUG, "PageWindPlot Chart: i: %d, chrtVal: %d, bufStart: %d, count: %d, linesToShow: %d", i, chrtVal, bufStart, count, (numWndVals / dataIntv)); - - if ((i == 0) || (chrtPrevVal == wdMAX_VAL)) { - // just a dot for 1st chart point or after some invalid values - prevX = x; - prevY = y; - } else { - // cross borders check; shift values to [-180..0..180]; when crossing borders, range is 2x 180 degrees - int wndLeftDlt = -180 - ((wndLeft >= 180) ? (wndLeft - 360) : wndLeft); - int chrtVal180 = ((chrtVal + wndLeftDlt + 180) % 360 + 360) % 360 - 180; - int chrtPrevVal180 = ((chrtPrevVal + wndLeftDlt + 180) % 360 + 360) % 360 - 180; - if (((chrtPrevVal180 >= -180) && (chrtPrevVal180 < -90) && (chrtVal180 > 90)) || ((chrtPrevVal180 <= 179) && (chrtPrevVal180 > 90) && chrtVal180 <= -90)) { - // If current value crosses chart borders compared to previous value, split line - int xSplit = (((chrtPrevVal180 > 0 ? wndRight : wndLeft) - wndLeft + 360) % 360) * chrtScl; - getdisplay().drawLine(prevX, prevY, xSplit, y, commonData->fgcolor); - getdisplay().drawLine(prevX, prevY - 1, ((xSplit != prevX) ? xSplit : xSplit - 1), ((xSplit != prevX) ? y - 1 : y), commonData->fgcolor); - prevX = (((chrtVal180 > 0 ? wndRight : wndLeft) - wndLeft + 360) % 360) * chrtScl; - } - } - - // Draw line with 2 pixels width + make sure vertical line are drawn correctly - getdisplay().drawLine(prevX, prevY, x, y, commonData->fgcolor); - getdisplay().drawLine(prevX, prevY - 1, ((x != prevX) ? x : x - 1), ((x != prevX) ? y - 1 : y), commonData->fgcolor); - chrtPrevVal = chrtVal; - prevX = x; - prevY = y; - } - // Reaching chart area top end - if (i >= (cHeight - 1)) { - oldDataIntv = 0; // force reset of buffer start and number of values to show in next display loop - - int minWndDir = wdHstry->getMin(numWndVals) / 1000.0 * radToDeg; - int maxWndDir = wdHstry->getMax(numWndVals) / 1000.0 * radToDeg; - LOG_DEBUG(GwLog::DEBUG, "PageWindPlot FreeTop: Minimum: %d, Maximum: %d, OldwndCenter: %d", minWndDir, maxWndDir, wndCenter); - // if (((minWndDir - wndCenter >= 0) && (minWndDir - wndCenter < 180)) || ((maxWndDir - wndCenter <= 0) && (maxWndDir - wndCenter >=180))) { - if ((wndRight > wndCenter && (minWndDir >= wndCenter && minWndDir <= wndRight)) || (wndRight <= wndCenter && (minWndDir >= wndCenter || minWndDir <= wndRight)) || (wndLeft < wndCenter && (maxWndDir <= wndCenter && maxWndDir >= wndLeft)) || (wndLeft >= wndCenter && (maxWndDir <= wndCenter || maxWndDir >= wndLeft))) { - // Check if all wind value are left or right of center value -> optimize chart center - wndCenter = getCntr(*wdHstry, numWndVals); - } - LOG_DEBUG(GwLog::DEBUG, "PageWindPlot FreeTop: cHeight: %d, bufStart: %d, numWndVals: %d, wndCenter: %d", cHeight, bufStart, numWndVals, wndCenter); - break; - } - } - - // Print wind speed value - int currentZone; - static int lastZone = 0; - static bool flipTws = false; - int xPosTws; - static const int yPosTws = yOffset + 40; - - xPosTws = flipTws ? 20 : width - 145; - currentZone = (y >= yPosTws - 38) && (y <= yPosTws + 6) && (x >= xPosTws - 4) && (x <= xPosTws + 146) ? 1 : 0; // Define current zone for TWS value - if (currentZone != lastZone) { - // Only flip when x moves to a different zone - if ((y >= yPosTws - 38) && (y <= yPosTws + 6) && (x >= xPosTws - 4) && (x <= xPosTws + 146)) { - flipTws = !flipTws; - xPosTws = flipTws ? 20 : width - 145; - } - } - lastZone = currentZone; - - wsValue = wsHstry->getLast(); - wsBVal->value = wsValue / 1000.0; // temp variable to retreive data unit from OBP60Formater - wsBVal->valid = (static_cast(wsValue) != wsHstry->getMinVal()); - String swsValue = formatValue(wsBVal, *commonData).svalue; // value (string) - wsUnit = formatValue(wsBVal, *commonData).unit; // Unit of value - getdisplay().fillRect(xPosTws - 4, yPosTws - 38, 142, 44, commonData->bgcolor); // Clear area for TWS value - getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b); - getdisplay().setCursor(xPosTws, yPosTws); - getdisplay().print(swsValue); // Value - /* if (!wsBVal->valid) { - getdisplay().print("--.-"); - } else { - wsValue = wsValue / 1000.0 * 1.94384; // Wind speed value in knots - if (wsValue < 10.0) { - getdisplay().printf("!%3.1f", wsValue); // Value, round to 1 decimal - } else { - getdisplay().printf("%4.1f", wsValue); // Value, round to 1 decimal - } - } */ /* - getdisplay().setFont(&Ubuntu_Bold12pt8b); - getdisplay().setCursor(xPosTws + 82, yPosTws - 14); - getdisplay().print(wsName); // Name - getdisplay().setFont(&Ubuntu_Bold8pt8b); - getdisplay().setCursor(xPosTws + 82, yPosTws + 1); - getdisplay().print(wsUnit); // Unit - - } else { - // No valid data available - LOG_DEBUG(GwLog::LOG, "PageWindPlot: No valid data available"); - getdisplay().setFont(&Ubuntu_Bold10pt8b); - getdisplay().fillRect(xCenter - 33, height / 2 - 20, 66, 24, commonData->bgcolor); // Clear area for message - drawTextCenter(xCenter, height / 2 - 10, "No data"); - } - - // chart Y axis labels; print at last to overwrite potential chart lines in label area - int yPos; - int chrtLbl; - getdisplay().setFont(&Ubuntu_Bold8pt8b); - for (int i = 1; i <= 3; i++) { - yPos = yOffset + (i * 60); - getdisplay().fillRect(0, yPos, width, 1, commonData->fgcolor); - getdisplay().fillRect(0, yPos - 8, 24, 16, commonData->bgcolor); // Clear small area to remove potential chart lines - getdisplay().setCursor(1, yPos + 4); - if (count >= intvBufSize) { - // Calculate minute value for label - chrtLbl = ((i - 1 + (prevY < yOffset + 30)) * dataIntv) * -1; // change label if last data point is more than 30 lines (= seconds) from chart line - } else { - int j = 3 - i; - chrtLbl = (int((((numWndVals / dataIntv) - 50) * dataIntv / 60) + 1) - (j * dataIntv)) * -1; // 50 lines left below last chart line - } - getdisplay().printf("%3d", chrtLbl); // Wind value label - } -*/ if (chrtMode == 'D') { wdBVal->value = wdHstry->getLast(); wdBVal->valid = wdBVal->value != wdHstry->getMaxVal(); -// twdFlChart->showChrt(dataIntv, *wdBVal); twdFlChart->showChrt(dataIntv, *bvalue[0]); } else if (chrtMode == 'S') { wsBVal->value = wsHstry->getLast(); wsBVal->valid = wsBVal->value != wsHstry->getMaxVal(); -// twsFlChart->showChrt(dataIntv, *wsBVal); twsFlChart->showChrt(dataIntv, *bvalue[1]); } else if (chrtMode == 'B') { @@ -539,17 +195,15 @@ public: wdBVal->valid = wdBVal->value != wdHstry->getMaxVal(); wsBVal->value = wsHstry->getLast(); wsBVal->valid = wsBVal->value != wsHstry->getMaxVal(); - LOG_DEBUG(GwLog::DEBUG, "PageWindPlot showChrt: wsBVal.name: %s, format: %s, wsBVal.value: %.1f, valid: %d, address: %p", wsBVal->getName(), wsBVal->getFormat(), wsBVal->value, - wsBVal->valid, wsBVal); -// twdHfChart->showChrt(dataIntv, *wdBVal); -// twsHfChart->showChrt(dataIntv, *wsBVal); + LOG_DEBUG(GwLog::DEBUG, "PageWindPlot showChrt: wsBVal.name: %s, format: %s, wsBVal.value: %.1f, valid: %d, address: %p", wsBVal->getName(), wsBVal->getFormat(), wsBVal->value, + wsBVal->valid, wsBVal); twdHfChart->showChrt(dataIntv, *bvalue[0]); twsHfChart->showChrt(dataIntv, *bvalue[1]); } - LOG_DEBUG(GwLog::DEBUG, "PageWindPlot time: %ld", millis() - timer); + LOG_DEBUG(GwLog::LOG, "PageWindPlot: runtime: %ldms", millis() - pageTime); return PAGE_UPDATE; - }; + } }; static Page* createPage(CommonData& common) From b31addf852ca1c1298d48b33b3d187c479d90621 Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Fri, 28 Nov 2025 23:45:44 +0100 Subject: [PATCH 06/10] Fixed typo in config.json files --- lib/obp60task/config.json | 2 +- lib/obp60task/config_obp40.json | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/obp60task/config.json b/lib/obp60task/config.json index f633762..ed476b6 100644 --- a/lib/obp60task/config.json +++ b/lib/obp60task/config.json @@ -224,7 +224,7 @@ "label": "Calculate True Wind", "type": "boolean", "default": "false", - "description": "If not available, calculate true wind data from appearant wind and other boat data", + "description": "If not available, calculate true wind data from apparent wind and other boat data", "category": "OBP60 Settings", "capabilities": { "obp60": "true" diff --git a/lib/obp60task/config_obp40.json b/lib/obp60task/config_obp40.json index 9addd67..5172874 100644 --- a/lib/obp60task/config_obp40.json +++ b/lib/obp60task/config_obp40.json @@ -224,7 +224,7 @@ "label": "Calculate True Wind", "type": "boolean", "default": "false", - "description": "If not available, calculate true wind data from appearant wind and other boat data", + "description": "If not available, calculate true wind data from apparent wind and other boat data", "category": "OBP40 Settings", "capabilities": { "obp40": "true" @@ -1542,7 +1542,7 @@ "description": "Wind source for page 1: [true|apparent]", "list": [ "True wind", - "Apparant wind" + "apparent wind" ], "category": "OBP40 Page 1", "capabilities": { @@ -1862,7 +1862,7 @@ "description": "Wind source for page 2: [true|apparent]", "list": [ "True wind", - "Apparant wind" + "apparent wind" ], "category": "OBP40 Page 2", "capabilities": { @@ -2173,7 +2173,7 @@ "description": "Wind source for page 3: [true|apparent]", "list": [ "True wind", - "Apparant wind" + "apparent wind" ], "category": "OBP40 Page 3", "capabilities": { @@ -2475,7 +2475,7 @@ "description": "Wind source for page 4: [true|apparent]", "list": [ "True wind", - "Apparant wind" + "apparent wind" ], "category": "OBP40 Page 4", "capabilities": { @@ -2768,7 +2768,7 @@ "description": "Wind source for page 5: [true|apparent]", "list": [ "True wind", - "Apparant wind" + "apparent wind" ], "category": "OBP40 Page 5", "capabilities": { @@ -3052,7 +3052,7 @@ "description": "Wind source for page 6: [true|apparent]", "list": [ "True wind", - "Apparant wind" + "apparent wind" ], "category": "OBP40 Page 6", "capabilities": { @@ -3327,7 +3327,7 @@ "description": "Wind source for page 7: [true|apparent]", "list": [ "True wind", - "Apparant wind" + "apparent wind" ], "category": "OBP40 Page 7", "capabilities": { @@ -3593,7 +3593,7 @@ "description": "Wind source for page 8: [true|apparent]", "list": [ "True wind", - "Apparant wind" + "apparent wind" ], "category": "OBP40 Page 8", "capabilities": { @@ -3850,7 +3850,7 @@ "description": "Wind source for page 9: [true|apparent]", "list": [ "True wind", - "Apparant wind" + "apparent wind" ], "category": "OBP40 Page 9", "capabilities": { @@ -4098,7 +4098,7 @@ "description": "Wind source for page 10: [true|apparent]", "list": [ "True wind", - "Apparant wind" + "apparent wind" ], "category": "OBP40 Page 10", "capabilities": { From 3fa7ca5e99890bf9123e9001aa4b1b1adcad7438 Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Fri, 28 Nov 2025 23:47:39 +0100 Subject: [PATCH 07/10] Optimized buffer change for T/A wind; pixel and font size adjustments; cleaned #includes --- lib/obp60task/OBPDataOperations.h | 5 +- lib/obp60task/OBPRingBuffer.h | 3 +- lib/obp60task/OBPRingBuffer.tpp | 1 + lib/obp60task/OBPcharts.cpp | 40 ++++++++-------- lib/obp60task/OBPcharts.h | 2 +- lib/obp60task/PageWindPlot.cpp | 77 +++++++++++++++++++++++-------- lib/obp60task/gen_set.py | 2 +- 7 files changed, 85 insertions(+), 45 deletions(-) diff --git a/lib/obp60task/OBPDataOperations.h b/lib/obp60task/OBPDataOperations.h index 5ee604b..8422894 100644 --- a/lib/obp60task/OBPDataOperations.h +++ b/lib/obp60task/OBPDataOperations.h @@ -1,6 +1,5 @@ // Function lib for history buffer handling, true wind calculation, and other operations on boat data #pragma once -#include #include "OBPRingBuffer.h" #include "obp60task.h" @@ -17,8 +16,8 @@ private: RingBuffer twdHstry; // Circular buffer to store true wind direction values RingBuffer twsHstry; // Circular buffer to store true wind speed values (TWS) - RingBuffer awdHstry; // Circular buffer to store apparant wind direction values - RingBuffer awsHstry; // Circular buffer to store apparant xwind speed values (AWS) + RingBuffer awdHstry; // Circular buffer to store apparent wind direction values + RingBuffer awsHstry; // Circular buffer to store apparent xwind speed values (AWS) double twdHstryMin; // Min value for wind direction (TWD) in history buffer double twdHstryMax; // Max value for wind direction (TWD) in history buffer double twsHstryMin; diff --git a/lib/obp60task/OBPRingBuffer.h b/lib/obp60task/OBPRingBuffer.h index 15ad5c1..970245e 100644 --- a/lib/obp60task/OBPRingBuffer.h +++ b/lib/obp60task/OBPRingBuffer.h @@ -1,7 +1,8 @@ #pragma once -//#include "FreeRTOS.h" +#include "FreeRTOS.h" #include "GwSynchronized.h" #include +#include template struct PSRAMAllocator { diff --git a/lib/obp60task/OBPRingBuffer.tpp b/lib/obp60task/OBPRingBuffer.tpp index 281e89d..7d73f46 100644 --- a/lib/obp60task/OBPRingBuffer.tpp +++ b/lib/obp60task/OBPRingBuffer.tpp @@ -1,6 +1,7 @@ #include "OBPRingBuffer.h" #include #include +#include template void RingBuffer::initCommon() diff --git a/lib/obp60task/OBPcharts.cpp b/lib/obp60task/OBPcharts.cpp index 96954dc..be3fd43 100644 --- a/lib/obp60task/OBPcharts.cpp +++ b/lib/obp60task/OBPcharts.cpp @@ -17,7 +17,7 @@ Chart::Chart(RingBuffer& dataBuf, int8_t chrtDir, int8_t chrtSz, double df fgColor = commonData->fgcolor; bgColor = commonData->bgcolor; - LOG_DEBUG(GwLog::DEBUG, "Chart Init: dataBuf: %p", (void*)&dataBuf); + // LOG_DEBUG(GwLog::DEBUG, "Chart Init: Chart::dataBuf: %p, passed dataBuf: %p", (void*)&this->dataBuf, (void*)&dataBuf); dWidth = getdisplay().width(); dHeight = getdisplay().height(); @@ -104,8 +104,8 @@ Chart::~Chart() template void Chart::showChrt(int8_t chrtIntv, GwApi::BoatValue currValue) { - drawChrtTimeAxis(chrtIntv); drawChrt(chrtIntv, currValue); + drawChrtTimeAxis(chrtIntv); drawChrtValAxis(); } @@ -185,8 +185,8 @@ void Chart::drawChrt(int8_t chrtIntv, GwApi::BoatValue& currValue) } } - if (i >= (numBufVals / chrtIntv) - 4) // log chart data of 1 line (adjust for test purposes) - LOG_DEBUG(GwLog::DEBUG, "PageWindPlot Chart: i: %d, chrtVal: %.4f, {x,y} {%d,%d}", i, chrtVal, x, y); + // if (i >= (numBufVals / chrtIntv) - 4) // log chart data of 1 line (adjust for test purposes) + // LOG_DEBUG(GwLog::DEBUG, "PageWindPlot Chart: i: %d, chrtVal: %.4f, {x,y} {%d,%d}", i, chrtVal, x, y); if ((i == 0) || (chrtPrevVal == dbMAX_VAL)) { // just a dot for 1st chart point or after some invalid values @@ -194,7 +194,6 @@ void Chart::drawChrt(int8_t chrtIntv, GwApi::BoatValue& currValue) prevY = y; } else if (chrtDataFmt != 0) { // cross borders check for degree values; shift values to [-PI..0..PI]; when crossing borders, range is 2x PI degrees - // Normalize both values relative to chrtMin (shift range to start at 0) double normCurr = WindUtils::to2PI(chrtVal - chrtMin); double normPrev = WindUtils::to2PI(chrtPrevVal - chrtMin); @@ -202,7 +201,7 @@ void Chart::drawChrt(int8_t chrtIntv, GwApi::BoatValue& currValue) bool crossedBorders = std::abs(normCurr - normPrev) > (chrtRng / 2.0); if (crossedBorders) { // If current value crosses chart borders compared to previous value, split line - LOG_DEBUG(GwLog::DEBUG, "PageWindPlot Chart: crossedBorders: %d, chrtVal: %.2f, chrtPrevVal: %.2f", crossedBorders, chrtVal, chrtPrevVal); + // LOG_DEBUG(GwLog::DEBUG, "PageWindPlot Chart: crossedBorders: %d, chrtVal: %.2f, chrtPrevVal: %.2f", crossedBorders, chrtVal, chrtPrevVal); bool wrappingFromHighToLow = normCurr < normPrev; // Determine which edge we're crossing int xSplit = wrappingFromHighToLow ? (cStart.x + valAxis) : cStart.x; getdisplay().drawLine(prevX, prevY, xSplit, y, fgColor); @@ -215,11 +214,9 @@ void Chart::drawChrt(int8_t chrtIntv, GwApi::BoatValue& currValue) if (chrtDir == 0 || x == prevX) { // vertical line getdisplay().drawLine(prevX, prevY, x, y, fgColor); getdisplay().drawLine(prevX - 1, prevY, x - 1, y, fgColor); -// getdisplay().drawLine(prevX + 1, prevY, x - 1, y, fgColor); } else if (chrtDir == 1 || x != prevX) { // line with some horizontal trend -> normal state getdisplay().drawLine(prevX, prevY, x, y, fgColor); getdisplay().drawLine(prevX, prevY - 1, x, y - 1, fgColor); -// getdisplay().drawLine(prevX, prevY + 1, x, y - 1, fgColor); } chrtPrevVal = chrtVal; prevX = x; @@ -480,15 +477,15 @@ void Chart::drawChrtValAxis() intv = static_cast(round(cchrtRng / slots)); i = intv; - getdisplay().setFont(&Ubuntu_Bold10pt8b); + getdisplay().setFont(&Ubuntu_Bold12pt8b); for (int j = 60; j < valAxis - 30; j += 60) { LOG_DEBUG(GwLog::DEBUG, "ChartValAxis: chrtRng: %.2f, cchrtRng: %.2f, intv: %d, slots: %.1f, valAxis: %d, i: %d, j: %d", chrtRng, cchrtRng, intv, slots, valAxis, i, j); getdisplay().drawLine(cStart.x, cStart.y + j, cStart.x + timAxis, cStart.y + j, fgColor); - getdisplay().fillRect(cStart.x, cStart.y + j - 9, cStart.x + 32, 18, bgColor); // Clear small area to remove potential chart lines + getdisplay().fillRect(cStart.x, cStart.y + j - 11, cStart.x + 39, 21, bgColor); // Clear small area to remove potential chart lines String sVal = String(i); - getdisplay().setCursor((3 - sVal.length()) * 8, cStart.y + j + 6); // value right-formated + getdisplay().setCursor((3 - sVal.length()) * 10, cStart.y + j + 7); // value right-formated getdisplay().printf("%s", sVal); // Range value i += intv; @@ -498,9 +495,14 @@ void Chart::drawChrtValAxis() drawTextRalign(cStart.x + timAxis, cStart.y - 3, dbName); // buffer data name } else { // chrtDir == 1; vertical chart - getdisplay().setFont(&Ubuntu_Bold10pt8b); - + if (chrtSz == 0) { // full size chart -> use larger font + getdisplay().setFont(&Ubuntu_Bold12pt8b); + drawTextCenter(cStart.x + (valAxis / 4) + 25, cStart.y - 10, dbName); // buffer data name + } else { + getdisplay().setFont(&Ubuntu_Bold10pt8b); + } getdisplay().fillRect(cStart.x, top, valAxis, 2, fgColor); // top chart line + getdisplay().setCursor(cStart.x, cStart.y - 2); tmpBVal->value = chrtMin; cVal = formatValue(tmpBVal.get(), *commonData).cvalue; // value (converted) @@ -515,16 +517,16 @@ void Chart::drawChrtValAxis() tmpBVal->value = chrtMax; cVal = formatValue(tmpBVal.get(), *commonData).cvalue; // value (converted) snprintf(sVal, sizeof(sVal), "%.0f", round(cVal)); - drawTextRalign(cStart.x + valAxis - 1, cStart.y - 2, sVal); // Range high end + drawTextRalign(cStart.x + valAxis - 2, cStart.y - 2, sVal); // Range high end for (int j = 0; j <= valAxis + 2; j += ((valAxis + 2) / 2)) { getdisplay().drawLine(cStart.x + j, cStart.y, cStart.x + j, cStart.y + timAxis, fgColor); } - if (chrtSz == 0) { - getdisplay().setFont(&Ubuntu_Bold12pt8b); - drawTextCenter(cStart.x + (valAxis / 4) + 5, cStart.y - 11, dbName); // buffer data name - } +// if (chrtSz == 0) { +// getdisplay().setFont(&Ubuntu_Bold12pt8b); +// drawTextCenter(cStart.x + (valAxis / 4) + 15, cStart.y - 11, dbName); // buffer data name +// } LOG_DEBUG(GwLog::DEBUG, "ChartGrd: chrtRng: %.2f, intv: %d, slots: %.1f, valAxis: %d, i: %d", chrtRng, intv, slots, valAxis, i); } } @@ -547,7 +549,7 @@ void Chart::prntCurrValue(GwApi::BoatValue& currValue, const Pos chrtPos) LOG_DEBUG(GwLog::DEBUG, "Chart CurrValue: dbValue: %.2f, sdbValue: %s, fmrtDbValue: %.2f, dbFormat: %s, dbUnit: %s, Valid: %d, Name: %s, Address: %p", currValue.value, sdbValue, testdbValue, currValue.getFormat(), dbUnit, currValue.valid, currValue.getName(), currValue); - getdisplay().fillRect(xPosVal, yPosVal - 34, 121, 40, bgColor); // Clear area for TWS value + getdisplay().fillRect(xPosVal, yPosVal - 34, 122, 40, bgColor); // Clear area for TWS value getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b); getdisplay().setCursor(xPosVal + 1, yPosVal); if (useSimuData) { diff --git a/lib/obp60task/OBPcharts.h b/lib/obp60task/OBPcharts.h index c33f52e..581ab85 100644 --- a/lib/obp60task/OBPcharts.h +++ b/lib/obp60task/OBPcharts.h @@ -1,4 +1,4 @@ -// Function lib for display of boat data in various chart formats +// Function lib for display of boat data in various graphical chart formats #pragma once #include "Pagedata.h" diff --git a/lib/obp60task/PageWindPlot.cpp b/lib/obp60task/PageWindPlot.cpp index 4c5216c..b9fbc67 100644 --- a/lib/obp60task/PageWindPlot.cpp +++ b/lib/obp60task/PageWindPlot.cpp @@ -15,7 +15,7 @@ private: bool keylock = false; // Keylock char chrtMode = 'D'; // Chart mode: 'D' for TWD, 'S' for TWS, 'B' for both - bool showTruW = true; // Show true wind or apparant wind in chart area + bool showTruW = true; // Show true wind or apparent wind in chart area bool oldShowTruW = false; // remember recent user selection of wind data type int dataIntv = 1; // Update interval for wind history chart: @@ -102,16 +102,15 @@ public: virtual void displayNew(PageData& pageData) { #ifdef BOARD_OBP40S3 - String wndSrc; // Wind source true/apparant wind - preselection for OBP40 + String wndSrc; // Wind source true/apparent wind - preselection for OBP40 wndSrc = commonData->config->getString("page" + String(pageData.pageNumber) + "wndsrc"); if (wndSrc == "True wind") { showTruW = true; } else { - showTruW = false; // Wind source is apparant wind + showTruW = false; // Wind source is apparent wind } LOG_DEBUG(GwLog::LOG, "New PageWindPlot; wind source=%s", wndSrc); - // commonData->logger->logDebug(GwLog::LOG, "New PageWindPlot: wind source=%s", wndSrc); #endif oldShowTruW = !showTruW; // makes wind source being initialized at initial page call @@ -127,10 +126,18 @@ public: static RingBuffer* wsHstry; // Wind speed data buffer static String wdName, wdFormat; // Wind direction name and format static String wsName, wsFormat; // Wind speed name and format - static std::unique_ptr> twdFlChart; // chart object for wind direction chart, full size - static std::unique_ptr> twsFlChart; // chart object for wind speed chart, full size - static std::unique_ptr> twdHfChart; // chart object for wind direction chart, half size - static std::unique_ptr> twsHfChart; // chart object for wind speed chart, half size + + // Separate chart objects for true wind and apparent wind + static std::unique_ptr> twdFlChart, awdFlChart; // chart object for wind direction chart, full size + static std::unique_ptr> twsFlChart, awsFlChart; // chart object for wind speed chart, full size + static std::unique_ptr> twdHfChart, awdHfChart; // chart object for wind direction chart, half size + static std::unique_ptr> twsHfChart, awsHfChart; // chart object for wind speed chart, half size + // Pointers to the currently active charts + static Chart* wdFlChart; + static Chart* wsFlChart; + static Chart* wdHfChart; + static Chart* wsHfChart; + static GwApi::BoatValue* wdBVal = new GwApi::BoatValue("TWD"); // temp BoatValue for wind direction unit identification; required by OBP60Formater static GwApi::BoatValue* wsBVal = new GwApi::BoatValue("TWS"); // temp BoatValue for wind speed unit identification; required by OBP60Formater */ double dfltRngWd = 60.0 * DEG_TO_RAD; // default range for course chart from min to max value in RAD @@ -154,22 +161,52 @@ public: } if (showTruW != oldShowTruW) { + if (!twdFlChart) { // Create true wind charts if they don't exist + + LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: Creating true wind charts"); + auto* twdHstry = pageData.boatHstry->hstryBufList.twdHstry; + auto* twsHstry = pageData.boatHstry->hstryBufList.twsHstry; + // LOG_DEBUG(GwLog::DEBUG,"History Buffer addresses PageWindPlot: twdBuf: %p, twsBuf: %p", (void*)pageData.boatHstry->hstryBufList.twdHstry, + // (void*)pageData.boatHstry->hstryBufList.twsHstry); + + twdFlChart = std::unique_ptr>(new Chart(*twdHstry, 1, 0, dfltRngWd, *commonData, useSimuData)); + twsFlChart = std::unique_ptr>(new Chart(*twsHstry, 0, 0, dfltRngWs, *commonData, useSimuData)); + twdHfChart = std::unique_ptr>(new Chart(*twdHstry, 1, 1, dfltRngWd, *commonData, useSimuData)); + twsHfChart = std::unique_ptr>(new Chart(*twsHstry, 1, 2, dfltRngWs, *commonData, useSimuData)); + // LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: twdHstry: %p, twsHstry: %p", (void*)twdHstry, (void*)twsHstry); + } + + if (!awdFlChart) { // Create apparent wind charts if they don't exist + LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: Creating apparent wind charts"); + auto* awdHstry = pageData.boatHstry->hstryBufList.awdHstry; + auto* awsHstry = pageData.boatHstry->hstryBufList.awsHstry; + + awdFlChart = std::unique_ptr>(new Chart(*awdHstry, 1, 0, dfltRngWd, *commonData, useSimuData)); + awsFlChart = std::unique_ptr>(new Chart(*awsHstry, 0, 0, dfltRngWs, *commonData, useSimuData)); + awdHfChart = std::unique_ptr>(new Chart(*awdHstry, 1, 1, dfltRngWd, *commonData, useSimuData)); + awsHfChart = std::unique_ptr>(new Chart(*awsHstry, 1, 2, dfltRngWs, *commonData, useSimuData)); + } + + // Switch active charts based on showTruW if (showTruW) { wdHstry = pageData.boatHstry->hstryBufList.twdHstry; wsHstry = pageData.boatHstry->hstryBufList.twsHstry; + wdFlChart = twdFlChart.get(); + wsFlChart = twsFlChart.get(); + wdHfChart = twdHfChart.get(); + wsHfChart = twsHfChart.get(); } else { wdHstry = pageData.boatHstry->hstryBufList.awdHstry; wsHstry = pageData.boatHstry->hstryBufList.awsHstry; + wdFlChart = awdFlChart.get(); + wsFlChart = awsFlChart.get(); + wdHfChart = awdHfChart.get(); + wsHfChart = awsHfChart.get(); } + wdHstry->getMetaData(wdName, wdFormat); wsHstry->getMetaData(wsName, wsFormat); - - LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: *wdHstry: %p, *wsHstry: %p", wdHstry, wsHstry); - twdFlChart = std::unique_ptr>(new Chart(*wdHstry, 1, 0, dfltRngWd, *commonData, useSimuData)); - twsFlChart = std::unique_ptr>(new Chart(*wsHstry, 0, 0, dfltRngWs, *commonData, useSimuData)); - twdHfChart = std::unique_ptr>(new Chart(*wdHstry, 1, 1, dfltRngWd, *commonData, useSimuData)); - twsHfChart = std::unique_ptr>(new Chart(*wsHstry, 1, 2, dfltRngWs, *commonData, useSimuData)); - + oldShowTruW = showTruW; } @@ -183,12 +220,12 @@ public: if (chrtMode == 'D') { wdBVal->value = wdHstry->getLast(); wdBVal->valid = wdBVal->value != wdHstry->getMaxVal(); - twdFlChart->showChrt(dataIntv, *bvalue[0]); + wdFlChart->showChrt(dataIntv, *bvalue[0]); } else if (chrtMode == 'S') { wsBVal->value = wsHstry->getLast(); wsBVal->valid = wsBVal->value != wsHstry->getMaxVal(); - twsFlChart->showChrt(dataIntv, *bvalue[1]); + wsFlChart->showChrt(dataIntv, *bvalue[1]); } else if (chrtMode == 'B') { wdBVal->value = wdHstry->getLast(); @@ -197,11 +234,11 @@ public: wsBVal->valid = wsBVal->value != wsHstry->getMaxVal(); LOG_DEBUG(GwLog::DEBUG, "PageWindPlot showChrt: wsBVal.name: %s, format: %s, wsBVal.value: %.1f, valid: %d, address: %p", wsBVal->getName(), wsBVal->getFormat(), wsBVal->value, wsBVal->valid, wsBVal); - twdHfChart->showChrt(dataIntv, *bvalue[0]); - twsHfChart->showChrt(dataIntv, *bvalue[1]); + wdHfChart->showChrt(dataIntv, *bvalue[0]); + wsHfChart->showChrt(dataIntv, *bvalue[1]); } - LOG_DEBUG(GwLog::LOG, "PageWindPlot: runtime: %ldms", millis() - pageTime); + LOG_DEBUG(GwLog::LOG, "PageWindPlot: page time %ldms", millis() - pageTime); return PAGE_UPDATE; } }; diff --git a/lib/obp60task/gen_set.py b/lib/obp60task/gen_set.py index 9d1b0ff..97ca0cf 100755 --- a/lib/obp60task/gen_set.py +++ b/lib/obp60task/gen_set.py @@ -157,7 +157,7 @@ def create_json(device, no_of_pages, pagedata): "description": f"Wind source for page {page_no}: [true|apparent]", "list": [ "True wind", - "Apparant wind" + "Apparent wind" ], "category": category, "capabilities": capabilities, From 625f9c087e0212cbab4dc8761863214f8b701a19 Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Sat, 29 Nov 2025 01:21:45 +0100 Subject: [PATCH 08/10] Fixed OBP60Formatter issue with speeds of 9.9999 knots --- lib/obp60task/OBP60Formatter.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/obp60task/OBP60Formatter.cpp b/lib/obp60task/OBP60Formatter.cpp index e4e73e0..8dcf50f 100644 --- a/lib/obp60task/OBP60Formatter.cpp +++ b/lib/obp60task/OBP60Formatter.cpp @@ -305,10 +305,13 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){ snprintf(buffer, bsize, "%2.0f", speed); } else{ - if (speed < 10){ + speed = std::round(speed * 100) / 100; // in rare cases, speed could be 9.9999 kn instead of 10.0 kn + LOG_DEBUG(GwLog::DEBUG,"OBPFormatter-formatValue: value->value: %.3f speed: %.15f speed<10: %d", value->value, speed, speed < 10.0); + + if (speed < 10.0){ snprintf(buffer, bsize, fmt_dec_1, speed); } - else if (speed < 100){ + else if (speed < 100.0){ snprintf(buffer, bsize, fmt_dec_10, speed); } else { From 1b5543913526c660e7d186d7d482fb1c52388ea2 Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Thu, 4 Dec 2025 23:31:20 +0100 Subject: [PATCH 09/10] Few more pixel adjustments for horizontal half screen charts --- lib/obp60task/OBP60Formatter.cpp | 2 - lib/obp60task/OBPcharts.cpp | 168 +++++++++++++++---------------- lib/obp60task/OBPcharts.h | 6 +- lib/obp60task/PageWindPlot.cpp | 2 + 4 files changed, 84 insertions(+), 94 deletions(-) diff --git a/lib/obp60task/OBP60Formatter.cpp b/lib/obp60task/OBP60Formatter.cpp index 8dcf50f..f51c5fa 100644 --- a/lib/obp60task/OBP60Formatter.cpp +++ b/lib/obp60task/OBP60Formatter.cpp @@ -306,8 +306,6 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){ } else{ speed = std::round(speed * 100) / 100; // in rare cases, speed could be 9.9999 kn instead of 10.0 kn - LOG_DEBUG(GwLog::DEBUG,"OBPFormatter-formatValue: value->value: %.3f speed: %.15f speed<10: %d", value->value, speed, speed < 10.0); - if (speed < 10.0){ snprintf(buffer, bsize, fmt_dec_1, speed); } diff --git a/lib/obp60task/OBPcharts.cpp b/lib/obp60task/OBPcharts.cpp index be3fd43..6ba5a83 100644 --- a/lib/obp60task/OBPcharts.cpp +++ b/lib/obp60task/OBPcharts.cpp @@ -30,12 +30,12 @@ Chart::Chart(RingBuffer& dataBuf, int8_t chrtDir, int8_t chrtSz, double df cStart = { 0, top }; break; case 1: - valAxis = (dHeight - top - bottom) / 2 - gap; + valAxis = (dHeight - top - bottom) / 2 - hGap; cStart = { 0, top }; break; case 2: - valAxis = (dHeight - top - bottom) / 2 - gap; - cStart = { 0, top + (valAxis + gap) + gap }; + valAxis = (dHeight - top - bottom) / 2 - hGap; + cStart = { 0, top + (valAxis + hGap) + hGap }; break; default: LOG_DEBUG(GwLog::ERROR, "displayChart: wrong init parameter"); @@ -50,12 +50,12 @@ Chart::Chart(RingBuffer& dataBuf, int8_t chrtDir, int8_t chrtSz, double df cStart = { 0, top }; break; case 1: - valAxis = dWidth / 2 - gap - 1; + valAxis = dWidth / 2 - vGap - 1; cStart = { 0, top }; break; case 2: - valAxis = dWidth / 2 - gap - 1; - cStart = { dWidth / 2 + gap, top }; + valAxis = dWidth / 2 - vGap - 1; + cStart = { dWidth / 2 + vGap, top }; break; default: LOG_DEBUG(GwLog::ERROR, "displayChart: wrong init parameter"); @@ -185,7 +185,7 @@ void Chart::drawChrt(int8_t chrtIntv, GwApi::BoatValue& currValue) } } - // if (i >= (numBufVals / chrtIntv) - 4) // log chart data of 1 line (adjust for test purposes) + // if (i >= (numBufVals / chrtIntv) - 5) // log chart data of 1 line (adjust for test purposes) // LOG_DEBUG(GwLog::DEBUG, "PageWindPlot Chart: i: %d, chrtVal: %.4f, {x,y} {%d,%d}", i, chrtVal, x, y); if ((i == 0) || (chrtPrevVal == dbMAX_VAL)) { @@ -193,8 +193,8 @@ void Chart::drawChrt(int8_t chrtIntv, GwApi::BoatValue& currValue) prevX = x; prevY = y; - } else if (chrtDataFmt != 0) { // cross borders check for degree values; shift values to [-PI..0..PI]; when crossing borders, range is 2x PI degrees - // Normalize both values relative to chrtMin (shift range to start at 0) + } else if (chrtDataFmt != 0) { + // cross borders check for degree values; shift values to [-PI..0..PI]; when crossing borders, range is 2x PI degrees double normCurr = WindUtils::to2PI(chrtVal - chrtMin); double normPrev = WindUtils::to2PI(chrtPrevVal - chrtMin); // Check if pixel positions are far apart (crossing chart boundary); happens when one value is near chrtMax and the other near chrtMin @@ -203,18 +203,31 @@ void Chart::drawChrt(int8_t chrtIntv, GwApi::BoatValue& currValue) if (crossedBorders) { // If current value crosses chart borders compared to previous value, split line // LOG_DEBUG(GwLog::DEBUG, "PageWindPlot Chart: crossedBorders: %d, chrtVal: %.2f, chrtPrevVal: %.2f", crossedBorders, chrtVal, chrtPrevVal); bool wrappingFromHighToLow = normCurr < normPrev; // Determine which edge we're crossing - int xSplit = wrappingFromHighToLow ? (cStart.x + valAxis) : cStart.x; - getdisplay().drawLine(prevX, prevY, xSplit, y, fgColor); - getdisplay().drawLine(prevX, prevY - 1, ((xSplit != prevX) ? xSplit : xSplit - 1), ((xSplit != prevX) ? y - 1 : y), fgColor); - prevX = wrappingFromHighToLow ? cStart.x : (cStart.x + valAxis); + if (chrtDir == 0) { + int ySplit = wrappingFromHighToLow ? (cStart.y + valAxis) : cStart.y; + getdisplay().drawLine(prevX, prevY, x, ySplit, fgColor); + if (x != prevX) { // line with some horizontal trend + getdisplay().drawLine(prevX, prevY - 1, x, ySplit - 1, fgColor); + } else { + getdisplay().drawLine(prevX, prevY - 1, x - 1, ySplit, fgColor); + } + prevY = wrappingFromHighToLow ? cStart.y : (cStart.y + valAxis); + } else { // vertical chart + int xSplit = wrappingFromHighToLow ? (cStart.x + valAxis) : cStart.x; + getdisplay().drawLine(prevX, prevY, xSplit, y, fgColor); + getdisplay().drawLine(prevX, prevY - 1, ((xSplit != prevX) ? xSplit : xSplit - 1), ((xSplit != prevX) ? y - 1 : y), fgColor); + prevX = wrappingFromHighToLow ? cStart.x : (cStart.x + valAxis); + } } } // Draw line with 2 pixels width + make sure vertical lines are drawn correctly - if (chrtDir == 0 || x == prevX) { // vertical line + if (chrtDir == 0 || x == prevX) { // horizontal chart & vertical line +// if (x == prevX) { // vertical line getdisplay().drawLine(prevX, prevY, x, y, fgColor); getdisplay().drawLine(prevX - 1, prevY, x - 1, y, fgColor); - } else if (chrtDir == 1 || x != prevX) { // line with some horizontal trend -> normal state + } else if (chrtDir == 1 || x != prevX) { // vertical chart & line with some horizontal trend -> normal state +// } else { // line with some horizontal trend -> normal state getdisplay().drawLine(prevX, prevY, x, y, fgColor); getdisplay().drawLine(prevX, prevY - 1, x, y - 1, fgColor); } @@ -239,7 +252,7 @@ void Chart::drawChrt(int8_t chrtIntv, GwApi::BoatValue& currValue) // doesn't work unfortunately when 'simulation data' is active, because OBP60Formatter generates own simulation value in that case currValue.value = dataBuf.getLast(); currValue.valid = currValue.value != dbMAX_VAL; - Chart::prntCurrValue(currValue, { x, y }); + Chart::prntCurrValue(currValue); LOG_DEBUG(GwLog::DEBUG, "Chart drawChrt: currValue-value: %.1f, Valid: %d, Name: %s, Address: %p", currValue.value, currValue.valid, currValue.getName(), (void*)&currValue); } else { @@ -403,7 +416,7 @@ void Chart::drawChrtTimeAxis(int8_t chrtIntv) getdisplay().setTextColor(fgColor); if (chrtDir == 0) { // horizontal chart - getdisplay().fillRect(0, top, dWidth, 2, fgColor); + getdisplay().fillRect(0, cStart.y, dWidth, 2, fgColor); timeRng = chrtIntv * 4; // Chart time interval: [1] 4 min., [2] 8 min., [3] 12 min., [4] 16 min., [8] 32 min. slots = timAxis / 80.0; // number of axis labels @@ -429,7 +442,7 @@ void Chart::drawChrtTimeAxis(int8_t chrtIntv) i -= intv; } - } else { // chrtDir == 1; vertical chart + } else { // vertical chart timeRng = chrtIntv * 4; // chart time interval: [1] 4 min., [2] 8 min., [3] 12 min., [4] 16 min., [8] 32 min. slots = timAxis / 75.0; // number of axis labels intv = timeRng / slots; // minutes per chart axis interval @@ -465,6 +478,7 @@ void Chart::drawChrtValAxis() int i, intv; double cVal, cchrtRng, crngMin; char sVal[6]; + int sLen; std::unique_ptr tmpBVal; // Temp variable to get formatted and converted data value from OBP60Formatter tmpBVal = std::unique_ptr(new GwApi::BoatValue(dataBuf.getName())); tmpBVal->setFormat(dataBuf.getFormat()); @@ -477,24 +491,48 @@ void Chart::drawChrtValAxis() intv = static_cast(round(cchrtRng / slots)); i = intv; - getdisplay().setFont(&Ubuntu_Bold12pt8b); + if (chrtSz == 0) { // full size chart -> print multiple value lines + getdisplay().setFont(&Ubuntu_Bold12pt8b); + for (int j = 60; j < valAxis - 30; j += 60) { + getdisplay().drawLine(cStart.x, cStart.y + j, cStart.x + timAxis, cStart.y + j, fgColor); - for (int j = 60; j < valAxis - 30; j += 60) { - LOG_DEBUG(GwLog::DEBUG, "ChartValAxis: chrtRng: %.2f, cchrtRng: %.2f, intv: %d, slots: %.1f, valAxis: %d, i: %d, j: %d", chrtRng, cchrtRng, intv, slots, valAxis, i, j); - getdisplay().drawLine(cStart.x, cStart.y + j, cStart.x + timAxis, cStart.y + j, fgColor); + getdisplay().fillRect(cStart.x, cStart.y + j - 11, 42, 21, bgColor); // Clear small area to remove potential chart lines + String sVal = String(i); + getdisplay().setCursor((3 - sVal.length()) * 10, cStart.y + j + 7); // value right-formated + getdisplay().printf("%s", sVal); // Range value - getdisplay().fillRect(cStart.x, cStart.y + j - 11, cStart.x + 39, 21, bgColor); // Clear small area to remove potential chart lines - String sVal = String(i); - getdisplay().setCursor((3 - sVal.length()) * 10, cStart.y + j + 7); // value right-formated - getdisplay().printf("%s", sVal); // Range value + i += intv; + } + } else { // half size chart -> print just edge values + middle chart line + getdisplay().setFont(&Ubuntu_Bold10pt8b); - i += intv; + tmpBVal->value = chrtMin; + cVal = formatValue(tmpBVal.get(), *commonData).cvalue; // value (converted) + sLen = snprintf(sVal, sizeof(sVal), "%.0f", round(cVal)); + getdisplay().fillRect(cStart.x, cStart.y + 2, 42, 16, bgColor); // Clear small area to remove potential chart lines + getdisplay().setCursor(cStart.x + ((3 - sLen) * 10), cStart.y + 16); + getdisplay().printf("%s", sVal); // Range low end + + tmpBVal->value = chrtMid; + cVal = formatValue(tmpBVal.get(), *commonData).cvalue; // value (converted) + sLen = snprintf(sVal, sizeof(sVal), "%.0f", round(cVal)); + getdisplay().fillRect(cStart.x, cStart.y + (valAxis / 2) - 9, 42, 16, bgColor); // Clear small area to remove potential chart lines + getdisplay().setCursor(cStart.x + ((3 - sLen) * 10), cStart.y + (valAxis / 2) + 5); + getdisplay().printf("%s", sVal); // Range mid value + getdisplay().drawLine(cStart.x + 43, cStart.y + (valAxis / 2), cStart.x + timAxis, cStart.y + (valAxis / 2), fgColor); + + tmpBVal->value = chrtMax; + cVal = formatValue(tmpBVal.get(), *commonData).cvalue; // value (converted) + sLen = snprintf(sVal, sizeof(sVal), "%.0f", round(cVal)); + getdisplay().fillRect(cStart.x, cStart.y + valAxis - 16, 42, 16, bgColor); // Clear small area to remove potential chart lines + getdisplay().setCursor(cStart.x + ((3 - sLen) * 10), cStart.y + valAxis - 1); + getdisplay().printf("%s", sVal); // Range high end } getdisplay().setFont(&Ubuntu_Bold12pt8b); drawTextRalign(cStart.x + timAxis, cStart.y - 3, dbName); // buffer data name - } else { // chrtDir == 1; vertical chart + } else { // vertical chart if (chrtSz == 0) { // full size chart -> use larger font getdisplay().setFont(&Ubuntu_Bold12pt8b); drawTextCenter(cStart.x + (valAxis / 4) + 25, cStart.y - 10, dbName); // buffer data name @@ -503,10 +541,10 @@ void Chart::drawChrtValAxis() } getdisplay().fillRect(cStart.x, top, valAxis, 2, fgColor); // top chart line - getdisplay().setCursor(cStart.x, cStart.y - 2); tmpBVal->value = chrtMin; cVal = formatValue(tmpBVal.get(), *commonData).cvalue; // value (converted) snprintf(sVal, sizeof(sVal), "%.0f", round(cVal)); + getdisplay().setCursor(cStart.x, cStart.y - 2); getdisplay().printf("%s", sVal); // Range low end tmpBVal->value = chrtMid; @@ -523,33 +561,29 @@ void Chart::drawChrtValAxis() getdisplay().drawLine(cStart.x + j, cStart.y, cStart.x + j, cStart.y + timAxis, fgColor); } -// if (chrtSz == 0) { -// getdisplay().setFont(&Ubuntu_Bold12pt8b); -// drawTextCenter(cStart.x + (valAxis / 4) + 15, cStart.y - 11, dbName); // buffer data name -// } - LOG_DEBUG(GwLog::DEBUG, "ChartGrd: chrtRng: %.2f, intv: %d, slots: %.1f, valAxis: %d, i: %d", chrtRng, intv, slots, valAxis, i); + // if (chrtSz == 0) { + // getdisplay().setFont(&Ubuntu_Bold12pt8b); + // drawTextCenter(cStart.x + (valAxis / 4) + 15, cStart.y - 11, dbName); // buffer data name + // } } } // Print current data value template -void Chart::prntCurrValue(GwApi::BoatValue& currValue, const Pos chrtPos) +void Chart::prntCurrValue(GwApi::BoatValue& currValue) { - int currentZone; - static int lastZone = 0; - static bool flipVal = false; - int xPosVal; - static const int yPosVal = (chrtDir == 0) ? cStart.y + valAxis - 5 : cStart.y + timAxis - 5; - xPosVal = cStart.x + 1; + const int xPosVal = (chrtDir == 0) ? cStart.x + (timAxis / 2) - 56 : cStart.x + 32; + const int yPosVal = (chrtDir == 0) ? cStart.y + valAxis - 7 : cStart.y + timAxis - 7; FormattedData frmtDbData = formatValue(&currValue, *commonData); double testdbValue = frmtDbData.value; String sdbValue = frmtDbData.svalue; // value (string) String dbUnit = frmtDbData.unit; // Unit of value - LOG_DEBUG(GwLog::DEBUG, "Chart CurrValue: dbValue: %.2f, sdbValue: %s, fmrtDbValue: %.2f, dbFormat: %s, dbUnit: %s, Valid: %d, Name: %s, Address: %p", currValue.value, sdbValue, - testdbValue, currValue.getFormat(), dbUnit, currValue.valid, currValue.getName(), currValue); + // LOG_DEBUG(GwLog::DEBUG, "Chart CurrValue: dbValue: %.2f, sdbValue: %s, fmrtDbValue: %.2f, dbFormat: %s, dbUnit: %s, Valid: %d, Name: %s, Address: %p", currValue.value, sdbValue, + // testdbValue, currValue.getFormat(), dbUnit, currValue.valid, currValue.getName(), currValue); - getdisplay().fillRect(xPosVal, yPosVal - 34, 122, 40, bgColor); // Clear area for TWS value + getdisplay().fillRect(xPosVal - 1, yPosVal - 34, 125, 42, bgColor); // Clear area for TWS value + getdisplay().drawRect(xPosVal, yPosVal - 33, 123, 40, fgColor); // Draw box for TWS value getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b); getdisplay().setCursor(xPosVal + 1, yPosVal); if (useSimuData) { @@ -563,54 +597,10 @@ void Chart::prntCurrValue(GwApi::BoatValue& currValue, const Pos chrtPos) getdisplay().print(dbName); // Name getdisplay().setFont(&Ubuntu_Bold8pt8b); - getdisplay().setCursor(xPosVal + 76, yPosVal + 1); + getdisplay().setCursor(xPosVal + 76, yPosVal + 0); getdisplay().print(dbUnit); // Unit } -// Identify Min and Max values of range for course data and select them considering smallest gap -// E.g., Min=30°, Max=270° will be converted to smaller range of Min=270° and Max=30° -// obsolete; creates random results by purpose with large data arrays when data is equally distributed -template -void Chart::getAngleMinMax(const std::vector& angles, double& rngMin, double& rngMax) -{ - if (angles.empty()) { - rngMin = 0; - rngMax = 0; - return; - } - - if (angles.size() == 1) { - rngMin = angles[0]; - rngMax = angles[0]; - return; - } - - // Sort angles - std::vector sorted = angles; - std::sort(sorted.begin(), sorted.end()); - - // Find the largest gap between consecutive angles - double maxGap = 0.0; - int maxGapIndex = 0; - for (size_t i = 0; i < sorted.size(); i++) { - double next = sorted[(i + 1) % sorted.size()]; - double curr = sorted[i]; - - // Calculate gap (wrapping around at 360°/2*Pi) - double gap = (i == sorted.size() - 1) ? (M_TWOPI - curr + next) : (next - curr); - - if (gap > maxGap) { - maxGap = gap; - maxGapIndex = i; - } - } - - // The range is on the opposite side of the largest gap - // Min is after the gap, max is before it - rngMin = sorted[(maxGapIndex + 1) % sorted.size()]; - rngMax = sorted[maxGapIndex]; -} - // Explicitly instantiate class with required data types to avoid linker errors template class Chart; // --- Class Chart --------------- diff --git a/lib/obp60task/OBPcharts.h b/lib/obp60task/OBPcharts.h index 581ab85..677be04 100644 --- a/lib/obp60task/OBPcharts.h +++ b/lib/obp60task/OBPcharts.h @@ -25,7 +25,8 @@ protected: int top = 48; // display top header lines int bottom = 22; // display bottom lines - int gap = 20; // gap between 2 charts; actual gap is 2x + int hGap = 10; // gap between 2 horizontal charts; actual gap is 2x + int vGap = 20; // gap between 2 vertical charts; actual gap is 2x int xOffset = 33; // offset for horizontal axis (time/value), because of space for left vertical axis labeling int yOffset = 10; // offset for vertical axis (time/value), because of space for top horizontal axis labeling int dWidth; // Display width @@ -58,8 +59,7 @@ protected: void calcChrtBorders(double& rngMid, double& rngMin, double& rngMax, double& rng); // Calculate chart points for value axis and return range between and void drawChrtTimeAxis(int8_t chrtIntv); // Draw time axis of chart, value and lines void drawChrtValAxis(); // Draw value axis of chart, value and lines - void prntCurrValue(GwApi::BoatValue& currValue, Pos chrtPos); // Add current boat data value to chart - void getAngleMinMax(const std::vector& angles, double& rngMin, double& rngMax); // Identify Min and Max for course data with smallest gap + void prntCurrValue(GwApi::BoatValue& currValue); // Add current boat data value to chart public: Chart(RingBuffer& dataBuf, int8_t chrtDir, int8_t chrtSz, double dfltRng, CommonData& common, bool useSimuData); // Chart object of data chart diff --git a/lib/obp60task/PageWindPlot.cpp b/lib/obp60task/PageWindPlot.cpp index b9fbc67..0b4e884 100644 --- a/lib/obp60task/PageWindPlot.cpp +++ b/lib/obp60task/PageWindPlot.cpp @@ -173,6 +173,8 @@ public: twsFlChart = std::unique_ptr>(new Chart(*twsHstry, 0, 0, dfltRngWs, *commonData, useSimuData)); twdHfChart = std::unique_ptr>(new Chart(*twdHstry, 1, 1, dfltRngWd, *commonData, useSimuData)); twsHfChart = std::unique_ptr>(new Chart(*twsHstry, 1, 2, dfltRngWs, *commonData, useSimuData)); + // twdHfChart = std::unique_ptr>(new Chart(*twdHstry, 0, 1, dfltRngWd, *commonData, useSimuData)); + // twsHfChart = std::unique_ptr>(new Chart(*twsHstry, 0, 2, dfltRngWs, *commonData, useSimuData)); // LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: twdHstry: %p, twsHstry: %p", (void*)twdHstry, (void*)twsHstry); } From 0f50b614eb510e1abb1e49d42daefcb1274386f3 Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Fri, 5 Dec 2025 00:10:31 +0100 Subject: [PATCH 10/10] Add lower chart line for horizontal half chart; write current value after chart lines --- lib/obp60task/OBPcharts.cpp | 23 +++++++++++++---------- lib/obp60task/OBPcharts.h | 3 ++- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/lib/obp60task/OBPcharts.cpp b/lib/obp60task/OBPcharts.cpp index 6ba5a83..17caf75 100644 --- a/lib/obp60task/OBPcharts.cpp +++ b/lib/obp60task/OBPcharts.cpp @@ -107,6 +107,15 @@ void Chart::showChrt(int8_t chrtIntv, GwApi::BoatValue currValue) drawChrt(chrtIntv, currValue); drawChrtTimeAxis(chrtIntv); drawChrtValAxis(); + + if (bufDataValid) { + // uses BoatValue temp variable to format latest buffer value + // doesn't work unfortunately when 'simulation data' is active, because OBP60Formatter generates own simulation value in that case + currValue.value = dataBuf.getLast(); + currValue.valid = currValue.value != dbMAX_VAL; + Chart::prntCurrValue(currValue); + LOG_DEBUG(GwLog::DEBUG, "Chart drawChrt: currValue-value: %.1f, Valid: %d, Name: %s, Address: %p", currValue.value, currValue.valid, currValue.getName(), (void*)&currValue); + } } // draw chart @@ -116,7 +125,7 @@ void Chart::drawChrt(int8_t chrtIntv, GwApi::BoatValue& currValue) double chrtVal; // Current data value double chrtScl; // Scale for data values in pixels per value static double chrtPrevVal; // Last data value in chart area - bool bufDataValid = false; // Flag to indicate if buffer data is valid + // bool bufDataValid = false; // Flag to indicate if buffer data is valid static int numNoData; // Counter for multiple invalid data values in a row int x, y; // x and y coordinates for drawing @@ -248,13 +257,6 @@ void Chart::drawChrt(int8_t chrtIntv, GwApi::BoatValue& currValue) } } - // uses BoatValue temp variable to format latest buffer value - // doesn't work unfortunately when 'simulation data' is active, because OBP60Formatter generates own simulation value in that case - currValue.value = dataBuf.getLast(); - currValue.valid = currValue.value != dbMAX_VAL; - Chart::prntCurrValue(currValue); - LOG_DEBUG(GwLog::DEBUG, "Chart drawChrt: currValue-value: %.1f, Valid: %d, Name: %s, Address: %p", currValue.value, currValue.valid, currValue.getName(), (void*)&currValue); - } else { // No valid data available getdisplay().setFont(&Ubuntu_Bold10pt8b); @@ -527,6 +529,7 @@ void Chart::drawChrtValAxis() getdisplay().fillRect(cStart.x, cStart.y + valAxis - 16, 42, 16, bgColor); // Clear small area to remove potential chart lines getdisplay().setCursor(cStart.x + ((3 - sLen) * 10), cStart.y + valAxis - 1); getdisplay().printf("%s", sVal); // Range high end + getdisplay().drawLine(cStart.x + 43, cStart.y + valAxis, cStart.x + timAxis, cStart.y + valAxis, fgColor); } getdisplay().setFont(&Ubuntu_Bold12pt8b); @@ -582,8 +585,8 @@ void Chart::prntCurrValue(GwApi::BoatValue& currValue) // LOG_DEBUG(GwLog::DEBUG, "Chart CurrValue: dbValue: %.2f, sdbValue: %s, fmrtDbValue: %.2f, dbFormat: %s, dbUnit: %s, Valid: %d, Name: %s, Address: %p", currValue.value, sdbValue, // testdbValue, currValue.getFormat(), dbUnit, currValue.valid, currValue.getName(), currValue); - getdisplay().fillRect(xPosVal - 1, yPosVal - 34, 125, 42, bgColor); // Clear area for TWS value - getdisplay().drawRect(xPosVal, yPosVal - 33, 123, 40, fgColor); // Draw box for TWS value + getdisplay().fillRect(xPosVal - 1, yPosVal - 34, 125, 41, bgColor); // Clear area for TWS value + getdisplay().drawRect(xPosVal, yPosVal - 33, 123, 39, fgColor); // Draw box for TWS value getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b); getdisplay().setCursor(xPosVal + 1, yPosVal); if (useSimuData) { diff --git a/lib/obp60task/OBPcharts.h b/lib/obp60task/OBPcharts.h index 677be04..db298a5 100644 --- a/lib/obp60task/OBPcharts.h +++ b/lib/obp60task/OBPcharts.h @@ -25,7 +25,7 @@ protected: int top = 48; // display top header lines int bottom = 22; // display bottom lines - int hGap = 10; // gap between 2 horizontal charts; actual gap is 2x + int hGap = 11; // gap between 2 horizontal charts; actual gap is 2x int vGap = 20; // gap between 2 vertical charts; actual gap is 2x int xOffset = 33; // offset for horizontal axis (time/value), because of space for left vertical axis labeling int yOffset = 10; // offset for vertical axis (time/value), because of space for top horizontal axis labeling @@ -52,6 +52,7 @@ protected: size_t currIdx; // Current index in TWD history buffer size_t lastIdx; // Last index of TWD history buffer size_t lastAddedIdx = 0; // Last index of TWD history buffer when new data was added + bool bufDataValid = false; // Flag to indicate if buffer data is valid int oldChrtIntv = 0; // remember recent user selection of data interval void drawChrt(int8_t chrtIntv, GwApi::BoatValue& currValue); // Draw chart line