diff --git a/lib/obp60task/OBPDataOperations.h b/lib/obp60task/OBPDataOperations.h index 7cb4320..0fb8647 100644 --- a/lib/obp60task/OBPDataOperations.h +++ b/lib/obp60task/OBPDataOperations.h @@ -1,8 +1,8 @@ // Function lib for history buffer handling, true wind calculation, and other operations on boat data #pragma once #include "OBPRingBuffer.h" -#include "obp60task.h" #include "Pagedata.h" +#include "obp60task.h" #include class HstryBuf { @@ -11,8 +11,8 @@ private: String boatDataName; double hstryMin; double hstryMax; - GwApi::BoatValue *boatValue; - GwLog *logger; + GwApi::BoatValue* boatValue; + GwLog* logger; friend class HstryBuffers; @@ -32,31 +32,32 @@ private: GwApi::BoatValue *awaBVal, *hdtBVal, *hdmBVal, *varBVal, *cogBVal, *sogBVal, *awdBVal; // boat values for true wind calculation struct HistoryParams { - int hstryUpdFreq; - int mltplr; - double bufferMinVal; - double bufferMaxVal; - String format; + int hstryUpdFreq; // update frequency of history buffer (documentation only) + int mltplr; // specifies actual value precision being storable: + // [10000: 0 - 6.5535 | 1000: 0 - 65.535 | 100: 0 - 650.35 | 10: 0 - 6503.5 + double bufferMinVal; // minimum valid data value + double bufferMaxVal; // maximum valid data value + String format; // format of data type }; // Define buffer parameters for supported boat data type std::map bufferParams = { - {"AWA", {1000, 10000, 0.0, M_TWOPI, "formatWind"}}, - {"AWD", {1000, 10000, 0.0, M_TWOPI, "formatCourse"}}, - {"AWS", {1000, 1000, 0.0, 65.0, "formatKnots"}}, - {"COG", {1000, 10000, 0.0, M_TWOPI, "formatCourse"}}, - {"DBS", {1000, 100, 0.0, 650.0, "formatDepth"}}, - {"DBT", {1000, 100, 0.0, 650.0, "formatDepth"}}, - {"DPT", {1000, 100, 0.0, 650.0, "formatDepth"}}, - {"HDM", {1000, 10000, 0.0, M_TWOPI, "formatCourse"}}, - {"HDT", {1000, 10000, 0.0, M_TWOPI, "formatCourse"}}, - {"ROT", {1000, 10000, -M_PI / 180.0 * 99.0, M_PI / 180.0 * 99.0, "formatRot"}}, // min/max is -/+ 99 degrees for "rate of turn" - {"SOG", {1000, 1000, 0.0, 65.0, "formatKnots"}}, - {"STW", {1000, 1000, 0.0, 65.0, "formatKnots"}}, - {"TWA", {1000, 10000, 0.0, M_TWOPI, "formatWind"}}, - {"TWD", {1000, 10000, 0.0, M_TWOPI, "formatCourse"}}, - {"TWS", {1000, 1000, 0.0, 65.0, "formatKnots"}}, - {"WTemp", {1000, 100, 233.0, 650.0, "kelvinToC"}} // [-50..376] °C + { "AWA", { 1000, 10000, 0.0, M_TWOPI, "formatWind" } }, + { "AWD", { 1000, 10000, 0.0, M_TWOPI, "formatCourse" } }, + { "AWS", { 1000, 1000, 0.0, 65.0, "formatKnots" } }, + { "COG", { 1000, 10000, 0.0, M_TWOPI, "formatCourse" } }, + { "DBS", { 1000, 100, 0.0, 650.0, "formatDepth" } }, + { "DBT", { 1000, 100, 0.0, 650.0, "formatDepth" } }, + { "DPT", { 1000, 100, 0.0, 650.0, "formatDepth" } }, + { "HDM", { 1000, 10000, 0.0, M_TWOPI, "formatCourse" } }, + { "HDT", { 1000, 10000, 0.0, M_TWOPI, "formatCourse" } }, + { "ROT", { 1000, 10000, -M_PI / 180.0 * 99.0, M_PI / 180.0 * 99.0, "formatRot" } }, // min/max is -/+ 99 degrees for "rate of turn" + { "SOG", { 1000, 1000, 0.0, 65.0, "formatKnots" } }, + { "STW", { 1000, 1000, 0.0, 65.0, "formatKnots" } }, + { "TWA", { 1000, 10000, 0.0, M_TWOPI, "formatWind" } }, + { "TWD", { 1000, 10000, 0.0, M_TWOPI, "formatCourse" } }, + { "TWS", { 1000, 1000, 0.0, 65.0, "formatKnots" } }, + { "WTemp", { 1000, 100, 233.0, 650.0, "kelvinToC" } } // [-50..376] °C }; public: @@ -75,7 +76,8 @@ private: public: WindUtils(BoatValueList* boatValues, GwLog* log) - : logger(log) { + : logger(log) + { twaBVal = boatValues->findValueOrCreate("TWA"); twsBVal = boatValues->findValueOrCreate("TWS"); twdBVal = boatValues->findValueOrCreate("TWD"); diff --git a/lib/obp60task/OBPcharts.cpp b/lib/obp60task/OBPcharts.cpp index 2e4aaea..e15fc83 100644 --- a/lib/obp60task/OBPcharts.cpp +++ b/lib/obp60task/OBPcharts.cpp @@ -1,13 +1,11 @@ // Function lib for display of boat data in various chart formats #include "OBPcharts.h" -// #include "OBP60Extensions.h" #include "OBPDataOperations.h" #include "OBPRingBuffer.h" std::map Chart::dfltChrtDta = { { "formatWind", { 60.0 * DEG_TO_RAD, 10.0 * DEG_TO_RAD } }, // default course range 60 degrees { "formatCourse", { 60.0 * DEG_TO_RAD, 10.0 * DEG_TO_RAD } }, // default course range 60 degrees - //{ "formatKnots", { 7.71, 2.57 } }, // default speed range in m/s { "formatKnots", { 7.71, 2.56 } }, // default speed range in m/s { "formatDepth", { 15.0, 5.0 } }, // default depth range in m { "kelvinToC", { 30.0, 5.0 } } // default temp range in °C/K @@ -16,16 +14,12 @@ std::map Chart::dfltChrtDta = { // --- Class Chart --------------- // Chart - object holding the actual chart, incl. data buffer and format definition -// Parameters: chart timeline direction: 'H' = horizontal, 'V' = vertical; -// chart size: [0] = full size, [1] = half size left/top, [2] half size right/bottom; -// default range of chart, e.g. 30 = [0..30]; +// Parameters: the history data buffer for the chart +// default range of chart, e.g. 30 = [0..30] // common program data; required for logger and color data // flag to indicate if simulation data is active -// Chart::Chart(RingBuffer& dataBuf, char chrtDir, int8_t chrtSz, double dfltRng, CommonData& common, bool useSimuData) Chart::Chart(RingBuffer& dataBuf, double dfltRng, CommonData& common, bool useSimuData) : dataBuf(dataBuf) - //, chrtDir(chrtDir) - //, chrtSz(chrtSz) , dfltRng(dfltRng) , commonData(&common) , useSimuData(useSimuData) @@ -37,52 +31,6 @@ Chart::Chart(RingBuffer& dataBuf, double dfltRng, CommonData& common, dWidth = getdisplay().width(); dHeight = getdisplay().height(); - /* if (chrtDir == 'H') { - // horizontal chart timeline direction - timAxis = dWidth - 1; - switch (chrtSz) { - case 0: - valAxis = dHeight - top - bottom; - cRoot = { 0, top - 1 }; - break; - case 1: - valAxis = (dHeight - top - bottom) / 2 - hGap; - cRoot = { 0, top - 1 }; - break; - case 2: - valAxis = (dHeight - top - bottom) / 2 - hGap; - cRoot = { 0, top + (valAxis + hGap) + hGap - 1 }; - break; - default: - LOG_DEBUG(GwLog::ERROR, "obp60:Chart %s: wrong init parameter", dataBuf.getName()); - return; - } - - } else if (chrtDir == 'V') { - // vertical chart timeline direction - timAxis = dHeight - top - bottom; - switch (chrtSz) { - case 0: - valAxis = dWidth - 1; - cRoot = { 0, top - 1 }; - break; - case 1: - valAxis = dWidth / 2 - vGap; - cRoot = { 0, top - 1 }; - break; - case 2: - valAxis = dWidth / 2 - vGap; - cRoot = { dWidth / 2 + vGap - 1, top - 1 }; - break; - default: - LOG_DEBUG(GwLog::ERROR, "obp60:Chart %s: wrong init parameter", dataBuf.getName()); - return; - } - } else { - LOG_DEBUG(GwLog::ERROR, "obp60:Chart %s: wrong init parameter", dataBuf.getName()); - return; - } */ - dataBuf.getMetaData(dbName, dbFormat); dbMIN_VAL = dataBuf.getMinVal(); dbMAX_VAL = dataBuf.getMaxVal(); @@ -90,22 +38,22 @@ Chart::Chart(RingBuffer& dataBuf, double dfltRng, CommonData& common, // Initialize chart data format; shorter version of standard format indicator if (dbFormat == "formatCourse" || dbFormat == "formatWind" || dbFormat == "formatRot") { - chrtDataFmt = 'W'; // Chart is showing data of course / wind format + chrtDataFmt = WIND; // Chart is showing data of course / wind format } else if (dbFormat == "formatRot") { - chrtDataFmt = 'R'; // Chart is showing data of rotational format + chrtDataFmt = ROTATION; // Chart is showing data of rotational format } else if (dbFormat == "formatKnots") { - chrtDataFmt = 'S'; // Chart is showing data of speed or windspeed format + chrtDataFmt = SPEED; // Chart is showing data of speed or windspeed format } else if (dbFormat == "formatDepth") { - chrtDataFmt = 'D'; // Chart ist showing data of format + chrtDataFmt = DEPTH; // Chart ist showing data of format } else if (dbFormat == "kelvinToC") { - chrtDataFmt = 'T'; // Chart ist showing data of format + chrtDataFmt = TEMPERATURE; // Chart ist showing data of format } else { - chrtDataFmt = 'O'; // Chart is showing any other data format + chrtDataFmt = OTHER; // Chart is showing any other data format } // "0" value is the same for any data format but for user defined temperature format zeroValue = 0.0; - if (chrtDataFmt == 'T') { + if (chrtDataFmt == TEMPERATURE) { tempFormat = commonData->config->getString(commonData->config->tempFormat); // [K|°C|°F] if (tempFormat == "K") { zeroValue = 0.0; @@ -130,9 +78,9 @@ Chart::Chart(RingBuffer& dataBuf, double dfltRng, CommonData& common, chrtMax = chrtMin + dfltRng; chrtMid = (chrtMin + chrtMax) / 2; chrtRng = dfltRng; - recalcRngCntr = true; // initialize and chart borders on first screen call + recalcRngMid = true; // initialize and chart borders on first screen call - LOG_DEBUG(GwLog::DEBUG, "Chart Init: dWidth: %d, dHeight: %d, timAxis: %d, valAxis: %d, cRoot {x,y}: %d, %d, dbname: %s, rngStep: %.4f, chrtDataFmt: %c", + LOG_DEBUG(GwLog::DEBUG, "Chart Init: dWidth: %d, dHeight: %d, timAxis: %d, valAxis: %d, cRoot {x,y}: %d, %d, dbname: %s, rngStep: %.4f, chrtDataFmt: %d", dWidth, dHeight, timAxis, valAxis, cRoot.x, cRoot.y, dbName, rngStep, chrtDataFmt); }; @@ -144,28 +92,25 @@ Chart::~Chart() // Parameters: : chart timeline direction: 'H' = horizontal, 'V' = vertical // : chart size: [0] = full size, [1] = half size left/top, [2] half size right/bottom // : chart timeline interval -// : current boat data shall be shown [true/false] -// : current boat data value to be printed -// void Chart::showChrt(GwApi::BoatValue currValue, int8_t& chrtIntv, const bool showCurrValue) -void Chart::showChrt(char chrtDir, int8_t chrtSz, int8_t& chrtIntv, bool showCurrValue, GwApi::BoatValue currValue) +// ; print data name on horizontal half chart [true|false] +// : print current boat data value [true|false] +// : current boat data value; used only for test on valid data +void Chart::showChrt(char chrtDir, int8_t chrtSz, const int8_t chrtIntv, bool prntName, bool showCurrValue, GwApi::BoatValue currValue) { - // this->chrtDir = chrtDir; - // this->chrtSz = chrtSz; - if (!setChartDimensions(chrtDir, chrtSz)) { return; // wrong chart dimension parameters } drawChrt(chrtDir, chrtIntv, currValue); drawChrtTimeAxis(chrtDir, chrtSz, chrtIntv); - drawChrtValAxis(chrtDir, chrtSz); + drawChrtValAxis(chrtDir, chrtSz, prntName); if (!bufDataValid) { // No valid data available prntNoValidData(chrtDir); return; } - if (showCurrValue) { // show latest value from history buffer; usually this should be the most current one + if (showCurrValue) { // show latest value from history buffer; this should be the most current one currValue.value = dataBuf.getLast(); currValue.valid = currValue.value != dbMAX_VAL; prntCurrValue(chrtDir, currValue); @@ -175,12 +120,12 @@ void Chart::showChrt(char chrtDir, int8_t chrtSz, int8_t& chrtIntv, bool showCur // define dimensions and start points for chart bool Chart::setChartDimensions(const char direction, const int8_t size) { - if ((direction != 'H' && direction != 'V') || (size < 0 || size > 2)) { + if ((direction != HORIZONTAL && direction != VERTICAL) || (size < 0 || size > 2)) { LOG_DEBUG(GwLog::ERROR, "obp60:setChartDimensions %s: wrong parameters", dataBuf.getName()); return false; } - if (direction == 'H') { + if (direction == HORIZONTAL) { // horizontal chart timeline direction timAxis = dWidth - 1; switch (size) { @@ -198,7 +143,7 @@ bool Chart::setChartDimensions(const char direction, const int8_t size) break; } - } else if (direction == 'V') { + } else if (direction == VERTICAL) { // vertical chart timeline direction timAxis = dHeight - top - bottom; switch (size) { @@ -222,139 +167,41 @@ bool Chart::setChartDimensions(const char direction, const int8_t size) } // draw chart -void Chart::drawChrt(const char chrtDir, int8_t& chrtIntv, GwApi::BoatValue& currValue) +void Chart::drawChrt(const char chrtDir, const int8_t chrtIntv, GwApi::BoatValue& currValue) { - double chrtVal; // Current data value - double chrtScl; // Scale for data values in pixels per value + double chrtScale; // Scale for data values in pixels per value - int x, y; // x and y coordinates for drawing - - getBufStartNSize(chrtIntv); + getBufferStartNSize(chrtIntv); // LOG_DEBUG(GwLog::DEBUG, "Chart:drawChart: min: %.1f, mid: %.1f, max: %.1f, rng: %.1f", chrtMin, chrtMid, chrtMax, chrtRng); calcChrtBorders(chrtMin, chrtMid, chrtMax, chrtRng); - LOG_DEBUG(GwLog::DEBUG, "Chart:drawChart2: min: %.1f, mid: %.1f, max: %.1f, rng: %.1f", chrtMin, chrtMid, chrtMax, chrtRng); - - // Chart scale: pixels per value step - chrtScl = double(valAxis) / chrtRng; + chrtScale = double(valAxis) / chrtRng; // Chart scale: pixels per value step + LOG_DEBUG(GwLog::DEBUG, "Chart:drawChart: min: %.1f, mid: %.1f, max: %.1f, rng: %.1f", chrtMin, chrtMid, chrtMax, chrtRng); // 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 + return; + + } else if (currValue.valid || useSimuData) { // latest boat data valid or simulation mode + numNoData = 0; // reset data error counter + bufDataValid = true; + + } else { // currently no valid data numNoData++; bufDataValid = true; - if (numNoData > THRESHOLD_NO_DATA) { // If more than 4 invalid values in a row, send message + + if (numNoData > THRESHOLD_NO_DATA) { // If more than 4 invalid values in a row, flag for invalid data 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 = 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 { - - if (chrtDir == 'H') { // horizontal chart - x = cRoot.x + i; // Position in chart area - - if (chrtDataFmt == 'S' or chrtDataFmt == 'T') { // speed or temperature data format -> print low values at bottom - y = cRoot.y + valAxis - static_cast(((chrtVal - chrtMin) * chrtScl) + 0.5); // calculate chart point and round - } else if (chrtDataFmt == 'W' || chrtDataFmt == 'R') { // degree type value - y = cRoot.y + static_cast((WindUtils::to2PI(chrtVal - chrtMin) * chrtScl) + 0.5); // calculate chart point and round - } else { // any other data format - y = cRoot.y + static_cast(((chrtVal - chrtMin) * chrtScl) + 0.5); // calculate chart point and round - } - - } else { // vertical chart - y = cRoot.y + timAxis - i; // Position in chart area - - // if (chrtDataFmt == 'S' || chrtDataFmt == 'D' || chrtDataFmt == 'T') { - if (chrtDataFmt == 'W' || chrtDataFmt == 'R') { // degree type value - x = cRoot.x + static_cast((WindUtils::to2PI(chrtVal - chrtMin) * chrtScl) + 0.5); // calculate chart point and round - } else { - x = cRoot.x + static_cast(((chrtVal - chrtMin) * chrtScl) + 0.5); // calculate chart point and round - } - } - - // if (i >= (numBufVals / chrtIntv) - 5) // log chart data of 1 line (adjust for test purposes) - // LOG_DEBUG(GwLog::DEBUG, "PageWindPlot Chart: i: %d, chrtVal: %.2f, chrtMin: %.2f, {x,y} {%d,%d}", i, chrtVal, chrtMin, x, y); - - 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 == 'W' || chrtDataFmt == 'R') { - // 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 - 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 - if (chrtDir == 'H') { - int ySplit = wrappingFromHighToLow ? (cRoot.y + valAxis) : cRoot.y; - drawBoldLine(prevX, prevY, x, ySplit); - prevY = wrappingFromHighToLow ? cRoot.y : (cRoot.y + valAxis); - } else { // vertical chart - int xSplit = wrappingFromHighToLow ? (cRoot.x + valAxis) : cRoot.x; - drawBoldLine(prevX, prevY, xSplit, y); - prevX = wrappingFromHighToLow ? cRoot.x : (cRoot.x + valAxis); - } - } - } - - if (chrtDataFmt == 'D') { - if (chrtDir == 'H') { // horizontal chart - drawBoldLine(x, y, x, cRoot.y + valAxis); - } else { // vertical chart - drawBoldLine(x, y, cRoot.x + valAxis, y); - } - } else { - drawBoldLine(prevX, prevY, x, y); - } - - /* if (chrtDir == 'H' || x == prevX) { // horizontal chart & vertical line - if (chrtDataFmt == 'D') { - drawBoldLine(x, y, x, cRoot.y + valAxis); - } - drawBoldLine(prevX, prevY, x, y); - } else if (chrtDir == 'V' || x != prevX) { // vertical chart & line with some horizontal trend -> normal state - if (chrtDataFmt == 'D') { - drawBoldLine(x, y, cRoot.x + valAxis, y); - } - drawBoldLine(prevX, prevY, x, y); - } */ - chrtPrevVal = chrtVal; - prevX = x; - prevY = y; - } - - // Reaching chart area top end - if (i >= timAxis - 1) { - oldChrtIntv = 0; // force reset of buffer start and number of values to show in next display loop - - if (chrtDataFmt == 'W') { // degree of course or wind - recalcRngCntr = true; - LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: chart end: timAxis: %d, i: %d, bufStart: %d, numBufVals: %d, recalcRngCntr: %d", timAxis, i, bufStart, numBufVals, recalcRngCntr); - } - break; - } + return; } } + + drawChartLines(chrtDir, chrtIntv, chrtScale); } // Identify buffer size and buffer start position for chart -void Chart::getBufStartNSize(int8_t& chrtIntv) +void Chart::getBufferStartNSize(const int8_t chrtIntv) { count = dataBuf.getCurrentSize(); currIdx = dataBuf.getLastIdx(); @@ -379,19 +226,24 @@ void Chart::getBufStartNSize(int8_t& chrtIntv) // check and adjust chart range and set range borders and range middle void Chart::calcChrtBorders(double& rngMin, double& rngMid, double& rngMax, double& rng) { - if (chrtDataFmt == 'W' || chrtDataFmt == 'R') { - // Chart data is of type 'course', 'wind' or 'rot' + if (chrtDataFmt == WIND || chrtDataFmt == ROTATION) { - if (chrtDataFmt == 'W') { - // Chart data is of type 'course' or 'wind' + if (chrtDataFmt == ROTATION) { + // if chart data is of type 'rotation', we want to have always to be '0' + rngMid = 0; + } else { // WIND: Chart data is of type 'course' or 'wind' + + // initialize if data buffer has just been started filling if ((count == 1 && rngMid == 0) || rngMid == dbMAX_VAL) { - recalcRngCntr = true; // initialize + recalcRngMid = true; } - // Set rngMid - if (recalcRngCntr) { + if (recalcRngMid) { + // Set rngMid + rngMid = dataBuf.getMid(numBufVals); + if (rngMid == dbMAX_VAL) { rngMid = 0; } else { @@ -401,31 +253,32 @@ void Chart::calcChrtBorders(double& rngMin, double& rngMid, double& rngMax, doub 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 + rng = std::max(rng, dfltRng); // keep at least default chart range + if (rng > M_PI) { // If wind range > 180°, adjust wndCenter to smaller wind range end rngMid = WindUtils::to2PI(rngMid + M_PI); } } - recalcRngCntr = false; // Reset flag for determination - LOG_DEBUG(GwLog::DEBUG, "calcChrtRange: rngMid: %.1f°, rngMin: %.1f°, rngMax: %.1f°, rng: %.1f°, rngStep: %.1f°", rngMid * RAD_TO_DEG, rngMin * RAD_TO_DEG, rngMax * RAD_TO_DEG, + recalcRngMid = false; // Reset flag for determination + + LOG_DEBUG(GwLog::DEBUG, "calcChrtRange: rngMin: %.1f°, rngMid: %.1f°, rngMax: %.1f°, rng: %.1f°, rngStep: %.1f°", rngMin * RAD_TO_DEG, rngMid * RAD_TO_DEG, rngMax * RAD_TO_DEG, rng * RAD_TO_DEG, rngStep * RAD_TO_DEG); } - - } else if (chrtDataFmt == 'R') { - // 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 + // check and adjust range between left, mid, and right chart limit double halfRng = rng / 2.0; // we calculate with range between and edges - double diffRng = getAngleRng(rngMid, numBufVals); - diffRng = (diffRng == dbMAX_VAL ? 0 : std::ceil(diffRng / rngStep) * rngStep); - // LOG_DEBUG(GwLog::DEBUG, "calcChrtBorders: diffRng: %.1f°, halfRng: %.1f°", diffRng * RAD_TO_DEG, halfRng * RAD_TO_DEG); + double tmpRng = getAngleRng(rngMid, numBufVals); + tmpRng = (tmpRng == dbMAX_VAL ? 0 : std::ceil(tmpRng / rngStep) * rngStep); - 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); + // LOG_DEBUG(GwLog::DEBUG, "calcChrtBorders: tmpRng: %.1f°, halfRng: %.1f°", tmpRng * RAD_TO_DEG, halfRng * RAD_TO_DEG); + + if (tmpRng > halfRng) { // expand chart range to new value + halfRng = tmpRng; + } + + else if (tmpRng + rngStep < halfRng) { // Contract chart range for higher resolution if possible + halfRng = std::max(dfltRng / 2.0, tmpRng); } rngMin = WindUtils::to2PI(rngMid - halfRng); @@ -433,14 +286,12 @@ void Chart::calcChrtBorders(double& rngMin, double& rngMid, double& rngMax, doub rngMax = WindUtils::to2PI(rngMax); rng = halfRng * 2.0; - LOG_DEBUG(GwLog::DEBUG, "calcChrtBorders: rngMin: %.1f°, rngMid: %.1f°, rngMax: %.1f°, diffRng: %.1f°, rng: %.1f°, rngStep: %.1f°", rngMin * RAD_TO_DEG, rngMid * RAD_TO_DEG, rngMax * RAD_TO_DEG, - diffRng * RAD_TO_DEG, rng * RAD_TO_DEG, rngStep * RAD_TO_DEG); + + LOG_DEBUG(GwLog::DEBUG, "calcChrtBorders: rngMin: %.1f°, rngMid: %.1f°, rngMax: %.1f°, tmpRng: %.1f°, rng: %.1f°, rngStep: %.1f°", rngMin * RAD_TO_DEG, rngMid * RAD_TO_DEG, rngMax * RAD_TO_DEG, + tmpRng * RAD_TO_DEG, rng * RAD_TO_DEG, rngStep * RAD_TO_DEG); } else { // chart data is of any other type - double oldRngMin = rngMin; - double oldRngMax = rngMax; - double currMinVal = dataBuf.getMin(numBufVals); double currMaxVal = dataBuf.getMax(numBufVals); @@ -449,10 +300,10 @@ void Chart::calcChrtBorders(double& rngMin, double& rngMid, double& rngMax, doub } // check if current chart border have to be adjusted - if (currMinVal < oldRngMin || (currMinVal > (oldRngMin + rngStep))) { // decrease rngMin if required or increase if lowest value is higher than old rngMin + if (currMinVal < rngMin || (currMinVal > (rngMin + rngStep))) { // decrease rngMin if required or increase if lowest value is higher than old rngMin rngMin = std::floor(currMinVal / rngStep) * rngStep; // align low range to lowest buffer value and nearest range interval } - if ((currMaxVal > oldRngMax) || (currMaxVal < (oldRngMax - rngStep))) { // increase rngMax if required or decrease if lowest value is lower than old rngMax + if ((currMaxVal > rngMax) || (currMaxVal < (rngMax - rngStep))) { // increase rngMax if required or decrease if lowest value is lower than old rngMax rngMax = std::ceil(currMaxVal / rngStep) * rngStep; } @@ -474,8 +325,112 @@ void Chart::calcChrtBorders(double& rngMin, double& rngMid, double& rngMax, doub } } +// Draw chart graph +void Chart::drawChartLines(const char direction, const int8_t chrtIntv, const double chrtScale) +{ + double chrtVal; // Current data value + Pos point, prevPoint; // current and previous chart point + + for (int i = 0; i < (numBufVals / chrtIntv); i++) { + + chrtVal = dataBuf.get(bufStart + (i * chrtIntv)); // show the latest wind values in buffer; keep 1st value constant in a rolling buffer + + if (chrtVal == dbMAX_VAL) { + chrtPrevVal = dbMAX_VAL; + } else { + + point = setCurrentChartPoint(i, direction, chrtVal, chrtScale); + + // if (i >= (numBufVals / chrtIntv) - 5) // log chart data of 1 line (adjust for test purposes) + // LOG_DEBUG(GwLog::DEBUG, "PageWindPlot Chart: i: %d, chrtVal: %.2f, chrtMin: %.2f, {x,y} {%d,%d}", i, chrtVal, chrtMin, x, y); + + if ((i == 0) || (chrtPrevVal == dbMAX_VAL)) { + // just a dot for 1st chart point or after some invalid values + prevPoint = point; + + } else if (chrtDataFmt == WIND || chrtDataFmt == ROTATION) { + // cross borders check for degree values; shift values to [-PI..0..PI]; when crossing borders, range is 2x PI degrees + + double normCurrVal = WindUtils::to2PI(chrtVal - chrtMin); + double normPrevVal = 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(normCurrVal - normPrevVal) > (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 = normCurrVal < normPrevVal; // Determine which edge we're crossing + + if (direction == HORIZONTAL) { + int ySplit = wrappingFromHighToLow ? (cRoot.y + valAxis) : cRoot.y; + drawBoldLine(prevPoint.x, prevPoint.y, point.x, ySplit); + prevPoint.y = wrappingFromHighToLow ? cRoot.y : (cRoot.y + valAxis); + + } else { // vertical chart + int xSplit = wrappingFromHighToLow ? (cRoot.x + valAxis) : cRoot.x; + drawBoldLine(prevPoint.x, prevPoint.y, xSplit, point.y); + prevPoint.x = wrappingFromHighToLow ? cRoot.x : (cRoot.x + valAxis); + } + } + } + + if (chrtDataFmt == DEPTH) { + if (direction == HORIZONTAL) { // horizontal chart + drawBoldLine(point.x, point.y, point.x, cRoot.y + valAxis); + } else { // vertical chart + drawBoldLine(point.x, point.y, cRoot.x + valAxis, point.y); + } + } else { + drawBoldLine(prevPoint.x, prevPoint.y, point.x, point.y); + } + + chrtPrevVal = chrtVal; + prevPoint = point; + } + + // Reaching chart area top end + if (i >= timAxis - 1) { + oldChrtIntv = 0; // force reset of buffer start and number of values to show in next display loop + + if (chrtDataFmt == WIND) { // degree of course or wind + recalcRngMid = true; + LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: chart end: timAxis: %d, i: %d, bufStart: %d, numBufVals: %d, recalcRngCntr: %d", timAxis, i, bufStart, numBufVals, recalcRngMid); + } + break; + } + } +} + +// Set current chart point to draw +Pos Chart::setCurrentChartPoint(const int i, const char direction, const double chrtVal, const double chrtScale) +{ + Pos currentPoint; + + if (direction == HORIZONTAL) { + currentPoint.x = cRoot.x + i; // Position in chart area + + if (chrtDataFmt == WIND || chrtDataFmt == ROTATION) { // degree type value + currentPoint.y = cRoot.y + static_cast((WindUtils::to2PI(chrtVal - chrtMin) * chrtScale) + 0.5); // calculate chart point and round + } else if (chrtDataFmt == SPEED or chrtDataFmt == TEMPERATURE) { // speed or temperature data format -> print low values at bottom + currentPoint.y = cRoot.y + valAxis - static_cast(((chrtVal - chrtMin) * chrtScale) + 0.5); // calculate chart point and round + } else { // any other data format + currentPoint.y = cRoot.y + static_cast(((chrtVal - chrtMin) * chrtScale) + 0.5); // calculate chart point and round + } + + } else { // vertical chart + currentPoint.y = cRoot.y + timAxis - i; // Position in chart area + + if (chrtDataFmt == WIND || chrtDataFmt == ROTATION) { // degree type value + currentPoint.x = cRoot.x + static_cast((WindUtils::to2PI(chrtVal - chrtMin) * chrtScale) + 0.5); // calculate chart point and round + } else { + currentPoint.x = cRoot.x + static_cast(((chrtVal - chrtMin) * chrtScale) + 0.5); // calculate chart point and round + } + } + + return currentPoint; +} + // chart time axis label + lines -void Chart::drawChrtTimeAxis(const char chrtDir, const int8_t chrtSz, int8_t& chrtIntv) +void Chart::drawChrtTimeAxis(const char chrtDir, const int8_t chrtSz, const int8_t chrtIntv) { float axSlots, intv, i; char sTime[6]; @@ -488,7 +443,7 @@ void Chart::drawChrtTimeAxis(const char chrtDir, const int8_t chrtSz, int8_t& ch intv = timAxis / (axSlots - 1); // minutes per chart axis interval (interval is 1 less than axSlots) i = timeRng; // Chart axis label start at -32, -16, -12, ... minutes - if (chrtDir == 'H') { // horizontal chart + if (chrtDir == HORIZONTAL) { getdisplay().fillRect(0, cRoot.y, dWidth, 2, fgColor); for (float j = 0; j < timAxis - 1; j += intv) { // fill time axis with values but keep area free on right hand side for value label @@ -510,11 +465,11 @@ void Chart::drawChrtTimeAxis(const char chrtDir, const int8_t chrtSz, int8_t& ch snprintf(sTime, sizeof(sTime), "-%.0f", i); getdisplay().drawLine(cRoot.x, cRoot.y + j, cRoot.x + valAxis, cRoot.y + j, fgColor); // Grid line - if (chrtSz == 0) { // full size chart + if (chrtSz == FULL_SIZE) { // full size chart getdisplay().fillRect(0, cRoot.y + j - 9, 32, 15, bgColor); // clear small area to remove potential chart lines getdisplay().setCursor((4 - strlen(sTime)) * 7, cRoot.y + j + 3); // time value; print left screen; value right-formated getdisplay().printf("%s", sTime); // Range value - } else if (chrtSz == 2) { // half size chart; right side + } else if (chrtSz == HALF_SIZE_RIGHT) { // half size chart; right side drawTextCenter(dWidth / 2, cRoot.y + j, sTime); // time value; print mid screen } } @@ -522,92 +477,72 @@ void Chart::drawChrtTimeAxis(const char chrtDir, const int8_t chrtSz, int8_t& ch } // chart value axis labels + lines -void Chart::drawChrtValAxis(const char chrtDir, const int8_t chrtSz) +void Chart::drawChrtValAxis(const char chrtDir, const int8_t chrtSz, bool prntName) { - double axLabel; - double cVal; - // char sVal[6]; + const GFXfont* font; + constexpr bool NO_LABEL = false; + constexpr bool LABEL = true; getdisplay().setTextColor(fgColor); - if (chrtDir == 'H') { + if (chrtDir == HORIZONTAL) { - // print buffer data name on right hand side of time axis (max. size 5 characters) - getdisplay().setFont(&Ubuntu_Bold12pt8b); - drawTextRalign(cRoot.x + timAxis, cRoot.y - 3, dbName.substring(0, 5)); + if (chrtSz == FULL_SIZE) { - if (chrtSz == 0) { // full size chart + font = &Ubuntu_Bold12pt8b; - if (chrtDataFmt == 'W') { - prntHorizThreeValueAxisLabel(&Ubuntu_Bold12pt8b); + // print buffer data name on right hand side of time axis (max. size 5 characters) + getdisplay().setFont(font); + drawTextRalign(cRoot.x + timAxis, cRoot.y - 3, dbName.substring(0, 5)); + + if (chrtDataFmt == WIND) { + prntHorizChartThreeValueAxisLabel(font); return; } // for any other data formats print multiple axis value lines on full charts - prntHorizMultiValueAxisLabel(&Ubuntu_Bold12pt8b); + prntHorizChartMultiValueAxisLabel(font); return; } else { // half size chart -> just print edge values + middle chart line - LOG_DEBUG(GwLog::DEBUG, "Chart::drawChrtValAxis: chrtDataFmt: %c, chrtMin: %.2f, chrtMid: %.2f, chrtMax: %.2f", chrtDataFmt, chrtMin, chrtMid, chrtMax); - prntHorizThreeValueAxisLabel(&Ubuntu_Bold10pt8b); + font = &Ubuntu_Bold10pt8b; + + if (prntName) { + // print buffer data name on right hand side of time axis (max. size 5 characters) + getdisplay().setFont(font); + drawTextRalign(cRoot.x + timAxis, cRoot.y - 3, dbName.substring(0, 5)); + } + + prntHorizChartThreeValueAxisLabel(font); return; } } else { // vertical chart - char sVal[6]; - if (chrtSz == 0) { // full size chart - getdisplay().setFont(&Ubuntu_Bold12pt8b); // use larger font + if (chrtSz == FULL_SIZE) { + font = &Ubuntu_Bold12pt8b; + getdisplay().setFont(font); // use larger font drawTextRalign(cRoot.x + (valAxis * 0.42), cRoot.y - 2, dbName.substring(0, 6)); // print buffer data name (max. size 5 characters) + } else { - getdisplay().setFont(&Ubuntu_Bold10pt8b); // use smaller font - } - getdisplay().fillRect(cRoot.x, cRoot.y, valAxis, 2, fgColor); // top chart line - cVal = chrtMin; - cVal = convertValue(cVal, dbName, dbFormat, *commonData); // value (converted) - if (useSimuData) { // dirty fix for problem that OBP60Formatter can only be used without data simulation -> returns random values in simulation mode - cVal = chrtMin; // no value conversion + font = &Ubuntu_Bold10pt8b; } - snprintf(sVal, sizeof(sVal), "%.0f", round(cVal)); - getdisplay().setCursor(cRoot.x, cRoot.y - 2); - getdisplay().printf("%s", sVal); // Range low end - cVal = chrtMid; - cVal = convertValue(cVal, dbName, dbFormat, *commonData); // value (converted) - if (useSimuData) { // dirty fix for problem that OBP60Formatter can only be used without data simulation -> returns random values in simulation mode - cVal = chrtMid; // no value conversion - } - snprintf(sVal, sizeof(sVal), "%.0f", round(cVal)); - drawTextCenter(cRoot.x + (valAxis / 2), cRoot.y - 9, sVal); // Range mid end - - cVal = chrtMax; - cVal = convertValue(cVal, dbName, dbFormat, *commonData); // value (converted) - if (useSimuData) { // dirty fix for problem that OBP60Formatter can only be used without data simulation -> returns random values in simulation mode - cVal = chrtMax; // no value conversion - } - snprintf(sVal, sizeof(sVal), "%.0f", round(cVal)); - drawTextRalign(cRoot.x + valAxis - 2, cRoot.y - 2, sVal); // Range high end - - // draw vertical grid lines for each axis label - for (int j = 0; j <= valAxis; j += (valAxis / 2)) { - getdisplay().drawLine(cRoot.x + j, cRoot.y, cRoot.x + j, cRoot.y + timAxis, fgColor); - } + prntVerticChartThreeValueAxisLabel(font); } } // Print current data value -void Chart::prntCurrValue(const char chrtDir, GwApi::BoatValue& currValue) +void Chart::prntCurrValue(const char direction, GwApi::BoatValue& currValue) { - const int xPosVal = (chrtDir == 'H') ? cRoot.x + (timAxis / 2) - 56 : cRoot.x + 32; - const int yPosVal = (chrtDir == 'H') ? cRoot.y + valAxis - 7 : cRoot.y + timAxis - 7; + const int xPosVal = (direction == HORIZONTAL) ? cRoot.x + (timAxis / 2) - 56 : cRoot.x + 32; + const int yPosVal = (direction == HORIZONTAL) ? cRoot.y + valAxis - 7 : cRoot.y + timAxis - 7; - FormattedData frmtDbData = formatValue(&currValue, *commonData); + FormattedData frmtDbData = formatValue(&currValue, *commonData, NO_SIMUDATA); String sdbValue = frmtDbData.svalue; // value as formatted string String dbUnit = frmtDbData.unit; // Unit of value; limit length to 3 characters - // LOG_DEBUG(GwLog::DEBUG, "Chart CurrValue: dbValue: %.2f, sdbValue: %s, dbFormat: %s, dbUnit: %s, Valid: %d, Name: %s, Address: %p", currValue.value, sdbValue, - // currValue.getFormat(), dbUnit, currValue.valid, currValue.getName(), currValue); getdisplay().fillRect(xPosVal - 1, yPosVal - 35, 128, 41, bgColor); // Clear area for TWS value getdisplay().drawRect(xPosVal, yPosVal - 34, 126, 40, fgColor); // Draw box for TWS value @@ -625,28 +560,28 @@ void Chart::prntCurrValue(const char chrtDir, GwApi::BoatValue& currValue) } // print message for no valid data availabletemplate -void Chart::prntNoValidData(const char chrtDir) +void Chart::prntNoValidData(const char direction) { - int pX, pY; + Pos p; getdisplay().setFont(&Ubuntu_Bold10pt8b); - if (chrtDir == 'H') { - pX = cRoot.x + (timAxis / 2); - pY = cRoot.y + (valAxis / 2) - 10; + if (direction == HORIZONTAL) { + p.x = cRoot.x + (timAxis / 2); + p.y = cRoot.y + (valAxis / 2) - 10; } else { - pX = cRoot.x + (valAxis / 2); - pY = cRoot.y + (timAxis / 2) - 10; + p.x = cRoot.x + (valAxis / 2); + p.y = cRoot.y + (timAxis / 2) - 10; } - getdisplay().fillRect(pX - 37, pY - 10, 78, 24, bgColor); // Clear area for message - drawTextCenter(pX, pY, "No data"); + getdisplay().fillRect(p.x - 37, p.y - 10, 78, 24, bgColor); // Clear area for message + drawTextCenter(p.x, p.y, "No data"); LOG_DEBUG(GwLog::LOG, "Page chart <%s>: No valid data available", dbName); } // Get maximum difference of last of dataBuf ringbuffer values to center chart; for angle data only -double Chart::getAngleRng(double center, size_t amount) +double Chart::getAngleRng(const double center, size_t amount) { size_t count = dataBuf.getCurrentSize(); @@ -680,8 +615,39 @@ double Chart::getAngleRng(double center, size_t amount) return (maxRng != dbMIN_VAL ? maxRng : dbMAX_VAL); // Return range from to } -// print horizontal axis label with only three values: top, mid, and bottom -void Chart::prntHorizThreeValueAxisLabel(const GFXfont* font) + // print value axis label with only three values: top, mid, and bottom for vertical chart + void Chart::prntVerticChartThreeValueAxisLabel(const GFXfont* font) +{ + double cVal; + char sVal[7]; + + getdisplay().fillRect(cRoot.x, cRoot.y, valAxis, 2, fgColor); // top chart line + getdisplay().setFont(font); + + cVal = chrtMin; + cVal = convertValue(cVal, dbName, dbFormat, *commonData); // value (converted) + snprintf(sVal, sizeof(sVal), "%.0f", round(cVal)); + getdisplay().setCursor(cRoot.x, cRoot.y - 2); + getdisplay().printf("%s", sVal); // Range low end + + cVal = chrtMid; + cVal = convertValue(cVal, dbName, dbFormat, *commonData); // value (converted) + snprintf(sVal, sizeof(sVal), "%.0f", round(cVal)); + drawTextCenter(cRoot.x + (valAxis / 2), cRoot.y - 9, sVal); // Range mid end + + cVal = chrtMax; + cVal = convertValue(cVal, dbName, dbFormat, *commonData); // value (converted) + snprintf(sVal, sizeof(sVal), "%.0f", round(cVal)); + drawTextRalign(cRoot.x + valAxis - 2, cRoot.y - 2, sVal); // Range high end + + // draw vertical grid lines for each axis label + for (int j = 0; j <= valAxis; j += (valAxis / 2)) { + getdisplay().drawLine(cRoot.x + j, cRoot.y, cRoot.x + j, cRoot.y + timAxis, fgColor); + } +} + +// print value axis label with only three values: top, mid, and bottom for horizontal chart +void Chart::prntHorizChartThreeValueAxisLabel(const GFXfont* font) { double axLabel; double chrtMin, chrtMid, chrtMax; @@ -706,28 +672,28 @@ void Chart::prntHorizThreeValueAxisLabel(const GFXfont* font) chrtMax = std::round(chrtMax * 100.0) / 100.0; // print top axis label - axLabel = (chrtDataFmt == 'S' || chrtDataFmt == 'T') ? chrtMax : chrtMin; + axLabel = (chrtDataFmt == SPEED || chrtDataFmt == TEMPERATURE) ? chrtMax : chrtMin; sVal = formatLabel(axLabel); - getdisplay().fillRect(cRoot.x, cRoot.y + 2, xOffset + 4, yOffset, bgColor); // Clear small area to remove potential chart lines + getdisplay().fillRect(cRoot.x, cRoot.y + 2, xOffset + 3, yOffset, bgColor); // Clear small area to remove potential chart lines drawTextRalign(cRoot.x + xOffset, cRoot.y + yOffset, sVal); // range value // print mid axis label axLabel = chrtMid; sVal = formatLabel(axLabel); - getdisplay().fillRect(cRoot.x, cRoot.y + (valAxis / 2) - 8, xOffset + 4, 16, bgColor); // Clear small area to remove potential chart lines + getdisplay().fillRect(cRoot.x, cRoot.y + (valAxis / 2) - 8, xOffset + 3, 16, bgColor); // Clear small area to remove potential chart lines drawTextRalign(cRoot.x + xOffset, cRoot.y + (valAxis / 2) + 6, sVal); // range value - getdisplay().drawLine(cRoot.x + xOffset + 4, cRoot.y + (valAxis / 2), cRoot.x + timAxis, cRoot.y + (valAxis / 2), fgColor); + getdisplay().drawLine(cRoot.x + xOffset + 3, cRoot.y + (valAxis / 2), cRoot.x + timAxis, cRoot.y + (valAxis / 2), fgColor); // print bottom axis label - axLabel = (chrtDataFmt == 'S' || chrtDataFmt == 'T') ? chrtMin : chrtMax; + axLabel = (chrtDataFmt == SPEED || chrtDataFmt == TEMPERATURE) ? chrtMin : chrtMax; sVal = formatLabel(axLabel); - getdisplay().fillRect(cRoot.x, cRoot.y + valAxis - 16, xOffset + 3, 16, bgColor); // Clear small area to remove potential chart lines + getdisplay().fillRect(cRoot.x, cRoot.y + valAxis - 14, xOffset + 3, 15, bgColor); // Clear small area to remove potential chart lines drawTextRalign(cRoot.x + xOffset, cRoot.y + valAxis, sVal); // range value - getdisplay().drawLine(cRoot.x + xOffset + 2, cRoot.y + valAxis, cRoot.x + timAxis, cRoot.y + valAxis, fgColor); + getdisplay().drawLine(cRoot.x + xOffset + 3, cRoot.y + valAxis, cRoot.x + timAxis, cRoot.y + valAxis, fgColor); } -// print horizontal axis label with multiple axis lines -void Chart::prntHorizMultiValueAxisLabel(const GFXfont* font) +// print value axis label with multiple axis lines for horizontal chart +void Chart::prntHorizChartMultiValueAxisLabel(const GFXfont* font) { double chrtMin, chrtMax, chrtRng; double axSlots, axIntv, axLabel; @@ -755,7 +721,7 @@ void Chart::prntHorizMultiValueAxisLabel(const GFXfont* font) LOG_DEBUG(GwLog::DEBUG, "Chart::printHorizMultiValueAxisLabel: chrtRng: %.2f, th-chrtRng: %.2f, axSlots: %.2f, axIntv: %.2f, axLabel: %.2f, chrtMin: %.2f, chrtMid: %.2f, chrtMax: %.2f", chrtRng, this->chrtRng, axSlots, axIntv, axLabel, this->chrtMin, chrtMid, chrtMax); int loopStrt, loopEnd, loopStp; - if (chrtDataFmt == 'S' || chrtDataFmt == 'T' || chrtDataFmt == 'O') { + if (chrtDataFmt == SPEED || chrtDataFmt == TEMPERATURE || chrtDataFmt == OTHER) { // High value at top loopStrt = valAxis - VALAXIS_STEP; loopEnd = VALAXIS_STEP / 2; @@ -769,8 +735,7 @@ void Chart::prntHorizMultiValueAxisLabel(const GFXfont* font) for (int j = loopStrt; (loopStp > 0) ? (j < loopEnd) : (j > loopEnd); j += loopStp) { sVal = formatLabel(axLabel); - // sVal = convNformatLabel(axLabel); - getdisplay().fillRect(cRoot.x, cRoot.y + j - 11, xOffset + 4, 21, bgColor); // Clear small area to remove potential chart lines + getdisplay().fillRect(cRoot.x, cRoot.y + j - 11, xOffset + 3, 21, bgColor); // Clear small area to remove potential chart lines drawTextRalign(cRoot.x + xOffset, cRoot.y + j + 7, sVal); // range value getdisplay().drawLine(cRoot.x + xOffset + 3, cRoot.y + j, cRoot.x + timAxis, cRoot.y + j, fgColor); @@ -779,9 +744,8 @@ void Chart::prntHorizMultiValueAxisLabel(const GFXfont* font) } // Draw chart line with thickness of 2px -void Chart::drawBoldLine(int16_t x1, int16_t y1, int16_t x2, int16_t y2) +void Chart::drawBoldLine(const int16_t x1, const int16_t y1, const int16_t x2, const int16_t y2) { - int16_t dx = std::abs(x2 - x1); int16_t dy = std::abs(y2 - y1); @@ -795,7 +759,7 @@ void Chart::drawBoldLine(int16_t x1, int16_t y1, int16_t x2, int16_t y2) } // Convert and format current axis label to user defined format; helper function for easier handling of OBP60Formatter -String Chart::convNformatLabel(double label) +String Chart::convNformatLabel(const double& label) { GwApi::BoatValue tmpBVal(dbName); // temporary boat value for string formatter String sVal; @@ -803,9 +767,9 @@ String Chart::convNformatLabel(double label) tmpBVal.setFormat(dbFormat); tmpBVal.valid = true; tmpBVal.value = label; - sVal = formatValue(&tmpBVal, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places + sVal = formatValue(&tmpBVal, *commonData, NO_SIMUDATA).svalue; // Formatted value as string including unit conversion and switching decimal places if (sVal.length() > 0 && sVal[0] == '!') { - sVal = sVal.substring(1); // cut leading "!" created at OBPFormatter for use with other font than 7SEG + sVal = sVal.substring(1); // cut leading "!" created at OBPFormatter; doesn't work for other fonts than 7SEG } return sVal; diff --git a/lib/obp60task/OBPcharts.h b/lib/obp60task/OBPcharts.h index c6d24b2..fbdcddd 100644 --- a/lib/obp60task/OBPcharts.h +++ b/lib/obp60task/OBPcharts.h @@ -22,6 +22,27 @@ protected: CommonData* commonData; GwLog* logger; + enum ChrtDataFormat { + WIND, + ROTATION, + SPEED, + DEPTH, + TEMPERATURE, + OTHER + }; + + static constexpr char HORIZONTAL = 'H'; + static constexpr char VERTICAL = 'V'; + static constexpr int8_t FULL_SIZE = 0; + static constexpr int8_t HALF_SIZE_LEFT = 1; + static constexpr int8_t HALF_SIZE_RIGHT = 2; + + static constexpr int8_t MIN_FREE_VALUES = 60; // free 60 values when chart line reaches chart end + static constexpr int8_t THRESHOLD_NO_DATA = 3; // max. seconds of invalid values in a row + static constexpr int8_t VALAXIS_STEP = 60; // pixels between two chart value axis labels + + static constexpr bool NO_SIMUDATA = true; // switch off simulation feature of function + RingBuffer& dataBuf; // Buffer to display //char chrtDir; // Chart timeline direction: 'H' = horizontal, 'V' = vertical //int8_t chrtSz; // Chart size: [0] = full size, [1] = half size left/top, [2] half size right/bottom @@ -45,10 +66,10 @@ protected: double chrtMax; // Range high end value double chrtMid; // Range mid value double rngStep; // Defines the step of adjustment (e.g. 10 m/s) for value axis range - bool recalcRngCntr = false; // Flag for re-calculation of mid value of chart for wind data types + bool recalcRngMid = false; // Flag for re-calculation of mid value of chart for wind data types String dbName, dbFormat; // Name and format of data buffer - char chrtDataFmt; // Data format of chart: 'S' = size values; 'D' = depth value, 'W' = degree of course or wind; 'R' rotational degrees + ChrtDataFormat chrtDataFmt; // Data format of chart boat data type 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 @@ -67,32 +88,29 @@ protected: int x, y; // x and y coordinates for drawing int prevX, prevY; // Last x and y coordinates for drawing - static constexpr int8_t MIN_FREE_VALUES = 60; - static constexpr int8_t THRESHOLD_NO_DATA = 3; - static constexpr int8_t VALAXIS_STEP = 60; - bool setChartDimensions(const char direction, const int8_t size); //define dimensions and start points for chart - void drawChrt(const char chrtDir, int8_t& chrtIntv, GwApi::BoatValue& currValue); // Draw chart line - void getBufStartNSize(int8_t& chrtIntv); // Identify buffer size and buffer start position for chart + void drawChrt(const char chrtDir, const int8_t chrtIntv, GwApi::BoatValue& currValue); // Draw chart line + void getBufferStartNSize(const int8_t chrtIntv); // Identify buffer size and buffer start position for chart void calcChrtBorders(double& rngMin, double& rngMid, double& rngMax, double& rng); // Calculate chart points for value axis and return range between and - void drawChrtTimeAxis(const char chrtDir, const int8_t chrtSz, int8_t& chrtIntv); // Draw time axis of chart, value and lines - void drawChrtValAxis(const char chrtDir, const int8_t chrtSz); // Draw value axis of chart, value and lines + void drawChartLines(const char direction, const int8_t chrtIntv, const double chrtScale); // Draw chart graph + Pos setCurrentChartPoint(const int i, const char direction, const double chrtVal, const double chrtScale); // Set current chart point to draw + void drawChrtTimeAxis(const char chrtDir, const int8_t chrtSz, const int8_t chrtIntv); // Draw time axis of chart, value and lines + void drawChrtValAxis(const char chrtDir, const int8_t chrtSz, bool prntLabel); // Draw value axis of chart, value and lines void prntCurrValue(const char chrtDir, GwApi::BoatValue& currValue); // Add current boat data value to chart void prntNoValidData(const char chrtDir); // print message for no valid data available - double getAngleRng(double center, size_t amount); // Calculate range between chart center and edges - void prntHorizThreeValueAxisLabel(const GFXfont* font); // print horizontal axis label with only three values: top, mid, and bottom - void prntHorizMultiValueAxisLabel(const GFXfont* font); // print horizontal axis label with multiple axis lines - void drawBoldLine(int16_t x1, int16_t y1, int16_t x2, int16_t y2); // Draw chart line with thickness of 2px - String convNformatLabel(double label); // Convert and format current axis label to user defined format; helper function for easier handling of OBP60Formatter + double getAngleRng(const double center, size_t amount); // Calculate range between chart center and edges + void prntVerticChartThreeValueAxisLabel(const GFXfont* font); // print value axis label with only three values: top, mid, and bottom for vertical chart + void prntHorizChartThreeValueAxisLabel(const GFXfont* font); // print value axis label with only three values: top, mid, and bottom for horizontal chart + void prntHorizChartMultiValueAxisLabel(const GFXfont* font); // print value axis label with multiple axis lines for horizontal chart + void drawBoldLine(const int16_t x1, const int16_t y1, const int16_t x2, const int16_t y2); // Draw chart line with thickness of 2px + String convNformatLabel(const double& label); // Convert and format current axis label to user defined format; helper function for easier handling of OBP60Formatter String formatLabel(const double& label); // Format current axis label for printing w/o data format conversion (has been done earlier) public: // Define default chart range and range step for each boat data type static std::map dfltChrtDta; - // Chart(RingBuffer& dataBuf, char chrtDir, int8_t chrtSz, double dfltRng, CommonData& common, bool useSimuData); // Chart object of data chart Chart(RingBuffer& dataBuf, double dfltRng, CommonData& common, bool useSimuData); // Chart object of data chart ~Chart(); - // void showChrt(GwApi::BoatValue currValue, int8_t& chrtIntv, bool showCurrValue); // Perform all actions to draw chart - void showChrt(char chrtDir, int8_t chrtSz, int8_t& chrtIntv, bool showCurrValue, GwApi::BoatValue currValue); // Perform all actions to draw chart + void showChrt(char chrtDir, int8_t chrtSz, const int8_t chrtIntv, bool prntName, bool showCurrValue, GwApi::BoatValue currValue); // Perform all actions to draw chart }; diff --git a/lib/obp60task/PageOneValue.cpp b/lib/obp60task/PageOneValue.cpp index 59a02bf..2a0075b 100644 --- a/lib/obp60task/PageOneValue.cpp +++ b/lib/obp60task/PageOneValue.cpp @@ -10,11 +10,32 @@ class PageOneValue : public Page { private: GwLog* logger; + enum PageMode { + VALUE, + CHART, + BOTH + }; + enum DisplayMode { + FULL, + HALF + }; + + static constexpr char HORIZONTAL = 'H'; + static constexpr char VERTICAL = 'V'; + static constexpr int8_t FULL_SIZE = 0; + static constexpr int8_t HALF_SIZE_TOP = 1; + static constexpr int8_t HALF_SIZE_BOTTOM = 2; + + static constexpr bool PRNT_NAME = true; + static constexpr bool NO_PRNT_NAME = false; + static constexpr bool PRNT_VALUE = true; + static constexpr bool NO_PRNT_VALUE = false; + int width; // Screen width int height; // Screen height bool keylock = false; // Keylock - char pageMode = 'V'; // Page mode: 'V' for value, 'C' for chart, 'B' for both + PageMode pageMode = VALUE; // Page display mode int8_t dataIntv = 1; // Update interval for wind history chart: // (1)|(2)|(3)|(4)|(8) x 240 seconds for 4, 8, 12, 16, 32 min. history chart @@ -31,14 +52,14 @@ private: // Data buffer pointer (owned by HstryBuffers) RingBuffer* dataHstryBuf = nullptr; - std::unique_ptr dataChart; // Chart object, full and half size + std::unique_ptr dataChart; // Chart object - void showData(GwApi::BoatValue* bValue1, char size) + void showData(GwApi::BoatValue* bValue1, DisplayMode mode) { int nameXoff, nameYoff, unitXoff, unitYoff, value1Xoff, value1Yoff; const GFXfont *nameFnt, *unitFnt, *valueFnt1, *valueFnt2, *valueFnt3; - if (size == 'F') { // full size data display + if (mode == FULL) { // full size data display nameXoff = 0; nameYoff = 0; nameFnt = &Ubuntu_Bold32pt8b; @@ -51,17 +72,17 @@ private: valueFnt2 = &Ubuntu_Bold32pt8b; valueFnt3 = &DSEG7Classic_BoldItalic60pt7b; } else { // half size data and chart display - nameXoff = 105; - nameYoff = -35; + nameXoff = -10; + nameYoff = -34; nameFnt = &Ubuntu_Bold20pt8b; - unitXoff = -35; - unitYoff = -102; + unitXoff = 63; + unitYoff = -119; unitFnt = &Ubuntu_Bold12pt8b; valueFnt1 = &Ubuntu_Bold12pt8b; - value1Xoff = 105; - value1Yoff = -102; + value1Xoff = 153; + value1Yoff = -119; valueFnt2 = &Ubuntu_Bold20pt8b; - valueFnt3 = &DSEG7Classic_BoldItalic30pt7b; + valueFnt3 = &DSEG7Classic_BoldItalic42pt7b; } String name1 = xdrDelete(bValue1->getName()); // Value name @@ -156,14 +177,18 @@ public: { if (dataHstryBuf) { // if boat data type supports charts - // Set page mode value | full chart | value/half chart + // Set page mode value | value/half chart | full chart if (key == 1) { - if (pageMode == 'V') { - pageMode = 'C'; - } else if (pageMode == 'C') { - pageMode = 'B'; - } else { - pageMode = 'V'; + switch (pageMode) { + case VALUE: + pageMode = BOTH; + break; + case BOTH: + pageMode = CHART; + break; + case CHART: + pageMode = VALUE; + break; } return 0; // Commit the key } @@ -208,6 +233,7 @@ public: #endif // buffer initialization will fail, if page is default page, because is not executed at system start for default page if (!dataChart) { // Create chart objects if they don't exist + GwApi::BoatValue* bValue1 = pageData.values[0]; // Page boat data element String bValName1 = bValue1->getName(); // Value name String bValFormat = bValue1->getFormat(); // Value format @@ -216,7 +242,6 @@ public: if (dataHstryBuf) { dataChart.reset(new Chart(*dataHstryBuf, Chart::dfltChrtDta[bValFormat].range, *commonData, useSimuData)); - //dataHfChart.reset(new Chart(*dataHstryBuf, 'H', 2, Chart::dfltChrtDta[bValFormat].range, *commonData, useSimuData)); LOG_DEBUG(GwLog::DEBUG, "PageOneValue: Created chart objects for %s", bValName1); } else { LOG_DEBUG(GwLog::DEBUG, "PageOneValue: No chart objects available for %s", bValName1); @@ -228,7 +253,6 @@ public: int displayPage(PageData& pageData) { - LOG_DEBUG(GwLog::LOG, "Display PageOneValue"); // Get boat value for page @@ -250,19 +274,19 @@ public: getdisplay().setPartialWindow(0, 0, width, height); // Set partial update - if (pageMode == 'V' || dataHstryBuf == nullptr) { + if (pageMode == VALUE || dataHstryBuf == nullptr) { // show only data value; ignore other pageMode options if no chart supported boat data history buffer is available - showData(bValue1, 'F'); + showData(bValue1, FULL); - } else if (pageMode == 'C') { // show only data chart + } else if (pageMode == CHART) { // show only data chart if (dataChart) { - dataChart->showChrt('H', 0, dataIntv, true, *bValue1); + dataChart->showChrt(HORIZONTAL, FULL_SIZE, dataIntv, PRNT_NAME, PRNT_VALUE, *bValue1); } - } else if (pageMode == 'B') { // show data value and chart - showData(bValue1, 'H'); + } else if (pageMode == BOTH) { // show data value and chart + showData(bValue1, HALF); if (dataChart) { - dataChart->showChrt('H', 2, dataIntv, false, *bValue1); + dataChart->showChrt(HORIZONTAL, HALF_SIZE_BOTTOM, dataIntv, NO_PRNT_NAME, NO_PRNT_VALUE, *bValue1); } } diff --git a/lib/obp60task/PageWindPlot.cpp b/lib/obp60task/PageWindPlot.cpp index d90bab9..924687c 100644 --- a/lib/obp60task/PageWindPlot.cpp +++ b/lib/obp60task/PageWindPlot.cpp @@ -11,20 +11,28 @@ class PageWindPlot : public Page { private: GwLog* logger; - static constexpr char SHOW_WIND_DIR = 'D'; - static constexpr char SHOW_WIND_SPEED = 'S'; - static constexpr char SHOW_BOTH = 'B'; + enum ChartMode { + DIRECTION, + SPEED, + BOTH + }; + static constexpr char HORIZONTAL = 'H'; static constexpr char VERTICAL = 'V'; static constexpr int8_t FULL_SIZE = 0; - static constexpr int8_t HALF_SIZE_TOP = 1; - static constexpr int8_t HALF_SIZE_BOTTOM = 2; + static constexpr int8_t HALF_SIZE_LEFT = 1; + static constexpr int8_t HALF_SIZE_RIGHT = 2; + + static constexpr bool PRNT_NAME = true; + static constexpr bool NO_PRNT_NAME = false; + static constexpr bool PRNT_VALUE = true; + static constexpr bool NO_PRNT_VALUE = false; int width; // Screen width int height; // Screen height bool keylock = false; // Keylock - char chrtMode = 'D'; // Chart mode: 'D' for TWD, 'S' for TWS, 'B' for both + ChartMode chrtMode = DIRECTION; bool showTruW = true; // Show true wind or apparent wind in chart area bool oldShowTruW = false; // remember recent user selection of wind data type @@ -46,8 +54,8 @@ private: RingBuffer* awsHstry = nullptr; // Chart objects - std::unique_ptr twdChart, awdChart; // Chart object for wind direction, full size - std::unique_ptr twsChart, awsChart; // Chart object for wind speed, full size + std::unique_ptr twdChart, awdChart; // Chart object for wind direction + std::unique_ptr twsChart, awsChart; // Chart object for wind speed // Active charts and values Chart* wdChart = nullptr; @@ -89,14 +97,14 @@ public: // Key functions virtual int handleKey(int key) { - // Set chart mode TWD | TWS + // Set chart mode if (key == 1) { - if (chrtMode == SHOW_WIND_DIR) { - chrtMode = SHOW_WIND_SPEED; - } else if (chrtMode == SHOW_WIND_SPEED) { - chrtMode = SHOW_BOTH; + if (chrtMode == DIRECTION) { + chrtMode = SPEED; + } else if (chrtMode == SPEED) { + chrtMode = BOTH; } else { - chrtMode = SHOW_WIND_DIR; + chrtMode = DIRECTION; } return 0; // Commit the key } @@ -192,35 +200,6 @@ public: LOG_DEBUG(GwLog::LOG, "Display PageWindPlot"); ulong pageTime = millis(); - /* if (!twdChart) { // Create true wind charts if they don't exist - twdHstry = pageData.hstryBuffers->getBuffer("TWD"); - twsHstry = pageData.hstryBuffers->getBuffer("TWS"); - - if (twdHstry) { - twdChart.reset(new Chart(*twdHstry, Chart::dfltChrtDta["formatCourse"].range, *commonData, useSimuData)); - } - if (twsHstry) { - twsChart.reset(new Chart(*twsHstry, Chart::dfltChrtDta["formatKnots"].range, *commonData, useSimuData)); - } - } - - if (!awdChart) { // Create apparent wind charts if they don't exist - awdHstry = pageData.hstryBuffers->getBuffer("AWD"); - awsHstry = pageData.hstryBuffers->getBuffer("AWS"); - - if (awdHstry) { - awdChart.reset(new Chart(*awdHstry, Chart::dfltChrtDta["formatCourse"].range, *commonData, useSimuData)); - } - if (awsHstry) { - awsChart.reset(new Chart(*awsHstry, Chart::dfltChrtDta["formatKnots"].range, *commonData, useSimuData)); - } - if (twdHstry && twsHstry && awdHstry && awsHstry) { - LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: Created wind charts"); - } else { - LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: Some/all chart objects for wind data missing"); - } - } */ - if (showTruW != oldShowTruW) { // Switch active charts based on showTruW @@ -247,22 +226,22 @@ public: getdisplay().setPartialWindow(0, 0, width, height); // Set partial update getdisplay().setTextColor(commonData->fgcolor); - if (chrtMode == SHOW_WIND_DIR) { + if (chrtMode == DIRECTION) { if (wdChart) { - wdChart->showChrt(VERTICAL, FULL_SIZE, dataIntv, true, *wdBVal); + wdChart->showChrt(VERTICAL, FULL_SIZE, dataIntv, PRNT_NAME, PRNT_VALUE, *wdBVal); } - } else if (chrtMode == SHOW_WIND_SPEED) { + } else if (chrtMode == SPEED) { if (wsChart) { - wsChart->showChrt(HORIZONTAL, FULL_SIZE, dataIntv, true, *wsBVal); + wsChart->showChrt(HORIZONTAL, FULL_SIZE, dataIntv, PRNT_NAME, PRNT_VALUE, *wsBVal); } - } else if (chrtMode == SHOW_BOTH) { + } else if (chrtMode == BOTH) { if (wdChart) { - wdChart->showChrt(VERTICAL, HALF_SIZE_TOP, dataIntv, true, *wdBVal); + wdChart->showChrt(VERTICAL, HALF_SIZE_LEFT, dataIntv, PRNT_NAME, PRNT_VALUE, *wdBVal); } if (wsChart) { - wsChart->showChrt(VERTICAL, HALF_SIZE_BOTTOM, dataIntv, true, *wsBVal); + wsChart->showChrt(VERTICAL, HALF_SIZE_RIGHT, dataIntv, PRNT_NAME, PRNT_VALUE, *wsBVal); } }