From 3ce1e31e64a6baf6ef732f402c185fbf44491c9f Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Sat, 13 Dec 2025 17:59:16 +0100 Subject: [PATCH 01/13] Initial change to history buffers taking any boat value --- lib/obp60task/OBPDataOperations.cpp | 189 +++++++++++++--------------- lib/obp60task/OBPDataOperations.h | 61 ++++----- lib/obp60task/PageWindPlot.cpp | 22 ++-- lib/obp60task/Pagedata.h | 2 +- lib/obp60task/obp60task.cpp | 14 +-- 5 files changed, 129 insertions(+), 159 deletions(-) diff --git a/lib/obp60task/OBPDataOperations.cpp b/lib/obp60task/OBPDataOperations.cpp index c68ef53..3eadde5 100644 --- a/lib/obp60task/OBPDataOperations.cpp +++ b/lib/obp60task/OBPDataOperations.cpp @@ -1,46 +1,81 @@ #include "OBPDataOperations.h" #include "BoatDataCalibration.h" // Functions lib for data instance calibration #include +#include +#include +#include // --- Class HstryBuf --------------- -// Init history buffers for selected boat data -void HstryBuf::init(BoatValueList* boatValues, GwLog *log) { +HstryBuf::HstryBuf(const String& name, int size, GwLog* log, BoatValueList* boatValues) + : logger(log), boatDataName(name) { + hstry.resize(size); + boatValue = boatValues->findValueOrCreate(name); +} - logger = log; +void HstryBuf::init(const String& format, int updFreq, int mltplr, double minVal, double maxVal) { + hstry.setMetaData(boatDataName, format, updFreq, mltplr, minVal, maxVal); + hstryMin = minVal; + hstryMax = maxVal; + if (!boatValue->valid) { + boatValue->setFormat(format); + boatValue->value = std::numeric_limits::max(); + } +} +void HstryBuf::add(double value) { + if (value >= hstryMin && value <= hstryMax) { + hstry.add(value); + } +} + +void HstryBuf::handle(bool useSimuData) { + GwApi::BoatValue *calBVal; + + if (boatValue->valid) { + calBVal = new GwApi::BoatValue(boatDataName.c_str()); + calBVal->setFormat(boatValue->getFormat()); + calBVal->value = boatValue->value; + calBVal->valid = boatValue->valid; + calibrationData.calibrateInstance(calBVal, logger); + add(calBVal->value); + delete calBVal; + calBVal = nullptr; + } else if (useSimuData) { + double simValue = hstry.getLast(); + if (boatDataName == "TWD" || boatDataName == "AWD") { + simValue += static_cast(random(-349, 349) / 1000.0); + simValue = WindUtils::to2PI(simValue); + } else if (boatDataName == "TWS" || boatDataName == "AWS") { + simValue += static_cast(random(-5000, 5000) / 1000.0); + simValue = constrain(simValue, 0, 40); + } + add(simValue); + } +} + +// --- Class HstryManager --------------- +HstryManager::HstryManager(int size, GwLog* log, BoatValueList* boatValues) { + // Create history buffers for each boat data type + hstryBufs["TWD"] = std::unique_ptr(new HstryBuf("TWD", size, log, boatValues)); + hstryBufs["TWS"] = std::unique_ptr(new HstryBuf("TWS", size, log, boatValues)); + hstryBufs["AWD"] = std::unique_ptr(new HstryBuf("AWD", size, log, boatValues)); + hstryBufs["AWS"] = std::unique_ptr(new HstryBuf("AWS", size, log, boatValues)); + + // Initialize metadata for each buffer int hstryUpdFreq = 1000; // Update frequency for history buffers in ms 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; - twsHstryMin = hstryMinVal; - awdHstryMin = hstryMinVal; - awsHstryMin = hstryMinVal; - const double DBL_MAX = std::numeric_limits::max(); + double courseMax = 2 * M_PI; + double speedMax = 65; - // Initialize history buffers with meta data 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); + hstryBufs["TWD"]->init("formatCourse", hstryUpdFreq, mltplr, hstryMinVal, courseMax); + hstryBufs["AWD"]->init("formatCourse", hstryUpdFreq, mltplr, hstryMinVal, courseMax); + 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()); - twsBVal = boatValues->findValueOrCreate(hstryBufList.twsHstry->getName()); - twaBVal = boatValues->findValueOrCreate("TWA"); - awdBVal = boatValues->findValueOrCreate(hstryBufList.awdHstry->getName()); - awsBVal = boatValues->findValueOrCreate(hstryBufList.awsHstry->getName()); - - if (!awdBVal->valid) { // AWD usually does not exist - awdBVal->setFormat(hstryBufList.awdHstry->getFormat()); - awdBVal->value = DBL_MAX; - } + hstryBufs["TWS"]->init("formatKnots", hstryUpdFreq, mltplr, hstryMinVal, speedMax); + hstryBufs["AWS"]->init("formatKnots", hstryUpdFreq, mltplr, hstryMinVal, speedMax); // collect boat values for true wind calculation awaBVal = boatValues->findValueOrCreate("AWA"); @@ -49,106 +84,58 @@ void HstryBuf::init(BoatValueList* boatValues, GwLog *log) { varBVal = boatValues->findValueOrCreate("VAR"); cogBVal = boatValues->findValueOrCreate("COG"); sogBVal = boatValues->findValueOrCreate("SOG"); + awdBVal = boatValues->findValueOrCreate("AWD"); } // Handle history buffers for TWD, TWS, AWD, AWS -//void HstryBuf::handleHstryBuf(GwApi* api, BoatValueList* boatValues, bool useSimuData) { -void HstryBuf::handleHstryBuf(bool useSimuData) { +void HstryManager::handleHstryBufs(bool useSimuData) { - static double twd, tws, awd, aws, hdt = 20; //initial value only relevant if we use simulation data + static double 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 = 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 += 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) { - calBVal = new GwApi::BoatValue("TWS"); // temporary solution for calibration of history buffer values - calBVal->setFormat(twsBVal->getFormat()); - calBVal->value = twsBVal->value; - calBVal->valid = twsBVal->valid; - calibrationData.calibrateInstance(calBVal, logger); // Check if boat data value is to be calibrated - 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 += 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); + // Handle all registered history buffers + for (auto& pair : hstryBufs) { + auto& buf = pair.second; + buf->handle(useSimuData); } + // Special handling for AWD which is calculated if (awaBVal->valid) { if (hdtBVal->valid) { hdt = hdtBVal->value; // Use HDT if available } else { hdt = WindUtils::calcHDT(&hdmBVal->value, &varBVal->value, &cogBVal->value, &sogBVal->value); } - + double awd; awd = awaBVal->value + hdt; awd = WindUtils::to2PI(awd); calBVal = new GwApi::BoatValue("AWD"); // temporary solution for calibration of history buffer values calBVal->value = awd; calBVal->setFormat(awdBVal->getFormat()); calBVal->valid = true; - calibrationData.calibrateInstance(calBVal, logger); // Check if boat data value is to be calibrated + // We don't have a logger here, so we pass nullptr. This should be improved. + calibrationData.calibrateInstance(calBVal, nullptr); // Check if boat data value is to be calibrated awdBVal->value = calBVal->value; awdBVal->valid = true; - awd = calBVal->value; - if (awd >= awdHstryMin && awd <= awdHstryMax) { - hstryBufList.awdHstry->add(awd); + // Find the AWD buffer and add the value. + auto it = hstryBufs.find("AWD"); + if (it != hstryBufs.end()) { + it->second->add(calBVal->value); } + delete calBVal; calBVal = nullptr; } else if (useSimuData) { - awd += static_cast(random(-349, 349) / 1000.0); // add up to +/- 20 degree in RAD - awd = WindUtils::to2PI(awd); - hstryBufList.awdHstry->add(awd); + // Simulation for AWD is handled inside HstryBuf::handle } - - if (awsBVal->valid) { - calBVal = new GwApi::BoatValue("AWS"); // temporary solution for calibration of history buffer values - calBVal->setFormat(awsBVal->getFormat()); - calBVal->value = awsBVal->value; - calBVal->valid = awsBVal->valid; - calibrationData.calibrateInstance(calBVal, logger); // Check if boat data value is to be calibrated - aws = calBVal->value; - if (aws >= awsHstryMin && aws <= awsHstryMax) { - hstryBufList.awsHstry->add(aws); - } - delete calBVal; - calBVal = nullptr; - } else if (useSimuData) { - 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); +} + +RingBuffer* HstryManager::getBuffer(const String& name) { + auto it = hstryBufs.find(name); + if (it != hstryBufs.end()) { + return &it->second->hstry; } - 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()); + return nullptr; } // --- Class HstryBuf --------------- diff --git a/lib/obp60task/OBPDataOperations.h b/lib/obp60task/OBPDataOperations.h index 8422894..de33156 100644 --- a/lib/obp60task/OBPDataOperations.h +++ b/lib/obp60task/OBPDataOperations.h @@ -2,51 +2,38 @@ #pragma once #include "OBPRingBuffer.h" #include "obp60task.h" - -typedef struct { - RingBuffer* twdHstry; - RingBuffer* twsHstry; - RingBuffer* awdHstry; - RingBuffer* awsHstry; -} tBoatHstryData; // Holds pointers to all history buffers for boat data +#include +#include +#include class HstryBuf { private: GwLog *logger; + RingBuffer hstry; // Circular buffer to store history values + String boatDataName; + double hstryMin; + double hstryMax; + GwApi::BoatValue *boatValue; - 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 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; - 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; - GwApi::BoatValue *awaBVal, *hdtBVal, *hdmBVal, *varBVal, *cogBVal, *sogBVal; + friend class HstryManager; + void handleHistory(bool useSimuData); public: - tBoatHstryData hstryBufList; + HstryBuf(const String& name, int size, GwLog* log, BoatValueList* boatValues); + void init(const String& format, int updFreq, int mltplr, double minVal, double maxVal); + void add(double value); + void handle(bool useSimuData); +}; - HstryBuf(){ - hstryBufList = {&twdHstry, &twsHstry, &awdHstry, &awsHstry}; // Generate history buffers of zero size - }; - - HstryBuf(int size) { - hstryBufList = {&twdHstry, &twsHstry, &awdHstry, &awsHstry}; - hstryBufList.twdHstry->resize(size); // store xWD values for /60 minutes history - hstryBufList.twsHstry->resize(size); - hstryBufList.awdHstry->resize(size); - hstryBufList.awsHstry->resize(size); - }; - void init(BoatValueList* boatValues, GwLog *log); - void handleHstryBuf(bool useSimuData); +class HstryManager { +private: + std::map> hstryBufs; + // boat values for true wind calculation + GwApi::BoatValue *awaBVal, *hdtBVal, *hdmBVal, *varBVal, *cogBVal, *sogBVal, *awdBVal; +public: + HstryManager(int size, GwLog* log, BoatValueList* boatValues); + void handleHstryBufs(bool useSimuData); + RingBuffer* getBuffer(const String& name); }; class WindUtils { diff --git a/lib/obp60task/PageWindPlot.cpp b/lib/obp60task/PageWindPlot.cpp index 0b4e884..a30c0c8 100644 --- a/lib/obp60task/PageWindPlot.cpp +++ b/lib/obp60task/PageWindPlot.cpp @@ -2,6 +2,7 @@ #include "Pagedata.h" #include "OBP60Extensions.h" +#include "OBPDataOperations.h" #include "OBPcharts.h" // **************************************************************** @@ -163,12 +164,9 @@ 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); - + LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: Creating true wind charts"); + auto* twdHstry = pageData.hstryManager->getBuffer("TWD"); + auto* twsHstry = pageData.hstryManager->getBuffer("TWS"); 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)); @@ -180,8 +178,8 @@ public: 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; + auto* awdHstry = pageData.hstryManager->getBuffer("AWD"); + auto* awsHstry = pageData.hstryManager->getBuffer("AWS"); awdFlChart = std::unique_ptr>(new Chart(*awdHstry, 1, 0, dfltRngWd, *commonData, useSimuData)); awsFlChart = std::unique_ptr>(new Chart(*awsHstry, 0, 0, dfltRngWs, *commonData, useSimuData)); @@ -191,15 +189,15 @@ public: // Switch active charts based on showTruW if (showTruW) { - wdHstry = pageData.boatHstry->hstryBufList.twdHstry; - wsHstry = pageData.boatHstry->hstryBufList.twsHstry; + wdHstry = pageData.hstryManager->getBuffer("TWD"); + wsHstry = pageData.hstryManager->getBuffer("TWS"); wdFlChart = twdFlChart.get(); wsFlChart = twsFlChart.get(); wdHfChart = twdHfChart.get(); wsHfChart = twsHfChart.get(); } else { - wdHstry = pageData.boatHstry->hstryBufList.awdHstry; - wsHstry = pageData.boatHstry->hstryBufList.awsHstry; + wdHstry = pageData.hstryManager->getBuffer("AWD"); + wsHstry = pageData.hstryManager->getBuffer("AWS"); wdFlChart = awdFlChart.get(); wsFlChart = awsFlChart.get(); wdHfChart = awdHfChart.get(); diff --git a/lib/obp60task/Pagedata.h b/lib/obp60task/Pagedata.h index 9c515b4..f40d187 100644 --- a/lib/obp60task/Pagedata.h +++ b/lib/obp60task/Pagedata.h @@ -16,7 +16,7 @@ typedef struct{ uint8_t pageNumber; // page number in sequence of visible pages //the values will always contain the user defined values first ValueList values; - HstryBuf* boatHstry; + HstryManager* hstryManager; } PageData; // Sensor data structure (only for extended sensors, not for NMEA bus sensors) diff --git a/lib/obp60task/obp60task.cpp b/lib/obp60task/obp60task.cpp index 17e586a..0626a90 100644 --- a/lib/obp60task/obp60task.cpp +++ b/lib/obp60task/obp60task.cpp @@ -433,7 +433,7 @@ void OBP60Task(GwApi *api){ int lastPage=pageNumber; BoatValueList boatValues; //all the boat values for the api query - HstryBuf hstryBufList(1920); // Create ring buffers for history storage of some boat data (1920 seconds = 32 minutes) + HstryManager hstryManager(1920, logger, &boatValues); // Create and manage history buffers WindUtils trueWind(&boatValues); // Create helper object for true wind calculation //commonData.distanceformat=config->getString(xxx); //add all necessary data to common data @@ -477,21 +477,19 @@ void OBP60Task(GwApi *api){ LOG_DEBUG(GwLog::DEBUG,"added fixed value %s to page %d",value->getName().c_str(),i); pages[i].parameters.values.push_back(value); } - // Add boat history data to page parameters - pages[i].parameters.boatHstry = &hstryBufList; + // Add history manager to page parameters + pages[i].parameters.hstryManager = &hstryManager; } // add out of band system page (always available) Page *syspage = allPages.pages[0]->creator(commonData); - // Read all calibration data settings from config - calibrationData.readConfig(config, logger); - // Check user settings for true wind calculation bool calcTrueWnds = api->getConfig()->getBool(api->getConfig()->calcTrueWnds, false); bool useSimuData = api->getConfig()->getBool(api->getConfig()->useSimuData, false); // Initialize history buffer for certain boat data - hstryBufList.init(&boatValues, logger); + // Read all calibration data settings from config + calibrationData.readConfig(config, logger); // Display screenshot handler for HTTP request // http://192.168.15.1/api/user/OBP60Task/screenshot @@ -809,7 +807,7 @@ void OBP60Task(GwApi *api){ trueWind.addTrueWind(api, &boatValues, logger); } // Handle history buffers for certain boat data for windplot page and other usage - hstryBufList.handleHstryBuf(useSimuData); + hstryManager.handleHstryBufs(useSimuData); // Clear display // getdisplay().fillRect(0, 0, getdisplay().width(), getdisplay().height(), commonData.bgcolor); From d65567452907d3b9e8fe096a285976369997f3f3 Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Sun, 14 Dec 2025 20:17:36 +0100 Subject: [PATCH 02/13] Revert wind speed chart for horizontal charts; changed chart format parameters to ; adjusted chart size to OBP standard Revert wind speed chart for horizontal charts; changed chart format parameters to ; adjusted chart size to OBP standard - commit from PageWindPlot-v2 --- lib/obp60task/OBPcharts.cpp | 106 ++++++++++++++++++--------------- lib/obp60task/OBPcharts.h | 13 ++-- lib/obp60task/PageWindPlot.cpp | 21 +++---- 3 files changed, 75 insertions(+), 65 deletions(-) diff --git a/lib/obp60task/OBPcharts.cpp b/lib/obp60task/OBPcharts.cpp index 17caf75..b78bc6e 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, double dfltRng, CommonData& common, bool useSimuData) +Chart::Chart(RingBuffer& dataBuf, char chrtDir, int8_t chrtSz, double dfltRng, CommonData& common, bool useSimuData) : dataBuf(dataBuf) , chrtDir(chrtDir) , chrtSz(chrtSz) @@ -21,41 +21,43 @@ Chart::Chart(RingBuffer& dataBuf, int8_t chrtDir, int8_t chrtSz, double df dWidth = getdisplay().width(); dHeight = getdisplay().height(); - if (chrtDir == 0) { + if (chrtDir == 'H') { // horizontal chart timeline direction timAxis = dWidth; switch (chrtSz) { case 0: valAxis = dHeight - top - bottom; - cStart = { 0, top }; + cStart = { 0, top - 1 }; break; case 1: valAxis = (dHeight - top - bottom) / 2 - hGap; - cStart = { 0, top }; + cStart = { 0, top - 1 }; break; case 2: valAxis = (dHeight - top - bottom) / 2 - hGap; - cStart = { 0, top + (valAxis + hGap) + hGap }; + cStart = { 0, top + (valAxis + hGap) + hGap - 1 }; break; default: LOG_DEBUG(GwLog::ERROR, "displayChart: wrong init parameter"); return; } - } else if (chrtDir == 1) { + } else if (chrtDir == 'V') { // vertical chart timeline direction timAxis = dHeight - top - bottom; switch (chrtSz) { case 0: valAxis = dWidth; - cStart = { 0, top }; + cStart = { 0, top - 1 }; break; case 1: - valAxis = dWidth / 2 - vGap - 1; - cStart = { 0, top }; + // valAxis = dWidth / 2 - vGap - 1; + valAxis = dWidth / 2 - vGap; + cStart = { 0, top - 1 }; break; case 2: - valAxis = dWidth / 2 - vGap - 1; - cStart = { dWidth / 2 + vGap, top }; + // valAxis = dWidth / 2 - vGap - 1; + valAxis = dWidth / 2 - vGap; + cStart = { dWidth / 2 + vGap - 1, top - 1 }; break; default: LOG_DEBUG(GwLog::ERROR, "displayChart: wrong init parameter"); @@ -71,17 +73,21 @@ Chart::Chart(RingBuffer& dataBuf, int8_t chrtDir, int8_t chrtSz, double df dbMAX_VAL = dataBuf.getMaxVal(); bufSize = dataBuf.getCapacity(); - if (dbFormat == "formatCourse" || dbFormat == "FormatWind" || dbFormat == "FormatRot") { + if (dbFormat == "formatCourse" || dbFormat == "formatWind" || dbFormat == "formatRot") { - if (dbFormat == "FormatRot") { - chrtDataFmt = 2; // Chart is showing data of rotational format + if (dbFormat == "formatRot") { + chrtDataFmt = 'R'; // Chart is showing data of rotational format } else { - chrtDataFmt = 1; // Chart is showing data of course / wind format + chrtDataFmt = 'W'; // 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 + if (dbFormat == "formatDepth") { + chrtDataFmt = 'D'; // Chart ist showing data of format + } else { + chrtDataFmt = 'S'; // Chart is showing any other data format than + } rngStep = 5.0; // +/- 10 for all other values (eg. m/s, m, K, mBar) } @@ -178,16 +184,19 @@ void Chart::drawChrt(int8_t chrtIntv, GwApi::BoatValue& currValue) chrtPrevVal = dbMAX_VAL; } else { - if (chrtDir == 0) { // horizontal chart + if (chrtDir == 'H') { // horizontal chart x = cStart.x + i; // Position in chart area - if (chrtDataFmt == 0) { + if (chrtDataFmt == 'S') { + // y = cStart.y + static_cast(((chrtVal - chrtMin) * chrtScl) + 0.5); // calculate chart point and round + y = cStart.y + valAxis - static_cast(((chrtVal - chrtMin) * chrtScl) + 0.5); // calculate chart point and round + } else if (chrtDataFmt == 'D') { 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 y = cStart.y + timAxis - i; // Position in chart area - if (chrtDataFmt == 0) { + if (chrtDataFmt == 'S' || chrtDataFmt == 'D') { 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 @@ -202,7 +211,7 @@ void Chart::drawChrt(int8_t chrtIntv, GwApi::BoatValue& currValue) prevX = x; prevY = y; - } else if (chrtDataFmt != 0) { + } 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); @@ -212,7 +221,7 @@ 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 - if (chrtDir == 0) { + if (chrtDir == 'H') { int ySplit = wrappingFromHighToLow ? (cStart.y + valAxis) : cStart.y; getdisplay().drawLine(prevX, prevY, x, ySplit, fgColor); if (x != prevX) { // line with some horizontal trend @@ -231,12 +240,18 @@ void Chart::drawChrt(int8_t chrtIntv, GwApi::BoatValue& currValue) } // Draw line with 2 pixels width + make sure vertical lines are drawn correctly - if (chrtDir == 0 || x == prevX) { // horizontal chart & vertical line -// if (x == prevX) { // vertical line + if (chrtDir == 'H' || x == prevX) { // horizontal chart & vertical line + if (chrtDataFmt == 'D') { + getdisplay().drawLine(x, y, x, cStart.y + valAxis, fgColor); + getdisplay().drawLine(x - 1, y, x - 1, cStart.y + valAxis, fgColor); + } getdisplay().drawLine(prevX, prevY, x, y, fgColor); getdisplay().drawLine(prevX - 1, prevY, x - 1, y, fgColor); - } else if (chrtDir == 1 || x != prevX) { // vertical chart & line with some horizontal trend -> normal state -// } else { // line with some horizontal trend -> normal state + } else if (chrtDir == 'V' || x != prevX) { // vertical chart & line with some horizontal trend -> normal state + if (chrtDataFmt == 'D') { + getdisplay().drawLine(x, y, cStart.x + valAxis, y, fgColor); + getdisplay().drawLine(x, y - 1, cStart.x + valAxis, y - 1, fgColor); + } getdisplay().drawLine(prevX, prevY, x, y, fgColor); getdisplay().drawLine(prevX, prevY - 1, x, y - 1, fgColor); } @@ -249,23 +264,22 @@ void Chart::drawChrt(int8_t chrtIntv, GwApi::BoatValue& currValue) 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 + if (chrtDataFmt == 'W') { // 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; } } - } else { // No valid data available getdisplay().setFont(&Ubuntu_Bold10pt8b); int pX, pY; - if (chrtDir == 0) { // horizontal chart + if (chrtDir == 'H') { pX = cStart.x + (timAxis / 2); pY = cStart.y + (valAxis / 2) - 10; - } else { // vertical chart + } else { pX = cStart.x + (valAxis / 2); pY = cStart.y + (timAxis / 2) - 10; } @@ -316,7 +330,7 @@ double Chart::getRng(double center, size_t amount) template void Chart::calcChrtBorders(double& rngMid, double& rngMin, double& rngMax, double& rng) { - if (chrtDataFmt == 0) { + if (chrtDataFmt == 'S' || chrtDataFmt == 'D') { // Chart data is of any type but 'degree' double oldRngMin = rngMin; @@ -349,7 +363,7 @@ void Chart::calcChrtBorders(double& rngMid, double& rngMin, double& rngMax, d currMinVal, currMaxVal, rngMin, rngMid, rngMax, rng, rngStep, oldRngMin, oldRngMax, dfltRng, numBufVals); } else { - if (chrtDataFmt == 1) { + if (chrtDataFmt == 'W') { // Chart data is of type 'course' or 'wind' if ((count == 1 && rngMid == 0) || rngMid == dbMAX_VAL) { @@ -378,7 +392,7 @@ void Chart::calcChrtBorders(double& rngMid, double& rngMin, double& rngMax, d rng * RAD_TO_DEG, rngStep * RAD_TO_DEG); } - } else if (chrtDataFmt == 2) { + } else if (chrtDataFmt == 'R') { // Chart data is of type 'rotation'; then we want to have always to be '0' rngMid = 0; } @@ -417,7 +431,7 @@ void Chart::drawChrtTimeAxis(int8_t chrtIntv) getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setTextColor(fgColor); - if (chrtDir == 0) { // horizontal chart + if (chrtDir == 'H') { // horizontal chart 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. @@ -478,7 +492,7 @@ void Chart::drawChrtValAxis() { double slots; int i, intv; - double cVal, cchrtRng, crngMin; + double cVal, cChrtRng, crngMin; char sVal[6]; int sLen; std::unique_ptr tmpBVal; // Temp variable to get formatted and converted data value from OBP60Formatter @@ -486,16 +500,17 @@ void Chart::drawChrtValAxis() tmpBVal->setFormat(dataBuf.getFormat()); tmpBVal->valid = true; - if (chrtDir == 0) { // horizontal chart + if (chrtDir == 'H') { 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)); + cChrtRng = formatValue(tmpBVal.get(), *commonData).cvalue; // value (converted) + intv = static_cast(round(cChrtRng / slots)); i = intv; if (chrtSz == 0) { // full size chart -> print multiple value lines getdisplay().setFont(&Ubuntu_Bold12pt8b); - for (int j = 60; j < valAxis - 30; j += 60) { +// for (int j = 60; j < valAxis - 30; j += 60) { + for (int j = valAxis - 60; j > 30; j -= 60) { 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 @@ -542,7 +557,7 @@ void Chart::drawChrtValAxis() } else { getdisplay().setFont(&Ubuntu_Bold10pt8b); } - getdisplay().fillRect(cStart.x, top, valAxis, 2, fgColor); // top chart line + getdisplay().fillRect(cStart.x, cStart.y, valAxis, 2, fgColor); // top chart line tmpBVal->value = chrtMin; cVal = formatValue(tmpBVal.get(), *commonData).cvalue; // value (converted) @@ -560,14 +575,9 @@ void Chart::drawChrtValAxis() snprintf(sVal, sizeof(sVal), "%.0f", round(cVal)); drawTextRalign(cStart.x + valAxis - 2, cStart.y - 2, sVal); // Range high end - for (int j = 0; j <= valAxis + 2; j += ((valAxis + 2) / 2)) { + for (int j = 0; j <= valAxis; j += (valAxis / 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) + 15, cStart.y - 11, dbName); // buffer data name - // } } } @@ -575,8 +585,8 @@ void Chart::drawChrtValAxis() template void Chart::prntCurrValue(GwApi::BoatValue& currValue) { - 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; + const int xPosVal = (chrtDir == 'H') ? cStart.x + (timAxis / 2) - 56 : cStart.x + 32; + const int yPosVal = (chrtDir == 'H') ? cStart.y + valAxis - 5 : cStart.y + timAxis - 5; FormattedData frmtDbData = formatValue(&currValue, *commonData); double testdbValue = frmtDbData.value; @@ -585,7 +595,7 @@ 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, 41, bgColor); // Clear area for TWS value + getdisplay().fillRect(xPosVal - 1, yPosVal - 34, 125, 40, 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); diff --git a/lib/obp60task/OBPcharts.h b/lib/obp60task/OBPcharts.h index db298a5..f160dd4 100644 --- a/lib/obp60task/OBPcharts.h +++ b/lib/obp60task/OBPcharts.h @@ -16,19 +16,18 @@ protected: GwLog *logger; RingBuffer &dataBuf; // Buffer to display - int8_t chrtDir; // Chart timeline direction: [0] = horizontal, [1] = vertical + char chrtDir; // Chart timeline direction: 'H' = horizontal, 'V' = vertical int8_t chrtSz; // Chart size: [0] = full size, [1] = half size left/top, [2] half size right/bottom double dfltRng; // Default range of chart, e.g. 30 = [0..30] uint16_t fgColor; // color code for any screen writing uint16_t bgColor; // color code for screen background bool useSimuData; // flag to indicate if simulation data is active - int top = 48; // display top header lines - int bottom = 22; // display bottom lines + // int top = 48; // display top header lines + int top = 44; // chart gap at top of display (25 lines for standard gap + 19 lines for axis labels) + int bottom = 25; // chart gap at bottom of display to keep space for status line 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 int dWidth; // Display width int dHeight; // Display height int timAxis, valAxis; // size of time and value chart axis @@ -41,7 +40,7 @@ protected: 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 - int chrtDataFmt; // Data format of chart: [0] size values; [1] degree of course or wind; [2] rotational degrees + char chrtDataFmt; // Data format of chart: 'S' = size values; 'D' = depth value, 'W' = degree of course or wind; 'R' 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 @@ -63,7 +62,7 @@ protected: 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 + Chart(RingBuffer& dataBuf, char chrtDir, int8_t chrtSz, double dfltRng, CommonData& common, bool useSimuData); // Chart object of data chart ~Chart(); void showChrt(int8_t chrtIntv, GwApi::BoatValue currValue); // Perform all actions to draw chart diff --git a/lib/obp60task/PageWindPlot.cpp b/lib/obp60task/PageWindPlot.cpp index a30c0c8..1ce55b9 100644 --- a/lib/obp60task/PageWindPlot.cpp +++ b/lib/obp60task/PageWindPlot.cpp @@ -167,12 +167,13 @@ public: LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: Creating true wind charts"); auto* twdHstry = pageData.hstryManager->getBuffer("TWD"); auto* twsHstry = pageData.hstryManager->getBuffer("TWS"); - 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)); - // twdHfChart = std::unique_ptr>(new Chart(*twdHstry, 0, 1, dfltRngWd, *commonData, useSimuData)); - // twsHfChart = std::unique_ptr>(new Chart(*twsHstry, 0, 2, dfltRngWs, *commonData, useSimuData)); + + twdFlChart = std::unique_ptr>(new Chart(*twdHstry, 'V', 0, dfltRngWd, *commonData, useSimuData)); + twsFlChart = std::unique_ptr>(new Chart(*twsHstry, 'H', 0, dfltRngWs, *commonData, useSimuData)); + twdHfChart = std::unique_ptr>(new Chart(*twdHstry, 'V', 1, dfltRngWd, *commonData, useSimuData)); + twsHfChart = std::unique_ptr>(new Chart(*twsHstry, 'V', 2, dfltRngWs, *commonData, useSimuData)); + // twdHfChart = std::unique_ptr>(new Chart(*twdHstry, 'H', 1, dfltRngWd, *commonData, useSimuData)); + // twsHfChart = std::unique_ptr>(new Chart(*twsHstry, 'H', 2, dfltRngWs, *commonData, useSimuData)); // LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: twdHstry: %p, twsHstry: %p", (void*)twdHstry, (void*)twsHstry); } @@ -181,10 +182,10 @@ public: auto* awdHstry = pageData.hstryManager->getBuffer("AWD"); auto* awsHstry = pageData.hstryManager->getBuffer("AWS"); - 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)); + awdFlChart = std::unique_ptr>(new Chart(*awdHstry, 'V', 0, dfltRngWd, *commonData, useSimuData)); + awsFlChart = std::unique_ptr>(new Chart(*awsHstry, 'H', 0, dfltRngWs, *commonData, useSimuData)); + awdHfChart = std::unique_ptr>(new Chart(*awdHstry, 'V', 1, dfltRngWd, *commonData, useSimuData)); + awsHfChart = std::unique_ptr>(new Chart(*awsHstry, 'V', 2, dfltRngWs, *commonData, useSimuData)); } // Switch active charts based on showTruW From 41a8e7d0785bf1f5be7cc4eb5cfa5755e7efd090 Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Sat, 20 Dec 2025 22:42:42 +0100 Subject: [PATCH 03/13] General history buffers working; fine tuning required --- lib/obp60task/OBPDataOperations.cpp | 130 ++++++++++++++-------------- lib/obp60task/OBPDataOperations.h | 61 ++++++++++--- lib/obp60task/PageWindPlot.cpp | 59 +++++-------- lib/obp60task/Pagedata.h | 2 +- lib/obp60task/obp60task.cpp | 22 +++-- 5 files changed, 153 insertions(+), 121 deletions(-) diff --git a/lib/obp60task/OBPDataOperations.cpp b/lib/obp60task/OBPDataOperations.cpp index 3eadde5..898ba5a 100644 --- a/lib/obp60task/OBPDataOperations.cpp +++ b/lib/obp60task/OBPDataOperations.cpp @@ -1,38 +1,34 @@ #include "OBPDataOperations.h" #include "BoatDataCalibration.h" // Functions lib for data instance calibration #include -#include -#include -#include // --- Class HstryBuf --------------- - -HstryBuf::HstryBuf(const String& name, int size, GwLog* log, BoatValueList* boatValues) +HstryBuf::HstryBuf(const String& name, int size, BoatValueList* boatValues, GwLog* log) : logger(log), boatDataName(name) { - hstry.resize(size); + hstryBuf.resize(size); boatValue = boatValues->findValueOrCreate(name); } void HstryBuf::init(const String& format, int updFreq, int mltplr, double minVal, double maxVal) { - hstry.setMetaData(boatDataName, format, updFreq, mltplr, minVal, maxVal); + hstryBuf.setMetaData(boatDataName, format, updFreq, mltplr, minVal, maxVal); hstryMin = minVal; hstryMax = maxVal; if (!boatValue->valid) { boatValue->setFormat(format); - boatValue->value = std::numeric_limits::max(); + boatValue->value = std::numeric_limits::max(); // mark current value invalid } } void HstryBuf::add(double value) { if (value >= hstryMin && value <= hstryMax) { - hstry.add(value); + hstryBuf.add(value); } } void HstryBuf::handle(bool useSimuData) { GwApi::BoatValue *calBVal; - if (boatValue->valid) { + if (boatValue->valid) { // add calibrated boat value to history buffer calBVal = new GwApi::BoatValue(boatDataName.c_str()); calBVal->setFormat(boatValue->getFormat()); calBVal->value = boatValue->value; @@ -41,8 +37,8 @@ void HstryBuf::handle(bool useSimuData) { add(calBVal->value); delete calBVal; calBVal = nullptr; - } else if (useSimuData) { - double simValue = hstry.getLast(); + } else if (useSimuData) { // add simulated value to history buffer + double simValue = hstryBuf.getLast(); if (boatDataName == "TWD" || boatDataName == "AWD") { simValue += static_cast(random(-349, 349) / 1000.0); simValue = WindUtils::to2PI(simValue); @@ -53,48 +49,53 @@ void HstryBuf::handle(bool useSimuData) { add(simValue); } } +// --- End Class HstryBuf --------------- -// --- Class HstryManager --------------- -HstryManager::HstryManager(int size, GwLog* log, BoatValueList* boatValues) { - // Create history buffers for each boat data type - hstryBufs["TWD"] = std::unique_ptr(new HstryBuf("TWD", size, log, boatValues)); - hstryBufs["TWS"] = std::unique_ptr(new HstryBuf("TWS", size, log, boatValues)); - hstryBufs["AWD"] = std::unique_ptr(new HstryBuf("AWD", size, log, boatValues)); - hstryBufs["AWS"] = std::unique_ptr(new HstryBuf("AWS", size, log, boatValues)); - - // Initialize metadata for each buffer - int hstryUpdFreq = 1000; // Update frequency for history buffers in ms - int mltplr = 1000; // Multiplier which transforms original value into buffer type format - double hstryMinVal = 0; // Minimum value for these history buffers - double courseMax = 2 * M_PI; - double speedMax = 65; - - mltplr = 10000; // Store 4 decimals for course data - hstryBufs["TWD"]->init("formatCourse", hstryUpdFreq, mltplr, hstryMinVal, courseMax); - hstryBufs["AWD"]->init("formatCourse", hstryUpdFreq, mltplr, hstryMinVal, courseMax); - - mltplr = 1000; // Store 3 decimals for windspeed data - hstryBufs["TWS"]->init("formatKnots", hstryUpdFreq, mltplr, hstryMinVal, speedMax); - hstryBufs["AWS"]->init("formatKnots", hstryUpdFreq, mltplr, hstryMinVal, speedMax); +// --- Class HstryBuffers --------------- +HstryBuffers::HstryBuffers(int size, BoatValueList* boatValues, GwLog* log) + : size(size), boatValueList(boatValues), logger(log) { // collect boat values for true wind calculation - awaBVal = boatValues->findValueOrCreate("AWA"); - hdtBVal = boatValues->findValueOrCreate("HDT"); - hdmBVal = boatValues->findValueOrCreate("HDM"); - varBVal = boatValues->findValueOrCreate("VAR"); - cogBVal = boatValues->findValueOrCreate("COG"); - sogBVal = boatValues->findValueOrCreate("SOG"); - awdBVal = boatValues->findValueOrCreate("AWD"); + // should have been already all created at true wind object initialization + // potentially to be moved to history buffer handling + awaBVal = boatValueList->findValueOrCreate("AWA"); + hdtBVal = boatValueList->findValueOrCreate("HDT"); + hdmBVal = boatValueList->findValueOrCreate("HDM"); + varBVal = boatValueList->findValueOrCreate("VAR"); + cogBVal = boatValueList->findValueOrCreate("COG"); + sogBVal = boatValueList->findValueOrCreate("SOG"); + awdBVal = boatValueList->findValueOrCreate("AWD"); +} + +void HstryBuffers::addBuffer(const String& name) { + // Create history buffer for boat data type + + if (HstryBuffers::getBuffer(name) != nullptr) { // buffer for this data type already exists + return; + } + + hstryBuffers[name] = std::unique_ptr(new HstryBuf(name, size, boatValueList, logger)); + + // Initialize metadata for buffer + // String valueFormat = boatValueList->findValueOrCreate(name)->getFormat().c_str(); // Unfortunately, format is not yet available during system initialization + String valueFormat = bufferParams[name].format; // Data format of boat data type + int hstryUpdFreq = bufferParams[name].hstryUpdFreq; // Update frequency for history buffers in ms + int mltplr = bufferParams[name].mltplr; // default multiplier which transforms original value into buffer type format + double bufferMinVal = bufferParams[name].bufferMinVal; // Min value for this history buffer + double bufferMaxVal = bufferParams[name].bufferMaxVal; // Max value for this history buffer + + hstryBuffers[name]->init(valueFormat, hstryUpdFreq, mltplr, bufferMinVal, bufferMaxVal); + LOG_DEBUG(GwLog::DEBUG,"HstryBuffers-new buffer added: name: %s, format: %s, multiplier: %d, min value: %.2f, max value: %.2f", name, valueFormat, mltplr, bufferMinVal, bufferMaxVal); } // Handle history buffers for TWD, TWS, AWD, AWS -void HstryManager::handleHstryBufs(bool useSimuData) { +void HstryBuffers::handleHstryBufs(bool useSimuData) { static double 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 // Handle all registered history buffers - for (auto& pair : hstryBufs) { + for (auto& pair : hstryBuffers) { auto& buf = pair.second; buf->handle(useSimuData); } @@ -118,8 +119,8 @@ void HstryManager::handleHstryBufs(bool useSimuData) { awdBVal->value = calBVal->value; awdBVal->valid = true; // Find the AWD buffer and add the value. - auto it = hstryBufs.find("AWD"); - if (it != hstryBufs.end()) { + auto it = hstryBuffers.find("AWD"); + if (it != hstryBuffers.end()) { it->second->add(calBVal->value); } @@ -130,14 +131,14 @@ void HstryManager::handleHstryBufs(bool useSimuData) { } } -RingBuffer* HstryManager::getBuffer(const String& name) { - auto it = hstryBufs.find(name); - if (it != hstryBufs.end()) { - return &it->second->hstry; +RingBuffer* HstryBuffers::getBuffer(const String& name) { + auto it = hstryBuffers.find(name); + if (it != hstryBuffers.end()) { + return &it->second->hstryBuf; } return nullptr; } -// --- Class HstryBuf --------------- +// --- End Class HstryBuffers --------------- // --- Class WindUtils -------------- double WindUtils::to2PI(double a) @@ -277,23 +278,24 @@ bool WindUtils::calcTrueWind(const double* awaVal, const double* awsVal, } // Calculate true wind data and add to obp60task boat data list -bool WindUtils::addTrueWind(GwApi* api, BoatValueList* boatValues, GwLog* log) { +//bool WindUtils::addTrueWind(GwApi* api, GwLog* log) { +bool WindUtils::addTrueWind() { - GwLog* logger = log; +// GwLog* logger = log; - double awaVal, awsVal, cogVal, stwVal, sogVal, hdtVal, hdmVal, varVal; +// double awaVal, awsVal, cogVal, stwVal, sogVal, hdtVal, hdmVal, varVal; double twd, tws, twa; bool isCalculated = false; - awaVal = awaBVal->valid ? awaBVal->value : DBL_MAX; - awsVal = awsBVal->valid ? awsBVal->value : DBL_MAX; - cogVal = cogBVal->valid ? cogBVal->value : DBL_MAX; - stwVal = stwBVal->valid ? stwBVal->value : DBL_MAX; - sogVal = sogBVal->valid ? sogBVal->value : DBL_MAX; - hdtVal = hdtBVal->valid ? hdtBVal->value : DBL_MAX; - hdmVal = hdmBVal->valid ? hdmBVal->value : DBL_MAX; - varVal = varBVal->valid ? varBVal->value : DBL_MAX; - LOG_DEBUG(GwLog::DEBUG,"obp60task addTrueWind: AWA %.1f, AWS %.1f, COG %.1f, STW %.1f, SOG %.2f, HDT %.1f, HDM %.1f, VAR %.1f", awaBVal->value * RAD_TO_DEG, awsBVal->value * 3.6 / 1.852, + double awaVal = awaBVal->valid ? awaBVal->value : DBL_MAX; + double awsVal = awsBVal->valid ? awsBVal->value : DBL_MAX; + double cogVal = cogBVal->valid ? cogBVal->value : DBL_MAX; + double stwVal = stwBVal->valid ? stwBVal->value : DBL_MAX; + double sogVal = sogBVal->valid ? sogBVal->value : DBL_MAX; + double hdtVal = hdtBVal->valid ? hdtBVal->value : DBL_MAX; + double hdmVal = hdmBVal->valid ? hdmBVal->value : DBL_MAX; + double varVal = varBVal->valid ? varBVal->value : DBL_MAX; + LOG_DEBUG(GwLog::DEBUG,"WindUtils:addTrueWind: AWA %.1f, AWS %.1f, COG %.1f, STW %.1f, SOG %.2f, HDT %.1f, HDM %.1f, VAR %.1f", awaBVal->value * RAD_TO_DEG, awsBVal->value * 3.6 / 1.852, cogBVal->value * RAD_TO_DEG, stwBVal->value * 3.6 / 1.852, sogBVal->value * 3.6 / 1.852, hdtBVal->value * RAD_TO_DEG, hdmBVal->value * RAD_TO_DEG, varBVal->value * RAD_TO_DEG); isCalculated = calcTrueWind(&awaVal, &awsVal, &cogVal, &stwVal, &sogVal, &hdtVal, &hdmVal, &varVal, &twd, &tws, &twa); @@ -312,9 +314,9 @@ bool WindUtils::addTrueWind(GwApi* api, BoatValueList* boatValues, GwLog* log) { twaBVal->valid = true; } } - LOG_DEBUG(GwLog::DEBUG,"obp60task addTrueWind: isCalculated %d, TWD %.1f, TWA %.1f, TWS %.1f", isCalculated, twdBVal->value * RAD_TO_DEG, + LOG_DEBUG(GwLog::DEBUG,"WindUtils:addTrueWind: isCalculated %d, TWD %.1f, TWA %.1f, TWS %.1f", isCalculated, twdBVal->value * RAD_TO_DEG, twaBVal->value * RAD_TO_DEG, twsBVal->value * 3.6 / 1.852); return isCalculated; } -// --- Class WindUtils -------------- +// --- End Class WindUtils -------------- diff --git a/lib/obp60task/OBPDataOperations.h b/lib/obp60task/OBPDataOperations.h index de33156..2eaccba 100644 --- a/lib/obp60task/OBPDataOperations.h +++ b/lib/obp60task/OBPDataOperations.h @@ -2,36 +2,66 @@ #pragma once #include "OBPRingBuffer.h" #include "obp60task.h" -#include -#include #include +#include +#include +#include class HstryBuf { private: - GwLog *logger; - RingBuffer hstry; // Circular buffer to store history values + RingBuffer hstryBuf; // Circular buffer to store history values String boatDataName; double hstryMin; double hstryMax; GwApi::BoatValue *boatValue; + GwLog *logger; - friend class HstryManager; - void handleHistory(bool useSimuData); + friend class HstryBuffers; public: - HstryBuf(const String& name, int size, GwLog* log, BoatValueList* boatValues); + HstryBuf(const String& name, int size, BoatValueList* boatValues, GwLog* log); void init(const String& format, int updFreq, int mltplr, double minVal, double maxVal); void add(double value); void handle(bool useSimuData); }; -class HstryManager { +class HstryBuffers { private: - std::map> hstryBufs; - // boat values for true wind calculation - GwApi::BoatValue *awaBVal, *hdtBVal, *hdmBVal, *varBVal, *cogBVal, *sogBVal, *awdBVal; + std::map> hstryBuffers; + int size; // size of all history buffers + BoatValueList* boatValueList; + GwLog* logger; + GwApi::BoatValue *awaBVal, *hdtBVal, *hdmBVal, *varBVal, *cogBVal, *sogBVal, *awdBVal; // boat values for true wind calculation + + struct HistoryParams { + int hstryUpdFreq; + int mltplr; + double bufferMinVal; + double bufferMaxVal; + String format; + }; + + // Define buffer parameters for each boat data type + std::map bufferParams = { + {"AWA", {1000, 10000, -M_PI, M_PI, "formatWind"}}, + {"AWD", {1000, 10000, 0.0, M_TWOPI, "formatCourse"}}, + {"AWS", {1000, 1000, 0.0, 65.0, "formatKnots"}}, + {"DBS", {1000, 100, 0.0, 650, "formatDepth"}}, + {"DBT", {1000, 100, 0.0, 650, "formatDepth"}}, + {"DPT", {1000, 100, 0.0, 650, "formatDepth"}}, + {"HDT", {1000, 10000, 0.0, M_TWOPI, "formatCourse"}}, + {"HDM", {1000, 10000, 0.0, M_TWOPI, "formatCourse"}}, + {"TWA", {1000, 10000, -M_PI, M_PI, "formatWind"}}, + {"TWD", {1000, 10000, 0.0, M_TWOPI, "formatCourse"}}, + {"TWS", {1000, 1000, 0.0, 65.0, "formatKnots"}}, + {"SOG", {1000, 1000, 0.0, 65.0, "formatKnots"}}, + {"STW", {1000, 1000, 0.0, 65.0, "formatKnots"}}, + {"WTemp", {1000, 100, 0.0, 650.0, "kelvinToC"}} + }; + public: - HstryManager(int size, GwLog* log, BoatValueList* boatValues); + HstryBuffers(int size, BoatValueList* boatValues, GwLog* log); + void addBuffer(const String& name); void handleHstryBufs(bool useSimuData); RingBuffer* getBuffer(const String& name); }; @@ -41,9 +71,11 @@ private: GwApi::BoatValue *twdBVal, *twsBVal, *twaBVal; GwApi::BoatValue *awaBVal, *awsBVal, *cogBVal, *stwBVal, *sogBVal, *hdtBVal, *hdmBVal, *varBVal; static constexpr double DBL_MAX = std::numeric_limits::max(); + GwLog* logger; public: - WindUtils(BoatValueList* boatValues){ + WindUtils(BoatValueList* boatValues, GwLog* log) + : logger(log) { twdBVal = boatValues->findValueOrCreate("TWD"); twsBVal = boatValues->findValueOrCreate("TWS"); twaBVal = boatValues->findValueOrCreate("TWA"); @@ -73,5 +105,6 @@ public: bool calcTrueWind(const double* awaVal, const double* awsVal, const double* cogVal, const double* stwVal, const double* sogVal, const double* hdtVal, const double* hdmVal, const double* varVal, double* twdVal, double* twsVal, double* twaVal); - bool addTrueWind(GwApi* api, BoatValueList* boatValues, GwLog *log); +// bool addTrueWind(GwApi* api, BoatValueList* boatValues, GwLog *log); + bool addTrueWind(); }; \ No newline at end of file diff --git a/lib/obp60task/PageWindPlot.cpp b/lib/obp60task/PageWindPlot.cpp index 1ce55b9..10d0d86 100644 --- a/lib/obp60task/PageWindPlot.cpp +++ b/lib/obp60task/PageWindPlot.cpp @@ -125,8 +125,10 @@ public: 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 RingBuffer* twdHstry; // Wind direction data buffer for TWD + static RingBuffer* twsHstry; // Wind speed data buffer for TWS + static RingBuffer* awdHstry; // Wind direction data buffer for AWD + static RingBuffer* awsHstry; // Wind speed data buffer for AWS // Separate chart objects for true wind and apparent wind static std::unique_ptr> twdFlChart, awdFlChart; // chart object for wind direction chart, full size @@ -139,8 +141,8 @@ public: 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 */ + static GwApi::BoatValue* wdBVal; // BoatValue for wind direction + static GwApi::BoatValue* wsBVal; // BoatValue for wind speed 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 @@ -163,24 +165,20 @@ 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.hstryManager->getBuffer("TWD"); - auto* twsHstry = pageData.hstryManager->getBuffer("TWS"); + LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: Creating true wind charts"); + twdHstry = pageData.hstryBuffers->getBuffer("TWD"); + twsHstry = pageData.hstryBuffers->getBuffer("TWS"); twdFlChart = std::unique_ptr>(new Chart(*twdHstry, 'V', 0, dfltRngWd, *commonData, useSimuData)); twsFlChart = std::unique_ptr>(new Chart(*twsHstry, 'H', 0, dfltRngWs, *commonData, useSimuData)); twdHfChart = std::unique_ptr>(new Chart(*twdHstry, 'V', 1, dfltRngWd, *commonData, useSimuData)); twsHfChart = std::unique_ptr>(new Chart(*twsHstry, 'V', 2, dfltRngWs, *commonData, useSimuData)); - // twdHfChart = std::unique_ptr>(new Chart(*twdHstry, 'H', 1, dfltRngWd, *commonData, useSimuData)); - // twsHfChart = std::unique_ptr>(new Chart(*twsHstry, 'H', 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.hstryManager->getBuffer("AWD"); - auto* awsHstry = pageData.hstryManager->getBuffer("AWS"); + awdHstry = pageData.hstryBuffers->getBuffer("AWD"); + awsHstry = pageData.hstryBuffers->getBuffer("AWS"); awdFlChart = std::unique_ptr>(new Chart(*awdHstry, 'V', 0, dfltRngWd, *commonData, useSimuData)); awsFlChart = std::unique_ptr>(new Chart(*awsHstry, 'H', 0, dfltRngWs, *commonData, useSimuData)); @@ -190,24 +188,25 @@ public: // Switch active charts based on showTruW if (showTruW) { - wdHstry = pageData.hstryManager->getBuffer("TWD"); - wsHstry = pageData.hstryManager->getBuffer("TWS"); + wdHstry = twdHstry; + wsHstry = twsHstry; wdFlChart = twdFlChart.get(); wsFlChart = twsFlChart.get(); wdHfChart = twdHfChart.get(); wsHfChart = twsHfChart.get(); + wdBVal = bvalue[0]; + wsBVal = bvalue[1]; } else { - wdHstry = pageData.hstryManager->getBuffer("AWD"); - wsHstry = pageData.hstryManager->getBuffer("AWS"); + wdHstry = awdHstry; + wsHstry = awsHstry; wdFlChart = awdFlChart.get(); wsFlChart = awsFlChart.get(); wdHfChart = awdHfChart.get(); wsHfChart = awsHfChart.get(); + wdBVal = bvalue[2]; + wsBVal = bvalue[3]; } - wdHstry->getMetaData(wdName, wdFormat); - wsHstry->getMetaData(wsName, wsFormat); - oldShowTruW = showTruW; } @@ -219,27 +218,17 @@ public: getdisplay().setTextColor(commonData->fgcolor); if (chrtMode == 'D') { - wdBVal->value = wdHstry->getLast(); - wdBVal->valid = wdBVal->value != wdHstry->getMaxVal(); - wdFlChart->showChrt(dataIntv, *bvalue[0]); + wdFlChart->showChrt(dataIntv, *wdBVal); } else if (chrtMode == 'S') { - wsBVal->value = wsHstry->getLast(); - wsBVal->valid = wsBVal->value != wsHstry->getMaxVal(); - wsFlChart->showChrt(dataIntv, *bvalue[1]); + wsFlChart->showChrt(dataIntv, *wsBVal); } else if (chrtMode == 'B') { - 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); - wdHfChart->showChrt(dataIntv, *bvalue[0]); - wsHfChart->showChrt(dataIntv, *bvalue[1]); + wdHfChart->showChrt(dataIntv, *wdBVal); + wsHfChart->showChrt(dataIntv, *wsBVal); } - LOG_DEBUG(GwLog::LOG, "PageWindPlot: page time %ldms", millis() - pageTime); + LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: page time %ldms", millis() - pageTime); return PAGE_UPDATE; } }; diff --git a/lib/obp60task/Pagedata.h b/lib/obp60task/Pagedata.h index f40d187..a12119f 100644 --- a/lib/obp60task/Pagedata.h +++ b/lib/obp60task/Pagedata.h @@ -16,7 +16,7 @@ typedef struct{ uint8_t pageNumber; // page number in sequence of visible pages //the values will always contain the user defined values first ValueList values; - HstryManager* hstryManager; + HstryBuffers* hstryBuffers; // list of all boat history buffers } PageData; // Sensor data structure (only for extended sensors, not for NMEA bus sensors) diff --git a/lib/obp60task/obp60task.cpp b/lib/obp60task/obp60task.cpp index 0626a90..bddb425 100644 --- a/lib/obp60task/obp60task.cpp +++ b/lib/obp60task/obp60task.cpp @@ -433,8 +433,8 @@ void OBP60Task(GwApi *api){ int lastPage=pageNumber; BoatValueList boatValues; //all the boat values for the api query - HstryManager hstryManager(1920, logger, &boatValues); // Create and manage history buffers - WindUtils trueWind(&boatValues); // Create helper object for true wind calculation + HstryBuffers hstryBufList(1920, &boatValues, logger); // Create empty list of boat data history buffers + WindUtils trueWind(&boatValues, logger); // Create helper object for true wind calculation //commonData.distanceformat=config->getString(xxx); //add all necessary data to common data @@ -477,9 +477,18 @@ void OBP60Task(GwApi *api){ LOG_DEBUG(GwLog::DEBUG,"added fixed value %s to page %d",value->getName().c_str(),i); pages[i].parameters.values.push_back(value); } - // Add history manager to page parameters - pages[i].parameters.hstryManager = &hstryManager; + + // Read the specified boat data type of relevant pages and create a history buffer for each type + if (pages[i].parameters.pageName == "OneValue" || pages[i].parameters.pageName == "TwoValues" || pages[i].parameters.pageName == "WindPlot") { + for (auto pVal : pages[i].parameters.values) { + hstryBufList.addBuffer(pVal->getName()); + } + } + // Add list of history buffers to page parameters + pages[i].parameters.hstryBuffers = &hstryBufList; + } + // add out of band system page (always available) Page *syspage = allPages.pages[0]->creator(commonData); @@ -487,7 +496,6 @@ void OBP60Task(GwApi *api){ bool calcTrueWnds = api->getConfig()->getBool(api->getConfig()->calcTrueWnds, false); bool useSimuData = api->getConfig()->getBool(api->getConfig()->useSimuData, false); - // Initialize history buffer for certain boat data // Read all calibration data settings from config calibrationData.readConfig(config, logger); @@ -804,10 +812,10 @@ void OBP60Task(GwApi *api){ api->getStatus(commonData.status); if (calcTrueWnds) { - trueWind.addTrueWind(api, &boatValues, logger); + trueWind.addTrueWind(); } // Handle history buffers for certain boat data for windplot page and other usage - hstryManager.handleHstryBufs(useSimuData); + hstryBufList.handleHstryBufs(useSimuData); // Clear display // getdisplay().fillRect(0, 0, getdisplay().width(), getdisplay().height(), commonData.bgcolor); From 362338a7ddbea47544d25dd8933ec1afc4a0b958 Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Sun, 21 Dec 2025 13:10:44 +0100 Subject: [PATCH 04/13] Optimized PageWindPlot code; added WindUtils AWD/TWD calculation from AWA/TWA if available --- lib/obp60task/OBPDataOperations.cpp | 208 ++++++++++++++++------------ lib/obp60task/OBPDataOperations.h | 19 +-- lib/obp60task/PageWindPlot.cpp | 141 +++++++++---------- lib/obp60task/obp60task.cpp | 2 +- 4 files changed, 200 insertions(+), 170 deletions(-) diff --git a/lib/obp60task/OBPDataOperations.cpp b/lib/obp60task/OBPDataOperations.cpp index 898ba5a..769ddbf 100644 --- a/lib/obp60task/OBPDataOperations.cpp +++ b/lib/obp60task/OBPDataOperations.cpp @@ -1,15 +1,18 @@ #include "OBPDataOperations.h" -#include "BoatDataCalibration.h" // Functions lib for data instance calibration +#include "BoatDataCalibration.h" // Functions lib for data instance calibration #include // --- Class HstryBuf --------------- HstryBuf::HstryBuf(const String& name, int size, BoatValueList* boatValues, GwLog* log) - : logger(log), boatDataName(name) { + : logger(log) + , boatDataName(name) +{ hstryBuf.resize(size); boatValue = boatValues->findValueOrCreate(name); } -void HstryBuf::init(const String& format, int updFreq, int mltplr, double minVal, double maxVal) { +void HstryBuf::init(const String& format, int updFreq, int mltplr, double minVal, double maxVal) +{ hstryBuf.setMetaData(boatDataName, format, updFreq, mltplr, minVal, maxVal); hstryMin = minVal; hstryMax = maxVal; @@ -19,26 +22,31 @@ void HstryBuf::init(const String& format, int updFreq, int mltplr, double minVal } } -void HstryBuf::add(double value) { +void HstryBuf::add(double value) +{ if (value >= hstryMin && value <= hstryMax) { hstryBuf.add(value); } } -void HstryBuf::handle(bool useSimuData) { - GwApi::BoatValue *calBVal; +void HstryBuf::handle(bool useSimuData) +{ + GwApi::BoatValue* calBVal; - if (boatValue->valid) { // add calibrated boat value to history buffer + if (boatValue->valid) { + // Calibrate boat value before adding it to history buffer calBVal = new GwApi::BoatValue(boatDataName.c_str()); calBVal->setFormat(boatValue->getFormat()); calBVal->value = boatValue->value; calBVal->valid = boatValue->valid; calibrationData.calibrateInstance(calBVal, logger); add(calBVal->value); + delete calBVal; calBVal = nullptr; + } else if (useSimuData) { // add simulated value to history buffer - double simValue = hstryBuf.getLast(); + double simValue = hstryBuf.getLast(); if (boatDataName == "TWD" || boatDataName == "AWD") { simValue += static_cast(random(-349, 349) / 1000.0); simValue = WindUtils::to2PI(simValue); @@ -53,10 +61,13 @@ void HstryBuf::handle(bool useSimuData) { // --- Class HstryBuffers --------------- HstryBuffers::HstryBuffers(int size, BoatValueList* boatValues, GwLog* log) - : size(size), boatValueList(boatValues), logger(log) { + : size(size) + , boatValueList(boatValues) + , logger(log) +{ // collect boat values for true wind calculation - // should have been already all created at true wind object initialization + // should all have been already created at true wind object initialization // potentially to be moved to history buffer handling awaBVal = boatValueList->findValueOrCreate("AWA"); hdtBVal = boatValueList->findValueOrCreate("HDT"); @@ -67,9 +78,9 @@ HstryBuffers::HstryBuffers(int size, BoatValueList* boatValues, GwLog* log) awdBVal = boatValueList->findValueOrCreate("AWD"); } -void HstryBuffers::addBuffer(const String& name) { - // Create history buffer for boat data type - +// Create history buffer for boat data type +void HstryBuffers::addBuffer(const String& name) +{ if (HstryBuffers::getBuffer(name) != nullptr) { // buffer for this data type already exists return; } @@ -77,61 +88,30 @@ void HstryBuffers::addBuffer(const String& name) { hstryBuffers[name] = std::unique_ptr(new HstryBuf(name, size, boatValueList, logger)); // Initialize metadata for buffer - // String valueFormat = boatValueList->findValueOrCreate(name)->getFormat().c_str(); // Unfortunately, format is not yet available during system initialization String valueFormat = bufferParams[name].format; // Data format of boat data type + // String valueFormat = boatValueList->findValueOrCreate(name)->getFormat().c_str(); // Unfortunately, format is not yet available during system initialization int hstryUpdFreq = bufferParams[name].hstryUpdFreq; // Update frequency for history buffers in ms int mltplr = bufferParams[name].mltplr; // default multiplier which transforms original value into buffer type format double bufferMinVal = bufferParams[name].bufferMinVal; // Min value for this history buffer double bufferMaxVal = bufferParams[name].bufferMaxVal; // Max value for this history buffer hstryBuffers[name]->init(valueFormat, hstryUpdFreq, mltplr, bufferMinVal, bufferMaxVal); - LOG_DEBUG(GwLog::DEBUG,"HstryBuffers-new buffer added: name: %s, format: %s, multiplier: %d, min value: %.2f, max value: %.2f", name, valueFormat, mltplr, bufferMinVal, bufferMaxVal); + LOG_DEBUG(GwLog::DEBUG, "HstryBuffers-new buffer added: name: %s, format: %s, multiplier: %d, min value: %.2f, max value: %.2f", name, valueFormat, mltplr, bufferMinVal, bufferMaxVal); } -// Handle history buffers for TWD, TWS, AWD, AWS -void HstryBuffers::handleHstryBufs(bool useSimuData) { - - static double 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 +// Handle history buffers +void HstryBuffers::handleHstryBufs(bool useSimuData) +{ // Handle all registered history buffers for (auto& pair : hstryBuffers) { auto& buf = pair.second; buf->handle(useSimuData); } - - // Special handling for AWD which is calculated - if (awaBVal->valid) { - if (hdtBVal->valid) { - hdt = hdtBVal->value; // Use HDT if available - } else { - hdt = WindUtils::calcHDT(&hdmBVal->value, &varBVal->value, &cogBVal->value, &sogBVal->value); - } - double awd; - awd = awaBVal->value + hdt; - awd = WindUtils::to2PI(awd); - calBVal = new GwApi::BoatValue("AWD"); // temporary solution for calibration of history buffer values - calBVal->value = awd; - calBVal->setFormat(awdBVal->getFormat()); - calBVal->valid = true; - // We don't have a logger here, so we pass nullptr. This should be improved. - calibrationData.calibrateInstance(calBVal, nullptr); // Check if boat data value is to be calibrated - awdBVal->value = calBVal->value; - awdBVal->valid = true; - // Find the AWD buffer and add the value. - auto it = hstryBuffers.find("AWD"); - if (it != hstryBuffers.end()) { - it->second->add(calBVal->value); - } - - delete calBVal; - calBVal = nullptr; - } else if (useSimuData) { - // Simulation for AWD is handled inside HstryBuf::handle - } } -RingBuffer* HstryBuffers::getBuffer(const String& name) { +RingBuffer* HstryBuffers::getBuffer(const String& name) +{ auto it = hstryBuffers.find(name); if (it != hstryBuffers.end()) { return &it->second->hstryBuf; @@ -204,14 +184,14 @@ void WindUtils::addPolar(const double* phi1, const double* r1, void WindUtils::calcTwdSA(const double* AWA, const double* AWS, const double* CTW, const double* STW, const double* HDT, - double* TWD, double* TWS, double* TWA) + double* TWD, double* TWS, double* TWA, double* AWD) { - double awd = *AWA + *HDT; - awd = to2PI(awd); + *AWD = *AWA + *HDT; + *AWD = to2PI(*AWD); double stw = -*STW; - addPolar(&awd, AWS, CTW, &stw, TWD, TWS); + addPolar(AWD, AWS, CTW, &stw, TWD, TWS); - // Normalize TWD and TWA to 0-360° + // Normalize TWD and TWA to 0-360°/2PI *TWD = to2PI(*TWD); *TWA = toPI(*TWD - *HDT); } @@ -233,12 +213,12 @@ double WindUtils::calcHDT(const double* hdmVal, const double* varVal, const doub return hdt; } -bool WindUtils::calcTrueWind(const double* awaVal, const double* awsVal, +bool WindUtils::calcWinds(const double* awaVal, const double* awsVal, const double* cogVal, const double* stwVal, const double* sogVal, const double* hdtVal, - const double* hdmVal, const double* varVal, double* twdVal, double* twsVal, double* twaVal) + const double* hdmVal, const double* varVal, double* twdVal, double* twsVal, double* twaVal, double* awdVal) { double stw, hdt, ctw; - double twd, tws, twa; + double twd, tws, twa, awd; double minSogVal = 0.1; // SOG below this value (m/s) is assumed to be data noise from GPS sensor if (*hdtVal != DBL_MAX) { @@ -262,31 +242,68 @@ 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)); + // LOG_DEBUG(GwLog::DEBUG, "WindUtils:calcWinds: HDT: %.1f, CTW %.1f, STW %.1f", hdt, ctw, stw); + if ((*awaVal == DBL_MAX) || (*awsVal == DBL_MAX)) { // Cannot calculate true wind without valid AWA, AWS; other checks are done earlier return false; } else { - calcTwdSA(awaVal, awsVal, &ctw, &stw, &hdt, &twd, &tws, &twa); + calcTwdSA(awaVal, awsVal, &ctw, &stw, &hdt, &twd, &tws, &twa, &awd); *twdVal = twd; *twsVal = tws; *twaVal = twa; + *awdVal = awd; return true; } } -// Calculate true wind data and add to obp60task boat data list -//bool WindUtils::addTrueWind(GwApi* api, GwLog* log) { -bool WindUtils::addTrueWind() { - -// GwLog* logger = log; - -// double awaVal, awsVal, cogVal, stwVal, sogVal, hdtVal, hdmVal, varVal; - double twd, tws, twa; +/* // we don't need this -> AWD is calculated in calcTwdSA +// Calc AWD from existing AWA and HDT/HDM +bool WindUtils::calcATWD(const double* waVal, const double* hdtVal, const double* hdmVal, const double* varVal, const double* cogVal, const double* sogVal, double* wdVal) +{ + double wd, hdt; + GwApi::BoatValue* calBVal; // temp variable just for data calibration bool isCalculated = false; + if (*waVal == DBL_MAX) { + return false; + } + + if (*hdtVal != DBL_MAX) { + hdt = *hdtVal; // Use HDT if available + } else { + hdt = calcHDT(hdmVal, varVal, cogVal, sogVal); + } + + if (hdt != DBL_MAX) { + wd = *waVal + hdt; + wd = to2PI(wd); + isCalculated = true; + } + + // Calibrate AWD/TWD if required + calBVal = new GwApi::BoatValue("AWD"); // temporary solution for calibration of history buffer values + calBVal->value = wd; + calBVal->setFormat(awdBVal->getFormat()); + calBVal->valid = true; + calibrationData.calibrateInstance(calBVal, logger); // Check if boat data value is to be calibrated + *wdVal = calBVal->value; + + delete calBVal; + calBVal = nullptr; + + return isCalculated; +} */ + +// Calculate true wind data and add to obp60task boat data list +bool WindUtils::addWinds() +{ + double twd, tws, twa, awd, hdt; + bool twCalculated = false; + bool awdCalculated = false; + double awaVal = awaBVal->valid ? awaBVal->value : DBL_MAX; double awsVal = awsBVal->valid ? awsBVal->value : DBL_MAX; double cogVal = cogBVal->valid ? cogBVal->value : DBL_MAX; @@ -295,28 +312,49 @@ bool WindUtils::addTrueWind() { double hdtVal = hdtBVal->valid ? hdtBVal->value : DBL_MAX; double hdmVal = hdmBVal->valid ? hdmBVal->value : DBL_MAX; double varVal = varBVal->valid ? varBVal->value : DBL_MAX; - LOG_DEBUG(GwLog::DEBUG,"WindUtils:addTrueWind: AWA %.1f, AWS %.1f, COG %.1f, STW %.1f, SOG %.2f, HDT %.1f, HDM %.1f, VAR %.1f", awaBVal->value * RAD_TO_DEG, awsBVal->value * 3.6 / 1.852, - cogBVal->value * RAD_TO_DEG, stwBVal->value * 3.6 / 1.852, sogBVal->value * 3.6 / 1.852, hdtBVal->value * RAD_TO_DEG, hdmBVal->value * RAD_TO_DEG, varBVal->value * RAD_TO_DEG); + LOG_DEBUG(GwLog::DEBUG, "WindUtils:addWinds: AWA %.1f, AWS %.1f, COG %.1f, STW %.1f, SOG %.2f, HDT %.1f, HDM %.1f, VAR %.1f", awaBVal->value * RAD_TO_DEG, awsBVal->value * 3.6 / 1.852, + cogBVal->value * RAD_TO_DEG, stwBVal->value * 3.6 / 1.852, sogBVal->value * 3.6 / 1.852, hdtBVal->value * RAD_TO_DEG, hdmBVal->value * RAD_TO_DEG, varBVal->value * RAD_TO_DEG); - isCalculated = calcTrueWind(&awaVal, &awsVal, &cogVal, &stwVal, &sogVal, &hdtVal, &hdmVal, &varVal, &twd, &tws, &twa); - - if (isCalculated) { // Replace values only, if successfully calculated and not already available + // Check if TWD can be calculated from TWA and HDT/HDM + if (twaBVal->valid) { if (!twdBVal->valid) { + if (hdtVal != DBL_MAX) { + hdt = hdtVal; // Use HDT if available + } else { + hdt = calcHDT(&hdmVal, &varVal, &cogVal, &sogVal); + } + twd = twaBVal->value + hdt; + twd = to2PI(twd); twdBVal->value = twd; twdBVal->valid = true; } - if (!twsBVal->valid) { - twsBVal->value = tws; - twsBVal->valid = true; - } - if (!twaBVal->valid) { - twaBVal->value = twa; - twaBVal->valid = true; + + } else { + // Calculate true winds and AWD; if true winds exist, use at least AWD calculation + twCalculated = calcWinds(&awaVal, &awsVal, &cogVal, &stwVal, &sogVal, &hdtVal, &hdmVal, &varVal, &twd, &tws, &twa, &awd); + + if (twCalculated) { // Replace values only, if successfully calculated and not already available + if (!twdBVal->valid) { + twdBVal->value = twd; + twdBVal->valid = true; + } + if (!twsBVal->valid) { + twsBVal->value = tws; + twsBVal->valid = true; + } + if (!twaBVal->valid) { + twaBVal->value = twa; + twaBVal->valid = true; + } + if (!awdBVal->valid) { + awdBVal->value = awd; + awdBVal->valid = true; + } } } - LOG_DEBUG(GwLog::DEBUG,"WindUtils:addTrueWind: isCalculated %d, TWD %.1f, TWA %.1f, TWS %.1f", isCalculated, twdBVal->value * RAD_TO_DEG, - twaBVal->value * RAD_TO_DEG, twsBVal->value * 3.6 / 1.852); + LOG_DEBUG(GwLog::DEBUG, "WindUtils:addWinds: twCalculated %d, TWD %.1f, TWA %.1f, TWS %.2f kn, AWD: %.1f", twCalculated, twdBVal->value * RAD_TO_DEG, + twaBVal->value * RAD_TO_DEG, twsBVal->value * 3.6 / 1.852, awdBVal->value * RAD_TO_DEG); - return isCalculated; + return twCalculated; } -// --- End Class WindUtils -------------- +// --- End Class WindUtils -------------- \ No newline at end of file diff --git a/lib/obp60task/OBPDataOperations.h b/lib/obp60task/OBPDataOperations.h index 2eaccba..0a4faaf 100644 --- a/lib/obp60task/OBPDataOperations.h +++ b/lib/obp60task/OBPDataOperations.h @@ -68,19 +68,20 @@ public: class WindUtils { private: - GwApi::BoatValue *twdBVal, *twsBVal, *twaBVal; - GwApi::BoatValue *awaBVal, *awsBVal, *cogBVal, *stwBVal, *sogBVal, *hdtBVal, *hdmBVal, *varBVal; + GwApi::BoatValue *twaBVal, *twsBVal, *twdBVal; + GwApi::BoatValue *awaBVal, *awsBVal, *awdBVal, *cogBVal, *stwBVal, *sogBVal, *hdtBVal, *hdmBVal, *varBVal; static constexpr double DBL_MAX = std::numeric_limits::max(); GwLog* logger; public: WindUtils(BoatValueList* boatValues, GwLog* log) : logger(log) { - twdBVal = boatValues->findValueOrCreate("TWD"); - twsBVal = boatValues->findValueOrCreate("TWS"); twaBVal = boatValues->findValueOrCreate("TWA"); + twsBVal = boatValues->findValueOrCreate("TWS"); + twdBVal = boatValues->findValueOrCreate("TWD"); awaBVal = boatValues->findValueOrCreate("AWA"); awsBVal = boatValues->findValueOrCreate("AWS"); + awdBVal = boatValues->findValueOrCreate("AWD"); cogBVal = boatValues->findValueOrCreate("COG"); stwBVal = boatValues->findValueOrCreate("STW"); sogBVal = boatValues->findValueOrCreate("SOG"); @@ -100,11 +101,11 @@ public: double* phi, double* r); void calcTwdSA(const double* AWA, const double* AWS, const double* CTW, const double* STW, const double* HDT, - double* TWD, double* TWS, double* TWA); + double* TWD, double* TWS, double* TWA, double* AWD); static double calcHDT(const double* hdmVal, const double* varVal, const double* cogVal, const double* sogVal); - bool calcTrueWind(const double* awaVal, const double* awsVal, + bool calcWinds(const double* awaVal, const double* awsVal, const double* cogVal, const double* stwVal, const double* sogVal, const double* hdtVal, - const double* hdmVal, const double* varVal, double* twdVal, double* twsVal, double* twaVal); -// bool addTrueWind(GwApi* api, BoatValueList* boatValues, GwLog *log); - bool addTrueWind(); + const double* hdmVal, const double* varVal, double* twdVal, double* twsVal, double* twaVal, double* awdVal); + bool calcATWD(const double* waVal, const double* hdtVal, const double* hdmVal, const double* varVal, const double* cogVal, const double* sogVal, double* wdVal); + bool addWinds(); }; \ No newline at end of file diff --git a/lib/obp60task/PageWindPlot.cpp b/lib/obp60task/PageWindPlot.cpp index 10d0d86..636d8f9 100644 --- a/lib/obp60task/PageWindPlot.cpp +++ b/lib/obp60task/PageWindPlot.cpp @@ -25,6 +25,33 @@ private: String flashLED; String backlightMode; +#ifdef BOARD_OBP40S3 + String wndSrc; // Wind source true/apparent wind - preselection for OBP40 +#endif + + // Data buffers pointers (owned by HstryBuffers) + RingBuffer* twdHstry = nullptr; + RingBuffer* twsHstry = nullptr; + RingBuffer* awdHstry = nullptr; + RingBuffer* awsHstry = nullptr; + + // Chart objects + std::unique_ptr> twdFlChart, awdFlChart; // Chart object for wind direction, full size + std::unique_ptr> twsFlChart, awsFlChart; // Chart object for wind speed, full size + std::unique_ptr> twdHfChart, awdHfChart; // Chart object for wind direction, half size + std::unique_ptr> twsHfChart, awsHfChart; // Chart object for wind speed, half size + + // Active charts and values + Chart* wdFlChart = nullptr; + Chart* wsFlChart = nullptr; + Chart* wdHfChart = nullptr; + Chart* wsHfChart = nullptr; + GwApi::BoatValue* wdBVal = nullptr; + GwApi::BoatValue* wsBVal = nullptr; + + const double dfltRngWd = 60.0 * DEG_TO_RAD; // default range for course chart from min to max value in RAD + const double dfltRngWs = 7.5; // default range for wind speed chart from min to max value in m/s + public: PageWindPlot(CommonData& common) { @@ -32,11 +59,16 @@ public: logger = commonData->logger; LOG_DEBUG(GwLog::LOG, "Instantiate PageWindPlot"); + width = getdisplay().width(); // Screen width + height = getdisplay().height(); // Screen height + // Get config data useSimuData = common.config->getBool(common.config->useSimuData); // holdValues = common.config->getBool(common.config->holdvalues); flashLED = common.config->getString(common.config->flashLED); backlightMode = common.config->getString(common.config->backlight); + + oldShowTruW = !showTruW; // makes wind source being initialized at initial page call } virtual void setupKeys() @@ -102,111 +134,70 @@ public: virtual void displayNew(PageData& pageData) { +#ifdef BOARD_OBP60S3 + // Clear optical warning + if (flashLED == "Limit Violation") { + setBlinkingLED(false); + setFlashLED(false); + } +#endif #ifdef BOARD_OBP40S3 - 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 apparent wind } - LOG_DEBUG(GwLog::LOG, "New PageWindPlot; wind source=%s", wndSrc); + oldShowTruW = !showTruW; // Force chart update in displayPage #endif - oldShowTruW = !showTruW; // makes wind source being initialized at initial page call - width = getdisplay().width(); // Screen width - height = getdisplay().height(); // Screen height + if (!twdFlChart) { // Create true wind charts if they don't exist + LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: Creating true wind charts"); + twdHstry = pageData.hstryBuffers->getBuffer("TWD"); + twsHstry = pageData.hstryBuffers->getBuffer("TWS"); + + twdFlChart.reset(new Chart(*twdHstry, 'V', 0, dfltRngWd, *commonData, useSimuData)); + twsFlChart.reset(new Chart(*twsHstry, 'H', 0, dfltRngWs, *commonData, useSimuData)); + twdHfChart.reset(new Chart(*twdHstry, 'V', 1, dfltRngWd, *commonData, useSimuData)); + twsHfChart.reset(new Chart(*twsHstry, 'V', 2, dfltRngWs, *commonData, useSimuData)); + } + + if (!awdFlChart) { // Create apparent wind charts if they don't exist + LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: Creating apparent wind charts"); + awdHstry = pageData.hstryBuffers->getBuffer("AWD"); + awsHstry = pageData.hstryBuffers->getBuffer("AWS"); + + awdFlChart.reset(new Chart(*awdHstry, 'V', 0, dfltRngWd, *commonData, useSimuData)); + awsFlChart.reset(new Chart(*awsHstry, 'H', 0, dfltRngWs, *commonData, useSimuData)); + awdHfChart.reset(new Chart(*awdHstry, 'V', 1, dfltRngWd, *commonData, useSimuData)); + awsHfChart.reset(new Chart(*awsHstry, 'V', 2, dfltRngWs, *commonData, useSimuData)); + } } int displayPage(PageData& pageData) { - GwConfigHandler* config = commonData->config; - - static RingBuffer* wdHstry; // Wind direction data buffer - static RingBuffer* wsHstry; // Wind speed data buffer - static RingBuffer* twdHstry; // Wind direction data buffer for TWD - static RingBuffer* twsHstry; // Wind speed data buffer for TWS - static RingBuffer* awdHstry; // Wind direction data buffer for AWD - static RingBuffer* awsHstry; // Wind speed data buffer for AWS - - // 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; // BoatValue for wind direction - static GwApi::BoatValue* wsBVal; // BoatValue for wind speed - 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 - - const int numBoatData = 4; - GwApi::BoatValue* bvalue[numBoatData]; // current boat data values - LOG_DEBUG(GwLog::LOG, "Display PageWindPlot"); ulong pageTime = millis(); - // read boat data values - for (int i = 0; i < numBoatData; i++) { - bvalue[i] = pageData.values[i]; - } - - // Optical warning by limit violation (unused) - if (String(flashLED) == "Limit Violation") { - setBlinkingLED(false); - setFlashLED(false); - } - if (showTruW != oldShowTruW) { - if (!twdFlChart) { // Create true wind charts if they don't exist - LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: Creating true wind charts"); - twdHstry = pageData.hstryBuffers->getBuffer("TWD"); - twsHstry = pageData.hstryBuffers->getBuffer("TWS"); - twdFlChart = std::unique_ptr>(new Chart(*twdHstry, 'V', 0, dfltRngWd, *commonData, useSimuData)); - twsFlChart = std::unique_ptr>(new Chart(*twsHstry, 'H', 0, dfltRngWs, *commonData, useSimuData)); - twdHfChart = std::unique_ptr>(new Chart(*twdHstry, 'V', 1, dfltRngWd, *commonData, useSimuData)); - twsHfChart = std::unique_ptr>(new Chart(*twsHstry, 'V', 2, dfltRngWs, *commonData, useSimuData)); - } - - if (!awdFlChart) { // Create apparent wind charts if they don't exist - LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: Creating apparent wind charts"); - awdHstry = pageData.hstryBuffers->getBuffer("AWD"); - awsHstry = pageData.hstryBuffers->getBuffer("AWS"); - - awdFlChart = std::unique_ptr>(new Chart(*awdHstry, 'V', 0, dfltRngWd, *commonData, useSimuData)); - awsFlChart = std::unique_ptr>(new Chart(*awsHstry, 'H', 0, dfltRngWs, *commonData, useSimuData)); - awdHfChart = std::unique_ptr>(new Chart(*awdHstry, 'V', 1, dfltRngWd, *commonData, useSimuData)); - awsHfChart = std::unique_ptr>(new Chart(*awsHstry, 'V', 2, dfltRngWs, *commonData, useSimuData)); - } - // Switch active charts based on showTruW if (showTruW) { - wdHstry = twdHstry; - wsHstry = twsHstry; wdFlChart = twdFlChart.get(); wsFlChart = twsFlChart.get(); wdHfChart = twdHfChart.get(); wsHfChart = twsHfChart.get(); - wdBVal = bvalue[0]; - wsBVal = bvalue[1]; + wdBVal = pageData.values[0]; + wsBVal = pageData.values[1]; } else { - wdHstry = awdHstry; - wsHstry = awsHstry; wdFlChart = awdFlChart.get(); wsFlChart = awsFlChart.get(); wdHfChart = awdHfChart.get(); wsHfChart = awsHfChart.get(); - wdBVal = bvalue[2]; - wsBVal = bvalue[3]; + wdBVal = pageData.values[2]; + wsBVal = pageData.values[3]; } - + oldShowTruW = showTruW; } diff --git a/lib/obp60task/obp60task.cpp b/lib/obp60task/obp60task.cpp index bddb425..4689a10 100644 --- a/lib/obp60task/obp60task.cpp +++ b/lib/obp60task/obp60task.cpp @@ -812,7 +812,7 @@ void OBP60Task(GwApi *api){ api->getStatus(commonData.status); if (calcTrueWnds) { - trueWind.addTrueWind(); + trueWind.addWinds(); } // Handle history buffers for certain boat data for windplot page and other usage hstryBufList.handleHstryBufs(useSimuData); From 69754b85fd619fc36ceddea6ba73867974bb3e6b Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Tue, 23 Dec 2025 18:16:53 +0100 Subject: [PATCH 05/13] optimized chart initialization for PageWindPlot; added chart options to PageOneValue; printing of current boat value on horizontal half charts is selectable; fixed value axis direction for depth and other boat data; changed time axis labels to full numbers; changed "INTV" button label to "ZOOM" --- lib/obp60task/OBP60Extensions.cpp | 2 +- lib/obp60task/OBPDataOperations.cpp | 38 ---- lib/obp60task/OBPDataOperations.h | 18 +- lib/obp60task/OBPcharts.cpp | 206 ++++++++++--------- lib/obp60task/OBPcharts.h | 26 ++- lib/obp60task/PageOneValue.cpp | 304 ++++++++++++++++++++-------- lib/obp60task/PageWindPlot.cpp | 13 +- 7 files changed, 364 insertions(+), 243 deletions(-) diff --git a/lib/obp60task/OBP60Extensions.cpp b/lib/obp60task/OBP60Extensions.cpp index e45c90b..de153db 100644 --- a/lib/obp60task/OBP60Extensions.cpp +++ b/lib/obp60task/OBP60Extensions.cpp @@ -434,7 +434,7 @@ void drawTextRalign(int16_t x, int16_t y, String text) { int16_t x1, y1; uint16_t w, h; getdisplay().getTextBounds(text, 0, 150, &x1, &y1, &w, &h); - getdisplay().setCursor(x - w, y); + getdisplay().setCursor(x - w - 1, y); // '-1' required since some strings wrap around w/o it getdisplay().print(text); } diff --git a/lib/obp60task/OBPDataOperations.cpp b/lib/obp60task/OBPDataOperations.cpp index 769ddbf..3d81fe0 100644 --- a/lib/obp60task/OBPDataOperations.cpp +++ b/lib/obp60task/OBPDataOperations.cpp @@ -259,44 +259,6 @@ bool WindUtils::calcWinds(const double* awaVal, const double* awsVal, } } -/* // we don't need this -> AWD is calculated in calcTwdSA -// Calc AWD from existing AWA and HDT/HDM -bool WindUtils::calcATWD(const double* waVal, const double* hdtVal, const double* hdmVal, const double* varVal, const double* cogVal, const double* sogVal, double* wdVal) -{ - double wd, hdt; - GwApi::BoatValue* calBVal; // temp variable just for data calibration - bool isCalculated = false; - - if (*waVal == DBL_MAX) { - return false; - } - - if (*hdtVal != DBL_MAX) { - hdt = *hdtVal; // Use HDT if available - } else { - hdt = calcHDT(hdmVal, varVal, cogVal, sogVal); - } - - if (hdt != DBL_MAX) { - wd = *waVal + hdt; - wd = to2PI(wd); - isCalculated = true; - } - - // Calibrate AWD/TWD if required - calBVal = new GwApi::BoatValue("AWD"); // temporary solution for calibration of history buffer values - calBVal->value = wd; - calBVal->setFormat(awdBVal->getFormat()); - calBVal->valid = true; - calibrationData.calibrateInstance(calBVal, logger); // Check if boat data value is to be calibrated - *wdVal = calBVal->value; - - delete calBVal; - calBVal = nullptr; - - return isCalculated; -} */ - // Calculate true wind data and add to obp60task boat data list bool WindUtils::addWinds() { diff --git a/lib/obp60task/OBPDataOperations.h b/lib/obp60task/OBPDataOperations.h index 0a4faaf..53cae41 100644 --- a/lib/obp60task/OBPDataOperations.h +++ b/lib/obp60task/OBPDataOperations.h @@ -3,9 +3,6 @@ #include "OBPRingBuffer.h" #include "obp60task.h" #include -#include -#include -#include class HstryBuf { private: @@ -46,16 +43,18 @@ private: {"AWA", {1000, 10000, -M_PI, M_PI, "formatWind"}}, {"AWD", {1000, 10000, 0.0, M_TWOPI, "formatCourse"}}, {"AWS", {1000, 1000, 0.0, 65.0, "formatKnots"}}, - {"DBS", {1000, 100, 0.0, 650, "formatDepth"}}, - {"DBT", {1000, 100, 0.0, 650, "formatDepth"}}, - {"DPT", {1000, 100, 0.0, 650, "formatDepth"}}, - {"HDT", {1000, 10000, 0.0, M_TWOPI, "formatCourse"}}, + {"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 rotational angle + {"SOG", {1000, 1000, 0.0, 65.0, "formatKnots"}}, + {"STW", {1000, 1000, 0.0, 65.0, "formatKnots"}}, {"TWA", {1000, 10000, -M_PI, M_PI, "formatWind"}}, {"TWD", {1000, 10000, 0.0, M_TWOPI, "formatCourse"}}, {"TWS", {1000, 1000, 0.0, 65.0, "formatKnots"}}, - {"SOG", {1000, 1000, 0.0, 65.0, "formatKnots"}}, - {"STW", {1000, 1000, 0.0, 65.0, "formatKnots"}}, {"WTemp", {1000, 100, 0.0, 650.0, "kelvinToC"}} }; @@ -106,6 +105,5 @@ public: bool calcWinds(const double* awaVal, const double* awsVal, const double* cogVal, const double* stwVal, const double* sogVal, const double* hdtVal, const double* hdmVal, const double* varVal, double* twdVal, double* twsVal, double* twaVal, double* awdVal); - bool calcATWD(const double* waVal, const double* hdtVal, const double* hdmVal, const double* varVal, const double* cogVal, const double* sogVal, double* wdVal); bool addWinds(); }; \ No newline at end of file diff --git a/lib/obp60task/OBPcharts.cpp b/lib/obp60task/OBPcharts.cpp index b78bc6e..97bd71a 100644 --- a/lib/obp60task/OBPcharts.cpp +++ b/lib/obp60task/OBPcharts.cpp @@ -4,6 +4,13 @@ #include "OBPRingBuffer.h" // --- 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]; +// common program data; required for logger and color data +// flag to indicate if simulation data is active template Chart::Chart(RingBuffer& dataBuf, char chrtDir, int8_t chrtSz, double dfltRng, CommonData& common, bool useSimuData) : dataBuf(dataBuf) @@ -23,7 +30,7 @@ Chart::Chart(RingBuffer& dataBuf, char chrtDir, int8_t chrtSz, double dflt if (chrtDir == 'H') { // horizontal chart timeline direction - timAxis = dWidth; + timAxis = dWidth - 1; switch (chrtSz) { case 0: valAxis = dHeight - top - bottom; @@ -41,12 +48,13 @@ Chart::Chart(RingBuffer& dataBuf, char chrtDir, int8_t chrtSz, double dflt LOG_DEBUG(GwLog::ERROR, "displayChart: wrong init parameter"); return; } + } else if (chrtDir == 'V') { // vertical chart timeline direction timAxis = dHeight - top - bottom; switch (chrtSz) { case 0: - valAxis = dWidth; + valAxis = dWidth - 1; cStart = { 0, top - 1 }; break; case 1: @@ -106,21 +114,39 @@ Chart::~Chart() } // Perform all actions to draw chart -// Parameters are chart time interval, and the current boat data value to be printed +// Parameters: chart time interval, current boat data value to be printed, current boat data shall be shown yes/no template -void Chart::showChrt(int8_t chrtIntv, GwApi::BoatValue currValue) +void Chart::showChrt(int8_t chrtIntv, GwApi::BoatValue currValue, bool showCurrValue) { 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); + if (showCurrValue) { + // 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, "OBPcharts showChrt: currValue-value: %.1f, Valid: %d, Name: %s, Address: %p", currValue.value, currValue.valid, currValue.getName(), (void*)&currValue); + } + + } else { // No valid data available -> print message + getdisplay().setFont(&Ubuntu_Bold10pt8b); + + int pX, pY; + if (chrtDir == 'H') { + pX = cStart.x + (timAxis / 2); + pY = cStart.y + (valAxis / 2) - 10; + } else { + pX = cStart.x + (valAxis / 2); + pY = cStart.y + (timAxis / 2) - 10; + } + + getdisplay().fillRect(pX - 37, pY - 10, 78, 24, bgColor); // Clear area for message + drawTextCenter(pX, pY, "No data"); + LOG_DEBUG(GwLog::LOG, "Page chart: No valid data available"); } } @@ -131,7 +157,6 @@ 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 static int numNoData; // Counter for multiple invalid data values in a row int x, y; // x and y coordinates for drawing @@ -144,7 +169,6 @@ void Chart::drawChrt(int8_t chrtIntv, GwApi::BoatValue& currValue) 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; // 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; @@ -186,7 +210,8 @@ void Chart::drawChrt(int8_t chrtIntv, GwApi::BoatValue& currValue) if (chrtDir == 'H') { // horizontal chart x = cStart.x + i; // Position in chart area - if (chrtDataFmt == 'S') { + + if (chrtDataFmt == 'S') { // speed data format -> print low values at bottom // y = cStart.y + static_cast(((chrtVal - chrtMin) * chrtScl) + 0.5); // calculate chart point and round y = cStart.y + valAxis - static_cast(((chrtVal - chrtMin) * chrtScl) + 0.5); // calculate chart point and round } else if (chrtDataFmt == 'D') { @@ -194,8 +219,10 @@ void Chart::drawChrt(int8_t chrtIntv, GwApi::BoatValue& currValue) } else { // degree type value y = cStart.y + static_cast((WindUtils::to2PI(chrtVal - chrtMin) * chrtScl) + 0.5); // calculate chart point and round } + } else { // vertical chart y = cStart.y + timAxis - i; // Position in chart area + if (chrtDataFmt == 'S' || chrtDataFmt == 'D') { x = cStart.x + static_cast(((chrtVal - chrtMin) * chrtScl) + 0.5); // calculate chart point and round } else { // degree type value @@ -266,27 +293,11 @@ void Chart::drawChrt(int8_t chrtIntv, GwApi::BoatValue& currValue) if (chrtDataFmt == 'W') { // 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); + LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: chart end: timAxis: %d, i: %d, bufStart: %d, numBufVals: %d, recalcRngCntr: %d", timAxis, i, bufStart, numBufVals, recalcRngCntr); } break; } } - } else { - // No valid data available - getdisplay().setFont(&Ubuntu_Bold10pt8b); - - int pX, pY; - if (chrtDir == 'H') { - pX = cStart.x + (timAxis / 2); - pY = cStart.y + (valAxis / 2) - 10; - } else { - pX = cStart.x + (valAxis / 2); - pY = cStart.y + (timAxis / 2) - 10; - } - - 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"); } } @@ -330,38 +341,8 @@ double Chart::getRng(double center, size_t amount) template void Chart::calcChrtBorders(double& rngMid, double& rngMin, double& rngMax, double& rng) { - if (chrtDataFmt == 'S' || chrtDataFmt == 'D') { - // 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 == 'W' || chrtDataFmt == 'R') { + // Chart data is of type 'course', 'wind' or 'rot' if (chrtDataFmt == 'W') { // Chart data is of type 'course' or 'wind' @@ -416,8 +397,38 @@ void Chart::calcChrtBorders(double& rngMid, double& rngMin, double& rngMax, d // 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); + // 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); + + } else { + 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); } } @@ -425,52 +436,40 @@ void Chart::calcChrtBorders(double& rngMid, double& rngMin, double& rngMax, d template void Chart::drawChrtTimeAxis(int8_t chrtIntv) { - int timeRng; float slots, intv, i; char sTime[6]; + int timeRng = chrtIntv * 4; // chart time interval: [1] 4 min., [2] 8 min., [3] 12 min., [4] 16 min., [8] 32 min. + getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setTextColor(fgColor); if (chrtDir == 'H') { // horizontal chart 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 - intv = timeRng / slots; // minutes per chart axis interval + slots = 5; // number of axis labels + intv = timAxis / (slots - 1); // minutes per chart axis interval (interval is 1 less than slots) 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)); - } + 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 // draw text with appropriate offset - // int tOffset = (j == 0) ? 13 : (chrtIntv < 3 ? -4 : -4); int tOffset = j == 0 ? 13 : -4; + snprintf(sTime, sizeof(sTime), "-%.0f", i); 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; + i -= chrtIntv; } } 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 - i = -intv; // chart axis label start at -32, -16, -12, ... minutes + slots = 5; // number of axis labels + intv = timAxis / (slots - 1); // minutes per chart axis interval (interval is 1 less than slots) + i = timeRng; // 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)); - } + for (float j = intv; j < timAxis - 1; j += intv) { // don't print time label at upper and lower end of time axis + i -= chrtIntv; // we start not at top chart position + snprintf(sTime, sizeof(sTime), "-%.0f", i); getdisplay().drawLine(cStart.x, cStart.y + j, cStart.x + valAxis, cStart.y + j, fgColor); // Grid line if (chrtSz == 0) { // full size chart @@ -480,8 +479,6 @@ void Chart::drawChrtTimeAxis(int8_t chrtIntv) } else if (chrtSz == 2) { // half size chart; right side drawTextCenter(dWidth / 2, cStart.y + j, sTime); // time value; print mid screen } - - i -= intv; } } } @@ -509,8 +506,21 @@ void Chart::drawChrtValAxis() if (chrtSz == 0) { // full size chart -> print multiple value lines getdisplay().setFont(&Ubuntu_Bold12pt8b); -// for (int j = 60; j < valAxis - 30; j += 60) { - for (int j = valAxis - 60; j > 30; j -= 60) { + + int loopStrt, loopEnd, loopStp; + if (chrtDataFmt == 'S') { + loopStrt = valAxis - 60; + loopEnd = 30; + loopStp = -60; + } else { + loopStrt = 60; + loopEnd = valAxis - 30; + loopStp = 60; + } + LOG_DEBUG(GwLog::DEBUG, "Chart drawValAxis: chrtDataFmt: %c, loopStrt: %d, loopEnd: %d, loopIntv: %d", chrtDataFmt, loopStrt, loopEnd, loopStp); + + for (int j = loopStrt; (loopStp > 0) ? (j < loopEnd) : (j > loopEnd); j += loopStp) { + LOG_DEBUG(GwLog::DEBUG, "Chart drawValAxis2: j: %d, i: %d", j, i); 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 @@ -523,7 +533,7 @@ void Chart::drawChrtValAxis() } else { // half size chart -> print just edge values + middle chart line getdisplay().setFont(&Ubuntu_Bold10pt8b); - tmpBVal->value = chrtMin; + tmpBVal->value = (chrtDataFmt == 'D') ? chrtMin : chrtMax; 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 @@ -538,7 +548,7 @@ void Chart::drawChrtValAxis() 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; + tmpBVal->value = (chrtDataFmt == 'D') ? chrtMax : chrtMin; 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 @@ -586,7 +596,7 @@ template void Chart::prntCurrValue(GwApi::BoatValue& currValue) { const int xPosVal = (chrtDir == 'H') ? cStart.x + (timAxis / 2) - 56 : cStart.x + 32; - const int yPosVal = (chrtDir == 'H') ? cStart.y + valAxis - 5 : cStart.y + timAxis - 5; + const int yPosVal = (chrtDir == 'H') ? cStart.y + valAxis - 7 : cStart.y + timAxis - 7; FormattedData frmtDbData = formatValue(&currValue, *commonData); double testdbValue = frmtDbData.value; @@ -595,8 +605,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, 40, bgColor); // Clear area for TWS value - getdisplay().drawRect(xPosVal, yPosVal - 33, 123, 39, fgColor); // Draw box for TWS value + getdisplay().fillRect(xPosVal - 1, yPosVal - 35, 125, 41, bgColor); // Clear area for TWS value + getdisplay().drawRect(xPosVal, yPosVal - 34, 123, 40, 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 f160dd4..2ffa141 100644 --- a/lib/obp60task/OBPcharts.h +++ b/lib/obp60task/OBPcharts.h @@ -6,16 +6,16 @@ struct Pos { int x; int y; }; + template class RingBuffer; class GwLog; -template -class Chart { +template class Chart { protected: - CommonData *commonData; - GwLog *logger; + CommonData* commonData; + GwLog* logger; - RingBuffer &dataBuf; // Buffer to display + RingBuffer& dataBuf; // Buffer to display char chrtDir; // Chart timeline direction: 'H' = horizontal, 'V' = vertical int8_t chrtSz; // Chart size: [0] = full size, [1] = half size left/top, [2] half size right/bottom double dfltRng; // Default range of chart, e.g. 30 = [0..30] @@ -23,7 +23,6 @@ protected: 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 top = 44; // chart gap at top of display (25 lines for standard gap + 19 lines for axis labels) int bottom = 25; // chart gap at bottom of display to keep space for status line int hGap = 11; // gap between 2 horizontal charts; actual gap is 2x @@ -59,11 +58,22 @@ 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); // Add current boat data value to chart + void prntCurrValue(GwApi::BoatValue& currValue); // Add current boat data value to chart public: + // Define default chart range for each boat data type + static std::map dfltChartRng; + Chart(RingBuffer& dataBuf, char chrtDir, int8_t chrtSz, double dfltRng, CommonData& common, bool useSimuData); // Chart object of data chart ~Chart(); - void showChrt(int8_t chrtIntv, GwApi::BoatValue currValue); // Perform all actions to draw chart + void showChrt(int8_t chrtIntv, GwApi::BoatValue currValue, bool showCurrValue); // Perform all actions to draw chart +}; +template +std::map Chart::dfltChartRng = { + { "formatWind", 60.0 * DEG_TO_RAD }, // default course range 60 degrees + { "formatCourse", 60.0 * DEG_TO_RAD }, // default course range 60 degrees + { "formatKnots", 5.1 }, // default speed range in m/s + { "formatDepth", 15 }, // default depth range in m + { "kelvinToC", 30 } // default temp range in °C/K }; \ No newline at end of file diff --git a/lib/obp60task/PageOneValue.cpp b/lib/obp60task/PageOneValue.cpp index 6f33597..fe2ac93 100644 --- a/lib/obp60task/PageOneValue.cpp +++ b/lib/obp60task/PageOneValue.cpp @@ -3,112 +3,254 @@ #include "Pagedata.h" #include "OBP60Extensions.h" #include "BoatDataCalibration.h" +#include "OBPcharts.h" -class PageOneValue : public Page -{ - public: - PageOneValue(CommonData &common){ - commonData = &common; - common.logger->logDebug(GwLog::LOG,"Instantiate PageOneValue"); +class PageOneValue : public Page { +private: + GwLog* logger; + + 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 + int 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 + + String lengthformat; + bool useSimuData; + bool holdValues; + String flashLED; + String backlightMode; + + // Old values for hold function + String sValue1Old = ""; + String unit1Old = ""; + + // Data buffer pointer (owned by HstryBuffers) + RingBuffer* dataHstryBuf = nullptr; + std::unique_ptr> dataFlChart, dataHfChart; // Chart object, full and half size + // Active chart and value + Chart* dataChart = nullptr; + + void showData(GwApi::BoatValue* bValue1, char size) + { + int nameXoff, nameYoff, unitXoff, unitYoff, value1Xoff, value1Yoff; + const GFXfont *nameFnt, *unitFnt, *valueFnt1, *valueFnt2, *valueFnt3; + + if (size == 'F') { // full size data display + nameXoff = 0; + nameYoff = 0; + nameFnt = &Ubuntu_Bold32pt8b; + unitXoff = 0; + unitYoff = 0; + unitFnt = &Ubuntu_Bold20pt8b; + value1Xoff = 0; + value1Yoff = 0; + valueFnt1 = &Ubuntu_Bold20pt8b; + valueFnt2 = &Ubuntu_Bold32pt8b; + valueFnt3 = &DSEG7Classic_BoldItalic60pt7b; + } else { // half size data and chart display + nameXoff = 105; + nameYoff = -40; + nameFnt = &Ubuntu_Bold20pt8b; + unitXoff = -33; + unitYoff = -40; + unitFnt = &Ubuntu_Bold12pt8b; + valueFnt1 = &Ubuntu_Bold12pt8b; + value1Xoff = 105; + value1Yoff = -105; + valueFnt2 = &Ubuntu_Bold20pt8b; + valueFnt3 = &DSEG7Classic_BoldItalic30pt7b; + } + + String name1 = xdrDelete(bValue1->getName()); // Value name + name1 = name1.substring(0, 6); // String length limit for value name + calibrationData.calibrateInstance(bValue1, logger); // Check if boat data value is to be calibrated + double value1 = bValue1->value; // Value as double in SI unit + bool valid1 = bValue1->valid; // Valid information + String sValue1 = formatValue(bValue1, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places + String unit1 = formatValue(bValue1, *commonData).unit; // Unit of value + + // Show name + getdisplay().setTextColor(commonData->fgcolor); + getdisplay().setFont(nameFnt); + getdisplay().setCursor(20 + nameXoff, 100 + nameYoff); + getdisplay().print(name1); // name + + // Show unit + getdisplay().setFont(unitFnt); + getdisplay().setCursor(270 + unitXoff, 100 + unitYoff); + if (holdValues == false) { + //getdisplay().print(unit1); // Unit + drawTextRalign(298 + unitXoff, 100 + unitYoff, unit1); // Unit + + } else { + // getdisplay().print(unit1Old); + drawTextRalign(298 + unitXoff, 100 + unitYoff, unit1Old); + } + + // Switch font if format for any values + if (bValue1->getFormat() == "formatLatitude" || bValue1->getFormat() == "formatLongitude") { + getdisplay().setFont(valueFnt1); + getdisplay().setCursor(20 + value1Xoff, 180 + value1Yoff); + } else if (bValue1->getFormat() == "formatTime" || bValue1->getFormat() == "formatDate") { + getdisplay().setFont(valueFnt2); + getdisplay().setCursor(20 + value1Xoff, 200 + value1Yoff); + } else { + getdisplay().setFont(valueFnt3); + getdisplay().setCursor(20 + value1Xoff, 240 + value1Yoff); + } + + // Show bus data + if (holdValues == false) { + getdisplay().print(sValue1); // Real value as formated string + } else { + getdisplay().print(sValue1Old); // Old value as formated string + } + if (valid1 == true) { + sValue1Old = sValue1; // Save the old value + unit1Old = unit1; // Save the old unit + } } - virtual int handleKey(int key){ - // Code for keylock - if(key == 11){ +public: + PageOneValue(CommonData& common) + { + commonData = &common; + logger = commonData->logger; + LOG_DEBUG(GwLog::LOG, "Instantiate PageOneValue"); + + width = getdisplay().width(); // Screen width + height = getdisplay().height(); // Screen height + + // Get config data + lengthformat = common.config->getString(common.config->lengthFormat); + useSimuData = common.config->getBool(common.config->useSimuData); + 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"; +#if defined BOARD_OBP60S3 + commonData->keydata[4].label = "ZOOM"; +#elif defined BOARD_OBP40S3 + commonData->keydata[1].label = "ZOOM"; +#endif + } + + // Key functions + virtual int handleKey(int key) + { + // Set page mode value | full chart | value/half chart + if (key == 1) { + if (pageMode == 'V') { + pageMode = 'C'; + } else if (pageMode == 'C') { + pageMode = 'B'; + } else { + pageMode = 'V'; + } + return 0; // Commit the key + } + + // Set interval for history chart update time (interval) +#if defined BOARD_OBP60S3 + if (key == 5) { +#elif defined BOARD_OBP40S3 + if (key == 2) { +#endif + if (dataIntv == 1) { + dataIntv = 2; + } else if (dataIntv == 2) { + dataIntv = 3; + } else if (dataIntv == 3) { + dataIntv = 4; + } else if (dataIntv == 4) { + dataIntv = 8; + } else { + dataIntv = 1; + } + return 0; // Commit the key + } + + // Keylock function + if (key == 11) { // Code for keylock commonData->keylock = !commonData->keylock; - return 0; // Commit the key + return 0; // Commit the key } return key; } - int displayPage(PageData &pageData){ - GwConfigHandler *config = commonData->config; - GwLog *logger = commonData->logger; + virtual void displayNew(PageData& pageData) + { +#ifdef BOARD_OBP60S3 + // Clear optical warning + if (flashLED == "Limit Violation") { + setBlinkingLED(false); + setFlashLED(false); + } +#endif + if (!dataFlChart) { // 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 - // Old values for hold function - static String svalue1old = ""; - static String unit1old = ""; + dataHstryBuf = pageData.hstryBuffers->getBuffer(bValName1); - // Get config data - String lengthformat = config->getString(config->lengthFormat); - // bool simulation = config->getBool(config->useSimuData); - bool holdvalues = config->getBool(config->holdvalues); - String flashLED = config->getString(config->flashLED); - String backlightMode = config->getString(config->backlight); - - // Get boat values - GwApi::BoatValue *bvalue1 = pageData.values[0]; // First element in list (only one value by PageOneValue) - String name1 = xdrDelete(bvalue1->getName()); // Value name - name1 = name1.substring(0, 6); // String length limit for value name - calibrationData.calibrateInstance(bvalue1, logger); // Check if boat data value is to be calibrated - double value1 = bvalue1->value; // Value as double in SI unit - bool valid1 = bvalue1->valid; // Valid information - String svalue1 = formatValue(bvalue1, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places - String unit1 = formatValue(bvalue1, *commonData).unit; // Unit of value + dataFlChart.reset(new Chart(*dataHstryBuf, 'H', 0, Chart::dfltChartRng[bValFormat], *commonData, useSimuData)); + dataHfChart.reset(new Chart(*dataHstryBuf, 'H', 2, Chart::dfltChartRng[bValFormat], *commonData, useSimuData)); + LOG_DEBUG(GwLog::DEBUG, "PageOneValue: Created chart objects for %s", bValName1); + } + } + + int displayPage(PageData& pageData) + { + + LOG_DEBUG(GwLog::LOG, "Display PageOneValue"); + GwConfigHandler* config = commonData->config; + GwLog* logger = commonData->logger; + + // Get boat value for page + GwApi::BoatValue* bValue1 = pageData.values[0]; // Page boat data element // Optical warning by limit violation (unused) - if(String(flashLED) == "Limit Violation"){ + if (String(flashLED) == "Limit Violation") { setBlinkingLED(false); - setFlashLED(false); + setFlashLED(false); } // Logging boat values - if (bvalue1 == NULL) return PAGE_OK; // WTF why this statement? - LOG_DEBUG(GwLog::LOG,"Drawing at PageOneValue, %s: %f", name1.c_str(), value1); + if (bValue1 == NULL) + return PAGE_OK; // WTF why this statement? + LOG_DEBUG(GwLog::LOG, "Drawing at PageOneValue, %s: %f", bValue1->getName().c_str(), bValue1->value); // Draw page //*********************************************************** - /// Set display in partial refresh mode - getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update + getdisplay().setPartialWindow(0, 0, width, height); // Set partial update - // Show name - getdisplay().setTextColor(commonData->fgcolor); - getdisplay().setFont(&Ubuntu_Bold32pt8b); - getdisplay().setCursor(20, 100); - getdisplay().print(name1); // Page name + if (pageMode == 'V') { // show only data value + showData(bValue1, 'F'); - // Show unit - getdisplay().setFont(&Ubuntu_Bold20pt8b); - getdisplay().setCursor(270, 100); - if(holdvalues == false){ - getdisplay().print(unit1); // Unit - } - else{ - getdisplay().print(unit1old); - } + } else if (pageMode == 'C') { // show only data chart + dataFlChart->showChrt(dataIntv, *bValue1, true); - // Switch font if format for any values - if(bvalue1->getFormat() == "formatLatitude" || bvalue1->getFormat() == "formatLongitude"){ - getdisplay().setFont(&Ubuntu_Bold20pt8b); - getdisplay().setCursor(20, 180); - } - else if(bvalue1->getFormat() == "formatTime" || bvalue1->getFormat() == "formatDate"){ - getdisplay().setFont(&Ubuntu_Bold32pt8b); - getdisplay().setCursor(20, 200); - } - else{ - getdisplay().setFont(&DSEG7Classic_BoldItalic60pt7b); - getdisplay().setCursor(20, 240); - } - - // Show bus data - if(holdvalues == false){ - getdisplay().print(svalue1); // Real value as formated string - } - else{ - getdisplay().print(svalue1old); // Old value as formated string - } - if(valid1 == true){ - svalue1old = svalue1; // Save the old value - unit1old = unit1; // Save the old unit + } else if (pageMode == 'B') { // show data value and chart + showData(bValue1, 'H'); + dataHfChart->showChrt(dataIntv, *bValue1, false); } return PAGE_UPDATE; }; }; -static Page* createPage(CommonData &common){ +static Page* createPage(CommonData& common) +{ return new PageOneValue(common); } @@ -120,10 +262,10 @@ static Page* createPage(CommonData &common){ * this will be number of BoatValue pointers in pageData.values */ PageDescription registerPageOneValue( - "OneValue", // Page name - createPage, // Action - 1, // Number of bus values depends on selection in Web configuration - true // Show display header on/off + "OneValue", // Page name + createPage, // Action + 1, // Number of bus values depends on selection in Web configuration + true // Show display header on/off ); #endif diff --git a/lib/obp60task/PageWindPlot.cpp b/lib/obp60task/PageWindPlot.cpp index 636d8f9..5042d7c 100644 --- a/lib/obp60task/PageWindPlot.cpp +++ b/lib/obp60task/PageWindPlot.cpp @@ -2,7 +2,6 @@ #include "Pagedata.h" #include "OBP60Extensions.h" -#include "OBPDataOperations.h" #include "OBPcharts.h" // **************************************************************** @@ -77,9 +76,9 @@ public: commonData->keydata[0].label = "MODE"; #if defined BOARD_OBP60S3 commonData->keydata[1].label = "SRC"; - commonData->keydata[4].label = "INTV"; + commonData->keydata[4].label = "ZOOM"; #elif defined BOARD_OBP40S3 - commonData->keydata[1].label = "INTV"; + commonData->keydata[1].label = "ZOOM"; #endif } @@ -209,14 +208,14 @@ public: getdisplay().setTextColor(commonData->fgcolor); if (chrtMode == 'D') { - wdFlChart->showChrt(dataIntv, *wdBVal); + wdFlChart->showChrt(dataIntv, *wdBVal, true); } else if (chrtMode == 'S') { - wsFlChart->showChrt(dataIntv, *wsBVal); + wsFlChart->showChrt(dataIntv, *wsBVal, true); } else if (chrtMode == 'B') { - wdHfChart->showChrt(dataIntv, *wdBVal); - wsHfChart->showChrt(dataIntv, *wsBVal); + wdHfChart->showChrt(dataIntv, *wdBVal, true); + wsHfChart->showChrt(dataIntv, *wsBVal, true); } LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: page time %ldms", millis() - pageTime); From 784cc15b8fec55e0ca02a3f6abd78b11b275baed Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Wed, 24 Dec 2025 01:40:37 +0100 Subject: [PATCH 06/13] adjusted simulation calc in OBPFormatter; WindPlot + PageOneValue: aligned simulation data handling to standard; added "holdValues"; improved data check for chart buffer data; changed handling of tmpBVal -> always unique_ptr --- lib/obp60task/OBP60Formatter.cpp | 10 ++-- lib/obp60task/OBPDataOperations.cpp | 42 ++++++-------- lib/obp60task/OBPDataOperations.h | 5 +- lib/obp60task/OBPcharts.cpp | 44 +++++++++----- lib/obp60task/OBPcharts.h | 2 +- lib/obp60task/PageOneValue.cpp | 61 +++++++++++++------ lib/obp60task/PageWindPlot.cpp | 90 +++++++++++++++++++++++------ lib/obp60task/Pagedata.h | 3 +- lib/obp60task/obp60task.cpp | 2 +- 9 files changed, 176 insertions(+), 83 deletions(-) diff --git a/lib/obp60task/OBP60Formatter.cpp b/lib/obp60task/OBP60Formatter.cpp index f51c5fa..79bd16a 100644 --- a/lib/obp60task/OBP60Formatter.cpp +++ b/lib/obp60task/OBP60Formatter.cpp @@ -192,10 +192,10 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){ rawvalue = value->value; } else { - course = 2.53 + float(random(0, 10) / 100.0); + course = M_PI_2 + float(random(-17, 17) / 100.0); // create random course/wind values with 90° +/- 10° rawvalue = course; } - course = course * 57.2958; // Unit conversion form rad to deg + course = course * RAD_TO_DEG; // Unit conversion form rad to deg // Format 3 numbers with prefix zero snprintf(buffer,bsize,"%03.0f",course); @@ -210,7 +210,7 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){ rawvalue = value->value; } else{ - rawvalue = 4.0 + float(random(0, 40)); + rawvalue = 4.0 + float(random(-30, 40) / 10.0); // create random speed values from [1..8] m/s speed = rawvalue; } if (String(speedFormat) == "km/h"){ @@ -244,7 +244,7 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){ rawvalue = value->value; } else { - rawvalue = 4.0 + float(random(0, 40)); + rawvalue = 4.0 + float(random(0, 40) / 10.0); // create random wind speed values from [4..8] m/s speed = rawvalue; } if (String(windspeedFormat) == "km/h"){ @@ -429,7 +429,7 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){ rawvalue = value->value; } else { - rawvalue = 18.0 + float(random(0, 100)) / 10.0; + rawvalue = 18.0 + float(random(0, 100)) / 10.0; // create random depth values from [18..28] metres depth = rawvalue; } if(String(lengthFormat) == "ft"){ diff --git a/lib/obp60task/OBPDataOperations.cpp b/lib/obp60task/OBPDataOperations.cpp index 3d81fe0..b966bf5 100644 --- a/lib/obp60task/OBPDataOperations.cpp +++ b/lib/obp60task/OBPDataOperations.cpp @@ -1,6 +1,5 @@ #include "OBPDataOperations.h" #include "BoatDataCalibration.h" // Functions lib for data instance calibration -#include // --- Class HstryBuf --------------- HstryBuf::HstryBuf(const String& name, int size, BoatValueList* boatValues, GwLog* log) @@ -26,34 +25,29 @@ void HstryBuf::add(double value) { if (value >= hstryMin && value <= hstryMax) { hstryBuf.add(value); + LOG_DEBUG(GwLog::DEBUG, "HstryBuf::add: name: %s, value: %.3f", hstryBuf.getName(), value); } } -void HstryBuf::handle(bool useSimuData) +void HstryBuf::handle(bool useSimuData, CommonData& common) { - GwApi::BoatValue* calBVal; + //GwApi::BoatValue* tmpBVal; + std::unique_ptr tmpBVal; // Temp variable to get formatted and converted data value from OBP60Formatter + + // create temporary boat value for calibration purposes and retrieval of simulation value + //tmpBVal = new GwApi::BoatValue(boatDataName.c_str()); + tmpBVal = std::unique_ptr(new GwApi::BoatValue(boatDataName)); + tmpBVal->setFormat(boatValue->getFormat()); + tmpBVal->value = boatValue->value; + tmpBVal->valid = boatValue->valid; if (boatValue->valid) { // Calibrate boat value before adding it to history buffer - calBVal = new GwApi::BoatValue(boatDataName.c_str()); - calBVal->setFormat(boatValue->getFormat()); - calBVal->value = boatValue->value; - calBVal->valid = boatValue->valid; - calibrationData.calibrateInstance(calBVal, logger); - add(calBVal->value); - - delete calBVal; - calBVal = nullptr; - + calibrationData.calibrateInstance(tmpBVal.get(), logger); + add(tmpBVal->value); + } else if (useSimuData) { // add simulated value to history buffer - double simValue = hstryBuf.getLast(); - if (boatDataName == "TWD" || boatDataName == "AWD") { - simValue += static_cast(random(-349, 349) / 1000.0); - simValue = WindUtils::to2PI(simValue); - } else if (boatDataName == "TWS" || boatDataName == "AWS") { - simValue += static_cast(random(-5000, 5000) / 1000.0); - simValue = constrain(simValue, 0, 40); - } + double simValue = formatValue(tmpBVal.get(), common).value; // simulated value is generated at add(simValue); } } @@ -96,17 +90,17 @@ void HstryBuffers::addBuffer(const String& name) double bufferMaxVal = bufferParams[name].bufferMaxVal; // Max value for this history buffer hstryBuffers[name]->init(valueFormat, hstryUpdFreq, mltplr, bufferMinVal, bufferMaxVal); - LOG_DEBUG(GwLog::DEBUG, "HstryBuffers-new buffer added: name: %s, format: %s, multiplier: %d, min value: %.2f, max value: %.2f", name, valueFormat, mltplr, bufferMinVal, bufferMaxVal); + LOG_DEBUG(GwLog::DEBUG, "HstryBuffers: new buffer added: name: %s, format: %s, multiplier: %d, min value: %.2f, max value: %.2f", name, valueFormat, mltplr, bufferMinVal, bufferMaxVal); } // Handle history buffers -void HstryBuffers::handleHstryBufs(bool useSimuData) +void HstryBuffers::handleHstryBufs(bool useSimuData, CommonData& common) { // Handle all registered history buffers for (auto& pair : hstryBuffers) { auto& buf = pair.second; - buf->handle(useSimuData); + buf->handle(useSimuData, common); } } diff --git a/lib/obp60task/OBPDataOperations.h b/lib/obp60task/OBPDataOperations.h index 53cae41..f8c9c6e 100644 --- a/lib/obp60task/OBPDataOperations.h +++ b/lib/obp60task/OBPDataOperations.h @@ -2,6 +2,7 @@ #pragma once #include "OBPRingBuffer.h" #include "obp60task.h" +#include "Pagedata.h" #include class HstryBuf { @@ -19,7 +20,7 @@ public: HstryBuf(const String& name, int size, BoatValueList* boatValues, GwLog* log); void init(const String& format, int updFreq, int mltplr, double minVal, double maxVal); void add(double value); - void handle(bool useSimuData); + void handle(bool useSimuData, CommonData& common); }; class HstryBuffers { @@ -61,7 +62,7 @@ private: public: HstryBuffers(int size, BoatValueList* boatValues, GwLog* log); void addBuffer(const String& name); - void handleHstryBufs(bool useSimuData); + void handleHstryBufs(bool useSimuData, CommonData& common); RingBuffer* getBuffer(const String& name); }; diff --git a/lib/obp60task/OBPcharts.cpp b/lib/obp60task/OBPcharts.cpp index 97bd71a..0b00cb7 100644 --- a/lib/obp60task/OBPcharts.cpp +++ b/lib/obp60task/OBPcharts.cpp @@ -1,6 +1,7 @@ // Function lib for display of boat data in various chart formats #include "OBPcharts.h" #include "OBP60Extensions.h" +#include "OBPDataOperations.h" #include "OBPRingBuffer.h" // --- Class Chart --------------- @@ -183,6 +184,8 @@ void Chart::drawChrt(int8_t chrtIntv, GwApi::BoatValue& currValue) } calcChrtBorders(chrtMid, chrtMin, chrtMax, chrtRng); + LOG_DEBUG(GwLog::DEBUG, "calcChrtBorders: min: %.3f, mid: %.3f, max: %.3f, rng: %.3f", chrtMin, chrtMid, chrtMax, chrtRng); + chrtScl = double(valAxis) / chrtRng; // Chart scale: pixels per value step // Do we have valid buffer data? @@ -501,6 +504,10 @@ void Chart::drawChrtValAxis() slots = valAxis / 60.0; // number of axis labels tmpBVal->value = chrtRng; cChrtRng = formatValue(tmpBVal.get(), *commonData).cvalue; // value (converted) + if (useSimuData) { + // cannot use in this case, because that would change the range value to some random data + cChrtRng = tmpBVal->value; // take SI value in this case -> need to be improved + } intv = static_cast(round(cChrtRng / slots)); i = intv; @@ -517,10 +524,8 @@ void Chart::drawChrtValAxis() loopEnd = valAxis - 30; loopStp = 60; } - LOG_DEBUG(GwLog::DEBUG, "Chart drawValAxis: chrtDataFmt: %c, loopStrt: %d, loopEnd: %d, loopIntv: %d", chrtDataFmt, loopStrt, loopEnd, loopStp); for (int j = loopStrt; (loopStp > 0) ? (j < loopEnd) : (j > loopEnd); j += loopStp) { - LOG_DEBUG(GwLog::DEBUG, "Chart drawValAxis2: j: %d, i: %d", j, i); 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 @@ -535,6 +540,9 @@ void Chart::drawChrtValAxis() tmpBVal->value = (chrtDataFmt == 'D') ? chrtMin : chrtMax; cVal = formatValue(tmpBVal.get(), *commonData).cvalue; // value (converted) + if (useSimuData) { // dirty fix for problem that OBP60Formatter can only be used without data simulation -> returns random values in simulation mode + cVal = tmpBVal->value; // no value conversion here + } 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); @@ -542,6 +550,9 @@ void Chart::drawChrtValAxis() tmpBVal->value = chrtMid; cVal = formatValue(tmpBVal.get(), *commonData).cvalue; // value (converted) + if (useSimuData) { // dirty fix for problem that OBP60Formatter can only be used without data simulation -> returns random values in simulation mode + cVal = tmpBVal->value; // no value conversion here + } 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); @@ -550,6 +561,9 @@ void Chart::drawChrtValAxis() tmpBVal->value = (chrtDataFmt == 'D') ? chrtMax : chrtMin; cVal = formatValue(tmpBVal.get(), *commonData).cvalue; // value (converted) + if (useSimuData) { // dirty fix for problem that OBP60Formatter can only be used without data simulation -> returns random values in simulation mode + cVal = tmpBVal->value; // no value conversion here + } 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); @@ -571,17 +585,26 @@ void Chart::drawChrtValAxis() tmpBVal->value = chrtMin; cVal = formatValue(tmpBVal.get(), *commonData).cvalue; // value (converted) + if (useSimuData) { // dirty fix for problem that OBP60Formatter can only be used without data simulation -> returns random values in simulation mode + cVal = tmpBVal->value; // no value conversion here + } snprintf(sVal, sizeof(sVal), "%.0f", round(cVal)); getdisplay().setCursor(cStart.x, cStart.y - 2); getdisplay().printf("%s", sVal); // Range low end tmpBVal->value = chrtMid; cVal = formatValue(tmpBVal.get(), *commonData).cvalue; // value (converted) + if (useSimuData) { // dirty fix for problem that OBP60Formatter can only be used without data simulation -> returns random values in simulation mode + cVal = tmpBVal->value; // no value conversion here + } 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) + if (useSimuData) { // dirty fix for problem that OBP60Formatter can only be used without data simulation -> returns random values in simulation mode + cVal = tmpBVal->value; // no value conversion here + } snprintf(sVal, sizeof(sVal), "%.0f", round(cVal)); drawTextRalign(cStart.x + valAxis - 2, cStart.y - 2, sVal); // Range high end @@ -599,21 +622,16 @@ void Chart::prntCurrValue(GwApi::BoatValue& currValue) const int yPosVal = (chrtDir == 'H') ? cStart.y + valAxis - 7 : cStart.y + timAxis - 7; FormattedData frmtDbData = formatValue(&currValue, *commonData); - double testdbValue = frmtDbData.value; - String sdbValue = frmtDbData.svalue; // value (string) + String sdbValue = frmtDbData.svalue; // value as formatted 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, 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, 125, 41, bgColor); // Clear area for TWS value - getdisplay().drawRect(xPosVal, yPosVal - 34, 123, 40, fgColor); // Draw box for TWS value + 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 getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b); getdisplay().setCursor(xPosVal + 1, yPosVal); - if (useSimuData) { - getdisplay().printf("%2.1f", currValue.value); // Value - } else { - getdisplay().print(sdbValue); // Value - } + getdisplay().print(sdbValue); // alue getdisplay().setFont(&Ubuntu_Bold10pt8b); getdisplay().setCursor(xPosVal + 76, yPosVal - 17); diff --git a/lib/obp60task/OBPcharts.h b/lib/obp60task/OBPcharts.h index 2ffa141..7740f91 100644 --- a/lib/obp60task/OBPcharts.h +++ b/lib/obp60task/OBPcharts.h @@ -26,7 +26,7 @@ protected: int top = 44; // chart gap at top of display (25 lines for standard gap + 19 lines for axis labels) int bottom = 25; // chart gap at bottom of display to keep space for status line 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 vGap = 17; // gap between 2 vertical charts; actual gap is 2x int dWidth; // Display width int dHeight; // Display height int timAxis, valAxis; // size of time and value chart axis diff --git a/lib/obp60task/PageOneValue.cpp b/lib/obp60task/PageOneValue.cpp index fe2ac93..8f1cc19 100644 --- a/lib/obp60task/PageOneValue.cpp +++ b/lib/obp60task/PageOneValue.cpp @@ -2,6 +2,7 @@ #include "Pagedata.h" #include "OBP60Extensions.h" +#include "OBPDataOperations.h" #include "BoatDataCalibration.h" #include "OBPcharts.h" @@ -30,8 +31,6 @@ private: // Data buffer pointer (owned by HstryBuffers) RingBuffer* dataHstryBuf = nullptr; std::unique_ptr> dataFlChart, dataHfChart; // Chart object, full and half size - // Active chart and value - Chart* dataChart = nullptr; void showData(GwApi::BoatValue* bValue1, char size) { @@ -70,7 +69,7 @@ private: double value1 = bValue1->value; // Value as double in SI unit bool valid1 = bValue1->valid; // Valid information String sValue1 = formatValue(bValue1, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places - String unit1 = formatValue(bValue1, *commonData).unit; // Unit of value + String unit1 = formatValue(bValue1, *commonData).unit; // Unit of value // Show name getdisplay().setTextColor(commonData->fgcolor); @@ -81,16 +80,13 @@ private: // Show unit getdisplay().setFont(unitFnt); getdisplay().setCursor(270 + unitXoff, 100 + unitYoff); - if (holdValues == false) { - //getdisplay().print(unit1); // Unit - drawTextRalign(298 + unitXoff, 100 + unitYoff, unit1); // Unit - - } else { - // getdisplay().print(unit1Old); + if (holdValues) { drawTextRalign(298 + unitXoff, 100 + unitYoff, unit1Old); + } else { + drawTextRalign(298 + unitXoff, 100 + unitYoff, unit1); // Unit } - // Switch font if format for any values + // Switch font depending on value format and adjust position if (bValue1->getFormat() == "formatLatitude" || bValue1->getFormat() == "formatLongitude") { getdisplay().setFont(valueFnt1); getdisplay().setCursor(20 + value1Xoff, 180 + value1Yoff); @@ -103,11 +99,12 @@ private: } // Show bus data - if (holdValues == false) { + if (!holdValues || useSimuData) { getdisplay().print(sValue1); // Real value as formated string } else { getdisplay().print(sValue1Old); // Old value as formated string } + if (valid1 == true) { sValue1Old = sValue1; // Save the old value unit1Old = unit1; // Save the old unit @@ -195,17 +192,22 @@ public: setFlashLED(false); } #endif - if (!dataFlChart) { // Create chart objects if they don't exist + // buffer initialization cannot be performed here, because is not executed at system start for default page + /* if (!dataFlChart) { // 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 dataHstryBuf = pageData.hstryBuffers->getBuffer(bValName1); - dataFlChart.reset(new Chart(*dataHstryBuf, 'H', 0, Chart::dfltChartRng[bValFormat], *commonData, useSimuData)); - dataHfChart.reset(new Chart(*dataHstryBuf, 'H', 2, Chart::dfltChartRng[bValFormat], *commonData, useSimuData)); - LOG_DEBUG(GwLog::DEBUG, "PageOneValue: Created chart objects for %s", bValName1); - } + if (dataHstryBuf) { + dataFlChart.reset(new Chart(*dataHstryBuf, 'H', 0, Chart::dfltChartRng[bValFormat], *commonData, useSimuData)); + dataHfChart.reset(new Chart(*dataHstryBuf, 'H', 2, Chart::dfltChartRng[bValFormat], *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); + } + } */ } int displayPage(PageData& pageData) @@ -224,10 +226,27 @@ public: setFlashLED(false); } + if (!dataFlChart) { // 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 + + dataHstryBuf = pageData.hstryBuffers->getBuffer(bValName1); + + if (dataHstryBuf) { + dataFlChart.reset(new Chart(*dataHstryBuf, 'H', 0, Chart::dfltChartRng[bValFormat], *commonData, useSimuData)); + dataHfChart.reset(new Chart(*dataHstryBuf, 'H', 2, Chart::dfltChartRng[bValFormat], *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); + } + } + // Logging boat values if (bValue1 == NULL) return PAGE_OK; // WTF why this statement? - LOG_DEBUG(GwLog::LOG, "Drawing at PageOneValue, %s: %f", bValue1->getName().c_str(), bValue1->value); + + LOG_DEBUG(GwLog::DEBUG, "Drawing at PageOneValue, %s, %.3f", bValue1->getName().c_str(), bValue1->value); // Draw page //*********************************************************** @@ -238,11 +257,15 @@ public: showData(bValue1, 'F'); } else if (pageMode == 'C') { // show only data chart - dataFlChart->showChrt(dataIntv, *bValue1, true); + if (dataFlChart) { + dataFlChart->showChrt(dataIntv, *bValue1, true); + } } else if (pageMode == 'B') { // show data value and chart showData(bValue1, 'H'); - dataHfChart->showChrt(dataIntv, *bValue1, false); + if (dataHfChart) { + dataHfChart->showChrt(dataIntv, *bValue1, false); + } } return PAGE_UPDATE; diff --git a/lib/obp60task/PageWindPlot.cpp b/lib/obp60task/PageWindPlot.cpp index 5042d7c..088be08 100644 --- a/lib/obp60task/PageWindPlot.cpp +++ b/lib/obp60task/PageWindPlot.cpp @@ -2,6 +2,7 @@ #include "Pagedata.h" #include "OBP60Extensions.h" +#include "OBPDataOperations.h" #include "OBPcharts.h" // **************************************************************** @@ -21,6 +22,7 @@ private: int 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 bool useSimuData; + //bool holdValues; String flashLED; String backlightMode; @@ -63,7 +65,7 @@ public: // Get config data useSimuData = common.config->getBool(common.config->useSimuData); - // holdValues = common.config->getBool(common.config->holdvalues); + //holdValues = common.config->getBool(common.config->holdvalues); flashLED = common.config->getString(common.config->flashLED); backlightMode = common.config->getString(common.config->backlight); @@ -149,28 +151,40 @@ public: } oldShowTruW = !showTruW; // Force chart update in displayPage #endif + // buffer initialization cannot be performed here, because is not executed at system start for default page - if (!twdFlChart) { // Create true wind charts if they don't exist - LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: Creating true wind charts"); + /* if (!twdFlChart) { // Create true wind charts if they don't exist twdHstry = pageData.hstryBuffers->getBuffer("TWD"); twsHstry = pageData.hstryBuffers->getBuffer("TWS"); - twdFlChart.reset(new Chart(*twdHstry, 'V', 0, dfltRngWd, *commonData, useSimuData)); - twsFlChart.reset(new Chart(*twsHstry, 'H', 0, dfltRngWs, *commonData, useSimuData)); - twdHfChart.reset(new Chart(*twdHstry, 'V', 1, dfltRngWd, *commonData, useSimuData)); - twsHfChart.reset(new Chart(*twsHstry, 'V', 2, dfltRngWs, *commonData, useSimuData)); + if (twdHstry) { + twdFlChart.reset(new Chart(*twdHstry, 'V', 0, dfltRngWd, *commonData, useSimuData)); + twdHfChart.reset(new Chart(*twdHstry, 'V', 1, dfltRngWd, *commonData, useSimuData)); + } + if (twsHstry) { + twsFlChart.reset(new Chart(*twsHstry, 'H', 0, dfltRngWs, *commonData, useSimuData)); + twsHfChart.reset(new Chart(*twsHstry, 'V', 2, dfltRngWs, *commonData, useSimuData)); + } } if (!awdFlChart) { // Create apparent wind charts if they don't exist - LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: Creating apparent wind charts"); awdHstry = pageData.hstryBuffers->getBuffer("AWD"); awsHstry = pageData.hstryBuffers->getBuffer("AWS"); - awdFlChart.reset(new Chart(*awdHstry, 'V', 0, dfltRngWd, *commonData, useSimuData)); - awsFlChart.reset(new Chart(*awsHstry, 'H', 0, dfltRngWs, *commonData, useSimuData)); - awdHfChart.reset(new Chart(*awdHstry, 'V', 1, dfltRngWd, *commonData, useSimuData)); - awsHfChart.reset(new Chart(*awsHstry, 'V', 2, dfltRngWs, *commonData, useSimuData)); - } + if (awdHstry) { + awdFlChart.reset(new Chart(*awdHstry, 'V', 0, dfltRngWd, *commonData, useSimuData)); + awdHfChart.reset(new Chart(*awdHstry, 'V', 1, dfltRngWd, *commonData, useSimuData)); + } + if (awsHstry) { + awsFlChart.reset(new Chart(*awsHstry, 'H', 0, dfltRngWs, *commonData, useSimuData)); + awsHfChart.reset(new Chart(*awsHstry, 'V', 2, dfltRngWs, *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"); + } + } */ } int displayPage(PageData& pageData) @@ -178,6 +192,39 @@ public: LOG_DEBUG(GwLog::LOG, "Display PageWindPlot"); ulong pageTime = millis(); + if (!twdFlChart) { // Create true wind charts if they don't exist + twdHstry = pageData.hstryBuffers->getBuffer("TWD"); + twsHstry = pageData.hstryBuffers->getBuffer("TWS"); + + if (twdHstry) { + twdFlChart.reset(new Chart(*twdHstry, 'V', 0, dfltRngWd, *commonData, useSimuData)); + twdHfChart.reset(new Chart(*twdHstry, 'V', 1, dfltRngWd, *commonData, useSimuData)); + } + if (twsHstry) { + twsFlChart.reset(new Chart(*twsHstry, 'H', 0, dfltRngWs, *commonData, useSimuData)); + twsHfChart.reset(new Chart(*twsHstry, 'V', 2, dfltRngWs, *commonData, useSimuData)); + } + } + + if (!awdFlChart) { // Create apparent wind charts if they don't exist + awdHstry = pageData.hstryBuffers->getBuffer("AWD"); + awsHstry = pageData.hstryBuffers->getBuffer("AWS"); + + if (awdHstry) { + awdFlChart.reset(new Chart(*awdHstry, 'V', 0, dfltRngWd, *commonData, useSimuData)); + awdHfChart.reset(new Chart(*awdHstry, 'V', 1, dfltRngWd, *commonData, useSimuData)); + } + if (awsHstry) { + awsFlChart.reset(new Chart(*awsHstry, 'H', 0, dfltRngWs, *commonData, useSimuData)); + awsHfChart.reset(new Chart(*awsHstry, 'V', 2, dfltRngWs, *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 @@ -199,6 +246,7 @@ public: oldShowTruW = showTruW; } + LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: draw with data %s: %.2f, %s: %.2f", wdBVal->getName().c_str(), wdBVal->value, wsBVal->getName().c_str(), wsBVal->value); // Draw page //*********************************************************** @@ -208,14 +256,22 @@ public: getdisplay().setTextColor(commonData->fgcolor); if (chrtMode == 'D') { - wdFlChart->showChrt(dataIntv, *wdBVal, true); + if (wdFlChart) { + wdFlChart->showChrt(dataIntv, *wdBVal, true); + } } else if (chrtMode == 'S') { - wsFlChart->showChrt(dataIntv, *wsBVal, true); + if (wsFlChart) { + wsFlChart->showChrt(dataIntv, *wsBVal, true); + } } else if (chrtMode == 'B') { - wdHfChart->showChrt(dataIntv, *wdBVal, true); - wsHfChart->showChrt(dataIntv, *wsBVal, true); + if (wdHfChart) { + wdHfChart->showChrt(dataIntv, *wdBVal, true); + } + if (wsHfChart) { + wsHfChart->showChrt(dataIntv, *wsBVal, true); + } } LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: page time %ldms", millis() - pageTime); diff --git a/lib/obp60task/Pagedata.h b/lib/obp60task/Pagedata.h index a12119f..103082f 100644 --- a/lib/obp60task/Pagedata.h +++ b/lib/obp60task/Pagedata.h @@ -4,12 +4,13 @@ #include #include #include "LedSpiTask.h" -#include "OBPDataOperations.h" #define MAX_PAGE_NUMBER 10 // Max number of pages for show data typedef std::vector ValueList; +class HstryBuffers; + typedef struct{ GwApi *api; String pageName; diff --git a/lib/obp60task/obp60task.cpp b/lib/obp60task/obp60task.cpp index 4689a10..15d859e 100644 --- a/lib/obp60task/obp60task.cpp +++ b/lib/obp60task/obp60task.cpp @@ -815,7 +815,7 @@ void OBP60Task(GwApi *api){ trueWind.addWinds(); } // Handle history buffers for certain boat data for windplot page and other usage - hstryBufList.handleHstryBufs(useSimuData); + hstryBufList.handleHstryBufs(useSimuData, commonData); // Clear display // getdisplay().fillRect(0, 0, getdisplay().width(), getdisplay().height(), commonData.bgcolor); From 2e836bc7501ce7ee720c0e0d63e3494f7d837ff5 Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Thu, 1 Jan 2026 22:52:33 +0100 Subject: [PATCH 07/13] added helper method for boat value conversion to OBP60Formatter; fixed range calculation for temperature and other value formats; fixed printing for names > len(3); show "mode" only for supported data types --- lib/obp60task/OBP60Formatter.cpp | 17 ++ lib/obp60task/OBPDataOperations.cpp | 20 +-- lib/obp60task/OBPDataOperations.h | 10 +- lib/obp60task/OBPcharts.cpp | 266 ++++++++++++++++------------ lib/obp60task/OBPcharts.h | 21 ++- lib/obp60task/PageOneValue.cpp | 72 ++++---- lib/obp60task/PageWindPlot.cpp | 15 +- lib/obp60task/Pagedata.h | 3 + 8 files changed, 246 insertions(+), 178 deletions(-) diff --git a/lib/obp60task/OBP60Formatter.cpp b/lib/obp60task/OBP60Formatter.cpp index 79bd16a..830a846 100644 --- a/lib/obp60task/OBP60Formatter.cpp +++ b/lib/obp60task/OBP60Formatter.cpp @@ -877,4 +877,21 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){ return result; } +// Helper method for conversion of boat data values from SI to user defined format +double convertValue(const double &value, const String &format, CommonData &commondata) +{ + std::unique_ptr tmpBValue; // Temp variable to get converted data value from + double result; // data value converted to user defined target data format + + // prepare dummy BoatValue structure for use in + tmpBValue = std::unique_ptr(new GwApi::BoatValue("dummy")); // we don't need boat value name for pure value conversion + tmpBValue->setFormat(format); + tmpBValue->valid = true; + tmpBValue->value = value; + + result = formatValue(tmpBValue.get(), commondata).cvalue; // get value (converted) + + return result; +} + #endif diff --git a/lib/obp60task/OBPDataOperations.cpp b/lib/obp60task/OBPDataOperations.cpp index b966bf5..aae0040 100644 --- a/lib/obp60task/OBPDataOperations.cpp +++ b/lib/obp60task/OBPDataOperations.cpp @@ -31,11 +31,11 @@ void HstryBuf::add(double value) void HstryBuf::handle(bool useSimuData, CommonData& common) { - //GwApi::BoatValue* tmpBVal; + // GwApi::BoatValue* tmpBVal; std::unique_ptr tmpBVal; // Temp variable to get formatted and converted data value from OBP60Formatter // create temporary boat value for calibration purposes and retrieval of simulation value - //tmpBVal = new GwApi::BoatValue(boatDataName.c_str()); + // tmpBVal = new GwApi::BoatValue(boatDataName.c_str()); tmpBVal = std::unique_ptr(new GwApi::BoatValue(boatDataName)); tmpBVal->setFormat(boatValue->getFormat()); tmpBVal->value = boatValue->value; @@ -45,7 +45,7 @@ void HstryBuf::handle(bool useSimuData, CommonData& common) // Calibrate boat value before adding it to history buffer calibrationData.calibrateInstance(tmpBVal.get(), logger); add(tmpBVal->value); - + } else if (useSimuData) { // add simulated value to history buffer double simValue = formatValue(tmpBVal.get(), common).value; // simulated value is generated at add(simValue); @@ -78,6 +78,9 @@ void HstryBuffers::addBuffer(const String& name) if (HstryBuffers::getBuffer(name) != nullptr) { // buffer for this data type already exists return; } + if (bufferParams.find(name) == bufferParams.end()) { // requested boat data type is not supported in list of + return; + } hstryBuffers[name] = std::unique_ptr(new HstryBuf(name, size, boatValueList, logger)); @@ -93,13 +96,11 @@ void HstryBuffers::addBuffer(const String& name) LOG_DEBUG(GwLog::DEBUG, "HstryBuffers: new buffer added: name: %s, format: %s, multiplier: %d, min value: %.2f, max value: %.2f", name, valueFormat, mltplr, bufferMinVal, bufferMaxVal); } -// Handle history buffers +// Handle all registered history buffers void HstryBuffers::handleHstryBufs(bool useSimuData, CommonData& common) { - - // Handle all registered history buffers - for (auto& pair : hstryBuffers) { - auto& buf = pair.second; + for (auto& bufMap : hstryBuffers) { + auto& buf = bufMap.second; buf->handle(useSimuData, common); } } @@ -238,7 +239,6 @@ bool WindUtils::calcWinds(const double* awaVal, const double* awsVal, } // LOG_DEBUG(GwLog::DEBUG, "WindUtils:calcWinds: HDT: %.1f, CTW %.1f, STW %.1f", hdt, ctw, stw); - if ((*awaVal == DBL_MAX) || (*awsVal == DBL_MAX)) { // Cannot calculate true wind without valid AWA, AWS; other checks are done earlier return false; @@ -284,7 +284,7 @@ bool WindUtils::addWinds() twdBVal->value = twd; twdBVal->valid = true; } - + } else { // Calculate true winds and AWD; if true winds exist, use at least AWD calculation twCalculated = calcWinds(&awaVal, &awsVal, &cogVal, &stwVal, &sogVal, &hdtVal, &hdmVal, &varVal, &twd, &tws, &twa, &awd); diff --git a/lib/obp60task/OBPDataOperations.h b/lib/obp60task/OBPDataOperations.h index f8c9c6e..7cb4320 100644 --- a/lib/obp60task/OBPDataOperations.h +++ b/lib/obp60task/OBPDataOperations.h @@ -39,9 +39,9 @@ private: String format; }; - // Define buffer parameters for each boat data type + // Define buffer parameters for supported boat data type std::map bufferParams = { - {"AWA", {1000, 10000, -M_PI, M_PI, "formatWind"}}, + {"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"}}, @@ -50,13 +50,13 @@ private: {"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 rotational angle + {"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, -M_PI, M_PI, "formatWind"}}, + {"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, 0.0, 650.0, "kelvinToC"}} + {"WTemp", {1000, 100, 233.0, 650.0, "kelvinToC"}} // [-50..376] °C }; public: diff --git a/lib/obp60task/OBPcharts.cpp b/lib/obp60task/OBPcharts.cpp index 0b00cb7..3143273 100644 --- a/lib/obp60task/OBPcharts.cpp +++ b/lib/obp60task/OBPcharts.cpp @@ -25,6 +25,17 @@ Chart::Chart(RingBuffer& dataBuf, char chrtDir, int8_t chrtSz, double dflt fgColor = commonData->fgcolor; bgColor = commonData->bgcolor; + // we need "0" value for any user defined temperature format + tempFormat = commonData->config->getString(commonData->config->tempFormat); // [K|°C|°F] + if (tempFormat == "K") { + zeroValue = 0.0; + } else if (tempFormat == "C") { + zeroValue = 273.15; + } else if (tempFormat == "F") { + zeroValue = 255.37; + } + // LOG_DEBUG(GwLog::DEBUG, "Chart-init: fgColor: %d, bgColor: %d, tempFormat: %s, zeroValue: %.1f, &commonData: %p", fgColor, bgColor, tempFormat, zeroValue, commonData); + // LOG_DEBUG(GwLog::DEBUG, "Chart Init: Chart::dataBuf: %p, passed dataBuf: %p", (void*)&this->dataBuf, (void*)&dataBuf); dWidth = getdisplay().width(); dHeight = getdisplay().height(); @@ -59,12 +70,10 @@ Chart::Chart(RingBuffer& dataBuf, char chrtDir, int8_t chrtSz, double dflt cStart = { 0, top - 1 }; break; case 1: - // valAxis = dWidth / 2 - vGap - 1; valAxis = dWidth / 2 - vGap; cStart = { 0, top - 1 }; break; case 2: - // valAxis = dWidth / 2 - vGap - 1; valAxis = dWidth / 2 - vGap; cStart = { dWidth / 2 + vGap - 1, top - 1 }; break; @@ -94,19 +103,22 @@ Chart::Chart(RingBuffer& dataBuf, char chrtDir, int8_t chrtSz, double dflt } else { if (dbFormat == "formatDepth") { chrtDataFmt = 'D'; // Chart ist showing data of format + } else if (dbFormat == "kelvinToC") { + chrtDataFmt = 'T'; // Chart ist showing data of format } else { - chrtDataFmt = 'S'; // Chart is showing any other data format than + chrtDataFmt = 'S'; // Chart is showing any other data format } - rngStep = 5.0; // +/- 10 for all other values (eg. m/s, m, K, mBar) + rngStep = 10.0; // +/- 10 for all other values (eg. m/s, m, K, mBar) } - chrtMin = 0; - chrtMax = 0; + chrtMin = dbMIN_VAL; + chrtMax = dbMAX_VAL; chrtMid = dbMAX_VAL; chrtRng = dfltRng; - recalcRngCntr = true; // initialize on first screen call + recalcRngCntr = true; // initialize and chart borders 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); + LOG_DEBUG(GwLog::DEBUG, "Chart Init: dWidth: %d, dHeight: %d, timAxis: %d, valAxis: %d, cStart {x,y}: %d, %d, dbname: %s, rngStep: %.4f, chrtDataFmt: %c", + dWidth, dHeight, timAxis, valAxis, cStart.x, cStart.y, dbName, rngStep, chrtDataFmt); }; template @@ -117,7 +129,7 @@ Chart::~Chart() // Perform all actions to draw chart // Parameters: chart time interval, current boat data value to be printed, current boat data shall be shown yes/no template -void Chart::showChrt(int8_t chrtIntv, GwApi::BoatValue currValue, bool showCurrValue) +void Chart::showChrt(GwApi::BoatValue currValue, int8_t chrtIntv, bool showCurrValue) { drawChrt(chrtIntv, currValue); drawChrtTimeAxis(chrtIntv); @@ -126,11 +138,10 @@ void Chart::showChrt(int8_t chrtIntv, GwApi::BoatValue currValue, bool showCu if (bufDataValid) { if (showCurrValue) { // 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 + // doesn't work unfortunately when 'simulation data' is active, because OBP60Formatter generates own simulation values in that case currValue.value = dataBuf.getLast(); currValue.valid = currValue.value != dbMAX_VAL; Chart::prntCurrValue(currValue); - LOG_DEBUG(GwLog::DEBUG, "OBPcharts showChrt: currValue-value: %.1f, Valid: %d, Name: %s, Address: %p", currValue.value, currValue.valid, currValue.getName(), (void*)&currValue); } } else { // No valid data available -> print message @@ -157,11 +168,8 @@ 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 - static int numNoData; // Counter for multiple invalid data values in a row 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(); @@ -183,10 +191,12 @@ void Chart::drawChrt(int8_t chrtIntv, GwApi::BoatValue& currValue) } } - calcChrtBorders(chrtMid, chrtMin, chrtMax, chrtRng); - LOG_DEBUG(GwLog::DEBUG, "calcChrtBorders: min: %.3f, mid: %.3f, max: %.3f, rng: %.3f", chrtMin, chrtMid, chrtMax, chrtRng); + // LOG_DEBUG(GwLog::DEBUG, "PageOneValue:drawChart: min: %.1f, mid: %.1f, max: %.1f, rng: %.1f", chrtMin, chrtMid, chrtMax, chrtRng); + calcChrtBorders(chrtMin, chrtMid, chrtMax, chrtRng); + LOG_DEBUG(GwLog::DEBUG, "PageOneValue:drawChart2: min: %.1f, mid: %.1f, max: %.1f, rng: %.1f", chrtMin, chrtMid, chrtMax, chrtRng); - chrtScl = double(valAxis) / chrtRng; // Chart scale: pixels per value step + // Chart scale: pixels per value step + chrtScl = double(valAxis) / chrtRng; // Do we have valid buffer data? if (dataBuf.getMax() == dbMAX_VAL) { // only values in buffer -> no valid wind data available @@ -214,8 +224,7 @@ void Chart::drawChrt(int8_t chrtIntv, GwApi::BoatValue& currValue) if (chrtDir == 'H') { // horizontal chart x = cStart.x + i; // Position in chart area - if (chrtDataFmt == 'S') { // speed data format -> print low values at bottom - // y = cStart.y + static_cast(((chrtVal - chrtMin) * chrtScl) + 0.5); // calculate chart point and round + if (chrtDataFmt == 'S' or chrtDataFmt == 'T') { // speed data format -> print low values at bottom y = cStart.y + valAxis - static_cast(((chrtVal - chrtMin) * chrtScl) + 0.5); // calculate chart point and round } else if (chrtDataFmt == 'D') { y = cStart.y + static_cast(((chrtVal - chrtMin) * chrtScl) + 0.5); // calculate chart point and round @@ -226,7 +235,7 @@ void Chart::drawChrt(int8_t chrtIntv, GwApi::BoatValue& currValue) } else { // vertical chart y = cStart.y + timAxis - i; // Position in chart area - if (chrtDataFmt == 'S' || chrtDataFmt == 'D') { + if (chrtDataFmt == 'S' || chrtDataFmt == 'D' || chrtDataFmt == 'T') { 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 @@ -234,7 +243,7 @@ void Chart::drawChrt(int8_t chrtIntv, GwApi::BoatValue& currValue) } // 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); + // 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 @@ -304,45 +313,9 @@ void Chart::drawChrt(int8_t chrtIntv, GwApi::BoatValue& currValue) } } -// 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; - } - - 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) +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' @@ -372,7 +345,7 @@ void Chart::calcChrtBorders(double& rngMid, double& rngMin, double& rngMax, d } } 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, + 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, rng * RAD_TO_DEG, rngStep * RAD_TO_DEG); } @@ -383,10 +356,9 @@ void Chart::calcChrtBorders(double& rngMid, double& rngMin, double& rngMax, d // 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); + double diffRng = getAngleRng(rngMid, numBufVals); 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); + // LOG_DEBUG(GwLog::DEBUG, "calcChrtBorders: diffRng: %.1f°, halfRng: %.1f°", diffRng * RAD_TO_DEG, halfRng * RAD_TO_DEG); if (diffRng > halfRng) { halfRng = diffRng; // round to next value @@ -397,41 +369,47 @@ void Chart::calcChrtBorders(double& rngMid, double& rngMin, double& rngMax, d 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); + LOG_DEBUG(GwLog::DEBUG, "calcChrtBorders: 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); + + } else { // chart data is of any other type - } else { double oldRngMin = rngMin; double oldRngMax = rngMax; - // Chart starts at lowest range value, but at least '0' or includes even negative values + // calculate low end range value 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); + LOG_DEBUG(GwLog::DEBUG, "calcChrtBorders: currMinVal: %.1f, currMaxVal: %.1f, rngMin: %.1f, rngMid: %.1f, rngMax: %.1f, rng: %.1f, rngStep: %.1f, zeroValue: %.1f, oldRngMin: %.1f, oldRngMax: %.1f, dfltRng: %.1f, dbMIN_VAL: %.1f", + currMinVal, dataBuf.getMax(numBufVals), rngMin, rngMid, rngMax, rng, rngStep, zeroValue, oldRngMin, oldRngMax, dfltRng, dbMIN_VAL); 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; + + if (currMinVal < oldRngMin || (currMinVal > (oldRngMin + rngStep))) { // recalculate 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 + rngMin = currMinVal; + LOG_DEBUG(GwLog::DEBUG, "calcChrtBorders2: currMinVal: %.1f, rngMin: %.1f, oldRngMin: %.1f, zeroValue: %.1f", currMinVal, rngMin, oldRngMin, zeroValue); + } + if (rngMin > zeroValue && dbMIN_VAL <= zeroValue) { // Chart range starts at least at '0' if minimum data value allows it + rngMin = zeroValue; + LOG_DEBUG(GwLog::DEBUG, "calcChrtBorders3: currMinVal: %.1f, rngMin: %.1f, oldRngMin: %.1f, zeroValue: %.1f", currMinVal, rngMin, oldRngMin, zeroValue); } } // 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::ceil(currMaxVal / rngStep) * rngStep; + rngMax = currMaxVal; 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); + // LOG_DEBUG(GwLog::DEBUG, "calcChrtRange-end: 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); } } @@ -492,30 +470,39 @@ void Chart::drawChrtValAxis() { double slots; int i, intv; - double cVal, cChrtRng, crngMin; + double cVal, cChrtRng; 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()); - tmpBVal->valid = true; if (chrtDir == 'H') { - slots = valAxis / 60.0; // number of axis labels - tmpBVal->value = chrtRng; - cChrtRng = formatValue(tmpBVal.get(), *commonData).cvalue; // value (converted) - if (useSimuData) { - // cannot use in this case, because that would change the range value to some random data - cChrtRng = tmpBVal->value; // take SI value in this case -> need to be improved - } - intv = static_cast(round(cChrtRng / slots)); - i = intv; - if (chrtSz == 0) { // full size chart -> print multiple value lines + // adjust value range to user defined data format + if (chrtDataFmt == 'T') { + if (tempFormat == "F") { + cChrtRng = chrtRng * (9 / 5); + } else { + // data steps for Kelvin and Celsius are identical and '1' + cChrtRng = chrtRng; + } + } else { + // any other data format can be converted with standard rules + cChrtRng = convertValue(chrtRng, dataBuf.getFormat(), *commonData); + } + if (useSimuData) { + // cannot use in this case, because that would change the range value to some random data + cChrtRng = chrtRng; // take SI value in this case -> need to be improved + } + + slots = valAxis / 60.0; // number of axis labels + intv = static_cast(round(cChrtRng / slots)); + i = static_cast(convertValue(chrtMin, dataBuf.getFormat(), *commonData) + intv + 0.5); // convert and round lower chart value end + LOG_DEBUG(GwLog::DEBUG, "Chart::drawChrtValAxis: chrtRng: %.2f, cChrtRng: %.2f, slots: %.2f, intv: %d, chrtMin: %.2f, chrtMid: %.2f, chrtMax: %.2f", chrtRng, cChrtRng, slots, intv, chrtMin, chrtMid, chrtMax); + + if (chrtSz == 0 && chrtDataFmt != 'W') { // full size chart -> print multiple value lines getdisplay().setFont(&Ubuntu_Bold12pt8b); int loopStrt, loopEnd, loopStp; - if (chrtDataFmt == 'S') { + if (chrtDataFmt == 'S' || chrtDataFmt == 'T') { loopStrt = valAxis - 60; loopEnd = 30; loopStp = -60; @@ -535,23 +522,27 @@ void Chart::drawChrtValAxis() i += intv; } - } else { // half size chart -> print just edge values + middle chart line + + } else { // half size chart or degree values -> print just edge values + middle chart line + LOG_DEBUG(GwLog::DEBUG, "Chart::drawChrtValAxis: chrtDataFmt: %c, chrtMin: %.2f, chrtMid: %.2f, chrtMax: %.2f", chrtDataFmt, chrtMin, chrtMid, chrtMax); getdisplay().setFont(&Ubuntu_Bold10pt8b); - tmpBVal->value = (chrtDataFmt == 'D') ? chrtMin : chrtMax; - cVal = formatValue(tmpBVal.get(), *commonData).cvalue; // value (converted) + // cVal = (chrtDataFmt == 'D') ? chrtMin : chrtMax; + cVal = (chrtDataFmt == 'S' || chrtDataFmt == 'T') ? chrtMax : chrtMin; + cVal = convertValue(cVal, dataBuf.getFormat(), *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 = tmpBVal->value; // no value conversion here + // cVal = (chrtDataFmt == 'D') ? chrtMin : chrtMax; // no value conversion + cVal = (chrtDataFmt == 'S' || chrtDataFmt == 'T') ? chrtMax : chrtMin; // no value conversion } 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) + cVal = chrtMid; + cVal = convertValue(cVal, dataBuf.getFormat(), *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 = tmpBVal->value; // no value conversion here + cVal = chrtMid; // no value conversion } 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 @@ -559,10 +550,12 @@ void Chart::drawChrtValAxis() 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 = (chrtDataFmt == 'D') ? chrtMax : chrtMin; - cVal = formatValue(tmpBVal.get(), *commonData).cvalue; // value (converted) + // cVal = (chrtDataFmt == 'D') ? chrtMax : chrtMin; + cVal = (chrtDataFmt == 'S' || chrtDataFmt == 'T') ? chrtMin : chrtMax; + cVal = convertValue(cVal, dataBuf.getFormat(), *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 = tmpBVal->value; // no value conversion here + // cVal = (chrtDataFmt == 'D') ? chrtMax : chrtMin; // no value conversion + cVal = (chrtDataFmt == 'S' || chrtDataFmt == 'T') ? chrtMax : chrtMin; // no value conversion } 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 @@ -572,42 +565,43 @@ void Chart::drawChrtValAxis() } getdisplay().setFont(&Ubuntu_Bold12pt8b); - drawTextRalign(cStart.x + timAxis, cStart.y - 3, dbName); // buffer data name + drawTextRalign(cStart.x + timAxis, cStart.y - 3, dbName.substring(0, 4)); // buffer data name (max. size 4 characters) } 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 + drawTextRalign(cStart.x + (valAxis * 0.42), cStart.y - 2, dbName.substring(0, 6)); // buffer data name (max. size 5 characters) } else { getdisplay().setFont(&Ubuntu_Bold10pt8b); } getdisplay().fillRect(cStart.x, cStart.y, valAxis, 2, fgColor); // top chart line - tmpBVal->value = chrtMin; - cVal = formatValue(tmpBVal.get(), *commonData).cvalue; // value (converted) + cVal = chrtMin; + cVal = convertValue(cVal, dataBuf.getFormat(), *commonData); if (useSimuData) { // dirty fix for problem that OBP60Formatter can only be used without data simulation -> returns random values in simulation mode - cVal = tmpBVal->value; // no value conversion here + cVal = chrtMin; // no value conversion } snprintf(sVal, sizeof(sVal), "%.0f", round(cVal)); getdisplay().setCursor(cStart.x, cStart.y - 2); getdisplay().printf("%s", sVal); // Range low end - tmpBVal->value = chrtMid; - cVal = formatValue(tmpBVal.get(), *commonData).cvalue; // value (converted) + cVal = chrtMid; + cVal = convertValue(cVal, dataBuf.getFormat(), *commonData); if (useSimuData) { // dirty fix for problem that OBP60Formatter can only be used without data simulation -> returns random values in simulation mode - cVal = tmpBVal->value; // no value conversion here + cVal = chrtMid; // no value conversion } snprintf(sVal, sizeof(sVal), "%.0f", round(cVal)); - drawTextCenter(cStart.x + (valAxis / 2), cStart.y - 10, sVal); // Range mid end + drawTextCenter(cStart.x + (valAxis / 2), cStart.y - 9, sVal); // Range mid end - tmpBVal->value = chrtMax; - cVal = formatValue(tmpBVal.get(), *commonData).cvalue; // value (converted) + cVal = chrtMax; + cVal = convertValue(cVal, dataBuf.getFormat(), *commonData); if (useSimuData) { // dirty fix for problem that OBP60Formatter can only be used without data simulation -> returns random values in simulation mode - cVal = tmpBVal->value; // no value conversion here + cVal = chrtMax; // no value conversion } snprintf(sVal, sizeof(sVal), "%.0f", round(cVal)); drawTextRalign(cStart.x + valAxis - 2, cStart.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(cStart.x + j, cStart.y, cStart.x + j, cStart.y + timAxis, fgColor); } @@ -623,25 +617,61 @@ void Chart::prntCurrValue(GwApi::BoatValue& currValue) FormattedData frmtDbData = formatValue(&currValue, *commonData); String sdbValue = frmtDbData.svalue; // value as formatted string - String dbUnit = frmtDbData.unit; // Unit of value + 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); + // 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 getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b); getdisplay().setCursor(xPosVal + 1, yPosVal); - getdisplay().print(sdbValue); // alue + getdisplay().print(sdbValue); // value getdisplay().setFont(&Ubuntu_Bold10pt8b); getdisplay().setCursor(xPosVal + 76, yPosVal - 17); - getdisplay().print(dbName); // Name + getdisplay().print(dbName.substring(0, 3)); // Name, limited to 3 characters getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setCursor(xPosVal + 76, yPosVal + 0); getdisplay().print(dbUnit); // Unit } +// Get maximum difference of last of dataBuf ringbuffer values to center chart; for angle data only +template +double Chart::getAngleRng(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; + } + + if (maxRng > M_PI) { + maxRng = M_PI; + } + + return (maxRng != dbMIN_VAL ? maxRng : dbMAX_VAL); // Return range from to +} + // 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 7740f91..9ce3017 100644 --- a/lib/obp60task/OBPcharts.h +++ b/lib/obp60task/OBPcharts.h @@ -22,6 +22,8 @@ protected: 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 + String tempFormat; // user defined format for temperature + double zeroValue; // "0" SI value for temperature int top = 44; // chart gap at top of display (25 lines for standard gap + 19 lines for axis labels) int bottom = 25; // chart gap at bottom of display to keep space for status line @@ -50,30 +52,35 @@ 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 + int numNoData; // Counter for multiple invalid data values in a row bool bufDataValid = false; // Flag to indicate if buffer data is valid int oldChrtIntv = 0; // remember recent user selection of data interval + double chrtPrevVal; // Last data value in chart area + int x, y; // x and y coordinates for drawing + int prevX, prevY; // Last x and y coordinates for drawing + 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 calcChrtBorders(double& rngMin, double& rngMid, 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); // Add current boat data value to chart + double getAngleRng(double center, size_t amount); // Calculate range between chart center and edges public: // Define default chart range for each boat data type - static std::map dfltChartRng; + static std::map dfltChrtRng; Chart(RingBuffer& dataBuf, char chrtDir, int8_t chrtSz, double dfltRng, CommonData& common, bool useSimuData); // Chart object of data chart ~Chart(); - void showChrt(int8_t chrtIntv, GwApi::BoatValue currValue, bool showCurrValue); // Perform all actions to draw chart + void showChrt(GwApi::BoatValue currValue, int8_t chrtIntv, bool showCurrValue); // Perform all actions to draw chart }; template -std::map Chart::dfltChartRng = { +std::map Chart::dfltChrtRng = { { "formatWind", 60.0 * DEG_TO_RAD }, // default course range 60 degrees { "formatCourse", 60.0 * DEG_TO_RAD }, // default course range 60 degrees { "formatKnots", 5.1 }, // default speed range in m/s - { "formatDepth", 15 }, // default depth range in m - { "kelvinToC", 30 } // default temp range in °C/K + { "formatDepth", 15.0 }, // default depth range in m + { "kelvinToC", 30.0 } // default temp range in °C/K }; \ No newline at end of file diff --git a/lib/obp60task/PageOneValue.cpp b/lib/obp60task/PageOneValue.cpp index 8f1cc19..a9ce6ff 100644 --- a/lib/obp60task/PageOneValue.cpp +++ b/lib/obp60task/PageOneValue.cpp @@ -18,11 +18,12 @@ private: int 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 - String lengthformat; + //String lengthformat; bool useSimuData; bool holdValues; String flashLED; String backlightMode; + String tempFormat; // Old values for hold function String sValue1Old = ""; @@ -51,14 +52,14 @@ private: valueFnt3 = &DSEG7Classic_BoldItalic60pt7b; } else { // half size data and chart display nameXoff = 105; - nameYoff = -40; + nameYoff = -35; nameFnt = &Ubuntu_Bold20pt8b; - unitXoff = -33; - unitYoff = -40; + unitXoff = -35; + unitYoff = -102; unitFnt = &Ubuntu_Bold12pt8b; valueFnt1 = &Ubuntu_Bold12pt8b; value1Xoff = 105; - value1Yoff = -105; + value1Yoff = -102; valueFnt2 = &Ubuntu_Bold20pt8b; valueFnt3 = &DSEG7Classic_BoldItalic30pt7b; } @@ -79,11 +80,12 @@ private: // Show unit getdisplay().setFont(unitFnt); - getdisplay().setCursor(270 + unitXoff, 100 + unitYoff); + getdisplay().setCursor(305 + unitXoff, 240 + unitYoff); + if (holdValues) { - drawTextRalign(298 + unitXoff, 100 + unitYoff, unit1Old); + getdisplay().print(unit1Old); // name } else { - drawTextRalign(298 + unitXoff, 100 + unitYoff, unit1); // Unit + getdisplay().print(unit1); // name } // Switch font depending on value format and adjust position @@ -122,17 +124,23 @@ public: height = getdisplay().height(); // Screen height // Get config data - lengthformat = common.config->getString(common.config->lengthFormat); - useSimuData = common.config->getBool(common.config->useSimuData); - holdValues = common.config->getBool(common.config->holdvalues); - flashLED = common.config->getString(common.config->flashLED); - backlightMode = common.config->getString(common.config->backlight); + //lengthformat = commonData->config->getString(commonData->config->lengthFormat); + useSimuData = commonData->config->getBool(commonData->config->useSimuData); + holdValues = commonData->config->getBool(commonData->config->holdvalues); + flashLED = commonData->config->getString(commonData->config->flashLED); + backlightMode = commonData->config->getString(commonData->config->backlight); + tempFormat = commonData->config->getString(commonData->config->tempFormat); // [K|°C|°F] } virtual void setupKeys() { Page::setupKeys(); - commonData->keydata[0].label = "MODE"; + + if (dataHstryBuf) { // show "Mode" key only if chart supported boat data type is available + commonData->keydata[0].label = "MODE"; + } else { + commonData->keydata[0].label = ""; + } #if defined BOARD_OBP60S3 commonData->keydata[4].label = "ZOOM"; #elif defined BOARD_OBP40S3 @@ -192,8 +200,8 @@ public: setFlashLED(false); } #endif - // buffer initialization cannot be performed here, because is not executed at system start for default page - /* if (!dataFlChart) { // Create chart objects if they don't exist + // buffer initialization will fail, if page is default page, because is not executed at system start for default page + if (!dataFlChart) { // 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 @@ -201,21 +209,23 @@ public: dataHstryBuf = pageData.hstryBuffers->getBuffer(bValName1); if (dataHstryBuf) { - dataFlChart.reset(new Chart(*dataHstryBuf, 'H', 0, Chart::dfltChartRng[bValFormat], *commonData, useSimuData)); - dataHfChart.reset(new Chart(*dataHstryBuf, 'H', 2, Chart::dfltChartRng[bValFormat], *commonData, useSimuData)); + dataFlChart.reset(new Chart(*dataHstryBuf, 'H', 0, Chart::dfltChrtRng[bValFormat], *commonData, useSimuData)); + dataHfChart.reset(new Chart(*dataHstryBuf, 'H', 2, Chart::dfltChrtRng[bValFormat], *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); } - } */ + } + + setupKeys(); // adjust key depending on chart supported boat data type } int displayPage(PageData& pageData) { LOG_DEBUG(GwLog::LOG, "Display PageOneValue"); - GwConfigHandler* config = commonData->config; - GwLog* logger = commonData->logger; + //GwConfigHandler* config = commonData->config; + //GwLog* logger = commonData->logger; // Get boat value for page GwApi::BoatValue* bValue1 = pageData.values[0]; // Page boat data element @@ -226,7 +236,7 @@ public: setFlashLED(false); } - if (!dataFlChart) { // Create chart objects if they don't exist +/* if (!dataFlChart) { // 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 @@ -234,37 +244,37 @@ public: dataHstryBuf = pageData.hstryBuffers->getBuffer(bValName1); if (dataHstryBuf) { - dataFlChart.reset(new Chart(*dataHstryBuf, 'H', 0, Chart::dfltChartRng[bValFormat], *commonData, useSimuData)); - dataHfChart.reset(new Chart(*dataHstryBuf, 'H', 2, Chart::dfltChartRng[bValFormat], *commonData, useSimuData)); + dataFlChart.reset(new Chart(*dataHstryBuf, 'H', 0, Chart::dfltChrtRng[bValFormat], *commonData, useSimuData)); + dataHfChart.reset(new Chart(*dataHstryBuf, 'H', 2, Chart::dfltChrtRng[bValFormat], *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); } - } + } */ - // Logging boat values if (bValue1 == NULL) - return PAGE_OK; // WTF why this statement? + return PAGE_OK; // no data, no display of page - LOG_DEBUG(GwLog::DEBUG, "Drawing at PageOneValue, %s, %.3f", bValue1->getName().c_str(), bValue1->value); + LOG_DEBUG(GwLog::DEBUG, "PageOneValue: printing %s, %.3f", bValue1->getName().c_str(), bValue1->value); // Draw page //*********************************************************** getdisplay().setPartialWindow(0, 0, width, height); // Set partial update - if (pageMode == 'V') { // show only data value + if (pageMode == 'V' || dataHstryBuf == nullptr) { + // show only data value; ignore other pageMode options if no chart supported boat data history buffer is available showData(bValue1, 'F'); } else if (pageMode == 'C') { // show only data chart if (dataFlChart) { - dataFlChart->showChrt(dataIntv, *bValue1, true); + dataFlChart->showChrt(*bValue1, dataIntv, true); } } else if (pageMode == 'B') { // show data value and chart showData(bValue1, 'H'); if (dataHfChart) { - dataHfChart->showChrt(dataIntv, *bValue1, false); + dataHfChart->showChrt(*bValue1, dataIntv, false); } } diff --git a/lib/obp60task/PageWindPlot.cpp b/lib/obp60task/PageWindPlot.cpp index 088be08..50da88f 100644 --- a/lib/obp60task/PageWindPlot.cpp +++ b/lib/obp60task/PageWindPlot.cpp @@ -22,7 +22,7 @@ private: int 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 bool useSimuData; - //bool holdValues; + // bool holdValues; String flashLED; String backlightMode; @@ -65,7 +65,7 @@ public: // Get config data useSimuData = common.config->getBool(common.config->useSimuData); - //holdValues = common.config->getBool(common.config->holdvalues); + // holdValues = common.config->getBool(common.config->holdvalues); flashLED = common.config->getString(common.config->flashLED); backlightMode = common.config->getString(common.config->backlight); @@ -143,6 +143,7 @@ public: } #endif #ifdef BOARD_OBP40S3 + // we can only initialize user defined wind source here, because "pageData" is not available at object instantiation wndSrc = commonData->config->getString("page" + String(pageData.pageNumber) + "wndsrc"); if (wndSrc == "True wind") { showTruW = true; @@ -151,8 +152,8 @@ public: } oldShowTruW = !showTruW; // Force chart update in displayPage #endif - // buffer initialization cannot be performed here, because is not executed at system start for default page + // buffer initialization cannot be performed here, because is not executed at system start for default page /* if (!twdFlChart) { // Create true wind charts if they don't exist twdHstry = pageData.hstryBuffers->getBuffer("TWD"); twsHstry = pageData.hstryBuffers->getBuffer("TWS"); @@ -257,20 +258,20 @@ public: if (chrtMode == 'D') { if (wdFlChart) { - wdFlChart->showChrt(dataIntv, *wdBVal, true); + wdFlChart->showChrt(*wdBVal, dataIntv, true); } } else if (chrtMode == 'S') { if (wsFlChart) { - wsFlChart->showChrt(dataIntv, *wsBVal, true); + wsFlChart->showChrt(*wsBVal, dataIntv, true); } } else if (chrtMode == 'B') { if (wdHfChart) { - wdHfChart->showChrt(dataIntv, *wdBVal, true); + wdHfChart->showChrt(*wdBVal, dataIntv, true); } if (wsHfChart) { - wsHfChart->showChrt(dataIntv, *wsBVal, true); + wsHfChart->showChrt(*wsBVal, dataIntv, true); } } diff --git a/lib/obp60task/Pagedata.h b/lib/obp60task/Pagedata.h index 103082f..d6f342e 100644 --- a/lib/obp60task/Pagedata.h +++ b/lib/obp60task/Pagedata.h @@ -204,3 +204,6 @@ typedef struct{ // Formatter for boat values FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata); + +// Helper method for conversion of any data value from SI to user defined format (defined in OBP60Formatter) +double convertValue(const double &value, const String &format, CommonData &commondata); From 559042da785233fa7af65349597f793fdd73dfb1 Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Mon, 5 Jan 2026 23:19:12 +0100 Subject: [PATCH 08/13] Code rework for OBPcharts, part 1 --- lib/obp60task/OBP60Formatter.cpp | 13 +- lib/obp60task/OBPcharts.cpp | 673 ++++++++++++++++++------------- lib/obp60task/OBPcharts.h | 52 ++- lib/obp60task/PageOneValue.cpp | 28 +- lib/obp60task/PageWindPlot.cpp | 23 +- lib/obp60task/Pagedata.h | 3 + 6 files changed, 458 insertions(+), 334 deletions(-) diff --git a/lib/obp60task/OBP60Formatter.cpp b/lib/obp60task/OBP60Formatter.cpp index 830a846..06d3dc5 100644 --- a/lib/obp60task/OBP60Formatter.cpp +++ b/lib/obp60task/OBP60Formatter.cpp @@ -878,19 +878,26 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){ } // Helper method for conversion of boat data values from SI to user defined format -double convertValue(const double &value, const String &format, CommonData &commondata) +double convertValue(const double &value, const String &name, const String &format, CommonData &commondata) { std::unique_ptr tmpBValue; // Temp variable to get converted data value from double result; // data value converted to user defined target data format - // prepare dummy BoatValue structure for use in - tmpBValue = std::unique_ptr(new GwApi::BoatValue("dummy")); // we don't need boat value name for pure value conversion + // prepare temporary BoatValue structure for use in + tmpBValue = std::unique_ptr(new GwApi::BoatValue(name)); // we don't need boat value name for pure value conversion tmpBValue->setFormat(format); tmpBValue->valid = true; tmpBValue->value = value; result = formatValue(tmpBValue.get(), commondata).cvalue; // get value (converted) + return result; +} +double convertValue(const double &value, const String &format, CommonData &commondata) +{ + double result; // data value converted to user defined target data format + + result = convertValue(value, "dummy", format, commondata); return result; } diff --git a/lib/obp60task/OBPcharts.cpp b/lib/obp60task/OBPcharts.cpp index 3143273..9830653 100644 --- a/lib/obp60task/OBPcharts.cpp +++ b/lib/obp60task/OBPcharts.cpp @@ -1,6 +1,6 @@ // Function lib for display of boat data in various chart formats #include "OBPcharts.h" -#include "OBP60Extensions.h" +// #include "OBP60Extensions.h" #include "OBPDataOperations.h" #include "OBPRingBuffer.h" @@ -25,18 +25,6 @@ Chart::Chart(RingBuffer& dataBuf, char chrtDir, int8_t chrtSz, double dflt fgColor = commonData->fgcolor; bgColor = commonData->bgcolor; - // we need "0" value for any user defined temperature format - tempFormat = commonData->config->getString(commonData->config->tempFormat); // [K|°C|°F] - if (tempFormat == "K") { - zeroValue = 0.0; - } else if (tempFormat == "C") { - zeroValue = 273.15; - } else if (tempFormat == "F") { - zeroValue = 255.37; - } - // LOG_DEBUG(GwLog::DEBUG, "Chart-init: fgColor: %d, bgColor: %d, tempFormat: %s, zeroValue: %.1f, &commonData: %p", fgColor, bgColor, tempFormat, zeroValue, commonData); - - // LOG_DEBUG(GwLog::DEBUG, "Chart Init: Chart::dataBuf: %p, passed dataBuf: %p", (void*)&this->dataBuf, (void*)&dataBuf); dWidth = getdisplay().width(); dHeight = getdisplay().height(); @@ -46,18 +34,18 @@ Chart::Chart(RingBuffer& dataBuf, char chrtDir, int8_t chrtSz, double dflt switch (chrtSz) { case 0: valAxis = dHeight - top - bottom; - cStart = { 0, top - 1 }; + cRoot = { 0, top - 1 }; break; case 1: valAxis = (dHeight - top - bottom) / 2 - hGap; - cStart = { 0, top - 1 }; + cRoot = { 0, top - 1 }; break; case 2: valAxis = (dHeight - top - bottom) / 2 - hGap; - cStart = { 0, top + (valAxis + hGap) + hGap - 1 }; + cRoot = { 0, top + (valAxis + hGap) + hGap - 1 }; break; default: - LOG_DEBUG(GwLog::ERROR, "displayChart: wrong init parameter"); + LOG_DEBUG(GwLog::ERROR, "obp60:Chart %s: wrong init parameter", dataBuf.getName()); return; } @@ -67,22 +55,22 @@ Chart::Chart(RingBuffer& dataBuf, char chrtDir, int8_t chrtSz, double dflt switch (chrtSz) { case 0: valAxis = dWidth - 1; - cStart = { 0, top - 1 }; + cRoot = { 0, top - 1 }; break; case 1: valAxis = dWidth / 2 - vGap; - cStart = { 0, top - 1 }; + cRoot = { 0, top - 1 }; break; case 2: valAxis = dWidth / 2 - vGap; - cStart = { dWidth / 2 + vGap - 1, top - 1 }; + cRoot = { dWidth / 2 + vGap - 1, top - 1 }; break; default: - LOG_DEBUG(GwLog::ERROR, "displayChart: wrong init parameter"); + LOG_DEBUG(GwLog::ERROR, "obp60:Chart %s: wrong init parameter", dataBuf.getName()); return; } } else { - LOG_DEBUG(GwLog::ERROR, "displayChart: wrong init parameter"); + LOG_DEBUG(GwLog::ERROR, "obp60:Chart %s: wrong init parameter", dataBuf.getName()); return; } @@ -91,34 +79,55 @@ Chart::Chart(RingBuffer& dataBuf, char chrtDir, int8_t chrtSz, double dflt dbMAX_VAL = dataBuf.getMaxVal(); bufSize = dataBuf.getCapacity(); + // Initialize chart data format; shorter version of standard format indicator if (dbFormat == "formatCourse" || dbFormat == "formatWind" || dbFormat == "formatRot") { - - if (dbFormat == "formatRot") { - chrtDataFmt = 'R'; // Chart is showing data of rotational format - } else { - chrtDataFmt = 'W'; // 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 - + chrtDataFmt = 'W'; // Chart is showing data of course / wind format + } else if (dbFormat == "formatRot") { + chrtDataFmt = 'R'; // Chart is showing data of rotational format + } else if (dbFormat == "formatKnots") { + chrtDataFmt = 'S'; // Chart is showing data of speed or windspeed format + } else if (dbFormat == "formatDepth") { + chrtDataFmt = 'D'; // Chart ist showing data of format + } else if (dbFormat == "kelvinToC") { + chrtDataFmt = 'T'; // Chart ist showing data of format } else { - if (dbFormat == "formatDepth") { - chrtDataFmt = 'D'; // Chart ist showing data of format - } else if (dbFormat == "kelvinToC") { - chrtDataFmt = 'T'; // Chart ist showing data of format - } else { - chrtDataFmt = 'S'; // Chart is showing any other data format - } - rngStep = 10.0; // +/- 10 for all other values (eg. m/s, m, K, mBar) + chrtDataFmt = 'O'; // Chart is showing any other data format } - chrtMin = dbMIN_VAL; - chrtMax = dbMAX_VAL; - chrtMid = dbMAX_VAL; + // "0" value is the same for any data format but for user defined temperature format + zeroValue = 0.0; + if (chrtDataFmt == 'T') { + tempFormat = commonData->config->getString(commonData->config->tempFormat); // [K|°C|°F] + if (tempFormat == "K") { + zeroValue = 0.0; + } else if (tempFormat == "C") { + zeroValue = 273.15; + } else if (tempFormat == "F") { + zeroValue = 255.37; + } + } + + // Read default range and range step for this chart type + if (dfltChrtDta.count(dbFormat)) { + dfltRng = dfltChrtDta[dbFormat].range; + rngStep = dfltChrtDta[dbFormat].step; + } else { + dfltRng = 15.0; + rngStep = 5.0; + } + + //chrtMin = dbMIN_VAL; + //chrtMax = dbMAX_VAL; + //chrtMid = dbMAX_VAL; + // Initialize chart range values + chrtMin = zeroValue; + chrtMax = chrtMin + dfltRng; + chrtMid = (chrtMin + chrtMax) / 2; chrtRng = dfltRng; recalcRngCntr = true; // initialize and chart borders 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, chrtDataFmt: %c", - dWidth, dHeight, timAxis, valAxis, cStart.x, cStart.y, dbName, rngStep, chrtDataFmt); + 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", + dWidth, dHeight, timAxis, valAxis, cRoot.x, cRoot.y, dbName, rngStep, chrtDataFmt); }; template @@ -129,67 +138,34 @@ Chart::~Chart() // Perform all actions to draw chart // Parameters: chart time interval, current boat data value to be printed, current boat data shall be shown yes/no template -void Chart::showChrt(GwApi::BoatValue currValue, int8_t chrtIntv, bool showCurrValue) +void Chart::showChrt(GwApi::BoatValue currValue, int8_t& chrtIntv, const bool showCurrValue) { drawChrt(chrtIntv, currValue); drawChrtTimeAxis(chrtIntv); drawChrtValAxis(); - if (bufDataValid) { - if (showCurrValue) { - // uses BoatValue temp variable to format latest buffer value - // doesn't work unfortunately when 'simulation data' is active, because OBP60Formatter generates own simulation values in that case - currValue.value = dataBuf.getLast(); - currValue.valid = currValue.value != dbMAX_VAL; - Chart::prntCurrValue(currValue); - } + if (!bufDataValid) { // No valid data available + prntNoValidData(); + return; + } - } else { // No valid data available -> print message - getdisplay().setFont(&Ubuntu_Bold10pt8b); - - int pX, pY; - if (chrtDir == 'H') { - pX = cStart.x + (timAxis / 2); - pY = cStart.y + (valAxis / 2) - 10; - } else { - pX = cStart.x + (valAxis / 2); - pY = cStart.y + (timAxis / 2) - 10; - } - - getdisplay().fillRect(pX - 37, pY - 10, 78, 24, bgColor); // Clear area for message - drawTextCenter(pX, pY, "No data"); - LOG_DEBUG(GwLog::LOG, "Page chart: No valid data available"); + if (showCurrValue) { // shows latest value from history buffer; usually this should be the most current one + currValue.value = dataBuf.getLast(); + currValue.valid = currValue.value != dbMAX_VAL; + Chart::prntCurrValue(currValue); } } // draw chart template -void Chart::drawChrt(int8_t chrtIntv, GwApi::BoatValue& currValue) +void Chart::drawChrt(int8_t& chrtIntv, GwApi::BoatValue& currValue) { double chrtVal; // Current data value double chrtScl; // Scale for data values in pixels per value int x, y; // 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 - 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; - if (count == bufSize) { - bufStart = max(0, bufStart - numAddedBufVals); - } - } + getBufStartNSize(chrtIntv); // LOG_DEBUG(GwLog::DEBUG, "PageOneValue:drawChart: min: %.1f, mid: %.1f, max: %.1f, rng: %.1f", chrtMin, chrtMid, chrtMax, chrtRng); calcChrtBorders(chrtMin, chrtMid, chrtMax, chrtRng); @@ -204,7 +180,7 @@ void Chart::drawChrt(int8_t chrtIntv, GwApi::BoatValue& currValue) } 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 > THRESHOLD_NO_DATA) { // If more than 4 invalid values in a row, send message bufDataValid = false; } } else { @@ -222,23 +198,24 @@ void Chart::drawChrt(int8_t chrtIntv, GwApi::BoatValue& currValue) } else { if (chrtDir == 'H') { // horizontal chart - x = cStart.x + i; // Position in chart area + x = cRoot.x + i; // Position in chart area - if (chrtDataFmt == 'S' or chrtDataFmt == 'T') { // speed data format -> print low values at bottom - y = cStart.y + valAxis - static_cast(((chrtVal - chrtMin) * chrtScl) + 0.5); // calculate chart point and round - } else if (chrtDataFmt == 'D') { - 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 + 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 = cStart.y + timAxis - i; // Position in chart area + y = cRoot.y + timAxis - i; // Position in chart area - if (chrtDataFmt == 'S' || chrtDataFmt == 'D' || chrtDataFmt == 'T') { - 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 (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 } } @@ -261,45 +238,44 @@ void Chart::drawChrt(int8_t chrtIntv, GwApi::BoatValue& currValue) // 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 ? (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); + 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 ? (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); + int xSplit = wrappingFromHighToLow ? (cRoot.x + valAxis) : cRoot.x; + drawBoldLine(prevX, prevY, xSplit, y); + prevX = wrappingFromHighToLow ? cRoot.x : (cRoot.x + valAxis); } } } - // Draw line with 2 pixels width + make sure vertical lines are drawn correctly - if (chrtDir == 'H' || x == prevX) { // horizontal chart & vertical line - if (chrtDataFmt == 'D') { - getdisplay().drawLine(x, y, x, cStart.y + valAxis, fgColor); - getdisplay().drawLine(x - 1, y, x - 1, cStart.y + valAxis, fgColor); + 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); } - getdisplay().drawLine(prevX, prevY, x, y, fgColor); - getdisplay().drawLine(prevX - 1, prevY, x - 1, y, fgColor); - } else if (chrtDir == 'V' || x != prevX) { // vertical chart & line with some horizontal trend -> normal state - if (chrtDataFmt == 'D') { - getdisplay().drawLine(x, y, cStart.x + valAxis, y, fgColor); - getdisplay().drawLine(x, y - 1, cStart.x + valAxis, y - 1, fgColor); - } - getdisplay().drawLine(prevX, prevY, x, y, fgColor); - getdisplay().drawLine(prevX, prevY - 1, x, y - 1, fgColor); + } 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 bottom end + // 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 @@ -313,6 +289,30 @@ void Chart::drawChrt(int8_t chrtIntv, GwApi::BoatValue& currValue) } } +// Identify buffer size and buffer start position for chart +template +void Chart::getBufStartNSize(int8_t& chrtIntv) +{ + 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 + numBufVals = min(count, (timAxis - MIN_FREE_VALUES) * chrtIntv); // keep free or release MIN_FREE_VALUES on chart for plotting of new values + bufStart = max(0, count - numBufVals); + lastAddedIdx = currIdx; + oldChrtIntv = chrtIntv; + + } else { + numBufVals = numBufVals + numAddedBufVals; + lastAddedIdx = currIdx; + if (count == bufSize) { + bufStart = max(0, bufStart - numAddedBufVals); + } + } +} + // check and adjust chart range and set range borders and range middle template void Chart::calcChrtBorders(double& rngMin, double& rngMid, double& rngMax, double& rng) @@ -371,94 +371,90 @@ void Chart::calcChrtBorders(double& rngMin, double& rngMid, double& rngMax, d rngMax = WindUtils::to2PI(rngMax); rng = halfRng * 2.0; - LOG_DEBUG(GwLog::DEBUG, "calcChrtBorders: 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); + 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); } else { // chart data is of any other type double oldRngMin = rngMin; double oldRngMax = rngMax; - // calculate low end range value double currMinVal = dataBuf.getMin(numBufVals); - LOG_DEBUG(GwLog::DEBUG, "calcChrtBorders: currMinVal: %.1f, currMaxVal: %.1f, rngMin: %.1f, rngMid: %.1f, rngMax: %.1f, rng: %.1f, rngStep: %.1f, zeroValue: %.1f, oldRngMin: %.1f, oldRngMax: %.1f, dfltRng: %.1f, dbMIN_VAL: %.1f", - currMinVal, dataBuf.getMax(numBufVals), rngMin, rngMid, rngMax, rng, rngStep, zeroValue, oldRngMin, oldRngMax, dfltRng, dbMIN_VAL); - - if (currMinVal != dbMAX_VAL) { // current min value is valid - - if (currMinVal < oldRngMin || (currMinVal > (oldRngMin + rngStep))) { // recalculate 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 - rngMin = currMinVal; - LOG_DEBUG(GwLog::DEBUG, "calcChrtBorders2: currMinVal: %.1f, rngMin: %.1f, oldRngMin: %.1f, zeroValue: %.1f", currMinVal, rngMin, oldRngMin, zeroValue); - } - if (rngMin > zeroValue && dbMIN_VAL <= zeroValue) { // Chart range starts at least at '0' if minimum data value allows it - rngMin = zeroValue; - LOG_DEBUG(GwLog::DEBUG, "calcChrtBorders3: currMinVal: %.1f, rngMin: %.1f, oldRngMin: %.1f, zeroValue: %.1f", currMinVal, rngMin, oldRngMin, zeroValue); - } - } // 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 = currMaxVal; - rngMax = std::max(rngMax, rngMin + dfltRng); // keep at least default chart range - } - } // otherwise keep rngMax unchanged + + if (currMinVal == dbMAX_VAL || currMaxVal == dbMAX_VAL) { + return; // no valid data + } + + // 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 + 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 + rngMax = std::ceil(currMaxVal / rngStep) * rngStep; + } + + // Chart range starts at least at '0' if minimum data value allows it + if (rngMin > zeroValue && dbMIN_VAL <= zeroValue) { + rngMin = zeroValue; + } + + // ensure minimum chart range in user format + if ((rngMax - rngMin) < dfltRng) { + rngMax = rngMin + dfltRng; + } rngMid = (rngMin + rngMax) / 2.0; rng = rngMax - rngMin; - // LOG_DEBUG(GwLog::DEBUG, "calcChrtRange-end: currMinVal: %.1f, currMaxVal: %.1f, rngMin: %.1f, rngMid: %.1f, rngMax: %.1f, rng: %.1f, rngStep: %.1f, oldRngMin: %.1f, oldRngMax: %.1f, dfltRng: %.1f, numBufVals: %d", - // currMinVal, currMaxVal, rngMin, rngMid, rngMax, rng, rngStep, oldRngMin, oldRngMax, dfltRng, numBufVals); + + LOG_DEBUG(GwLog::DEBUG, "calcChrtRange-end: currMinVal: %.1f, currMaxVal: %.1f, rngMin: %.1f, rngMid: %.1f, rngMax: %.1f, rng: %.1f, rngStep: %.1f, zeroValue: %.1f, dbMIN_VAL: %.1f", + currMinVal, currMaxVal, rngMin, rngMid, rngMax, rng, rngStep, zeroValue, dbMIN_VAL); } } // chart time axis label + lines template -void Chart::drawChrtTimeAxis(int8_t chrtIntv) +void Chart::drawChrtTimeAxis(int8_t& chrtIntv) { - float slots, intv, i; + float axSlots, intv, i; char sTime[6]; int timeRng = chrtIntv * 4; // chart time interval: [1] 4 min., [2] 8 min., [3] 12 min., [4] 16 min., [8] 32 min. getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setTextColor(fgColor); - if (chrtDir == 'H') { // horizontal chart - getdisplay().fillRect(0, cStart.y, dWidth, 2, fgColor); + axSlots = 5; // number of axis labels + intv = timAxis / (axSlots - 1); // minutes per chart axis interval (interval is 1 less than axSlots) + i = timeRng; // Chart axis label start at -32, -16, -12, ... minutes - slots = 5; // number of axis labels - intv = timAxis / (slots - 1); // minutes per chart axis interval (interval is 1 less than slots) - i = timeRng; // Chart axis label start at -32, -16, -12, ... minutes + if (chrtDir == 'H') { // horizontal chart + 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 // draw text with appropriate offset int tOffset = j == 0 ? 13 : -4; snprintf(sTime, sizeof(sTime), "-%.0f", i); - 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 + drawTextCenter(cRoot.x + j + tOffset, cRoot.y - 8, sTime); + getdisplay().drawLine(cRoot.x + j, cRoot.y, cRoot.x + j, cRoot.y + 5, fgColor); // draw short vertical time mark i -= chrtIntv; } } else { // vertical chart - slots = 5; // number of axis labels - intv = timAxis / (slots - 1); // minutes per chart axis interval (interval is 1 less than slots) - i = timeRng; // Chart axis label start at -32, -16, -12, ... minutes for (float j = intv; j < timAxis - 1; j += intv) { // don't print time label at upper and lower end of time axis i -= chrtIntv; // we start not at top chart position snprintf(sTime, sizeof(sTime), "-%.0f", i); - getdisplay().drawLine(cStart.x, cStart.y + j, cStart.x + valAxis, cStart.y + j, fgColor); // Grid line + getdisplay().drawLine(cRoot.x, cRoot.y + j, cRoot.x + valAxis, cRoot.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().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 - drawTextCenter(dWidth / 2, cStart.y + j, sTime); // time value; print mid screen + drawTextCenter(dWidth / 2, cRoot.y + j, sTime); // time value; print mid screen } } } @@ -468,142 +464,75 @@ void Chart::drawChrtTimeAxis(int8_t chrtIntv) template void Chart::drawChrtValAxis() { - double slots; - int i, intv; - double cVal, cChrtRng; - char sVal[6]; - int sLen; + double axLabel; + double cVal; + // char sVal[6]; + + getdisplay().setTextColor(fgColor); if (chrtDir == 'H') { - // adjust value range to user defined data format - if (chrtDataFmt == 'T') { - if (tempFormat == "F") { - cChrtRng = chrtRng * (9 / 5); - } else { - // data steps for Kelvin and Celsius are identical and '1' - cChrtRng = chrtRng; - } - } else { - // any other data format can be converted with standard rules - cChrtRng = convertValue(chrtRng, dataBuf.getFormat(), *commonData); - } - if (useSimuData) { - // cannot use in this case, because that would change the range value to some random data - cChrtRng = chrtRng; // take SI value in this case -> need to be improved - } - - slots = valAxis / 60.0; // number of axis labels - intv = static_cast(round(cChrtRng / slots)); - i = static_cast(convertValue(chrtMin, dataBuf.getFormat(), *commonData) + intv + 0.5); // convert and round lower chart value end - LOG_DEBUG(GwLog::DEBUG, "Chart::drawChrtValAxis: chrtRng: %.2f, cChrtRng: %.2f, slots: %.2f, intv: %d, chrtMin: %.2f, chrtMid: %.2f, chrtMax: %.2f", chrtRng, cChrtRng, slots, intv, chrtMin, chrtMid, chrtMax); - - if (chrtSz == 0 && chrtDataFmt != 'W') { // full size chart -> print multiple value lines - getdisplay().setFont(&Ubuntu_Bold12pt8b); - - int loopStrt, loopEnd, loopStp; - if (chrtDataFmt == 'S' || chrtDataFmt == 'T') { - loopStrt = valAxis - 60; - loopEnd = 30; - loopStp = -60; - } else { - loopStrt = 60; - loopEnd = valAxis - 30; - loopStp = 60; - } - - for (int j = loopStrt; (loopStp > 0) ? (j < loopEnd) : (j > loopEnd); j += loopStp) { - 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 - - i += intv; - } - - } else { // half size chart or degree values -> print just edge values + middle chart line - LOG_DEBUG(GwLog::DEBUG, "Chart::drawChrtValAxis: chrtDataFmt: %c, chrtMin: %.2f, chrtMid: %.2f, chrtMax: %.2f", chrtDataFmt, chrtMin, chrtMid, chrtMax); - getdisplay().setFont(&Ubuntu_Bold10pt8b); - - // cVal = (chrtDataFmt == 'D') ? chrtMin : chrtMax; - cVal = (chrtDataFmt == 'S' || chrtDataFmt == 'T') ? chrtMax : chrtMin; - cVal = convertValue(cVal, dataBuf.getFormat(), *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 = (chrtDataFmt == 'D') ? chrtMin : chrtMax; // no value conversion - cVal = (chrtDataFmt == 'S' || chrtDataFmt == 'T') ? chrtMax : chrtMin; // no value conversion - } - 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 - - cVal = chrtMid; - cVal = convertValue(cVal, dataBuf.getFormat(), *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 - } - 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); - - // cVal = (chrtDataFmt == 'D') ? chrtMax : chrtMin; - cVal = (chrtDataFmt == 'S' || chrtDataFmt == 'T') ? chrtMin : chrtMax; - cVal = convertValue(cVal, dataBuf.getFormat(), *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 = (chrtDataFmt == 'D') ? chrtMax : chrtMin; // no value conversion - cVal = (chrtDataFmt == 'S' || chrtDataFmt == 'T') ? chrtMax : chrtMin; // no value conversion - } - 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().drawLine(cStart.x + 43, cStart.y + valAxis, cStart.x + timAxis, cStart.y + valAxis, fgColor); - } - + // print buffer data name on right hand side of time axis (max. size 5 characters) getdisplay().setFont(&Ubuntu_Bold12pt8b); - drawTextRalign(cStart.x + timAxis, cStart.y - 3, dbName.substring(0, 4)); // buffer data name (max. size 4 characters) + drawTextRalign(cRoot.x + timAxis, cRoot.y - 3, dbName.substring(0, 5)); + + if (chrtSz == 0) { // full size chart + + if (chrtDataFmt == 'W') { + prntHorizThreeValueAxisLabel(&Ubuntu_Bold12pt8b); + return; + } + + // for any other data formats print multiple axis value lines on full charts + prntHorizMultiValueAxisLabel(&Ubuntu_Bold12pt8b); + 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); + return; + } } else { // vertical chart - if (chrtSz == 0) { // full size chart -> use larger font - getdisplay().setFont(&Ubuntu_Bold12pt8b); - drawTextRalign(cStart.x + (valAxis * 0.42), cStart.y - 2, dbName.substring(0, 6)); // buffer data name (max. size 5 characters) + char sVal[6]; + + if (chrtSz == 0) { // full size chart + getdisplay().setFont(&Ubuntu_Bold12pt8b); // 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); + getdisplay().setFont(&Ubuntu_Bold10pt8b); // use smaller font } - getdisplay().fillRect(cStart.x, cStart.y, valAxis, 2, fgColor); // top chart line + getdisplay().fillRect(cRoot.x, cRoot.y, valAxis, 2, fgColor); // top chart line cVal = chrtMin; - cVal = convertValue(cVal, dataBuf.getFormat(), *commonData); + 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 } snprintf(sVal, sizeof(sVal), "%.0f", round(cVal)); - getdisplay().setCursor(cStart.x, cStart.y - 2); + getdisplay().setCursor(cRoot.x, cRoot.y - 2); getdisplay().printf("%s", sVal); // Range low end cVal = chrtMid; - cVal = convertValue(cVal, dataBuf.getFormat(), *commonData); + 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(cStart.x + (valAxis / 2), cStart.y - 9, sVal); // Range mid end + drawTextCenter(cRoot.x + (valAxis / 2), cRoot.y - 9, sVal); // Range mid end cVal = chrtMax; - cVal = convertValue(cVal, dataBuf.getFormat(), *commonData); + 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(cStart.x + valAxis - 2, cStart.y - 2, sVal); // Range high end + 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(cStart.x + j, cStart.y, cStart.x + j, cStart.y + timAxis, fgColor); + getdisplay().drawLine(cRoot.x + j, cRoot.y, cRoot.x + j, cRoot.y + timAxis, fgColor); } } } @@ -612,8 +541,8 @@ void Chart::drawChrtValAxis() template void Chart::prntCurrValue(GwApi::BoatValue& currValue) { - const int xPosVal = (chrtDir == 'H') ? cStart.x + (timAxis / 2) - 56 : cStart.x + 32; - const int yPosVal = (chrtDir == 'H') ? cStart.y + valAxis - 7 : cStart.y + timAxis - 7; + 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; FormattedData frmtDbData = formatValue(&currValue, *commonData); String sdbValue = frmtDbData.svalue; // value as formatted string @@ -636,6 +565,28 @@ void Chart::prntCurrValue(GwApi::BoatValue& currValue) getdisplay().print(dbUnit); // Unit } +// print message for no valid data availabletemplate +template +void Chart::prntNoValidData() +{ + int pX, pY; + + getdisplay().setFont(&Ubuntu_Bold10pt8b); + + if (chrtDir == 'H') { + pX = cRoot.x + (timAxis / 2); + pY = cRoot.y + (valAxis / 2) - 10; + } else { + pX = cRoot.x + (valAxis / 2); + pY = cRoot.y + (timAxis / 2) - 10; + } + + getdisplay().fillRect(pX - 37, pY - 10, 78, 24, bgColor); // Clear area for message + drawTextCenter(pX, pY, "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 template double Chart::getAngleRng(double center, size_t amount) @@ -672,6 +623,170 @@ 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 +template +void Chart::prntHorizThreeValueAxisLabel(const GFXfont* font) +{ + double axLabel; + double chrtMin, chrtMid, chrtMax; + int xOffset, yOffset; // offset for text position of x axis label for different font sizes + String sVal; + + if (font == &Ubuntu_Bold10pt8b) { + xOffset = 39; + yOffset = 15; + } else if (font == &Ubuntu_Bold12pt8b) { + xOffset = 51; + yOffset = 17; + } + getdisplay().setFont(font); + + // convert & round chart bottom+top label to next range step + chrtMin = convertValue(this->chrtMin, dbName, dbFormat, *commonData); + chrtMid = convertValue(this->chrtMid, dbName, dbFormat, *commonData); + chrtMax = convertValue(this->chrtMax, dbName, dbFormat, *commonData); + chrtMin = std::round(chrtMin * 100.0) / 100.0; + chrtMid = std::round(chrtMid * 100.0) / 100.0; + chrtMax = std::round(chrtMax * 100.0) / 100.0; + + // print top axis label + axLabel = (chrtDataFmt == 'S' || chrtDataFmt == 'T') ? chrtMax : chrtMin; + sVal = formatLabel(axLabel); + getdisplay().fillRect(cRoot.x, cRoot.y + 2, xOffset + 4, 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) - 9, xOffset + 4, 16, bgColor); // Clear small area to remove potential chart lines + drawTextRalign(cRoot.x + xOffset, cRoot.y + (valAxis / 2) + 5, sVal); // range value + getdisplay().drawLine(cRoot.x + xOffset + 4, cRoot.y + (valAxis / 2), cRoot.x + timAxis, cRoot.y + (valAxis / 2), fgColor); + + // print bottom axis label + axLabel = (chrtDataFmt == 'S' || chrtDataFmt == 'T') ? 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 + 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); +} + +// print horizontal axis label with multiple axis lines +template +void Chart::prntHorizMultiValueAxisLabel(const GFXfont* font) +{ + double chrtMin, chrtMax, chrtRng; + double axSlots, axIntv, axLabel; + int xOffset; // offset for text position of x axis label for different font sizes + String sVal; + + if (font == &Ubuntu_Bold10pt8b) { + xOffset = 38; + } else if (font == &Ubuntu_Bold12pt8b) { + xOffset = 50; + } + getdisplay().setFont(font); + + chrtMin = convertValue(this->chrtMin, dbName, dbFormat, *commonData); + // chrtMin = std::floor(chrtMin / rngStep) * rngStep; + chrtMin = std::round(chrtMin * 100.0) / 100.0; + chrtMax = convertValue(this->chrtMax, dbName, dbFormat, *commonData); + // chrtMax = std::ceil(chrtMax / rngStep) * rngStep; + chrtMax = std::round(chrtMax * 100.0) / 100.0; + chrtRng = std::round((chrtMax - chrtMin) * 100) / 100; + + axSlots = valAxis / static_cast(VALAXIS_STEP); // number of axis labels (and we want to have a double calculation, no integer) + axIntv = chrtRng / axSlots; + axLabel = chrtMin + axIntv; + 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') { + // High value at top + loopStrt = valAxis - VALAXIS_STEP; + loopEnd = VALAXIS_STEP / 2; + loopStp = VALAXIS_STEP * -1; + } else { + // Low value at top + loopStrt = VALAXIS_STEP; + loopEnd = valAxis - (VALAXIS_STEP / 2); + loopStp = VALAXIS_STEP; + } + + 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 + 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); + + axLabel += axIntv; + } +} + +// Draw chart line with thickness of 2px +template +void Chart::drawBoldLine(int16_t x1, int16_t y1, int16_t x2, int16_t y2) +{ + + int16_t dx = std::abs(x2 - x1); + int16_t dy = std::abs(y2 - y1); + + getdisplay().drawLine(x1, y1, x2, y2, fgColor); + + if (dx >= dy) { // line has horizontal tendency + getdisplay().drawLine(x1, y1 - 1, x2, y2 - 1, fgColor); + } else { // line has vertical tendency + getdisplay().drawLine(x1 - 1, y1, x2 - 1, y2, fgColor); + } +} + +// Convert and format current axis label to user defined format; helper function for easier handling of OBP60Formatter +template +String Chart::convNformatLabel(double label) +{ + GwApi::BoatValue tmpBVal(dbName); // temporary boat value for string formatter + String sVal; + + 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 + if (sVal.length() > 0 && sVal[0] == '!') { + sVal = sVal.substring(1); // cut leading "!" created at OBPFormatter for use with other font than 7SEG + } + + return sVal; +} + +// Format current axis label for printing w/o data format conversion (has been done earlier) +template +String Chart::formatLabel(const double& label) +{ + char sVal[11]; + + if (dbFormat == "formatCourse" || dbFormat == "formatWind") { + // Format 3 numbers with prefix zero + snprintf(sVal, sizeof(sVal), "%03.0f", label); + + } else if (dbFormat == "formatRot") { + if (label > -10 && label < 10) { + snprintf(sVal, sizeof(sVal), "%3.2f", label); + } else { + snprintf(sVal, sizeof(sVal), "%3.0f", label); + } + } + + else { + if (label < 10) { + snprintf(sVal, sizeof(sVal), "%3.1f", label); + } else { + snprintf(sVal, sizeof(sVal), "%3.0f", label); + } + } + + return String(sVal); +} + // 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 9ce3017..db62a3c 100644 --- a/lib/obp60task/OBPcharts.h +++ b/lib/obp60task/OBPcharts.h @@ -1,16 +1,24 @@ // Function lib for display of boat data in various graphical chart formats #pragma once #include "Pagedata.h" +#include "OBP60Extensions.h" struct Pos { int x; int y; }; -template class RingBuffer; +struct ChartProps { + double range; + double step; +}; + +template +class RingBuffer; class GwLog; -template class Chart { +template +class Chart { protected: CommonData* commonData; GwLog* logger; @@ -25,14 +33,14 @@ protected: String tempFormat; // user defined format for temperature double zeroValue; // "0" SI value for temperature + int dWidth; // Display width + int dHeight; // Display height int top = 44; // chart gap at top of display (25 lines for standard gap + 19 lines for axis labels) int bottom = 25; // chart gap at bottom of display to keep space for status line int hGap = 11; // gap between 2 horizontal charts; actual gap is 2x int vGap = 17; // gap between 2 vertical charts; actual gap is 2x - 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 + Pos cRoot; // start point of chart area double chrtRng; // Range of buffer values from min to max value double chrtMin; // Range low end value double chrtMax; // Range high end value @@ -60,27 +68,39 @@ protected: int x, y; // x and y coordinates for drawing int prevX, prevY; // Last x and y coordinates for drawing - void drawChrt(int8_t chrtIntv, GwApi::BoatValue& currValue); // Draw chart line + static constexpr int8_t MIN_FREE_VALUES = 60; + static constexpr int8_t THRESHOLD_NO_DATA = 3; + static constexpr int8_t VALAXIS_STEP = 60; + + void drawChrt(int8_t& chrtIntv, GwApi::BoatValue& currValue); // Draw chart line + void getBufStartNSize(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(int8_t chrtIntv); // Draw time axis of chart, value and lines + 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); // Add current boat data value to chart + void prntNoValidData(); // 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 + 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 for each boat data type - static std::map dfltChrtRng; + // 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(); - void showChrt(GwApi::BoatValue currValue, int8_t chrtIntv, bool showCurrValue); // Perform all actions to draw chart + void showChrt(GwApi::BoatValue currValue, int8_t& chrtIntv, bool showCurrValue); // Perform all actions to draw chart }; template -std::map Chart::dfltChrtRng = { - { "formatWind", 60.0 * DEG_TO_RAD }, // default course range 60 degrees - { "formatCourse", 60.0 * DEG_TO_RAD }, // default course range 60 degrees - { "formatKnots", 5.1 }, // default speed range in m/s - { "formatDepth", 15.0 }, // default depth range in m - { "kelvinToC", 30.0 } // default temp range in °C/K +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 }; \ No newline at end of file diff --git a/lib/obp60task/PageOneValue.cpp b/lib/obp60task/PageOneValue.cpp index a9ce6ff..57175f1 100644 --- a/lib/obp60task/PageOneValue.cpp +++ b/lib/obp60task/PageOneValue.cpp @@ -15,8 +15,8 @@ private: bool keylock = false; // Keylock char pageMode = 'V'; // Page mode: 'V' for value, 'C' for chart, 'B' for both - int 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 + 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 //String lengthformat; bool useSimuData; @@ -209,8 +209,8 @@ public: dataHstryBuf = pageData.hstryBuffers->getBuffer(bValName1); if (dataHstryBuf) { - dataFlChart.reset(new Chart(*dataHstryBuf, 'H', 0, Chart::dfltChrtRng[bValFormat], *commonData, useSimuData)); - dataHfChart.reset(new Chart(*dataHstryBuf, 'H', 2, Chart::dfltChrtRng[bValFormat], *commonData, useSimuData)); + dataFlChart.reset(new Chart(*dataHstryBuf, 'H', 0, 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); @@ -224,8 +224,6 @@ public: { LOG_DEBUG(GwLog::LOG, "Display PageOneValue"); - //GwConfigHandler* config = commonData->config; - //GwLog* logger = commonData->logger; // Get boat value for page GwApi::BoatValue* bValue1 = pageData.values[0]; // Page boat data element @@ -236,24 +234,8 @@ public: setFlashLED(false); } -/* if (!dataFlChart) { // 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 - - dataHstryBuf = pageData.hstryBuffers->getBuffer(bValName1); - - if (dataHstryBuf) { - dataFlChart.reset(new Chart(*dataHstryBuf, 'H', 0, Chart::dfltChrtRng[bValFormat], *commonData, useSimuData)); - dataHfChart.reset(new Chart(*dataHstryBuf, 'H', 2, Chart::dfltChrtRng[bValFormat], *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); - } - } */ - if (bValue1 == NULL) - return PAGE_OK; // no data, no display of page + return PAGE_OK; // no data, no page to display LOG_DEBUG(GwLog::DEBUG, "PageOneValue: printing %s, %.3f", bValue1->getName().c_str(), bValue1->value); diff --git a/lib/obp60task/PageWindPlot.cpp b/lib/obp60task/PageWindPlot.cpp index 50da88f..49d11d3 100644 --- a/lib/obp60task/PageWindPlot.cpp +++ b/lib/obp60task/PageWindPlot.cpp @@ -19,8 +19,8 @@ private: 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: - // (1)|(2)|(3)|(4)|(8) x 240 seconds for 4, 8, 12, 16, 32 min. history chart + 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 bool useSimuData; // bool holdValues; String flashLED; @@ -50,9 +50,6 @@ private: GwApi::BoatValue* wdBVal = nullptr; GwApi::BoatValue* wsBVal = nullptr; - const double dfltRngWd = 60.0 * DEG_TO_RAD; // default range for course chart from min to max value in RAD - const double dfltRngWs = 7.5; // default range for wind speed chart from min to max value in m/s - public: PageWindPlot(CommonData& common) { @@ -198,12 +195,12 @@ public: twsHstry = pageData.hstryBuffers->getBuffer("TWS"); if (twdHstry) { - twdFlChart.reset(new Chart(*twdHstry, 'V', 0, dfltRngWd, *commonData, useSimuData)); - twdHfChart.reset(new Chart(*twdHstry, 'V', 1, dfltRngWd, *commonData, useSimuData)); + twdFlChart.reset(new Chart(*twdHstry, 'V', 0, Chart::dfltChrtDta["formatCourse"].range, *commonData, useSimuData)); + twdHfChart.reset(new Chart(*twdHstry, 'V', 1, Chart::dfltChrtDta["formatCourse"].range, *commonData, useSimuData)); } if (twsHstry) { - twsFlChart.reset(new Chart(*twsHstry, 'H', 0, dfltRngWs, *commonData, useSimuData)); - twsHfChart.reset(new Chart(*twsHstry, 'V', 2, dfltRngWs, *commonData, useSimuData)); + twsFlChart.reset(new Chart(*twsHstry, 'H', 0, Chart::dfltChrtDta["formatKnots"].range, *commonData, useSimuData)); + twsHfChart.reset(new Chart(*twsHstry, 'V', 2, Chart::dfltChrtDta["formatKnots"].range, *commonData, useSimuData)); } } @@ -212,12 +209,12 @@ public: awsHstry = pageData.hstryBuffers->getBuffer("AWS"); if (awdHstry) { - awdFlChart.reset(new Chart(*awdHstry, 'V', 0, dfltRngWd, *commonData, useSimuData)); - awdHfChart.reset(new Chart(*awdHstry, 'V', 1, dfltRngWd, *commonData, useSimuData)); + awdFlChart.reset(new Chart(*awdHstry, 'V', 0, Chart::dfltChrtDta["formatCourse"].range, *commonData, useSimuData)); + awdHfChart.reset(new Chart(*awdHstry, 'V', 1, Chart::dfltChrtDta["formatCourse"].range, *commonData, useSimuData)); } if (awsHstry) { - awsFlChart.reset(new Chart(*awsHstry, 'H', 0, dfltRngWs, *commonData, useSimuData)); - awsHfChart.reset(new Chart(*awsHstry, 'V', 2, dfltRngWs, *commonData, useSimuData)); + awsFlChart.reset(new Chart(*awsHstry, 'H', 0, Chart::dfltChrtDta["formatKnots"].range, *commonData, useSimuData)); + awsHfChart.reset(new Chart(*awsHstry, 'V', 2, Chart::dfltChrtDta["formatKnots"].range, *commonData, useSimuData)); } if (twdHstry && twsHstry && awdHstry && awsHstry) { LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: Created wind charts"); diff --git a/lib/obp60task/Pagedata.h b/lib/obp60task/Pagedata.h index d6f342e..79d1ad9 100644 --- a/lib/obp60task/Pagedata.h +++ b/lib/obp60task/Pagedata.h @@ -207,3 +207,6 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata); // Helper method for conversion of any data value from SI to user defined format (defined in OBP60Formatter) double convertValue(const double &value, const String &format, CommonData &commondata); +double convertValue(const double &value, const String &name, const String &format, CommonData &commondata); +// Helper method for conversion of boat data values from user defined format to SI (defined in OBP60Formatter) +double convertToSItemp(const double &value, const String &name, const String &format, CommonData &commondata); From 2d4f49659d4daa7023841b0fc0dd076f82cc9de5 Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Tue, 6 Jan 2026 22:57:07 +0100 Subject: [PATCH 09/13] Code rework for OBPcharts, part 2 --- lib/obp60task/OBPcharts.cpp | 237 ++++++++++++++++++++------------- lib/obp60task/OBPcharts.h | 34 ++--- lib/obp60task/PageOneValue.cpp | 94 +++++++------ lib/obp60task/PageWindPlot.cpp | 140 ++++++++++--------- 4 files changed, 273 insertions(+), 232 deletions(-) diff --git a/lib/obp60task/OBPcharts.cpp b/lib/obp60task/OBPcharts.cpp index 9830653..2e4aaea 100644 --- a/lib/obp60task/OBPcharts.cpp +++ b/lib/obp60task/OBPcharts.cpp @@ -4,6 +4,15 @@ #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 +}; + // --- Class Chart --------------- // Chart - object holding the actual chart, incl. data buffer and format definition @@ -12,11 +21,11 @@ // 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 -template -Chart::Chart(RingBuffer& dataBuf, char chrtDir, int8_t chrtSz, double dfltRng, CommonData& common, bool useSimuData) +// 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) + //, chrtDir(chrtDir) + //, chrtSz(chrtSz) , dfltRng(dfltRng) , commonData(&common) , useSimuData(useSimuData) @@ -28,51 +37,51 @@ Chart::Chart(RingBuffer& dataBuf, char chrtDir, int8_t chrtSz, double dflt 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; - } + /* 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: + } 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; - } - } else { - LOG_DEBUG(GwLog::ERROR, "obp60:Chart %s: wrong init parameter", dataBuf.getName()); - return; - } + } */ dataBuf.getMetaData(dbName, dbFormat); dbMIN_VAL = dataBuf.getMinVal(); @@ -116,9 +125,6 @@ Chart::Chart(RingBuffer& dataBuf, char chrtDir, int8_t chrtSz, double dflt rngStep = 5.0; } - //chrtMin = dbMIN_VAL; - //chrtMax = dbMAX_VAL; - //chrtMid = dbMAX_VAL; // Initialize chart range values chrtMin = zeroValue; chrtMax = chrtMin + dfltRng; @@ -130,35 +136,93 @@ Chart::Chart(RingBuffer& dataBuf, char chrtDir, int8_t chrtSz, double dflt dWidth, dHeight, timAxis, valAxis, cRoot.x, cRoot.y, dbName, rngStep, chrtDataFmt); }; -template -Chart::~Chart() +Chart::~Chart() { } // Perform all actions to draw chart -// Parameters: chart time interval, current boat data value to be printed, current boat data shall be shown yes/no -template -void Chart::showChrt(GwApi::BoatValue currValue, int8_t& chrtIntv, const bool showCurrValue) +// 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) { - drawChrt(chrtIntv, currValue); - drawChrtTimeAxis(chrtIntv); - drawChrtValAxis(); + // 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); if (!bufDataValid) { // No valid data available - prntNoValidData(); + prntNoValidData(chrtDir); return; } - if (showCurrValue) { // shows latest value from history buffer; usually this should be the most current one + if (showCurrValue) { // show latest value from history buffer; usually this should be the most current one currValue.value = dataBuf.getLast(); currValue.valid = currValue.value != dbMAX_VAL; - Chart::prntCurrValue(currValue); + prntCurrValue(chrtDir, currValue); } } +// 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)) { + LOG_DEBUG(GwLog::ERROR, "obp60:setChartDimensions %s: wrong parameters", dataBuf.getName()); + return false; + } + + if (direction == 'H') { + // horizontal chart timeline direction + timAxis = dWidth - 1; + switch (size) { + 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; + } + + } else if (direction == 'V') { + // vertical chart timeline direction + timAxis = dHeight - top - bottom; + switch (size) { + 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; + } + } + LOG_DEBUG(GwLog::ERROR, "obp60:setChartDimensions %s: direction: %c, size: %d, dWidth: %d, dHeight: %d, timAxis: %d, valAxis: %d, cRoot{%d, %d}, top: %d, bottom: %d, hGap: %d, vGap: %d", + dataBuf.getName(), direction, size, dWidth, dHeight, timAxis, valAxis, cRoot.x, cRoot.y, top, bottom, hGap, vGap); + return true; +} + // draw chart -template -void Chart::drawChrt(int8_t& chrtIntv, GwApi::BoatValue& currValue) +void Chart::drawChrt(const char chrtDir, int8_t& chrtIntv, GwApi::BoatValue& currValue) { double chrtVal; // Current data value double chrtScl; // Scale for data values in pixels per value @@ -167,9 +231,9 @@ void Chart::drawChrt(int8_t& chrtIntv, GwApi::BoatValue& currValue) getBufStartNSize(chrtIntv); - // LOG_DEBUG(GwLog::DEBUG, "PageOneValue:drawChart: min: %.1f, mid: %.1f, max: %.1f, rng: %.1f", chrtMin, chrtMid, chrtMax, chrtRng); + // 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, "PageOneValue:drawChart2: min: %.1f, mid: %.1f, max: %.1f, rng: %.1f", 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; @@ -290,8 +354,7 @@ void Chart::drawChrt(int8_t& chrtIntv, GwApi::BoatValue& currValue) } // Identify buffer size and buffer start position for chart -template -void Chart::getBufStartNSize(int8_t& chrtIntv) +void Chart::getBufStartNSize(int8_t& chrtIntv) { count = dataBuf.getCurrentSize(); currIdx = dataBuf.getLastIdx(); @@ -314,8 +377,7 @@ void Chart::getBufStartNSize(int8_t& chrtIntv) } // check and adjust chart range and set range borders and range middle -template -void Chart::calcChrtBorders(double& rngMin, double& rngMid, double& rngMax, double& rng) +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' @@ -413,8 +475,7 @@ void Chart::calcChrtBorders(double& rngMin, double& rngMid, double& rngMax, d } // chart time axis label + lines -template -void Chart::drawChrtTimeAxis(int8_t& chrtIntv) +void Chart::drawChrtTimeAxis(const char chrtDir, const int8_t chrtSz, int8_t& chrtIntv) { float axSlots, intv, i; char sTime[6]; @@ -461,8 +522,7 @@ void Chart::drawChrtTimeAxis(int8_t& chrtIntv) } // chart value axis labels + lines -template -void Chart::drawChrtValAxis() +void Chart::drawChrtValAxis(const char chrtDir, const int8_t chrtSz) { double axLabel; double cVal; @@ -538,8 +598,7 @@ void Chart::drawChrtValAxis() } // Print current data value -template -void Chart::prntCurrValue(GwApi::BoatValue& currValue) +void Chart::prntCurrValue(const char chrtDir, 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; @@ -566,8 +625,7 @@ void Chart::prntCurrValue(GwApi::BoatValue& currValue) } // print message for no valid data availabletemplate -template -void Chart::prntNoValidData() +void Chart::prntNoValidData(const char chrtDir) { int pX, pY; @@ -588,8 +646,7 @@ void Chart::prntNoValidData() } // Get maximum difference of last of dataBuf ringbuffer values to center chart; for angle data only -template -double Chart::getAngleRng(double center, size_t amount) +double Chart::getAngleRng(double center, size_t amount) { size_t count = dataBuf.getCurrentSize(); @@ -624,8 +681,7 @@ double Chart::getAngleRng(double center, size_t amount) } // print horizontal axis label with only three values: top, mid, and bottom -template -void Chart::prntHorizThreeValueAxisLabel(const GFXfont* font) +void Chart::prntHorizThreeValueAxisLabel(const GFXfont* font) { double axLabel; double chrtMin, chrtMid, chrtMax; @@ -637,7 +693,7 @@ void Chart::prntHorizThreeValueAxisLabel(const GFXfont* font) yOffset = 15; } else if (font == &Ubuntu_Bold12pt8b) { xOffset = 51; - yOffset = 17; + yOffset = 18; } getdisplay().setFont(font); @@ -658,8 +714,8 @@ void Chart::prntHorizThreeValueAxisLabel(const GFXfont* font) // print mid axis label axLabel = chrtMid; sVal = formatLabel(axLabel); - getdisplay().fillRect(cRoot.x, cRoot.y + (valAxis / 2) - 9, xOffset + 4, 16, bgColor); // Clear small area to remove potential chart lines - drawTextRalign(cRoot.x + xOffset, cRoot.y + (valAxis / 2) + 5, sVal); // range value + getdisplay().fillRect(cRoot.x, cRoot.y + (valAxis / 2) - 8, xOffset + 4, 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); // print bottom axis label @@ -671,8 +727,7 @@ void Chart::prntHorizThreeValueAxisLabel(const GFXfont* font) } // print horizontal axis label with multiple axis lines -template -void Chart::prntHorizMultiValueAxisLabel(const GFXfont* font) +void Chart::prntHorizMultiValueAxisLabel(const GFXfont* font) { double chrtMin, chrtMax, chrtRng; double axSlots, axIntv, axLabel; @@ -724,8 +779,7 @@ void Chart::prntHorizMultiValueAxisLabel(const GFXfont* font) } // Draw chart line with thickness of 2px -template -void Chart::drawBoldLine(int16_t x1, int16_t y1, int16_t x2, int16_t y2) +void Chart::drawBoldLine(int16_t x1, int16_t y1, int16_t x2, int16_t y2) { int16_t dx = std::abs(x2 - x1); @@ -741,8 +795,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 -template -String Chart::convNformatLabel(double label) +String Chart::convNformatLabel(double label) { GwApi::BoatValue tmpBVal(dbName); // temporary boat value for string formatter String sVal; @@ -759,8 +812,7 @@ String Chart::convNformatLabel(double label) } // Format current axis label for printing w/o data format conversion (has been done earlier) -template -String Chart::formatLabel(const double& label) +String Chart::formatLabel(const double& label) { char sVal[11]; @@ -786,7 +838,4 @@ String Chart::formatLabel(const double& label) return String(sVal); } - -// 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 db62a3c..c6d24b2 100644 --- a/lib/obp60task/OBPcharts.h +++ b/lib/obp60task/OBPcharts.h @@ -17,15 +17,14 @@ template class RingBuffer; class GwLog; -template class Chart { protected: CommonData* commonData; GwLog* logger; - 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 + RingBuffer& dataBuf; // Buffer to display + //char chrtDir; // Chart timeline direction: 'H' = horizontal, 'V' = vertical + //int8_t chrtSz; // Chart size: [0] = full size, [1] = half size left/top, [2] half size right/bottom double dfltRng; // Default range of chart, e.g. 30 = [0..30] uint16_t fgColor; // color code for any screen writing uint16_t bgColor; // color code for screen background @@ -72,13 +71,14 @@ protected: static constexpr int8_t THRESHOLD_NO_DATA = 3; static constexpr int8_t VALAXIS_STEP = 60; - void drawChrt(int8_t& chrtIntv, GwApi::BoatValue& currValue); // Draw chart line + 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 calcChrtBorders(double& rngMin, double& rngMid, 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); // Add current boat data value to chart - void prntNoValidData(); // print message for no valid data available + 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 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 @@ -90,17 +90,9 @@ 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, 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(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 }; - -template -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 -}; \ No newline at end of file diff --git a/lib/obp60task/PageOneValue.cpp b/lib/obp60task/PageOneValue.cpp index 57175f1..59a02bf 100644 --- a/lib/obp60task/PageOneValue.cpp +++ b/lib/obp60task/PageOneValue.cpp @@ -2,8 +2,8 @@ #include "Pagedata.h" #include "OBP60Extensions.h" -#include "OBPDataOperations.h" #include "BoatDataCalibration.h" +#include "OBPDataOperations.h" #include "OBPcharts.h" class PageOneValue : public Page { @@ -18,7 +18,7 @@ private: 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 - //String lengthformat; + // String lengthformat; bool useSimuData; bool holdValues; String flashLED; @@ -31,7 +31,7 @@ private: // Data buffer pointer (owned by HstryBuffers) RingBuffer* dataHstryBuf = nullptr; - std::unique_ptr> dataFlChart, dataHfChart; // Chart object, full and half size + std::unique_ptr dataChart; // Chart object, full and half size void showData(GwApi::BoatValue* bValue1, char size) { @@ -124,7 +124,7 @@ public: height = getdisplay().height(); // Screen height // Get config data - //lengthformat = commonData->config->getString(commonData->config->lengthFormat); + // lengthformat = commonData->config->getString(commonData->config->lengthFormat); useSimuData = commonData->config->getBool(commonData->config->useSimuData); holdValues = commonData->config->getBool(commonData->config->holdvalues); flashLED = commonData->config->getString(commonData->config->flashLED); @@ -136,51 +136,57 @@ public: { Page::setupKeys(); +#if defined BOARD_OBP60S3 + constexpr int ZOOM_IDX = 4; +#elif defined BOARD_OBP40S3 + constexpr int ZOOM_IDX = 1; +#endif + if (dataHstryBuf) { // show "Mode" key only if chart supported boat data type is available commonData->keydata[0].label = "MODE"; + commonData->keydata[ZOOM_IDX].label = "ZOOM"; } else { commonData->keydata[0].label = ""; + commonData->keydata[ZOOM_IDX].label = ""; } -#if defined BOARD_OBP60S3 - commonData->keydata[4].label = "ZOOM"; -#elif defined BOARD_OBP40S3 - commonData->keydata[1].label = "ZOOM"; -#endif } // Key functions virtual int handleKey(int key) { - // Set page mode value | full chart | value/half chart - if (key == 1) { - if (pageMode == 'V') { - pageMode = 'C'; - } else if (pageMode == 'C') { - pageMode = 'B'; - } else { - pageMode = 'V'; - } - return 0; // Commit the key - } + if (dataHstryBuf) { // if boat data type supports charts - // Set interval for history chart update time (interval) -#if defined BOARD_OBP60S3 - if (key == 5) { -#elif defined BOARD_OBP40S3 - if (key == 2) { -#endif - if (dataIntv == 1) { - dataIntv = 2; - } else if (dataIntv == 2) { - dataIntv = 3; - } else if (dataIntv == 3) { - dataIntv = 4; - } else if (dataIntv == 4) { - dataIntv = 8; - } else { - dataIntv = 1; + // Set page mode value | full chart | value/half chart + if (key == 1) { + if (pageMode == 'V') { + pageMode = 'C'; + } else if (pageMode == 'C') { + pageMode = 'B'; + } else { + pageMode = 'V'; + } + return 0; // Commit the key + } + + // Set time frame to show for history chart +#if defined BOARD_OBP60S3 + if (key == 5) { +#elif defined BOARD_OBP40S3 + if (key == 2) { +#endif + if (dataIntv == 1) { + dataIntv = 2; + } else if (dataIntv == 2) { + dataIntv = 3; + } else if (dataIntv == 3) { + dataIntv = 4; + } else if (dataIntv == 4) { + dataIntv = 8; + } else { + dataIntv = 1; + } + return 0; // Commit the key } - return 0; // Commit the key } // Keylock function @@ -201,7 +207,7 @@ public: } #endif // buffer initialization will fail, if page is default page, because is not executed at system start for default page - if (!dataFlChart) { // Create chart objects if they don't exist + 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 @@ -209,8 +215,8 @@ public: dataHstryBuf = pageData.hstryBuffers->getBuffer(bValName1); if (dataHstryBuf) { - dataFlChart.reset(new Chart(*dataHstryBuf, 'H', 0, Chart::dfltChrtDta[bValFormat].range, *commonData, useSimuData)); - dataHfChart.reset(new Chart(*dataHstryBuf, 'H', 2, Chart::dfltChrtDta[bValFormat].range, *commonData, useSimuData)); + 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); @@ -249,14 +255,14 @@ public: showData(bValue1, 'F'); } else if (pageMode == 'C') { // show only data chart - if (dataFlChart) { - dataFlChart->showChrt(*bValue1, dataIntv, true); + if (dataChart) { + dataChart->showChrt('H', 0, dataIntv, true, *bValue1); } } else if (pageMode == 'B') { // show data value and chart showData(bValue1, 'H'); - if (dataHfChart) { - dataHfChart->showChrt(*bValue1, dataIntv, false); + if (dataChart) { + dataChart->showChrt('H', 2, dataIntv, false, *bValue1); } } diff --git a/lib/obp60task/PageWindPlot.cpp b/lib/obp60task/PageWindPlot.cpp index 49d11d3..d90bab9 100644 --- a/lib/obp60task/PageWindPlot.cpp +++ b/lib/obp60task/PageWindPlot.cpp @@ -11,6 +11,15 @@ 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'; + 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; + int width; // Screen width int height; // Screen height @@ -37,16 +46,12 @@ private: RingBuffer* awsHstry = nullptr; // Chart objects - std::unique_ptr> twdFlChart, awdFlChart; // Chart object for wind direction, full size - std::unique_ptr> twsFlChart, awsFlChart; // Chart object for wind speed, full size - std::unique_ptr> twdHfChart, awdHfChart; // Chart object for wind direction, half size - std::unique_ptr> twsHfChart, awsHfChart; // Chart object for wind speed, half size + std::unique_ptr twdChart, awdChart; // Chart object for wind direction, full size + std::unique_ptr twsChart, awsChart; // Chart object for wind speed, full size // Active charts and values - Chart* wdFlChart = nullptr; - Chart* wsFlChart = nullptr; - Chart* wdHfChart = nullptr; - Chart* wsHfChart = nullptr; + Chart* wdChart = nullptr; + Chart* wsChart = nullptr; GwApi::BoatValue* wdBVal = nullptr; GwApi::BoatValue* wsBVal = nullptr; @@ -86,12 +91,12 @@ public: { // Set chart mode TWD | TWS if (key == 1) { - if (chrtMode == 'D') { - chrtMode = 'S'; - } else if (chrtMode == 'S') { - chrtMode = 'B'; + if (chrtMode == SHOW_WIND_DIR) { + chrtMode = SHOW_WIND_SPEED; + } else if (chrtMode == SHOW_WIND_SPEED) { + chrtMode = SHOW_BOTH; } else { - chrtMode = 'D'; + chrtMode = SHOW_WIND_DIR; } return 0; // Commit the key } @@ -150,39 +155,36 @@ public: oldShowTruW = !showTruW; // Force chart update in displayPage #endif - // buffer initialization cannot be performed here, because is not executed at system start for default page - /* if (!twdFlChart) { // Create true wind charts if they don't exist + // With chart object initialization being performed here, PageWindPlot won't properly work as default page, + // because is not executed at system start for default page + if (!twdChart) { // Create true wind charts if they don't exist twdHstry = pageData.hstryBuffers->getBuffer("TWD"); twsHstry = pageData.hstryBuffers->getBuffer("TWS"); if (twdHstry) { - twdFlChart.reset(new Chart(*twdHstry, 'V', 0, dfltRngWd, *commonData, useSimuData)); - twdHfChart.reset(new Chart(*twdHstry, 'V', 1, dfltRngWd, *commonData, useSimuData)); + twdChart.reset(new Chart(*twdHstry, Chart::dfltChrtDta["formatCourse"].range, *commonData, useSimuData)); } if (twsHstry) { - twsFlChart.reset(new Chart(*twsHstry, 'H', 0, dfltRngWs, *commonData, useSimuData)); - twsHfChart.reset(new Chart(*twsHstry, 'V', 2, dfltRngWs, *commonData, useSimuData)); + twsChart.reset(new Chart(*twsHstry, Chart::dfltChrtDta["formatKnots"].range, *commonData, useSimuData)); } } - if (!awdFlChart) { // Create apparent wind charts if they don't exist + if (!awdChart) { // Create apparent wind charts if they don't exist awdHstry = pageData.hstryBuffers->getBuffer("AWD"); awsHstry = pageData.hstryBuffers->getBuffer("AWS"); if (awdHstry) { - awdFlChart.reset(new Chart(*awdHstry, 'V', 0, dfltRngWd, *commonData, useSimuData)); - awdHfChart.reset(new Chart(*awdHstry, 'V', 1, dfltRngWd, *commonData, useSimuData)); + awdChart.reset(new Chart(*awdHstry, Chart::dfltChrtDta["formatCourse"].range, *commonData, useSimuData)); } if (awsHstry) { - awsFlChart.reset(new Chart(*awsHstry, 'H', 0, dfltRngWs, *commonData, useSimuData)); - awsHfChart.reset(new Chart(*awsHstry, 'V', 2, dfltRngWs, *commonData, useSimuData)); + 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"); } - } */ + } } int displayPage(PageData& pageData) @@ -190,54 +192,46 @@ public: LOG_DEBUG(GwLog::LOG, "Display PageWindPlot"); ulong pageTime = millis(); - if (!twdFlChart) { // Create true wind charts if they don't exist - twdHstry = pageData.hstryBuffers->getBuffer("TWD"); - twsHstry = pageData.hstryBuffers->getBuffer("TWS"); + /* if (!twdChart) { // Create true wind charts if they don't exist + twdHstry = pageData.hstryBuffers->getBuffer("TWD"); + twsHstry = pageData.hstryBuffers->getBuffer("TWS"); - if (twdHstry) { - twdFlChart.reset(new Chart(*twdHstry, 'V', 0, Chart::dfltChrtDta["formatCourse"].range, *commonData, useSimuData)); - twdHfChart.reset(new Chart(*twdHstry, 'V', 1, Chart::dfltChrtDta["formatCourse"].range, *commonData, useSimuData)); - } - if (twsHstry) { - twsFlChart.reset(new Chart(*twsHstry, 'H', 0, Chart::dfltChrtDta["formatKnots"].range, *commonData, useSimuData)); - twsHfChart.reset(new Chart(*twsHstry, 'V', 2, Chart::dfltChrtDta["formatKnots"].range, *commonData, useSimuData)); - } - } + 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 (!awdFlChart) { // Create apparent wind charts if they don't exist - awdHstry = pageData.hstryBuffers->getBuffer("AWD"); - awsHstry = pageData.hstryBuffers->getBuffer("AWS"); + if (!awdChart) { // Create apparent wind charts if they don't exist + awdHstry = pageData.hstryBuffers->getBuffer("AWD"); + awsHstry = pageData.hstryBuffers->getBuffer("AWS"); - if (awdHstry) { - awdFlChart.reset(new Chart(*awdHstry, 'V', 0, Chart::dfltChrtDta["formatCourse"].range, *commonData, useSimuData)); - awdHfChart.reset(new Chart(*awdHstry, 'V', 1, Chart::dfltChrtDta["formatCourse"].range, *commonData, useSimuData)); - } - if (awsHstry) { - awsFlChart.reset(new Chart(*awsHstry, 'H', 0, Chart::dfltChrtDta["formatKnots"].range, *commonData, useSimuData)); - awsHfChart.reset(new Chart(*awsHstry, 'V', 2, 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 (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 if (showTruW) { - wdFlChart = twdFlChart.get(); - wsFlChart = twsFlChart.get(); - wdHfChart = twdHfChart.get(); - wsHfChart = twsHfChart.get(); + wdChart = twdChart.get(); + wsChart = twsChart.get(); wdBVal = pageData.values[0]; wsBVal = pageData.values[1]; } else { - wdFlChart = awdFlChart.get(); - wsFlChart = awsFlChart.get(); - wdHfChart = awdHfChart.get(); - wsHfChart = awsHfChart.get(); + wdChart = awdChart.get(); + wsChart = awsChart.get(); wdBVal = pageData.values[2]; wsBVal = pageData.values[3]; } @@ -253,22 +247,22 @@ public: getdisplay().setPartialWindow(0, 0, width, height); // Set partial update getdisplay().setTextColor(commonData->fgcolor); - if (chrtMode == 'D') { - if (wdFlChart) { - wdFlChart->showChrt(*wdBVal, dataIntv, true); + if (chrtMode == SHOW_WIND_DIR) { + if (wdChart) { + wdChart->showChrt(VERTICAL, FULL_SIZE, dataIntv, true, *wdBVal); } - } else if (chrtMode == 'S') { - if (wsFlChart) { - wsFlChart->showChrt(*wsBVal, dataIntv, true); + } else if (chrtMode == SHOW_WIND_SPEED) { + if (wsChart) { + wsChart->showChrt(HORIZONTAL, FULL_SIZE, dataIntv, true, *wsBVal); } - } else if (chrtMode == 'B') { - if (wdHfChart) { - wdHfChart->showChrt(*wdBVal, dataIntv, true); + } else if (chrtMode == SHOW_BOTH) { + if (wdChart) { + wdChart->showChrt(VERTICAL, HALF_SIZE_TOP, dataIntv, true, *wdBVal); } - if (wsHfChart) { - wsHfChart->showChrt(*wsBVal, dataIntv, true); + if (wsChart) { + wsChart->showChrt(VERTICAL, HALF_SIZE_BOTTOM, dataIntv, true, *wsBVal); } } From 84736e6769c7f51b5c3ab2ca3795f4f24213d008 Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Sat, 10 Jan 2026 01:50:19 +0100 Subject: [PATCH 10/13] OBP60Formatter: add option to switch of creation of simulation data and use pure conversion/formatting function --- lib/obp60task/OBP60Formatter.cpp | 24 ++++++++++++++++++++---- lib/obp60task/Pagedata.h | 3 +-- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/lib/obp60task/OBP60Formatter.cpp b/lib/obp60task/OBP60Formatter.cpp index 456eab0..ebb2142 100644 --- a/lib/obp60task/OBP60Formatter.cpp +++ b/lib/obp60task/OBP60Formatter.cpp @@ -49,7 +49,15 @@ String formatLongitude(double lon) { return String(degree, 0) + "\x90 " + String(minute, 4) + "' " + ((lon > 0) ? "E" : "W"); } -FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){ +// Convert and format boat value from SI to user defined format (definition for compatibility purposes) +FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata) { + + return formatValue(value, commondata, false); // call with standard handling of user setting for simulation data +} + +// Convert and format boat value from SI to user defined format +// generate random simulation data; can be deselected to use conversion+formatting function even in simulation mode +FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool ignoreSimuDataSetting){ GwLog *logger = commondata.logger; FormattedData result; static int dayoffset = 0; @@ -66,9 +74,15 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){ String windspeedFormat = commondata.config->getString(commondata.config->windspeedFormat); // [m/s|km/h|kn|bft] String tempFormat = commondata.config->getString(commondata.config->tempFormat); // [K|°C|°F] String dateFormat = commondata.config->getString(commondata.config->dateFormat); // [DE|GB|US] - bool usesimudata = commondata.config->getBool(commondata.config->useSimuData); // [on|off] String precision = commondata.config->getString(commondata.config->valueprecision); // [1|2] + bool usesimudata; + if (ignoreSimuDataSetting){ + usesimudata = false; // ignore user setting for simulation data; we want to format the boat value passed to this function + } else { + usesimudata = commondata.config->getBool(commondata.config->useSimuData); // [on|off] + } + // If boat value not valid if (! value->valid && !usesimudata){ result.svalue = "---"; @@ -881,11 +895,12 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){ return result; } -// Helper method for conversion of boat data values from SI to user defined format +// Helper method for conversion of any data value from SI to user defined format double convertValue(const double &value, const String &name, const String &format, CommonData &commondata) { std::unique_ptr tmpBValue; // Temp variable to get converted data value from double result; // data value converted to user defined target data format + constexpr bool NO_SIMUDATA = true; // switch off simulation feature of function // prepare temporary BoatValue structure for use in tmpBValue = std::unique_ptr(new GwApi::BoatValue(name)); // we don't need boat value name for pure value conversion @@ -893,10 +908,11 @@ double convertValue(const double &value, const String &name, const String &forma tmpBValue->valid = true; tmpBValue->value = value; - result = formatValue(tmpBValue.get(), commondata).cvalue; // get value (converted) + result = formatValue(tmpBValue.get(), commondata, NO_SIMUDATA).cvalue; // get value (converted); ignore any simulation data setting return result; } +// Helper method for conversion of any data value from SI to user defined format double convertValue(const double &value, const String &format, CommonData &commondata) { double result; // data value converted to user defined target data format diff --git a/lib/obp60task/Pagedata.h b/lib/obp60task/Pagedata.h index 79d1ad9..02afba9 100644 --- a/lib/obp60task/Pagedata.h +++ b/lib/obp60task/Pagedata.h @@ -204,9 +204,8 @@ typedef struct{ // Formatter for boat values FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata); +FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool ignoreSimuDataSetting); // Helper method for conversion of any data value from SI to user defined format (defined in OBP60Formatter) double convertValue(const double &value, const String &format, CommonData &commondata); double convertValue(const double &value, const String &name, const String &format, CommonData &commondata); -// Helper method for conversion of boat data values from user defined format to SI (defined in OBP60Formatter) -double convertToSItemp(const double &value, const String &name, const String &format, CommonData &commondata); From 4747336a699f9b8b960bedc40edb56fbdc4b642f Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Sat, 10 Jan 2026 12:31:37 +0100 Subject: [PATCH 11/13] Code rework for OBPcharts, part 3 --- lib/obp60task/OBPDataOperations.h | 52 +-- lib/obp60task/OBPcharts.cpp | 576 ++++++++++++++---------------- lib/obp60task/OBPcharts.h | 54 ++- lib/obp60task/PageOneValue.cpp | 78 ++-- lib/obp60task/PageWindPlot.cpp | 79 ++-- 5 files changed, 413 insertions(+), 426 deletions(-) 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); } } From 86a078690a864d09996004afacdbe27c4a0540de Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Sun, 11 Jan 2026 15:30:13 +0100 Subject: [PATCH 12/13] PageTwoValues: added chart display option --- lib/obp60task/PageOneValue.cpp | 15 +- lib/obp60task/PageTwoValues.cpp | 445 +++++++++++++++++++++----------- lib/obp60task/PageWindPlot.cpp | 2 - 3 files changed, 306 insertions(+), 156 deletions(-) diff --git a/lib/obp60task/PageOneValue.cpp b/lib/obp60task/PageOneValue.cpp index 2a0075b..c0ddc7e 100644 --- a/lib/obp60task/PageOneValue.cpp +++ b/lib/obp60task/PageOneValue.cpp @@ -12,8 +12,8 @@ private: enum PageMode { VALUE, - CHART, - BOTH + BOTH, + CHART }; enum DisplayMode { FULL, @@ -54,6 +54,7 @@ private: RingBuffer* dataHstryBuf = nullptr; std::unique_ptr dataChart; // Chart object + // display data value in display [FULL|HALF] void showData(GwApi::BoatValue* bValue1, DisplayMode mode) { int nameXoff, nameYoff, unitXoff, unitYoff, value1Xoff, value1Yoff; @@ -158,17 +159,17 @@ public: Page::setupKeys(); #if defined BOARD_OBP60S3 - constexpr int ZOOM_IDX = 4; + constexpr int ZOOM_KEY = 4; #elif defined BOARD_OBP40S3 - constexpr int ZOOM_IDX = 1; + constexpr int ZOOM_KEY = 1; #endif if (dataHstryBuf) { // show "Mode" key only if chart supported boat data type is available commonData->keydata[0].label = "MODE"; - commonData->keydata[ZOOM_IDX].label = "ZOOM"; + commonData->keydata[ZOOM_KEY].label = "ZOOM"; } else { commonData->keydata[0].label = ""; - commonData->keydata[ZOOM_IDX].label = ""; + commonData->keydata[ZOOM_KEY].label = ""; } } @@ -177,7 +178,7 @@ public: { if (dataHstryBuf) { // if boat data type supports charts - // Set page mode value | value/half chart | full chart + // Set page mode: value | value/half chart | full chart if (key == 1) { switch (pageMode) { case VALUE: diff --git a/lib/obp60task/PageTwoValues.cpp b/lib/obp60task/PageTwoValues.cpp index eaa59d4..d4c0d98 100644 --- a/lib/obp60task/PageTwoValues.cpp +++ b/lib/obp60task/PageTwoValues.cpp @@ -3,176 +3,327 @@ #include "Pagedata.h" #include "OBP60Extensions.h" #include "BoatDataCalibration.h" +#include "OBPDataOperations.h" +#include "OBPcharts.h" -class PageTwoValues : public Page -{ - public: - PageTwoValues(CommonData &common){ - commonData = &common; - common.logger->logDebug(GwLog::LOG,"Instantiate PageTwoValue"); +class PageTwoValues : public Page { +private: + GwLog* logger; + + enum PageMode { + VALUES, + VAL1_CHART, + VAL2_CHART, + CHARTS + }; + 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; + + static constexpr int YOFFSET = 130; // y offset for display of 2nd boat value + + int width; // Screen width + int height; // Screen height + + bool keylock = false; // Keylock + PageMode pageMode = VALUES; // 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 + + // String lengthformat; + bool useSimuData; + bool holdValues; + String flashLED; + String backlightMode; + String tempFormat; + + // Data buffer pointer (owned by HstryBuffers) + static constexpr int NUMVALUES = 2; // two data values in this page + RingBuffer* dataHstryBuf[NUMVALUES] = { nullptr }; + std::unique_ptr dataChart[NUMVALUES]; // Chart object + + // Old values for hold function + String sValueOld[NUMVALUES] = { "", "" }; + String unitOld[NUMVALUES] = { "", "" }; + + // display data values in display [FULL|HALF] + void showData(const std::vector& bValue, DisplayMode mode) + { + getdisplay().setTextColor(commonData->fgcolor); + + int numValues = bValue.size(); // do we have to handle 1 or 2 values? + + for (int i = 0; i < numValues; i++) { + int yOffset = YOFFSET * i; + String name = xdrDelete(bValue[i]->getName()); // Value name + name = name.substring(0, 6); // String length limit for value name + calibrationData.calibrateInstance(bValue[i], logger); // Check if boat data value is to be calibrated + double value = bValue[i]->value; // Value as double in SI unit + bool valid = bValue[i]->valid; // Valid information + String sValue = formatValue(bValue[i], *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places + String unit = formatValue(bValue[i], *commonData).unit; // Unit of value + + // Show name + getdisplay().setFont(&Ubuntu_Bold20pt8b); + getdisplay().setCursor(20, 75 + yOffset); + getdisplay().print(name); // name + + // Show unit + getdisplay().setFont(&Ubuntu_Bold12pt8b); + getdisplay().setCursor(20, 125 + yOffset); + + if (holdValues) { + getdisplay().print(unitOld[i]); // name + } else { + getdisplay().print(unit); // name + } + + // Switch font depending on value format and adjust position + if (bValue[i]->getFormat() == "formatLatitude" || bValue[i]->getFormat() == "formatLongitude") { + getdisplay().setFont(&Ubuntu_Bold20pt8b); + getdisplay().setCursor(50, 125 + yOffset); + } else if (bValue[i]->getFormat() == "formatTime" || bValue[i]->getFormat() == "formatDate") { + getdisplay().setFont(&Ubuntu_Bold20pt8b); + getdisplay().setCursor(170, 105 + yOffset); + } else { // Default font for other formats + getdisplay().setFont(&DSEG7Classic_BoldItalic42pt7b); + getdisplay().setCursor(180, 125 + yOffset); + } + + // Show bus data + if (!holdValues || useSimuData) { + getdisplay().print(sValue); // Real value as formated string + } else { + getdisplay().print(sValueOld[i]); // Old value as formated string + } + + if (valid == true) { + sValueOld[i] = sValue; // Save the old value + unitOld[i] = unit; // Save the old unit + } + } + + if (numValues == 2 && mode == FULL) { // print line only, if we want to show 2 data values + getdisplay().fillRect(0, 145, width, 3, commonData->fgcolor); // Horizontal line 3 pix + } } - virtual int handleKey(int key){ - // Code for keylock - if(key == 11){ +public: + PageTwoValues(CommonData& common) + { + commonData = &common; + logger = commonData->logger; + LOG_DEBUG(GwLog::LOG, "Instantiate PageTwoValues"); + + width = getdisplay().width(); // Screen width + height = getdisplay().height(); // Screen height + + // Get config data + // lengthformat = commonData->config->getString(commonData->config->lengthFormat); + useSimuData = commonData->config->getBool(commonData->config->useSimuData); + holdValues = commonData->config->getBool(commonData->config->holdvalues); + flashLED = commonData->config->getString(commonData->config->flashLED); + backlightMode = commonData->config->getString(commonData->config->backlight); + tempFormat = commonData->config->getString(commonData->config->tempFormat); // [K|°C|°F] + } + + virtual void setupKeys() + { + Page::setupKeys(); + +#if defined BOARD_OBP60S3 + constexpr int ZOOM_KEY = 4; +#elif defined BOARD_OBP40S3 + constexpr int ZOOM_KEY = 1; +#endif + + if (dataHstryBuf[0] || dataHstryBuf[1]) { // show "Mode" key only if at least 1 chart supported boat data type is available + commonData->keydata[0].label = "MODE"; + commonData->keydata[ZOOM_KEY].label = "ZOOM"; + } else { + commonData->keydata[0].label = ""; + commonData->keydata[ZOOM_KEY].label = ""; + } + } + + // Key functions + virtual int handleKey(int key) + { + if (dataHstryBuf[0] || dataHstryBuf[1]) { // if at least 1 boat data type supports charts + + // Set page mode: value | value/half chart | full charts + if (key == 1) { + switch (pageMode) { + + case VALUES: + + if (dataHstryBuf[0]) { + pageMode = VAL1_CHART; + } else if (dataHstryBuf[1]) { + pageMode = VAL2_CHART; + } + break; + + case VAL1_CHART: + + if (dataHstryBuf[1]) { + pageMode = VAL2_CHART; + } else { + pageMode = CHARTS; + } + break; + + case VAL2_CHART: + pageMode = CHARTS; + break; + + case CHARTS: + pageMode = VALUES; + break; + } + return 0; // Commit the key + } + + // Set time frame to show for history chart +#if defined BOARD_OBP60S3 + if (key == 5) { +#elif defined BOARD_OBP40S3 + if (key == 2) { +#endif + if (dataIntv == 1) { + dataIntv = 2; + } else if (dataIntv == 2) { + dataIntv = 3; + } else if (dataIntv == 3) { + dataIntv = 4; + } else if (dataIntv == 4) { + dataIntv = 8; + } else { + dataIntv = 1; + } + return 0; // Commit the key + } + } + + // Keylock function + if (key == 11) { // Code for keylock commonData->keylock = !commonData->keylock; - return 0; // Commit the key + return 0; // Commit the key } return key; } - int displayPage(PageData &pageData){ - GwConfigHandler *config = commonData->config; - GwLog *logger = commonData->logger; - - // Old values for hold function - static String svalue1old = ""; - static String unit1old = ""; - static String svalue2old = ""; - static String unit2old = ""; - - // Get config data - String lengthformat = config->getString(config->lengthFormat); - // bool simulation = config->getBool(config->useSimuData); - bool holdvalues = config->getBool(config->holdvalues); - String flashLED = config->getString(config->flashLED); - String backlightMode = config->getString(config->backlight); - - // Get boat values #1 - GwApi::BoatValue *bvalue1 = pageData.values[0]; // First element in list (only one value by PageOneValue) - String name1 = xdrDelete(bvalue1->getName()); // Value name - name1 = name1.substring(0, 6); // String length limit for value name - calibrationData.calibrateInstance(bvalue1, logger); // Check if boat data value is to be calibrated - double value1 = bvalue1->value; // Value as double in SI unit - bool valid1 = bvalue1->valid; // Valid information - String svalue1 = formatValue(bvalue1, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places - String unit1 = formatValue(bvalue1, *commonData).unit; // Unit of value - - // Get boat values #2 - GwApi::BoatValue *bvalue2 = pageData.values[1]; // Second element in list - String name2 = xdrDelete(bvalue2->getName()); // Value name - name2 = name2.substring(0, 6); // String length limit for value name - calibrationData.calibrateInstance(bvalue2, logger); // Check if boat data value is to be calibrated - double value2 = bvalue2->value; // Value as double in SI unit - bool valid2 = bvalue2->valid; // Valid information - String svalue2 = formatValue(bvalue2, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places - String unit2 = formatValue(bvalue2, *commonData).unit; // Unit of value - - // Optical warning by limit violation (unused) - if(String(flashLED) == "Limit Violation"){ + virtual void displayNew(PageData& pageData) + { +#ifdef BOARD_OBP60S3 + // Clear optical warning + if (flashLED == "Limit Violation") { setBlinkingLED(false); - setFlashLED(false); + setFlashLED(false); + } +#endif + // buffer initialization will fail, if page is default page, because is not executed at system start for default page + for (int i = 0; i < NUMVALUES; i++) { + if (!dataChart[i]) { // Create chart objects if they don't exist + + GwApi::BoatValue* bValue = pageData.values[i]; // Page boat data element + String bValName = bValue->getName(); // Value name + String bValFormat = bValue->getFormat(); // Value format + + dataHstryBuf[i] = pageData.hstryBuffers->getBuffer(bValName); + + if (dataHstryBuf[i]) { + dataChart[i].reset(new Chart(*dataHstryBuf[i], Chart::dfltChrtDta[bValFormat].range, *commonData, useSimuData)); + LOG_DEBUG(GwLog::DEBUG, "PageTwoValues: Created chart object%d for %s", i, bValName.c_str()); + } else { + LOG_DEBUG(GwLog::DEBUG, "PageTwoValues: No chart object available for %s", bValName.c_str()); + } + } } - // Logging boat values - if (bvalue1 == NULL) return PAGE_OK; // WTF why this statement? - LOG_DEBUG(GwLog::LOG,"Drawing at PageTwoValues, %s: %f, %s: %f", name1.c_str(), value1, name2.c_str(), value2); + setupKeys(); // adjust key depending on chart supported boat data type + } + + int displayPage(PageData& pageData) + { + LOG_DEBUG(GwLog::LOG, "Display PageTwoValues"); + + // Get boat values for page + std::vector bValue; + bValue.push_back(pageData.values[0]); // Page boat data element 1 + bValue.push_back(pageData.values[1]); // Page boat data element 2 + + // Optical warning by limit violation (unused) + if (String(flashLED) == "Limit Violation") { + setBlinkingLED(false); + setFlashLED(false); + } + + if (bValue[0] == NULL && bValue[1] == NULL) + return PAGE_OK; // no data, no page to display + + LOG_DEBUG(GwLog::DEBUG, "PageTwoValues: printing #1: %s, %.3f, #2: %s, %.3f", + bValue[0]->getName().c_str(), bValue[0]->value, bValue[1]->getName().c_str(), bValue[1]->value); // Draw page //*********************************************************** - // Set display in partial refresh mode - getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update + getdisplay().setPartialWindow(0, 0, width, height); // Set partial update - // ############### Value 1 ################ + if (pageMode == VALUES || (dataHstryBuf[0] == nullptr && dataHstryBuf[1] == nullptr)) { + // show only data value; ignore other pageMode options if no chart supported boat data history buffer is available + showData(bValue, FULL); - // Show name - getdisplay().setTextColor(commonData->fgcolor); - getdisplay().setFont(&Ubuntu_Bold20pt8b); - getdisplay().setCursor(20, 80); - getdisplay().print(name1); // Page name + } else if (pageMode == VAL1_CHART) { // show data value 1 and chart + showData({bValue[0]}, HALF); + if (dataChart[0]) { + dataChart[0]->showChrt(HORIZONTAL, HALF_SIZE_BOTTOM, dataIntv, NO_PRNT_NAME, NO_PRNT_VALUE, *bValue[0]); + } - // Show unit - getdisplay().setFont(&Ubuntu_Bold12pt8b); - getdisplay().setCursor(20, 130); - if(holdvalues == false){ - getdisplay().print(unit1); // Unit - } - else{ - getdisplay().print(unit1old); - } + } else if (pageMode == VAL2_CHART) { // show data value 2 and chart + showData({bValue[1]}, HALF); + if (dataChart[1]) { + dataChart[1]->showChrt(HORIZONTAL, HALF_SIZE_BOTTOM, dataIntv, NO_PRNT_NAME, NO_PRNT_VALUE, *bValue[1]); + } - // Switch font if format for any values - if(bvalue1->getFormat() == "formatLatitude" || bvalue1->getFormat() == "formatLongitude"){ - getdisplay().setFont(&Ubuntu_Bold20pt8b); - getdisplay().setCursor(50, 130); - } - else if(bvalue1->getFormat() == "formatTime" || bvalue1->getFormat() == "formatDate"){ - getdisplay().setFont(&Ubuntu_Bold20pt8b); - getdisplay().setCursor(170, 105); - } - else{ - getdisplay().setFont(&DSEG7Classic_BoldItalic42pt7b); - getdisplay().setCursor(180, 130); - } - - // Show bus data - if(holdvalues == false){ - getdisplay().print(svalue1); // Real value as formated string - } - else{ - getdisplay().print(svalue1old); // Old value as formated string - } - if(valid1 == true){ - svalue1old = svalue1; // Save the old value - unit1old = unit1; // Save the old unit - } - - // ############### Horizontal Line ################ - - // Horizontal line 3 pix - getdisplay().fillRect(0, 145, 400, 3, commonData->fgcolor); - - // ############### Value 2 ################ - - // Show name - getdisplay().setFont(&Ubuntu_Bold20pt8b); - getdisplay().setCursor(20, 190); - getdisplay().print(name2); // Page name - - // Show unit - getdisplay().setFont(&Ubuntu_Bold12pt8b); - getdisplay().setCursor(20, 240); - if(holdvalues == false){ - getdisplay().print(unit2); // Unit - } - else{ - getdisplay().print(unit2old); - } - - // Switch font if format for any values - if(bvalue2->getFormat() == "formatLatitude" || bvalue2->getFormat() == "formatLongitude"){ - getdisplay().setFont(&Ubuntu_Bold20pt8b); - getdisplay().setCursor(50, 240); - } - else if(bvalue2->getFormat() == "formatTime" || bvalue2->getFormat() == "formatDate"){ - getdisplay().setFont(&Ubuntu_Bold20pt8b); - getdisplay().setCursor(170, 215); - } - else{ - getdisplay().setFont(&DSEG7Classic_BoldItalic42pt7b); - getdisplay().setCursor(180, 240); - } - - // Show bus data - if(holdvalues == false){ - getdisplay().print(svalue2); // Real value as formated string - } - else{ - getdisplay().print(svalue2old); // Old value as formated string - } - if(valid2 == true){ - svalue2old = svalue2; // Save the old value - unit2old = unit2; // Save the old unit + } else if (pageMode == CHARTS) { // show both data charts + if (dataChart[0]) { + if (dataChart[1]) { + dataChart[0]->showChrt(HORIZONTAL, HALF_SIZE_TOP, dataIntv, PRNT_NAME, PRNT_VALUE, *bValue[0]); + } else { + dataChart[0]->showChrt(HORIZONTAL, FULL_SIZE, dataIntv, PRNT_NAME, PRNT_VALUE, *bValue[0]); + } + } + if (dataChart[1]) { + if (dataChart[0]) { + dataChart[1]->showChrt(HORIZONTAL, HALF_SIZE_BOTTOM, dataIntv, PRNT_NAME, PRNT_VALUE, *bValue[1]); + } else { + dataChart[1]->showChrt(HORIZONTAL, FULL_SIZE, dataIntv, PRNT_NAME, PRNT_VALUE, *bValue[1]); + } + } } return PAGE_UPDATE; }; }; -static Page *createPage(CommonData &common){ +static Page* createPage(CommonData& common) +{ return new PageTwoValues(common); } + /** * with the code below we make this page known to the PageTask * we give it a type (name) that can be selected in the config @@ -181,10 +332,10 @@ static Page *createPage(CommonData &common){ * this will be number of BoatValue pointers in pageData.values */ PageDescription registerPageTwoValues( - "TwoValues", // Page name - createPage, // Action - 2, // Number of bus values depends on selection in Web configuration - true // Show display header on/off + "TwoValues", // Page name + createPage, // Action + 2, // Number of bus values depends on selection in Web configuration + true // Show display header on/off ); #endif diff --git a/lib/obp60task/PageWindPlot.cpp b/lib/obp60task/PageWindPlot.cpp index 924687c..f5745f7 100644 --- a/lib/obp60task/PageWindPlot.cpp +++ b/lib/obp60task/PageWindPlot.cpp @@ -163,8 +163,6 @@ public: oldShowTruW = !showTruW; // Force chart update in displayPage #endif - // With chart object initialization being performed here, PageWindPlot won't properly work as default page, - // because is not executed at system start for default page if (!twdChart) { // Create true wind charts if they don't exist twdHstry = pageData.hstryBuffers->getBuffer("TWD"); twsHstry = pageData.hstryBuffers->getBuffer("TWS"); From cd3c99d509ff0eb272b13b8045de7591c45bed1b Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Sun, 11 Jan 2026 17:07:27 +0100 Subject: [PATCH 13/13] PageOneValue: fix unit position --- lib/obp60task/PageOneValue.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/obp60task/PageOneValue.cpp b/lib/obp60task/PageOneValue.cpp index c0ddc7e..6349f29 100644 --- a/lib/obp60task/PageOneValue.cpp +++ b/lib/obp60task/PageOneValue.cpp @@ -76,7 +76,7 @@ private: nameXoff = -10; nameYoff = -34; nameFnt = &Ubuntu_Bold20pt8b; - unitXoff = 63; + unitXoff = -295; unitYoff = -119; unitFnt = &Ubuntu_Bold12pt8b; valueFnt1 = &Ubuntu_Bold12pt8b;