diff --git a/lib/obp60task/OBP60Extensions.cpp b/lib/obp60task/OBP60Extensions.cpp index d386159..c8729f9 100644 --- a/lib/obp60task/OBP60Extensions.cpp +++ b/lib/obp60task/OBP60Extensions.cpp @@ -442,7 +442,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/OBP60Formatter.cpp b/lib/obp60task/OBP60Formatter.cpp index 44ce771..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 = "---"; @@ -196,10 +210,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); @@ -214,7 +228,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"){ @@ -248,7 +262,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"){ @@ -433,7 +447,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"){ @@ -881,4 +895,30 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){ return result; } +// 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 + tmpBValue->setFormat(format); + tmpBValue->valid = true; + tmpBValue->value = value; + + 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 + + result = convertValue(value, "dummy", format, commondata); + return result; +} + #endif diff --git a/lib/obp60task/OBPDataOperations.cpp b/lib/obp60task/OBPDataOperations.cpp index c68ef53..aae0040 100644 --- a/lib/obp60task/OBPDataOperations.cpp +++ b/lib/obp60task/OBPDataOperations.cpp @@ -1,156 +1,119 @@ #include "OBPDataOperations.h" -#include "BoatDataCalibration.h" // Functions lib for data instance calibration -#include +#include "BoatDataCalibration.h" // Functions lib for data instance calibration // --- Class HstryBuf --------------- +HstryBuf::HstryBuf(const String& name, int size, BoatValueList* boatValues, GwLog* log) + : logger(log) + , boatDataName(name) +{ + hstryBuf.resize(size); + boatValue = boatValues->findValueOrCreate(name); +} -// Init history buffers for selected boat data -void HstryBuf::init(BoatValueList* boatValues, GwLog *log) { - - logger = log; - - int hstryUpdFreq = 1000; // Update frequency for history buffers in ms - int 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(); - - // 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); - 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; +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; + if (!boatValue->valid) { + boatValue->setFormat(format); + boatValue->value = std::numeric_limits::max(); // mark current value invalid } +} + +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, CommonData& common) +{ + // GwApi::BoatValue* tmpBVal; + std::unique_ptr tmpBVal; // Temp variable to get formatted and converted data value from OBP60Formatter + + // create temporary boat value for calibration purposes and retrieval of simulation value + // tmpBVal = new GwApi::BoatValue(boatDataName.c_str()); + tmpBVal = std::unique_ptr(new GwApi::BoatValue(boatDataName)); + tmpBVal->setFormat(boatValue->getFormat()); + tmpBVal->value = boatValue->value; + tmpBVal->valid = boatValue->valid; + + if (boatValue->valid) { + // Calibrate boat value before adding it to history buffer + calibrationData.calibrateInstance(tmpBVal.get(), logger); + add(tmpBVal->value); + + } else if (useSimuData) { // add simulated value to history buffer + double simValue = formatValue(tmpBVal.get(), common).value; // simulated value is generated at + add(simValue); + } +} +// --- End Class HstryBuf --------------- + +// --- 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"); + // should all have been already created at true wind object initialization + // potentially to be moved to history buffer handling + awaBVal = boatValueList->findValueOrCreate("AWA"); + hdtBVal = boatValueList->findValueOrCreate("HDT"); + hdmBVal = boatValueList->findValueOrCreate("HDM"); + varBVal = boatValueList->findValueOrCreate("VAR"); + cogBVal = boatValueList->findValueOrCreate("COG"); + sogBVal = boatValueList->findValueOrCreate("SOG"); + awdBVal = boatValueList->findValueOrCreate("AWD"); } -// Handle history buffers for TWD, TWS, AWD, AWS -//void HstryBuf::handleHstryBuf(GwApi* api, BoatValueList* boatValues, bool useSimuData) { -void HstryBuf::handleHstryBuf(bool useSimuData) { - - static double twd, tws, awd, aws, hdt = 20; //initial value only relevant if we use simulation data - GwApi::BoatValue *calBVal; // temp variable just for data calibration -> we don't want to calibrate the original data here - - LOG_DEBUG(GwLog::DEBUG,"obp60task handleHstryBuf: TWD_isValid? %d, twdBVal: %.1f, twaBVal: %.1f, twsBVal: %.1f", twdBVal->valid, twdBVal->value * RAD_TO_DEG, - twaBVal->value * RAD_TO_DEG, twsBVal->value * 3.6 / 1.852); - - if (twdBVal->valid) { -// if (!useSimuData) { - calBVal = new GwApi::BoatValue("TWD"); // temporary solution for calibration of history buffer values - calBVal->setFormat(twdBVal->getFormat()); - calBVal->value = twdBVal->value; - calBVal->valid = twdBVal->valid; - calibrationData.calibrateInstance(calBVal, logger); // Check if boat data value is to be calibrated - twd = 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); +// 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; + } + if (bufferParams.find(name) == bufferParams.end()) { // requested boat data type is not supported in list of + return; } - 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); - } + hstryBuffers[name] = std::unique_ptr(new HstryBuf(name, size, boatValueList, logger)); - 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); - } + // Initialize metadata for buffer + String valueFormat = bufferParams[name].format; // Data format of boat data type + // String valueFormat = boatValueList->findValueOrCreate(name)->getFormat().c_str(); // Unfortunately, format is not yet available during system initialization + 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 - 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 - awdBVal->value = calBVal->value; - awdBVal->valid = true; - awd = calBVal->value; - if (awd >= awdHstryMin && awd <= awdHstryMax) { - hstryBufList.awdHstry->add(awd); - } - 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); - } - - 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); - } - 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()); + 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); } -// --- Class HstryBuf --------------- + +// Handle all registered history buffers +void HstryBuffers::handleHstryBufs(bool useSimuData, CommonData& common) +{ + for (auto& bufMap : hstryBuffers) { + auto& buf = bufMap.second; + buf->handle(useSimuData, common); + } +} + +RingBuffer* HstryBuffers::getBuffer(const String& name) +{ + auto it = hstryBuffers.find(name); + if (it != hstryBuffers.end()) { + return &it->second->hstryBuf; + } + return nullptr; +} +// --- End Class HstryBuffers --------------- // --- Class WindUtils -------------- double WindUtils::to2PI(double a) @@ -216,14 +179,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); } @@ -245,12 +208,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) { @@ -274,60 +237,80 @@ 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, BoatValueList* boatValues, GwLog* log) { +bool WindUtils::addWinds() +{ + double twd, tws, twa, awd, hdt; + bool twCalculated = false; + bool awdCalculated = false; - GwLog* logger = log; + 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:addWinds: AWA %.1f, AWS %.1f, COG %.1f, STW %.1f, SOG %.2f, HDT %.1f, HDM %.1f, VAR %.1f", awaBVal->value * RAD_TO_DEG, awsBVal->value * 3.6 / 1.852, + cogBVal->value * RAD_TO_DEG, stwBVal->value * 3.6 / 1.852, sogBVal->value * 3.6 / 1.852, hdtBVal->value * RAD_TO_DEG, hdmBVal->value * RAD_TO_DEG, varBVal->value * RAD_TO_DEG); - double 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, - 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,"obp60task 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; } -// --- Class WindUtils -------------- +// --- End Class WindUtils -------------- \ No newline at end of file diff --git a/lib/obp60task/OBPDataOperations.h b/lib/obp60task/OBPDataOperations.h index 8422894..0fb8647 100644 --- a/lib/obp60task/OBPDataOperations.h +++ b/lib/obp60task/OBPDataOperations.h @@ -1,67 +1,89 @@ // Function lib for history buffer handling, true wind calculation, and other operations on boat data #pragma once #include "OBPRingBuffer.h" +#include "Pagedata.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 class HstryBuf { private: - GwLog *logger; + RingBuffer hstryBuf; // Circular buffer to store history values + String boatDataName; + double hstryMin; + double hstryMax; + GwApi::BoatValue* boatValue; + GwLog* logger; - 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 HstryBuffers; public: - tBoatHstryData hstryBufList; + 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, CommonData& common); +}; - HstryBuf(){ - hstryBufList = {&twdHstry, &twsHstry, &awdHstry, &awsHstry}; // Generate history buffers of zero size +class HstryBuffers { +private: + std::map> hstryBuffers; + int size; // size of all history buffers + BoatValueList* boatValueList; + GwLog* logger; + GwApi::BoatValue *awaBVal, *hdtBVal, *hdmBVal, *varBVal, *cogBVal, *sogBVal, *awdBVal; // boat values for true wind calculation + + struct HistoryParams { + int hstryUpdFreq; // update frequency of history buffer (documentation only) + 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 }; - - 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); + + // 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 }; - void init(BoatValueList* boatValues, GwLog *log); - void handleHstryBuf(bool useSimuData); + +public: + HstryBuffers(int size, BoatValueList* boatValues, GwLog* log); + void addBuffer(const String& name); + void handleHstryBufs(bool useSimuData, CommonData& common); + RingBuffer* getBuffer(const String& name); }; 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){ - twdBVal = boatValues->findValueOrCreate("TWD"); - twsBVal = boatValues->findValueOrCreate("TWS"); + WindUtils(BoatValueList* boatValues, GwLog* log) + : logger(log) + { 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"); @@ -81,10 +103,10 @@ 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); + const double* hdmVal, const double* varVal, double* twdVal, double* twsVal, double* twaVal, double* awdVal); + bool addWinds(); }; \ No newline at end of file diff --git a/lib/obp60task/OBPcharts.cpp b/lib/obp60task/OBPcharts.cpp index 17caf75..e15fc83 100644 --- a/lib/obp60task/OBPcharts.cpp +++ b/lib/obp60task/OBPcharts.cpp @@ -1,14 +1,25 @@ // 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.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 --------------- -template -Chart::Chart(RingBuffer& dataBuf, int8_t chrtDir, int8_t chrtSz, double dfltRng, CommonData& common, bool useSimuData) + +// Chart - object holding the actual chart, incl. data buffer and format definition +// 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, double dfltRng, CommonData& common, bool useSimuData) : dataBuf(dataBuf) - , chrtDir(chrtDir) - , chrtSz(chrtSz) , dfltRng(dfltRng) , commonData(&common) , useSimuData(useSimuData) @@ -17,129 +28,188 @@ Chart::Chart(RingBuffer& dataBuf, int8_t chrtDir, int8_t chrtSz, double df fgColor = commonData->fgcolor; bgColor = commonData->bgcolor; - // LOG_DEBUG(GwLog::DEBUG, "Chart Init: Chart::dataBuf: %p, passed dataBuf: %p", (void*)&this->dataBuf, (void*)&dataBuf); dWidth = getdisplay().width(); dHeight = getdisplay().height(); - if (chrtDir == 0) { - // horizontal chart timeline direction - timAxis = dWidth; - switch (chrtSz) { - case 0: - valAxis = dHeight - top - bottom; - cStart = { 0, top }; - break; - case 1: - valAxis = (dHeight - top - bottom) / 2 - hGap; - cStart = { 0, top }; - break; - case 2: - valAxis = (dHeight - top - bottom) / 2 - hGap; - cStart = { 0, top + (valAxis + hGap) + hGap }; - break; - default: - LOG_DEBUG(GwLog::ERROR, "displayChart: wrong init parameter"); - return; - } - } else if (chrtDir == 1) { - // vertical chart timeline direction - timAxis = dHeight - top - bottom; - switch (chrtSz) { - case 0: - valAxis = dWidth; - cStart = { 0, top }; - break; - case 1: - valAxis = dWidth / 2 - vGap - 1; - cStart = { 0, top }; - break; - case 2: - valAxis = dWidth / 2 - vGap - 1; - cStart = { dWidth / 2 + vGap, top }; - break; - default: - LOG_DEBUG(GwLog::ERROR, "displayChart: wrong init parameter"); - return; - } - } else { - LOG_DEBUG(GwLog::ERROR, "displayChart: wrong init parameter"); - return; - } - dataBuf.getMetaData(dbName, dbFormat); dbMIN_VAL = dataBuf.getMinVal(); dbMAX_VAL = dataBuf.getMaxVal(); bufSize = dataBuf.getCapacity(); - if (dbFormat == "formatCourse" || dbFormat == "FormatWind" || dbFormat == "FormatRot") { - - if (dbFormat == "FormatRot") { - chrtDataFmt = 2; // Chart is showing data of rotational format - } else { - chrtDataFmt = 1; // Chart is showing data of course / wind format - } - rngStep = M_TWOPI / 360.0 * 10.0; // +/-10 degrees on each end of chrtMid; we are calculating with SI values - + // Initialize chart data format; shorter version of standard format indicator + if (dbFormat == "formatCourse" || dbFormat == "formatWind" || dbFormat == "formatRot") { + chrtDataFmt = WIND; // Chart is showing data of course / wind format + } else if (dbFormat == "formatRot") { + chrtDataFmt = ROTATION; // Chart is showing data of rotational format + } else if (dbFormat == "formatKnots") { + chrtDataFmt = SPEED; // Chart is showing data of speed or windspeed format + } else if (dbFormat == "formatDepth") { + chrtDataFmt = DEPTH; // Chart ist showing data of format + } else if (dbFormat == "kelvinToC") { + chrtDataFmt = TEMPERATURE; // Chart ist showing data of format } else { - chrtDataFmt = 0; // Chart is showing any other data format than - rngStep = 5.0; // +/- 10 for all other values (eg. m/s, m, K, mBar) + chrtDataFmt = OTHER; // Chart is showing any other data format } - chrtMin = 0; - chrtMax = 0; - chrtMid = dbMAX_VAL; - chrtRng = dfltRng; - recalcRngCntr = true; // initialize on first screen call + // "0" value is the same for any data format but for user defined temperature format + zeroValue = 0.0; + if (chrtDataFmt == TEMPERATURE) { + 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: 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); + // 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; + } + + // Initialize chart range values + chrtMin = zeroValue; + chrtMax = chrtMin + dfltRng; + chrtMid = (chrtMin + chrtMax) / 2; + chrtRng = dfltRng; + 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: %d", + dWidth, dHeight, timAxis, valAxis, cRoot.x, cRoot.y, dbName, rngStep, chrtDataFmt); }; -template -Chart::~Chart() +Chart::~Chart() { } // Perform all actions to draw chart -// Parameters are chart time interval, and the current boat data value to be printed -template -void Chart::showChrt(int8_t chrtIntv, GwApi::BoatValue currValue) +// 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 +// ; 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) { - drawChrt(chrtIntv, currValue); - drawChrtTimeAxis(chrtIntv); - drawChrtValAxis(); + if (!setChartDimensions(chrtDir, chrtSz)) { + return; // wrong chart dimension parameters + } - 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 + drawChrt(chrtDir, chrtIntv, currValue); + drawChrtTimeAxis(chrtDir, chrtSz, chrtIntv); + drawChrtValAxis(chrtDir, chrtSz, prntName); + + if (!bufDataValid) { // No valid data available + prntNoValidData(chrtDir); + return; + } + + 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; - 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); + prntCurrValue(chrtDir, currValue); } } -// draw chart -template -void Chart::drawChrt(int8_t chrtIntv, GwApi::BoatValue& currValue) +// define dimensions and start points for chart +bool Chart::setChartDimensions(const char direction, const int8_t size) { - 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 + if ((direction != HORIZONTAL && direction != VERTICAL) || (size < 0 || size > 2)) { + LOG_DEBUG(GwLog::ERROR, "obp60:setChartDimensions %s: wrong parameters", dataBuf.getName()); + return false; + } - int x, y; // x and y coordinates for drawing - static int prevX, prevY; // Last x and y coordinates for drawing + if (direction == HORIZONTAL) { + // 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; + } - // Identify buffer size and buffer start position for chart + } else if (direction == VERTICAL) { + // 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 +void Chart::drawChrt(const char chrtDir, const int8_t chrtIntv, GwApi::BoatValue& currValue) +{ + double chrtScale; // Scale for data values in pixels per value + + 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); + 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; + 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, flag for invalid data + bufDataValid = false; + return; + } + } + + drawChartLines(chrtDir, chrtIntv, chrtScale); +} + +// Identify buffer size and buffer start position for chart +void Chart::getBufferStartNSize(const 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 - // intvBufSize = timAxis * chrtIntv; // obsolete - numBufVals = min(count, (timAxis - 60) * chrtIntv); // keep free or release 60 values on chart for plotting of new values + 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; @@ -151,134 +221,367 @@ void Chart::drawChrt(int8_t chrtIntv, GwApi::BoatValue& currValue) bufStart = max(0, bufStart - numAddedBufVals); } } +} - calcChrtBorders(chrtMid, chrtMin, chrtMax, chrtRng); - chrtScl = double(valAxis) / chrtRng; // Chart scale: pixels per value step +// 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 == WIND || chrtDataFmt == ROTATION) { - // Do we have valid buffer data? - if (dataBuf.getMax() == dbMAX_VAL) { // only values in buffer -> no valid wind data available - bufDataValid = false; - } else if (!currValue.valid && !useSimuData) { // currently no valid boat data available and no simulation mode - numNoData++; - bufDataValid = true; - if (numNoData > 3) { // If more than 4 invalid values in a row, send message - bufDataValid = false; - } - } else { - numNoData = 0; // reset data error counter - bufDataValid = true; // At least some wind data available - } + if (chrtDataFmt == ROTATION) { + // if chart data is of type 'rotation', we want to have always to be '0' + rngMid = 0; - // 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 { + } else { // WIND: Chart data is of type 'course' or 'wind' - if (chrtDir == 0) { // horizontal chart - x = cStart.x + i; // Position in chart area - if (chrtDataFmt == 0) { - y = cStart.y + static_cast(((chrtVal - chrtMin) * chrtScl) + 0.5); // calculate chart point and round - } else { // degree type value - y = cStart.y + static_cast((WindUtils::to2PI(chrtVal - chrtMin) * chrtScl) + 0.5); // calculate chart point and round - } - } else { // vertical chart - y = cStart.y + timAxis - i; // Position in chart area - if (chrtDataFmt == 0) { - x = cStart.x + static_cast(((chrtVal - chrtMin) * chrtScl) + 0.5); // calculate chart point and round - } else { // degree type value - x = cStart.x + static_cast((WindUtils::to2PI(chrtVal - chrtMin) * chrtScl) + 0.5); // calculate chart point and round - } - } - - // if (i >= (numBufVals / chrtIntv) - 5) // log chart data of 1 line (adjust for test purposes) - // LOG_DEBUG(GwLog::DEBUG, "PageWindPlot Chart: i: %d, chrtVal: %.4f, {x,y} {%d,%d}", i, chrtVal, x, y); - - if ((i == 0) || (chrtPrevVal == dbMAX_VAL)) { - // just a dot for 1st chart point or after some invalid values - prevX = x; - prevY = y; - - } else if (chrtDataFmt != 0) { - // cross borders check for degree values; shift values to [-PI..0..PI]; when crossing borders, range is 2x PI degrees - 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 == 0) { - int ySplit = wrappingFromHighToLow ? (cStart.y + valAxis) : cStart.y; - getdisplay().drawLine(prevX, prevY, x, ySplit, fgColor); - if (x != prevX) { // line with some horizontal trend - getdisplay().drawLine(prevX, prevY - 1, x, ySplit - 1, fgColor); - } else { - getdisplay().drawLine(prevX, prevY - 1, x - 1, ySplit, fgColor); - } - prevY = wrappingFromHighToLow ? cStart.y : (cStart.y + valAxis); - } else { // vertical chart - int xSplit = wrappingFromHighToLow ? (cStart.x + valAxis) : cStart.x; - getdisplay().drawLine(prevX, prevY, xSplit, y, fgColor); - getdisplay().drawLine(prevX, prevY - 1, ((xSplit != prevX) ? xSplit : xSplit - 1), ((xSplit != prevX) ? y - 1 : y), fgColor); - prevX = wrappingFromHighToLow ? cStart.x : (cStart.x + valAxis); - } - } - } - - // Draw line with 2 pixels width + make sure vertical lines are drawn correctly - if (chrtDir == 0 || x == prevX) { // horizontal chart & vertical line -// if (x == prevX) { // vertical line - getdisplay().drawLine(prevX, prevY, x, y, fgColor); - getdisplay().drawLine(prevX - 1, prevY, x - 1, y, fgColor); - } else if (chrtDir == 1 || x != prevX) { // vertical chart & line with some horizontal trend -> normal state -// } else { // line with some horizontal trend -> normal state - getdisplay().drawLine(prevX, prevY, x, y, fgColor); - getdisplay().drawLine(prevX, prevY - 1, x, y - 1, fgColor); - } - chrtPrevVal = chrtVal; - prevX = x; - prevY = y; + // initialize if data buffer has just been started filling + if ((count == 1 && rngMid == 0) || rngMid == dbMAX_VAL) { + recalcRngMid = true; } - // Reaching chart area bottom end - if (i >= timAxis - 1) { - oldChrtIntv = 0; // force reset of buffer start and number of values to show in next display loop + if (recalcRngMid) { + // Set rngMid - if (chrtDataFmt == 1) { // degree of course or wind - recalcRngCntr = true; - LOG_DEBUG(GwLog::DEBUG, "PageWindPlot FreeTop: timAxis: %d, i: %d, bufStart: %d, numBufVals: %d, recalcRngCntr: %d", timAxis, i, bufStart, numBufVals, recalcRngCntr); + rngMid = dataBuf.getMid(numBufVals); + + if (rngMid == dbMAX_VAL) { + rngMid = 0; + } else { + rngMid = std::round(rngMid / rngStep) * rngStep; // Set new center value; round to next value + + // Check if range between 'min' and 'max' is > 180° or crosses '0' + rngMin = dataBuf.getMin(numBufVals); + rngMax = dataBuf.getMax(numBufVals); + rng = (rngMax >= rngMin ? rngMax - rngMin : M_TWOPI - rngMin + rngMax); + rng = 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); + } } - break; + 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 { - // No valid data available - getdisplay().setFont(&Ubuntu_Bold10pt8b); + // check and adjust range between left, mid, and right chart limit + double halfRng = rng / 2.0; // we calculate with range between and edges + double tmpRng = getAngleRng(rngMid, numBufVals); + tmpRng = (tmpRng == dbMAX_VAL ? 0 : std::ceil(tmpRng / rngStep) * rngStep); - int pX, pY; - if (chrtDir == 0) { // horizontal chart - pX = cStart.x + (timAxis / 2); - pY = cStart.y + (valAxis / 2) - 10; - } else { // vertical chart - pX = cStart.x + (valAxis / 2); - pY = cStart.y + (timAxis / 2) - 10; + // 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; } - 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"); + 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); + 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); + + rng = halfRng * 2.0; + + LOG_DEBUG(GwLog::DEBUG, "calcChrtBorders: rngMin: %.1f°, rngMid: %.1f°, rngMax: %.1f°, tmpRng: %.1f°, rng: %.1f°, rngStep: %.1f°", rngMin * RAD_TO_DEG, rngMid * RAD_TO_DEG, rngMax * RAD_TO_DEG, + tmpRng * RAD_TO_DEG, rng * RAD_TO_DEG, rngStep * RAD_TO_DEG); + + } else { // chart data is of any other type + + double currMinVal = dataBuf.getMin(numBufVals); + double currMaxVal = dataBuf.getMax(numBufVals); + + if (currMinVal == dbMAX_VAL || currMaxVal == dbMAX_VAL) { + return; // no valid data + } + + // check if current chart border have to be adjusted + 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 > rngMax) || (currMaxVal < (rngMax - 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, zeroValue: %.1f, dbMIN_VAL: %.1f", + currMinVal, currMaxVal, rngMin, rngMid, rngMax, rng, rngStep, zeroValue, dbMIN_VAL); } } -// Get maximum difference of last of dataBuf ringbuffer values to center chart -template -double Chart::getRng(double center, size_t amount) +// 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, const int8_t chrtIntv) +{ + 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); + + 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 + + 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 + + // draw text with appropriate offset + int tOffset = j == 0 ? 13 : -4; + snprintf(sTime, sizeof(sTime), "-%.0f", i); + drawTextCenter(cRoot.x + j + tOffset, cRoot.y - 8, sTime); + getdisplay().drawLine(cRoot.x + j, cRoot.y, cRoot.x + j, cRoot.y + 5, fgColor); // draw short vertical time mark + + i -= chrtIntv; + } + + } else { // vertical chart + + for (float j = intv; j < timAxis - 1; j += intv) { // don't print time label at upper and lower end of time axis + + i -= chrtIntv; // we start not at top chart position + snprintf(sTime, sizeof(sTime), "-%.0f", i); + getdisplay().drawLine(cRoot.x, cRoot.y + j, cRoot.x + valAxis, cRoot.y + j, fgColor); // Grid line + + if (chrtSz == FULL_SIZE) { // full size chart + getdisplay().fillRect(0, cRoot.y + j - 9, 32, 15, bgColor); // clear small area to remove potential chart lines + getdisplay().setCursor((4 - strlen(sTime)) * 7, cRoot.y + j + 3); // time value; print left screen; value right-formated + getdisplay().printf("%s", sTime); // Range value + } else if (chrtSz == HALF_SIZE_RIGHT) { // half size chart; right side + drawTextCenter(dWidth / 2, cRoot.y + j, sTime); // time value; print mid screen + } + } + } +} + +// chart value axis labels + lines +void Chart::drawChrtValAxis(const char chrtDir, const int8_t chrtSz, bool prntName) +{ + const GFXfont* font; + constexpr bool NO_LABEL = false; + constexpr bool LABEL = true; + + getdisplay().setTextColor(fgColor); + + if (chrtDir == HORIZONTAL) { + + if (chrtSz == FULL_SIZE) { + + font = &Ubuntu_Bold12pt8b; + + // print buffer data name on right hand side of time axis (max. size 5 characters) + getdisplay().setFont(font); + drawTextRalign(cRoot.x + timAxis, cRoot.y - 3, dbName.substring(0, 5)); + + if (chrtDataFmt == WIND) { + prntHorizChartThreeValueAxisLabel(font); + return; + } + + // for any other data formats print multiple axis value lines on full charts + prntHorizChartMultiValueAxisLabel(font); + return; + + } else { // half size chart -> just print edge values + middle chart line + + font = &Ubuntu_Bold10pt8b; + + if (prntName) { + // print buffer data name on right hand side of time axis (max. size 5 characters) + getdisplay().setFont(font); + drawTextRalign(cRoot.x + timAxis, cRoot.y - 3, dbName.substring(0, 5)); + } + + prntHorizChartThreeValueAxisLabel(font); + return; + } + + } else { // vertical chart + + 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 { + + font = &Ubuntu_Bold10pt8b; + } + + prntVerticChartThreeValueAxisLabel(font); + } +} + +// Print current data value +void Chart::prntCurrValue(const char direction, GwApi::BoatValue& currValue) +{ + 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, NO_SIMUDATA); + String sdbValue = frmtDbData.svalue; // value as formatted string + String dbUnit = frmtDbData.unit; // Unit of value; limit length to 3 characters + + 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); // value + + getdisplay().setFont(&Ubuntu_Bold10pt8b); + getdisplay().setCursor(xPosVal + 76, yPosVal - 17); + 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 +} + +// print message for no valid data availabletemplate +void Chart::prntNoValidData(const char direction) +{ + Pos p; + + getdisplay().setFont(&Ubuntu_Bold10pt8b); + + if (direction == HORIZONTAL) { + p.x = cRoot.x + (timAxis / 2); + p.y = cRoot.y + (valAxis / 2) - 10; + } else { + p.x = cRoot.x + (valAxis / 2); + p.y = cRoot.y + (timAxis / 2) - 10; + } + + 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(const double center, size_t amount) { size_t count = dataBuf.getCurrentSize(); @@ -312,298 +615,191 @@ double Chart::getRng(double center, size_t amount) 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) + // print value axis label with only three values: top, mid, and bottom for vertical chart + void Chart::prntVerticChartThreeValueAxisLabel(const GFXfont* font) { - if (chrtDataFmt == 0) { - // Chart data is of any type but 'degree' + double cVal; + char sVal[7]; - double oldRngMin = rngMin; - double oldRngMax = rngMax; + getdisplay().fillRect(cRoot.x, cRoot.y, valAxis, 2, fgColor); // top chart line + getdisplay().setFont(font); - // 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); + 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 - 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 + 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 - 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 + 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 - 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); + // 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; + 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 = 18; + } + 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 == SPEED || chrtDataFmt == TEMPERATURE) ? chrtMax : chrtMin; + sVal = formatLabel(axLabel); + 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 + 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 + 3, cRoot.y + (valAxis / 2), cRoot.x + timAxis, cRoot.y + (valAxis / 2), fgColor); + + // print bottom axis label + axLabel = (chrtDataFmt == SPEED || chrtDataFmt == TEMPERATURE) ? chrtMin : chrtMax; + sVal = formatLabel(axLabel); + 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 + 3, cRoot.y + valAxis, cRoot.x + timAxis, cRoot.y + valAxis, fgColor); +} + +// 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; + 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 == SPEED || chrtDataFmt == TEMPERATURE || chrtDataFmt == OTHER) { + // 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; + } - if (chrtDataFmt == 1) { - // Chart data is of type 'course' or 'wind' + for (int j = loopStrt; (loopStp > 0) ? (j < loopEnd) : (j > loopEnd); j += loopStp) { + sVal = formatLabel(axLabel); + 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); - if ((count == 1 && rngMid == 0) || rngMid == dbMAX_VAL) { - recalcRngCntr = true; // initialize - } - - // Set rngMid - if (recalcRngCntr) { - rngMid = dataBuf.getMid(numBufVals); - if (rngMid == dbMAX_VAL) { - rngMid = 0; - } else { - rngMid = std::round(rngMid / rngStep) * rngStep; // Set new center value; round to next value - - // Check if range between 'min' and 'max' is > 180° or crosses '0' - rngMin = dataBuf.getMin(numBufVals); - rngMax = dataBuf.getMax(numBufVals); - rng = (rngMax >= rngMin ? rngMax - rngMin : M_TWOPI - rngMin + rngMax); - rng = max(rng, dfltRng); // keep at least default chart range - if (rng > M_PI) { // If wind range > 180°, adjust wndCenter to smaller wind range end - rngMid = WindUtils::to2PI(rngMid + M_PI); - } - } - recalcRngCntr = false; // Reset flag for determination - LOG_DEBUG(GwLog::DEBUG, "calcChrtRange1b: rngMid: %.1f°, rngMin: %.1f°, rngMax: %.1f°, rng: %.1f°, rngStep: %.1f°", rngMid * RAD_TO_DEG, rngMin * RAD_TO_DEG, rngMax * RAD_TO_DEG, - rng * RAD_TO_DEG, rngStep * RAD_TO_DEG); - } - - } else if (chrtDataFmt == 2) { - // Chart data is of type 'rotation'; then we want to have always to be '0' - rngMid = 0; - } - - // check and adjust range between left, center, and right chart limit - double halfRng = rng / 2.0; // we calculate with range between and edges - double diffRng = getRng(rngMid, numBufVals); - // LOG_DEBUG(GwLog::DEBUG, "calcChrtRange2: diffRng: %.1f°, halfRng: %.1f°", diffRng * RAD_TO_DEG, halfRng * RAD_TO_DEG); - diffRng = (diffRng == dbMAX_VAL ? 0 : std::ceil(diffRng / rngStep) * rngStep); - // LOG_DEBUG(GwLog::DEBUG, "calcChrtRange2: diffRng: %.1f°, halfRng: %.1f°", diffRng * RAD_TO_DEG, halfRng * RAD_TO_DEG); - - if (diffRng > halfRng) { - halfRng = diffRng; // round to next value - } else if (diffRng + rngStep < halfRng) { // Reduce chart range for higher resolution if possible - halfRng = max(dfltRng / 2.0, diffRng); - } - - rngMin = WindUtils::to2PI(rngMid - halfRng); - rngMax = (halfRng < M_PI ? rngMid + halfRng : rngMid + halfRng - (M_TWOPI / 360)); // if chart range is 360°, then make 1° smaller than - rngMax = WindUtils::to2PI(rngMax); - // LOG_DEBUG(GwLog::DEBUG, "calcChrtRange2: diffRng: %.1f°, halfRng: %.1f°", diffRng * RAD_TO_DEG, halfRng * RAD_TO_DEG); - - rng = halfRng * 2.0; - LOG_DEBUG(GwLog::DEBUG, "calcChrtRange2b: rngMid: %.1f°, rngMin: %.1f°, rngMax: %.1f°, diffRng: %.1f°, rng: %.1f°, rngStep: %.1f°", rngMid * RAD_TO_DEG, rngMin * RAD_TO_DEG, rngMax * RAD_TO_DEG, - diffRng * RAD_TO_DEG, rng * RAD_TO_DEG, rngStep * RAD_TO_DEG); + axLabel += axIntv; } } -// chart time axis label + lines -template -void Chart::drawChrtTimeAxis(int8_t chrtIntv) +// Draw chart line with thickness of 2px +void Chart::drawBoldLine(const int16_t x1, const int16_t y1, const int16_t x2, const int16_t y2) { - int timeRng; - float slots, intv, i; - char sTime[6]; - getdisplay().setFont(&Ubuntu_Bold8pt8b); - getdisplay().setTextColor(fgColor); + int16_t dx = std::abs(x2 - x1); + int16_t dy = std::abs(y2 - y1); - if (chrtDir == 0) { // horizontal chart - getdisplay().fillRect(0, cStart.y, dWidth, 2, fgColor); + getdisplay().drawLine(x1, y1, x2, y2, fgColor); - timeRng = chrtIntv * 4; // Chart time interval: [1] 4 min., [2] 8 min., [3] 12 min., [4] 16 min., [8] 32 min. - slots = timAxis / 80.0; // number of axis labels - intv = timeRng / slots; // minutes per chart axis interval - i = timeRng; // Chart axis label start at -32, -16, -12, ... minutes - - for (int j = 0; j < timAxis - 30; j += 80) { // fill time axis with values but keep area free on right hand side for value label - // LOG_DEBUG(GwLog::DEBUG, "ChartTimeAxis: timAxis: %d, {x,y}: {%d,%d}, i: %.1f, j: %d, chrtIntv: %d, intv: %.1f, slots: %.1f", timAxis, cStart.x, cStart.y, i, j, chrtIntv, intv, slots); - - // Format time label based on interval - if (chrtIntv < 3) { - snprintf(sTime, sizeof(sTime), "-%.1f", i); - } else { - snprintf(sTime, sizeof(sTime), "-%.0f", std::round(i)); - } - - // draw text with appropriate offset - // int tOffset = (j == 0) ? 13 : (chrtIntv < 3 ? -4 : -4); - int tOffset = j == 0 ? 13 : -4; - drawTextCenter(cStart.x + j + tOffset, cStart.y - 8, sTime); - getdisplay().drawLine(cStart.x + j, cStart.y, cStart.x + j, cStart.y + 5, fgColor); // draw short vertical time mark - - i -= intv; - } - - } else { // vertical chart - timeRng = chrtIntv * 4; // chart time interval: [1] 4 min., [2] 8 min., [3] 12 min., [4] 16 min., [8] 32 min. - slots = timAxis / 75.0; // number of axis labels - intv = timeRng / slots; // minutes per chart axis interval - i = -intv; // chart axis label start at -32, -16, -12, ... minutes - - for (int j = 75; j < (timAxis - 75); j += 75) { // don't print time label at upper and lower end of time axis - if (chrtIntv < 3) { // print 1 decimal if time range is single digit (4 or 8 minutes) - snprintf(sTime, sizeof(sTime), "%.1f", i); - } else { - snprintf(sTime, sizeof(sTime), "%.0f", std::floor(i)); - } - - getdisplay().drawLine(cStart.x, cStart.y + j, cStart.x + valAxis, cStart.y + j, fgColor); // Grid line - - if (chrtSz == 0) { // full size chart - getdisplay().fillRect(0, cStart.y + j - 9, 32, 15, bgColor); // clear small area to remove potential chart lines - getdisplay().setCursor((4 - strlen(sTime)) * 7, cStart.y + j + 3); // time value; print left screen; value right-formated - getdisplay().printf("%s", sTime); // Range value - } else if (chrtSz == 2) { // half size chart; right side - drawTextCenter(dWidth / 2, cStart.y + j, sTime); // time value; print mid screen - } - - i -= intv; - } + 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); } } -// chart value axis labels + lines -template -void Chart::drawChrtValAxis() +// Convert and format current axis label to user defined format; helper function for easier handling of OBP60Formatter +String Chart::convNformatLabel(const double& label) { - double slots; - int i, intv; - double cVal, cchrtRng, crngMin; - char sVal[6]; - int sLen; - std::unique_ptr tmpBVal; // Temp variable to get formatted and converted data value from OBP60Formatter - tmpBVal = std::unique_ptr(new GwApi::BoatValue(dataBuf.getName())); - tmpBVal->setFormat(dataBuf.getFormat()); - tmpBVal->valid = true; + GwApi::BoatValue tmpBVal(dbName); // temporary boat value for string formatter + String sVal; - if (chrtDir == 0) { // horizontal chart - slots = valAxis / 60.0; // number of axis labels - tmpBVal->value = chrtRng; - cchrtRng = formatValue(tmpBVal.get(), *commonData).cvalue; // value (converted) - intv = static_cast(round(cchrtRng / slots)); - i = intv; + tmpBVal.setFormat(dbFormat); + tmpBVal.valid = true; + tmpBVal.value = label; + 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; doesn't work for other fonts than 7SEG + } - if (chrtSz == 0) { // full size chart -> print multiple value lines - getdisplay().setFont(&Ubuntu_Bold12pt8b); - for (int j = 60; j < valAxis - 30; j += 60) { - getdisplay().drawLine(cStart.x, cStart.y + j, cStart.x + timAxis, cStart.y + j, fgColor); + return sVal; +} - 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 +// Format current axis label for printing w/o data format conversion (has been done earlier) +String Chart::formatLabel(const double& label) +{ + char sVal[11]; - i += intv; - } - } else { // half size chart -> print just edge values + middle chart line - getdisplay().setFont(&Ubuntu_Bold10pt8b); + if (dbFormat == "formatCourse" || dbFormat == "formatWind") { + // Format 3 numbers with prefix zero + snprintf(sVal, sizeof(sVal), "%03.0f", label); - tmpBVal->value = chrtMin; - cVal = formatValue(tmpBVal.get(), *commonData).cvalue; // value (converted) - sLen = snprintf(sVal, sizeof(sVal), "%.0f", round(cVal)); - getdisplay().fillRect(cStart.x, cStart.y + 2, 42, 16, bgColor); // Clear small area to remove potential chart lines - getdisplay().setCursor(cStart.x + ((3 - sLen) * 10), cStart.y + 16); - getdisplay().printf("%s", sVal); // Range low end - - tmpBVal->value = chrtMid; - cVal = formatValue(tmpBVal.get(), *commonData).cvalue; // value (converted) - sLen = snprintf(sVal, sizeof(sVal), "%.0f", round(cVal)); - getdisplay().fillRect(cStart.x, cStart.y + (valAxis / 2) - 9, 42, 16, bgColor); // Clear small area to remove potential chart lines - getdisplay().setCursor(cStart.x + ((3 - sLen) * 10), cStart.y + (valAxis / 2) + 5); - getdisplay().printf("%s", sVal); // Range mid value - getdisplay().drawLine(cStart.x + 43, cStart.y + (valAxis / 2), cStart.x + timAxis, cStart.y + (valAxis / 2), fgColor); - - tmpBVal->value = chrtMax; - cVal = formatValue(tmpBVal.get(), *commonData).cvalue; // value (converted) - sLen = snprintf(sVal, sizeof(sVal), "%.0f", round(cVal)); - getdisplay().fillRect(cStart.x, cStart.y + valAxis - 16, 42, 16, bgColor); // Clear small area to remove potential chart lines - getdisplay().setCursor(cStart.x + ((3 - sLen) * 10), cStart.y + valAxis - 1); - getdisplay().printf("%s", sVal); // Range high end - getdisplay().drawLine(cStart.x + 43, cStart.y + valAxis, cStart.x + timAxis, cStart.y + valAxis, fgColor); - } - - getdisplay().setFont(&Ubuntu_Bold12pt8b); - drawTextRalign(cStart.x + timAxis, cStart.y - 3, dbName); // buffer data name - - } 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 + } else if (dbFormat == "formatRot") { + if (label > -10 && label < 10) { + snprintf(sVal, sizeof(sVal), "%3.2f", label); } else { - getdisplay().setFont(&Ubuntu_Bold10pt8b); + snprintf(sVal, sizeof(sVal), "%3.0f", label); } - getdisplay().fillRect(cStart.x, top, valAxis, 2, fgColor); // top chart line - - tmpBVal->value = chrtMin; - cVal = formatValue(tmpBVal.get(), *commonData).cvalue; // value (converted) - snprintf(sVal, sizeof(sVal), "%.0f", round(cVal)); - getdisplay().setCursor(cStart.x, cStart.y - 2); - getdisplay().printf("%s", sVal); // Range low end - - tmpBVal->value = chrtMid; - cVal = formatValue(tmpBVal.get(), *commonData).cvalue; // value (converted) - snprintf(sVal, sizeof(sVal), "%.0f", round(cVal)); - drawTextCenter(cStart.x + (valAxis / 2), cStart.y - 10, sVal); // Range mid end - - tmpBVal->value = chrtMax; - cVal = formatValue(tmpBVal.get(), *commonData).cvalue; // value (converted) - snprintf(sVal, sizeof(sVal), "%.0f", round(cVal)); - drawTextRalign(cStart.x + valAxis - 2, cStart.y - 2, sVal); // Range high end - - for (int j = 0; j <= valAxis + 2; j += ((valAxis + 2) / 2)) { - getdisplay().drawLine(cStart.x + j, cStart.y, cStart.x + j, cStart.y + timAxis, fgColor); - } - - // if (chrtSz == 0) { - // getdisplay().setFont(&Ubuntu_Bold12pt8b); - // drawTextCenter(cStart.x + (valAxis / 4) + 15, cStart.y - 11, dbName); // buffer data name - // } - } -} - -// Print current data value -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; - - FormattedData frmtDbData = formatValue(&currValue, *commonData); - double testdbValue = frmtDbData.value; - String sdbValue = frmtDbData.svalue; // value (string) - String dbUnit = frmtDbData.unit; // Unit of value - // LOG_DEBUG(GwLog::DEBUG, "Chart CurrValue: dbValue: %.2f, sdbValue: %s, fmrtDbValue: %.2f, dbFormat: %s, dbUnit: %s, Valid: %d, Name: %s, Address: %p", currValue.value, sdbValue, - // testdbValue, currValue.getFormat(), dbUnit, currValue.valid, currValue.getName(), currValue); - - getdisplay().fillRect(xPosVal - 1, yPosVal - 34, 125, 41, bgColor); // Clear area for TWS value - getdisplay().drawRect(xPosVal, yPosVal - 33, 123, 39, fgColor); // Draw box for TWS value - getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b); - getdisplay().setCursor(xPosVal + 1, yPosVal); - if (useSimuData) { - getdisplay().printf("%2.1f", currValue.value); // Value - } else { - getdisplay().print(sdbValue); // Value } - getdisplay().setFont(&Ubuntu_Bold10pt8b); - getdisplay().setCursor(xPosVal + 76, yPosVal - 17); - getdisplay().print(dbName); // Name + else { + if (label < 10) { + snprintf(sVal, sizeof(sVal), "%3.1f", label); + } else { + snprintf(sVal, sizeof(sVal), "%3.0f", label); + } + } - getdisplay().setFont(&Ubuntu_Bold8pt8b); - getdisplay().setCursor(xPosVal + 76, yPosVal + 0); - getdisplay().print(dbUnit); // Unit + 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 db298a5..fbdcddd 100644 --- a/lib/obp60task/OBPcharts.h +++ b/lib/obp60task/OBPcharts.h @@ -1,47 +1,75 @@ // 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; -class GwLog; + +struct ChartProps { + double range; + double step; +}; template +class RingBuffer; +class GwLog; + class Chart { protected: - CommonData *commonData; - GwLog *logger; + CommonData* commonData; + GwLog* logger; - RingBuffer &dataBuf; // Buffer to display - int8_t chrtDir; // Chart timeline direction: [0] = horizontal, [1] = vertical - int8_t chrtSz; // Chart size: [0] = full size, [1] = half size left/top, [2] half size right/bottom + 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 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 + String tempFormat; // user defined format for temperature + double zeroValue; // "0" SI value for temperature - int top = 48; // display top header lines - int bottom = 22; // display bottom lines - 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 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 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 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 - int chrtDataFmt; // Data format of chart: [0] size values; [1] degree of course or wind; [2] 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 @@ -52,19 +80,37 @@ 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 - void drawChrt(int8_t chrtIntv, GwApi::BoatValue& currValue); // Draw chart line - double getRng(double center, size_t amount); // Calculate range between chart center and edges - void calcChrtBorders(double& rngMid, double& rngMin, double& rngMax, double& rng); // Calculate chart points for value axis and return range between and - void drawChrtTimeAxis(int8_t chrtIntv); // Draw time axis of chart, value and lines - void drawChrtValAxis(); // Draw value axis of chart, value and lines - void prntCurrValue(GwApi::BoatValue& currValue); // Add current boat data value to chart + 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 + + bool setChartDimensions(const char direction, const int8_t size); //define dimensions and start points 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 drawChartLines(const char direction, const int8_t chrtIntv, const double chrtScale); // Draw chart graph + Pos setCurrentChartPoint(const int i, const char direction, const double chrtVal, const double chrtScale); // Set current chart point to draw + void drawChrtTimeAxis(const char chrtDir, const int8_t chrtSz, const int8_t chrtIntv); // Draw time axis of chart, value and lines + void drawChrtValAxis(const char chrtDir, const int8_t chrtSz, bool prntLabel); // Draw value axis of chart, value and lines + void prntCurrValue(const char chrtDir, GwApi::BoatValue& currValue); // Add current boat data value to chart + void prntNoValidData(const char chrtDir); // print message for no valid data available + double getAngleRng(const double center, size_t amount); // Calculate range between chart center and edges + void 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: - Chart(RingBuffer& dataBuf, int8_t 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 + // Define default chart range and range step for each boat data type + static std::map dfltChrtDta; -}; \ No newline at end of file + Chart(RingBuffer& dataBuf, double dfltRng, CommonData& common, bool useSimuData); // Chart object of data chart + ~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 6f33597..6349f29 100644 --- a/lib/obp60task/PageOneValue.cpp +++ b/lib/obp60task/PageOneValue.cpp @@ -3,112 +3,300 @@ #include "Pagedata.h" #include "OBP60Extensions.h" #include "BoatDataCalibration.h" +#include "OBPDataOperations.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; + + enum PageMode { + VALUE, + BOTH, + CHART + }; + enum DisplayMode { + FULL, + HALF + }; + + static constexpr char HORIZONTAL = 'H'; + static constexpr char VERTICAL = 'V'; + static constexpr int8_t FULL_SIZE = 0; + static constexpr int8_t HALF_SIZE_TOP = 1; + static constexpr int8_t HALF_SIZE_BOTTOM = 2; + + static constexpr 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 + 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 + + // String lengthformat; + bool useSimuData; + bool holdValues; + String flashLED; + String backlightMode; + String tempFormat; + + // Old values for hold function + String sValue1Old = ""; + String unit1Old = ""; + + // Data buffer pointer (owned by HstryBuffers) + 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; + const GFXfont *nameFnt, *unitFnt, *valueFnt1, *valueFnt2, *valueFnt3; + + if (mode == FULL) { // 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 = -10; + nameYoff = -34; + nameFnt = &Ubuntu_Bold20pt8b; + unitXoff = -295; + unitYoff = -119; + unitFnt = &Ubuntu_Bold12pt8b; + valueFnt1 = &Ubuntu_Bold12pt8b; + value1Xoff = 153; + value1Yoff = -119; + valueFnt2 = &Ubuntu_Bold20pt8b; + valueFnt3 = &DSEG7Classic_BoldItalic42pt7b; + } + + 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(305 + unitXoff, 240 + unitYoff); + + if (holdValues) { + getdisplay().print(unit1Old); // name + } else { + getdisplay().print(unit1); // name + } + + // 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); + } 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 || 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 + } } - 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 = 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) { // show "Mode" key only if 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) { // if boat data type supports charts + + // Set page mode: value | value/half chart | full chart + if (key == 1) { + switch (pageMode) { + case VALUE: + pageMode = BOTH; + break; + case BOTH: + pageMode = CHART; + break; + case CHART: + pageMode = VALUE; + 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 = ""; - - // 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 - - // 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 + 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 + + dataHstryBuf = pageData.hstryBuffers->getBuffer(bValName1); + + if (dataHstryBuf) { + dataChart.reset(new Chart(*dataHstryBuf, 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); + } } - // 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); + setupKeys(); // adjust key depending on chart supported boat data type + } + + int displayPage(PageData& pageData) + { + LOG_DEBUG(GwLog::LOG, "Display PageOneValue"); + + // 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") { + setBlinkingLED(false); + setFlashLED(false); + } + + if (bValue1 == NULL) + return PAGE_OK; // no data, no page to display + + LOG_DEBUG(GwLog::DEBUG, "PageOneValue: printing %s, %.3f", 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 == VALUE || dataHstryBuf == nullptr) { + // show only data value; ignore other pageMode options if no chart supported boat data history buffer is available + showData(bValue1, FULL); - // Show unit - getdisplay().setFont(&Ubuntu_Bold20pt8b); - getdisplay().setCursor(270, 100); - if(holdvalues == false){ - getdisplay().print(unit1); // Unit - } - else{ - getdisplay().print(unit1old); - } + } else if (pageMode == CHART) { // show only data chart + if (dataChart) { + dataChart->showChrt(HORIZONTAL, FULL_SIZE, dataIntv, PRNT_NAME, PRNT_VALUE, *bValue1); + } - // 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 == BOTH) { // show data value and chart + showData(bValue1, HALF); + if (dataChart) { + dataChart->showChrt(HORIZONTAL, HALF_SIZE_BOTTOM, dataIntv, NO_PRNT_NAME, NO_PRNT_VALUE, *bValue1); + } } return PAGE_UPDATE; }; }; -static Page* createPage(CommonData &common){ +static Page* createPage(CommonData& common) +{ return new PageOneValue(common); } @@ -120,10 +308,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/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 0b4e884..f5745f7 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" // **************************************************************** @@ -10,20 +11,58 @@ class PageWindPlot : public Page { private: GwLog* logger; + 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_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 - 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; 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 twdChart, awdChart; // Chart object for wind direction + std::unique_ptr twsChart, awsChart; // Chart object for wind speed + + // Active charts and values + Chart* wdChart = nullptr; + Chart* wsChart = nullptr; + GwApi::BoatValue* wdBVal = nullptr; + GwApi::BoatValue* wsBVal = nullptr; + public: PageWindPlot(CommonData& common) { @@ -31,11 +70,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() @@ -44,23 +88,23 @@ 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 } // Key functions virtual int handleKey(int key) { - // Set chart mode TWD | TWS + // Set chart mode if (key == 1) { - if (chrtMode == 'D') { - chrtMode = 'S'; - } else if (chrtMode == 'S') { - chrtMode = 'B'; + if (chrtMode == DIRECTION) { + chrtMode = SPEED; + } else if (chrtMode == SPEED) { + chrtMode = BOTH; } else { - chrtMode = 'D'; + chrtMode = DIRECTION; } return 0; // Commit the key } @@ -101,116 +145,77 @@ 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 - + // 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; } 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 (!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"); + } + } } int displayPage(PageData& pageData) { - GwConfigHandler* config = commonData->config; - - 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 - - // Separate chart objects for true wind and apparent wind - static std::unique_ptr> twdFlChart, awdFlChart; // chart object for wind direction chart, full size - static std::unique_ptr> twsFlChart, awsFlChart; // chart object for wind speed chart, full size - static std::unique_ptr> twdHfChart, awdHfChart; // chart object for wind direction chart, half size - static std::unique_ptr> twsHfChart, awsHfChart; // chart object for wind speed chart, half size - // Pointers to the currently active charts - static Chart* wdFlChart; - static Chart* wsFlChart; - static Chart* wdHfChart; - static Chart* wsHfChart; - - static GwApi::BoatValue* wdBVal = new GwApi::BoatValue("TWD"); // temp BoatValue for wind direction unit identification; required by OBP60Formater - static GwApi::BoatValue* wsBVal = new GwApi::BoatValue("TWS"); // temp BoatValue for wind speed unit identification; required by OBP60Formater */ - double dfltRngWd = 60.0 * DEG_TO_RAD; // default range for course chart from min to max value in RAD - 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"); - auto* twdHstry = pageData.boatHstry->hstryBufList.twdHstry; - auto* twsHstry = pageData.boatHstry->hstryBufList.twsHstry; - // LOG_DEBUG(GwLog::DEBUG,"History Buffer addresses PageWindPlot: twdBuf: %p, twsBuf: %p", (void*)pageData.boatHstry->hstryBufList.twdHstry, - // (void*)pageData.boatHstry->hstryBufList.twsHstry); - - twdFlChart = std::unique_ptr>(new Chart(*twdHstry, 1, 0, dfltRngWd, *commonData, useSimuData)); - twsFlChart = std::unique_ptr>(new Chart(*twsHstry, 0, 0, dfltRngWs, *commonData, useSimuData)); - twdHfChart = std::unique_ptr>(new Chart(*twdHstry, 1, 1, dfltRngWd, *commonData, useSimuData)); - twsHfChart = std::unique_ptr>(new Chart(*twsHstry, 1, 2, dfltRngWs, *commonData, useSimuData)); - // twdHfChart = std::unique_ptr>(new Chart(*twdHstry, 0, 1, dfltRngWd, *commonData, useSimuData)); - // twsHfChart = std::unique_ptr>(new Chart(*twsHstry, 0, 2, dfltRngWs, *commonData, useSimuData)); - // LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: twdHstry: %p, twsHstry: %p", (void*)twdHstry, (void*)twsHstry); - } - - if (!awdFlChart) { // Create apparent wind charts if they don't exist - LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: Creating apparent wind charts"); - auto* awdHstry = pageData.boatHstry->hstryBufList.awdHstry; - auto* awsHstry = pageData.boatHstry->hstryBufList.awsHstry; - - awdFlChart = std::unique_ptr>(new Chart(*awdHstry, 1, 0, dfltRngWd, *commonData, useSimuData)); - awsFlChart = std::unique_ptr>(new Chart(*awsHstry, 0, 0, dfltRngWs, *commonData, useSimuData)); - awdHfChart = std::unique_ptr>(new Chart(*awdHstry, 1, 1, dfltRngWd, *commonData, useSimuData)); - awsHfChart = std::unique_ptr>(new Chart(*awsHstry, 1, 2, dfltRngWs, *commonData, useSimuData)); - } - // Switch active charts based on showTruW if (showTruW) { - wdHstry = pageData.boatHstry->hstryBufList.twdHstry; - wsHstry = pageData.boatHstry->hstryBufList.twsHstry; - wdFlChart = twdFlChart.get(); - wsFlChart = twsFlChart.get(); - wdHfChart = twdHfChart.get(); - wsHfChart = twsHfChart.get(); + wdChart = twdChart.get(); + wsChart = twsChart.get(); + wdBVal = pageData.values[0]; + wsBVal = pageData.values[1]; } else { - wdHstry = pageData.boatHstry->hstryBufList.awdHstry; - wsHstry = pageData.boatHstry->hstryBufList.awsHstry; - 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]; } - - wdHstry->getMetaData(wdName, wdFormat); - wsHstry->getMetaData(wsName, wsFormat); - + 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 //*********************************************************** @@ -219,28 +224,26 @@ public: getdisplay().setPartialWindow(0, 0, width, height); // Set partial update getdisplay().setTextColor(commonData->fgcolor); - if (chrtMode == 'D') { - wdBVal->value = wdHstry->getLast(); - wdBVal->valid = wdBVal->value != wdHstry->getMaxVal(); - wdFlChart->showChrt(dataIntv, *bvalue[0]); + if (chrtMode == DIRECTION) { + if (wdChart) { + wdChart->showChrt(VERTICAL, FULL_SIZE, dataIntv, PRNT_NAME, PRNT_VALUE, *wdBVal); + } - } else if (chrtMode == 'S') { - wsBVal->value = wsHstry->getLast(); - wsBVal->valid = wsBVal->value != wsHstry->getMaxVal(); - wsFlChart->showChrt(dataIntv, *bvalue[1]); + } else if (chrtMode == SPEED) { + if (wsChart) { + wsChart->showChrt(HORIZONTAL, FULL_SIZE, dataIntv, PRNT_NAME, PRNT_VALUE, *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]); + } else if (chrtMode == BOTH) { + if (wdChart) { + wdChart->showChrt(VERTICAL, HALF_SIZE_LEFT, dataIntv, PRNT_NAME, PRNT_VALUE, *wdBVal); + } + if (wsChart) { + wsChart->showChrt(VERTICAL, HALF_SIZE_RIGHT, dataIntv, PRNT_NAME, PRNT_VALUE, *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 9c515b4..02afba9 100644 --- a/lib/obp60task/Pagedata.h +++ b/lib/obp60task/Pagedata.h @@ -4,19 +4,20 @@ #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; uint8_t pageNumber; // page number in sequence of visible pages //the values will always contain the user defined values first ValueList values; - HstryBuf* boatHstry; + HstryBuffers* hstryBuffers; // list of all boat history buffers } PageData; // Sensor data structure (only for extended sensors, not for NMEA bus sensors) @@ -203,3 +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); diff --git a/lib/obp60task/obp60task.cpp b/lib/obp60task/obp60task.cpp index a381590..1058ae0 100644 --- a/lib/obp60task/obp60task.cpp +++ b/lib/obp60task/obp60task.cpp @@ -435,8 +435,8 @@ void OBP60Task(GwApi *api){ int lastPage=-1; // initialize with an impiossible value, so we can detect wether we are during startup and no page has been displayed yet 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) - 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 @@ -479,21 +479,27 @@ 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; + + // 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); - // 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 @@ -808,10 +814,10 @@ void OBP60Task(GwApi *api){ api->getStatus(commonData.status); if (calcTrueWnds) { - trueWind.addTrueWind(api, &boatValues, logger); + trueWind.addWinds(); } // Handle history buffers for certain boat data for windplot page and other usage - hstryBufList.handleHstryBuf(useSimuData); + hstryBufList.handleHstryBufs(useSimuData, commonData); // Clear display // getdisplay().fillRect(0, 0, getdisplay().width(), getdisplay().height(), commonData.bgcolor);