diff --git a/lib/obp60task/BoatDataCalibration.cpp b/lib/obp60task/BoatDataCalibration.cpp index c68e965..55af0ee 100644 --- a/lib/obp60task/BoatDataCalibration.cpp +++ b/lib/obp60task/BoatDataCalibration.cpp @@ -173,7 +173,7 @@ void CalibrationDataList::smoothInstance(GwApi::BoatValue* boatDataValue, GwLog* if (!boatDataValue->valid) { // no valid boat data value, so we don't want to smoothen value return; } else if (calibMap.find(instance) == calibMap.end()) { - logger->logDebug(GwLog::DEBUG, "BoatDataCalibration: smooth factor for %s not found in calibration data list", instance.c_str()); + logger->logDebug(GwLog::DEBUG, "BoatDataCalibration: smooth factor for %s not found in calibration list", instance.c_str()); return; } else { smoothFactor = calibMap[instance].smooth; @@ -184,8 +184,6 @@ void CalibrationDataList::smoothInstance(GwApi::BoatValue* boatDataValue, GwLog* } lastValue[instance] = dataValue; // store the new value for next cycle; first time, store only the current value and return boatDataValue->value = dataValue; // set the smoothed value to the boat data value - - logger->logDebug(GwLog::DEBUG, "BoatDataCalibration: %s: Smoothing factor: %f, Smoothed value: %f", instance.c_str(), smoothFactor, dataValue); } } diff --git a/lib/obp60task/OBP60Formatter.cpp b/lib/obp60task/OBP60Formatter.cpp index b1d6888..8079bc3 100644 --- a/lib/obp60task/OBP60Formatter.cpp +++ b/lib/obp60task/OBP60Formatter.cpp @@ -439,21 +439,30 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata){ //######################################################## else if (value->getFormat() == "formatXte"){ double xte = 0; - if (!usesimudata) { - xte = abs(value->value); + if (usesimudata == false) { + xte = value->value; rawvalue = value->value; } else { rawvalue = 6.0 + float(random(0, 4)); xte = rawvalue; } - if (xte >= 100) { - snprintf(buffer, bsize, fmt_dec_100, value->value); - } else if (xte >= 10) { - snprintf(buffer, bsize, fmt_dec_10, value->value); + if (distanceFormat == "km") { + xte = xte * 0.001; + result.unit = "km"; + } else if (distanceFormat == "nm") { + xte = xte * 0.000539957; + result.unit = "nm"; } else { - snprintf(buffer, bsize, fmt_dec_1, value->value); + result.unit = "m"; + } + if (xte < 10) { + snprintf(buffer, bsize, "%3.2f", xte); + } else if (xte < 100) { + snprintf(buffer,bsize,"%3.1f",xte); + } + else { + snprintf(buffer, bsize, "%3.0f", xte); } - result.unit = "nm"; } //######################################################## else if (value->getFormat() == "kelvinToC"){ diff --git a/lib/obp60task/OBPDataOperations.cpp b/lib/obp60task/OBPDataOperations.cpp index eca2fe9..a2e96fe 100644 --- a/lib/obp60task/OBPDataOperations.cpp +++ b/lib/obp60task/OBPDataOperations.cpp @@ -77,30 +77,56 @@ void WindUtils::calcTwdSA(const double* AWA, const double* AWS, // Serial.println("calcTwdSA: TWD: " + String(*TWD) + ", TWS: " + String(*TWS)); } +double WindUtils::calcHDT(const double* hdmVal, const double* varVal, const double* cogVal, const double* sogVal) +{ + double hdt; + double minSogVal = 0.1; // SOG below this value (m/s) is assumed to be data noise from GPS sensor + static const double DBL_MIN = std::numeric_limits::lowest(); + + // Serial.println("\ncalcTrueWind: HDT: " + String(*hdtVal) + ", HDM: " + String(*hdmVal) + ", VAR: " + String(*varVal) + ", SOG: " + String(*sogVal) + ", COG: " + String(*cogVal)); + if (*hdmVal != DBL_MIN) { + hdt = *hdmVal + (*varVal != DBL_MIN ? *varVal : 0.0); // Use corrected HDM if HDT is not available (or just HDM if VAR is not available) + hdt = to2PI(hdt); + } else if (*cogVal != DBL_MIN && *sogVal >= minSogVal) { + hdt = *cogVal; // Use COG as fallback if HDT and HDM are not available, and SOG is not data noise + } else { + hdt = DBL_MIN; // Cannot calculate HDT without valid HDM or HDM+VAR or COG + } + + return hdt; +} + bool WindUtils::calcTrueWind(const double* awaVal, const double* awsVal, const double* cogVal, const double* stwVal, const double* sogVal, const double* hdtVal, const double* hdmVal, const double* varVal, double* twdVal, double* twsVal, double* twaVal) { double stw, hdt, ctw; double twd, tws, twa; + double minSogVal = 0.1; // SOG below this value (m/s) is assumed to be data noise from GPS sensor static const double DBL_MIN = std::numeric_limits::lowest(); - if (*hdtVal != DBL_MIN) { + // Serial.println("\ncalcTrueWind: HDT: " + String(*hdtVal) + ", HDM: " + String(*hdmVal) + ", VAR: " + String(*varVal) + ", SOG: " + String(*sogVal) + ", COG: " + String(*cogVal)); +/* if (*hdtVal != DBL_MIN) { hdt = *hdtVal; // Use HDT if available } else { - if (*hdmVal != DBL_MIN && *varVal != DBL_MIN) { - hdt = *hdmVal + *varVal; // Use corrected HDM if HDT is not available + if (*hdmVal != DBL_MIN) { + hdt = *hdmVal + (*varVal != DBL_MIN ? *varVal : 0.0); // Use corrected HDM if HDT is not available (or just HDM if VAR is not available) hdt = to2PI(hdt); - } else if (*cogVal != DBL_MIN) { - hdt = *cogVal; // Use COG as fallback if HDT and HDM are not available + } else if (*cogVal != DBL_MIN && *sogVal >= minSogVal) { + hdt = *cogVal; // Use COG as fallback if HDT and HDM are not available, and SOG is not data noise } else { return false; // Cannot calculate without valid HDT or HDM+VAR or COG } + } */ + if (*hdtVal != DBL_MIN) { + hdt = *hdtVal; // Use HDT if available + } else { + hdt = calcHDT(hdmVal, varVal, cogVal, sogVal); } - if (*cogVal != DBL_MIN) { - ctw = *cogVal; // Use COG as CTW if available - // ctw = *cogVal + ((*cogVal - hdt) / 2); // Estimate CTW from COG + if (*cogVal != DBL_MIN && *sogVal >= minSogVal) { // if SOG is data noise, we don't trust COG + + ctw = *cogVal; // Use COG for CTW if available } else { ctw = hdt; // 2nd approximation for CTW; hdt must exist if we reach this part of the code } @@ -113,6 +139,7 @@ bool WindUtils::calcTrueWind(const double* awaVal, const double* awsVal, // If STW and SOG are not available, we cannot calculate true wind return false; } + // Serial.println("\ncalcTrueWind: HDT: " + String(hdt) + ", CTW: " + String(ctw) + ", STW: " + String(stw)); if ((*awaVal == DBL_MIN) || (*awsVal == DBL_MIN)) { // Cannot calculate true wind without valid AWA, AWS; other checks are done earlier @@ -126,32 +153,3 @@ bool WindUtils::calcTrueWind(const double* awaVal, const double* awsVal, return true; } } - -void HstryBuf::fillWndBufSimData(tBoatHstryData& hstryBufs) -// Fill most part of TWD and TWS history buffer with simulated data -{ - double value = 20.0; - int16_t value2 = 0; - for (int i = 0; i < 900; i++) { - value += random(-20, 20); - value = WindUtils::to360(value); - value2 = static_cast(value * DEG_TO_RAD * 1000); - hstryBufs.twdHstry->add(value2); - } -} - -/* double genTwdSimDat() -{ - simTwd += random(-20, 20); - if (simTwd < 0.0) - simTwd += 360.0; - if (simTwd >= 360.0) - simTwd -= 360.0; - - int16_t z = static_cast(DegToRad(simTwd) * 1000.0); - pageData.boatHstry.twdHstry->add(z); // Fill the buffer with some test data - - simTws += random(-200, 150) / 10.0; // TWS value in knots - simTws = constrain(simTws, 0.0f, 50.0f); // Ensure TWS is between 0 and 50 knots - twsValue = simTws; -}*/ diff --git a/lib/obp60task/OBPDataOperations.h b/lib/obp60task/OBPDataOperations.h index c9e4386..af9d7f5 100644 --- a/lib/obp60task/OBPDataOperations.h +++ b/lib/obp60task/OBPDataOperations.h @@ -1,18 +1,20 @@ #pragma once #include "GwApi.h" #include "OBPRingBuffer.h" -#include +// #include #include typedef struct { RingBuffer* twdHstry; RingBuffer* twsHstry; + RingBuffer* awdHstry; + RingBuffer* awsHstry; } tBoatHstryData; // Holds pointers to all history buffers for boat data class HstryBuf { public: - void fillWndBufSimData(tBoatHstryData& hstryBufs); // Fill most part of the TWD and TWS history buffer with simulated data + }; class WindUtils { @@ -30,7 +32,8 @@ public: static void calcTwdSA(const double* AWA, const double* AWS, const double* CTW, const double* STW, const double* HDT, double* TWD, double* TWS, double* TWA); + static double calcHDT(const double* hdmVal, const double* varVal, const double* cogVal, const double* sogVal); static bool calcTrueWind(const double* awaVal, const double* awsVal, const double* cogVal, const double* stwVal, const double* sogVal, const double* hdtVal, const double* hdmVal, const double* varVal, double* twdVal, double* twsVal, double* twaVal); -}; \ No newline at end of file +}; diff --git a/lib/obp60task/OBPRingBuffer.h b/lib/obp60task/OBPRingBuffer.h index 4b5e5bd..0f56bb4 100644 --- a/lib/obp60task/OBPRingBuffer.h +++ b/lib/obp60task/OBPRingBuffer.h @@ -32,6 +32,7 @@ public: void setMetaData(String name, String format, int updateFrequency, T minValue, T maxValue); // Set meta data for buffer bool getMetaData(String& name, String& format, int& updateFrequency, T& minValue, T& maxValue); // Get meta data of buffer String getName() const; // Get buffer name + String getFormat() const; // Get buffer data format void add(const T& value); // Add a new value to buffer T get(size_t index) const; // Get value at specific position (0-based index from oldest to newest) T getFirst() const; // Get the first (oldest) value in buffer @@ -57,4 +58,4 @@ public: std::vector getAllValues() const; // Get all current values as a vector }; -#include "OBPRingBuffer.tpp" \ No newline at end of file +#include "OBPRingBuffer.tpp" diff --git a/lib/obp60task/OBPRingBuffer.tpp b/lib/obp60task/OBPRingBuffer.tpp index a0da425..eb0c5f1 100644 --- a/lib/obp60task/OBPRingBuffer.tpp +++ b/lib/obp60task/OBPRingBuffer.tpp @@ -64,6 +64,13 @@ String RingBuffer::getName() const return dataName; } +// Get buffer data format +template +String RingBuffer::getFormat() const +{ + return dataFmt; +} + // Add a new value to buffer template void RingBuffer::add(const T& value) @@ -373,4 +380,4 @@ std::vector RingBuffer::getAllValues() const } return result; -} \ No newline at end of file +} diff --git a/lib/obp60task/PageWindPlot.cpp b/lib/obp60task/PageWindPlot.cpp index 94e3512..5a30590 100644 --- a/lib/obp60task/PageWindPlot.cpp +++ b/lib/obp60task/PageWindPlot.cpp @@ -9,13 +9,40 @@ static const double radToDeg = 180.0 / M_PI; // Conversion factor from radians to degrees +// Get maximum difference of last of TWD ringbuffer values to center chart; returns "0" if data is not valid +int getCntr(const RingBuffer& windDirHstry, size_t amount) +{ + int minVal = windDirHstry.getMinVal(); + size_t count = windDirHstry.getCurrentSize(); + + if (windDirHstry.isEmpty() || amount <= 0) { + return 0; + } + if (amount > count) + amount = count; + + int16_t midWndDir, minWndDir, maxWndDir = 0; + int wndCenter = 0; + + midWndDir = windDirHstry.getMid(amount); + if (midWndDir != INT16_MIN) { + midWndDir = midWndDir / 1000.0 * radToDeg; + wndCenter = int((midWndDir + (midWndDir >= 0 ? 5 : -5)) / 10) * 10; // Set new center value; round to nearest 10 degree value + minWndDir = windDirHstry.getMin(amount) / 1000.0 * radToDeg; + maxWndDir = windDirHstry.getMax(amount) / 1000.0 * radToDeg; + if ((maxWndDir - minWndDir) > 180 && !(minWndDir > maxWndDir)) { // if wind range is > 180 and no 0° crossover, adjust wndCenter to smaller wind range end + wndCenter = WindUtils::to360(wndCenter + 180); + } + } + + return wndCenter; +} + // Get maximum difference of last of TWD ringbuffer values to center chart int getRng(const RingBuffer& windDirHstry, int center, size_t amount) { int minVal = windDirHstry.getMinVal(); size_t count = windDirHstry.getCurrentSize(); - // size_t capacity = windDirHstry.getCapacity(); - // size_t last = windDirHstry.getLastIdx(); if (windDirHstry.isEmpty() || amount <= 0) { return minVal; @@ -28,7 +55,6 @@ int getRng(const RingBuffer& windDirHstry, int center, size_t amount) int maxRng = minVal; // Start from the newest value (last) and go backwards x times for (size_t i = 0; i < amount; i++) { - // value = windDirHstry.get(((last - i) % capacity + capacity) % capacity); value = windDirHstry.get(count - 1 - i); if (value == minVal) { @@ -52,9 +78,9 @@ class PageWindPlot : public Page { private: bool keylock = false; // Keylock char chrtMode = 'D'; // Chart mode: 'D' for TWD, 'S' for TWS, 'B' for both + bool showTruW = true; // Show true wind or apparant wind in chart area int dataIntv = 1; // Update interval for wind history chart: // (1)|(2)|(3)|(4) seconds for approx. 4, 8, 12, 16 min. history chart - bool showTWS = true; // Show TWS value in chart area public: PageWindPlot(CommonData& common) : Page(common) @@ -65,8 +91,12 @@ public: void setupKeys() { Page::setupKeys(); // commonData->keydata[0].label = "MODE"; +#if defined BOARD_OBP60S3 + commonData->keydata[1].label = "SRC"; + commonData->keydata[4].label = "INTV"; +#elif defined BOARD_OBP40S3 commonData->keydata[1].label = "INTV"; - commonData->keydata[4].label = "TWS"; +#endif } // Key functions @@ -83,8 +113,18 @@ public: return 0; // Commit the key } - // Set interval for wind history chart update time +#if defined BOARD_OBP60S3 + // Set data source TRUE | APP if (key == 2) { + showTruW = !showTruW; + return 0; // Commit the key + } + + // Set interval for wind history chart update time (interval) + if (key == 5) { +#elif defined BOARD_OBP40S3 + if (key == 2) { +#endif if (dataIntv == 1) { dataIntv = 2; } else if (dataIntv == 2) { @@ -97,12 +137,6 @@ public: return 0; // Commit the key } - // Switch TWS on/off - if (key == 5) { - showTWS = !showTWS; - return 0; // Commit the key - } - // Keylock function if (key == 11) { // Code for keylock commonData->keylock = !commonData->keylock; @@ -118,19 +152,35 @@ public: setBlinkingLED(false); setFlashLED(false); } +#endif +#ifdef BOARD_OBP40S3 + String wndSrc; // Wind source true/apparant wind - preselection for OBP40 + + wndSrc = commonData->config->getString("page" + String(pageData.pageNumber) + "wndsrc"); + if (wndSrc =="True wind") { + showTruW = true; + } else { + showTruW = false; // Wind source is apparant wind + } + commonData->logger->logDebug(GwLog::LOG,"New PageWindPlot: wind source=%s", wndSrc); + #endif }; int displayPage(PageData& pageData) { - float twsValue; // TWS value in chart area - static String twdName, twdUnit; // TWD name and unit - static int updFreq; // Update frequency for TWD - static int16_t twdLowest, twdHighest; // TWD range - // static int16_t twdBufMinVal; // lowest possible twd buffer value; used for non-set data + static RingBuffer* wdHstry; // Wind direction data buffer + static RingBuffer* wsHstry; // Wind speed data buffer + static String wdName, wdFormat; // Wind direction name and format + static String wsName, wsFormat; // Wind speed name and format + static int updFreq; // Update frequency for wind direction + static int16_t wdLowest, wdHighest; // Wind direction range + float wsValue; // Wind speed value in chart area + String wsUnit; // Wind speed unit in chart area + static GwApi::BoatValue* wsBVal = new GwApi::BoatValue("TWS"); // temp BoatValue for wind speed unit identification; required by OBP60Formater - // current boat data values; TWD only for validation test, TWS for display of current value - const int numBoatData = 2; + // current boat data values; TWD/AWD only for validation test, TWS/AWS for display of current value + const int numBoatData = 4; GwApi::BoatValue* bvalue; String BDataName[numBoatData]; double BDataValue[numBoatData]; @@ -158,16 +208,15 @@ public: static size_t lastIdx; // Last index of TWD history buffer static size_t lastAddedIdx = 0; // Last index of TWD history buffer when new data was added static int oldDataIntv; // remember recent user selection of data interval + static bool oldShowTruW; // remember recent user selection of wind data type static int wndCenter; // chart wind center value position static int wndLeft; // chart wind left value position static int wndRight; // chart wind right value position static int chrtRng; // Range of wind values from mid wind value to min/max wind value in degrees int diffRng; // Difference between mid and current wind value - static const int dfltRng = 40; // Default range for chart + static const int dfltRng = 60; // Default range for chart int midWndDir; // New value for wndCenter after chart start / shift - static int simTwd; // Simulation value for TWD - static float simTws; // Simulation value for TWS int x, y; // x and y coordinates for drawing static int prevX, prevY; // Last x and y coordinates for drawing @@ -175,23 +224,27 @@ public: int chrtVal; // Current wind value static int chrtPrevVal; // Last wind value in chart area for check if value crosses 180 degree line - logger->logDebug(GwLog::LOG, "Display page WindPlot"); + logger->logDebug(GwLog::LOG, "Display PageWindPlot"); if (!isInitialized) { width = epd->width(); height = epd->height(); xCenter = width / 2; cHeight = height - yOffset - 22; - bufSize = pageData.boatHstry.twdHstry->getCapacity(); numNoData = 0; - simTwd = pageData.boatHstry.twdHstry->getLast() / 1000.0 * radToDeg; - simTws = 0; - twsValue = 0; bufStart = 0; oldDataIntv = 0; + oldShowTruW = false; // we want to initialize wind buffers at 1st time routine runs + wdHstry = pageData.boatHstry.twdHstry; + bufSize = wdHstry->getCapacity(); + wsHstry = pageData.boatHstry.twsHstry; + bufSize = wsHstry->getCapacity(); + wdHstry->getMetaData(wdName, wdFormat, updFreq, wdLowest, wdHighest); + wsHstry->getMetaData(wsName, wsFormat, updFreq, wdLowest, wdHighest); + wsValue = 0; + wsBVal->setFormat(wsHstry->getFormat()); numAddedBufVals, currIdx, lastIdx = 0; - lastAddedIdx = pageData.boatHstry.twdHstry->getLastIdx(); - pageData.boatHstry.twdHstry->getMetaData(twdName, twdUnit, updFreq, twdLowest, twdHighest); + lastAddedIdx = wdHstry->getLastIdx(); wndCenter = INT_MIN; midWndDir = 0; diffRng = dfltRng; @@ -219,9 +272,25 @@ public: setFlashLED(false); } + if (showTruW != oldShowTruW) { + if (showTruW) { + wdHstry = pageData.boatHstry.twdHstry; + wsHstry = pageData.boatHstry.twsHstry; + } else { + wdHstry = pageData.boatHstry.awdHstry; + wsHstry = pageData.boatHstry.awsHstry; + } + wdHstry->getMetaData(wdName, wdFormat, updFreq, wdLowest, wdHighest); + wsHstry->getMetaData(wsName, wsFormat, updFreq, wdLowest, wdHighest); + bufSize = wdHstry->getCapacity(); + wsBVal->setFormat(wsHstry->getFormat()); + + oldShowTruW = showTruW; + } + // Identify buffer size and buffer start position for chart - count = pageData.boatHstry.twdHstry->getCurrentSize(); - currIdx = pageData.boatHstry.twdHstry->getLastIdx(); + count = wdHstry->getCurrentSize(); + currIdx = wdHstry->getLastIdx(); numAddedBufVals = (currIdx - lastAddedIdx + bufSize) % bufSize; // Number of values added to buffer since last display if (dataIntv != oldDataIntv || count == 1) { // new data interval selected by user @@ -237,29 +306,25 @@ public: bufStart = max(0, bufStart - numAddedBufVals); } } - logger->logDebug(GwLog::DEBUG, "PageWindPlot Dataset: count: %d, TWD: %.0f, TWS: %.1f, TWD_valid? %d, intvBufSize: %d, numWndVals: %d, bufStart: %d, numAddedBufVals: %d, lastIdx: %d, old: %d, act: %d", - count, pageData.boatHstry.twdHstry->getLast() / 1000.0 * radToDeg, pageData.boatHstry.twsHstry->getLast() / 10.0 * 1.94384, BDataValid[0], - intvBufSize, numWndVals, bufStart, numAddedBufVals, pageData.boatHstry.twdHstry->getLastIdx(), oldDataIntv, dataIntv); + logger->logDebug(GwLog::DEBUG, "PageWindPlot Dataset: count: %d, xWD: %.0f, xWS: %.1f, xWD_valid? %d, intvBufSize: %d, numWndVals: %d, bufStart: %d, numAddedBufVals: %d, lastIdx: %d, wind source: %s", + count, wdHstry->getLast() / 1000.0 * radToDeg, wsHstry->getLast() / 10.0 * 1.94384, BDataValid[0], intvBufSize, numWndVals, bufStart, numAddedBufVals, wdHstry->getLastIdx(), + showTruW ? "True" : "App"); // Set wndCenter from 1st real buffer value if (wndCenter == INT_MIN || (wndCenter == 0 && count == 1)) { - midWndDir = pageData.boatHstry.twdHstry->getMid(numWndVals); - if (midWndDir != INT16_MIN) { - midWndDir = midWndDir / 1000.0 * radToDeg; - wndCenter = int((midWndDir + (midWndDir >= 0 ? 5 : -5)) / 10) * 10; // Set new center value; round to nearest 10 degree value - } else { - wndCenter = 0; - } - logger->logDebug(GwLog::DEBUG, "PageWindPlot Range Init: count: %d, TWD: %.0f, wndCenter: %d, diffRng: %d, chrtRng: %d", count, pageData.boatHstry.twdHstry->getLast() / 1000.0 * radToDeg, - wndCenter, diffRng, chrtRng); + wndCenter = getCntr(*wdHstry, numWndVals); + logger->logDebug(GwLog::DEBUG, "PageWindPlot Range Init: count: %d, xWD: %.0f, wndCenter: %d, diffRng: %d, chrtRng: %d, Min: %.0f, Max: %.0f", count, wdHstry->getLast() / 1000.0 * radToDeg, + wndCenter, diffRng, chrtRng, wdHstry->getMin(numWndVals) / 1000.0 * radToDeg, wdHstry->getMax(numWndVals) / 1000.0 * radToDeg); } else { // check and adjust range between left, center, and right chart limit - diffRng = getRng(*pageData.boatHstry.twdHstry, wndCenter, numWndVals); + diffRng = getRng(*wdHstry, wndCenter, numWndVals); diffRng = (diffRng == INT16_MIN ? 0 : diffRng); if (diffRng > chrtRng) { chrtRng = int((diffRng + (diffRng >= 0 ? 9 : -1)) / 10) * 10; // Round up to next 10 degree value } else if (diffRng + 10 < chrtRng) { // Reduce chart range for higher resolution if possible chrtRng = max(dfltRng, int((diffRng + (diffRng >= 0 ? 9 : -1)) / 10) * 10); + logger->logDebug(GwLog::DEBUG, "PageWindPlot Range adjust: wndCenter: %d, diffRng: %d, chrtRng: %d, Min: %.0f, Max: %.0f", wndCenter, diffRng, chrtRng, + wdHstry->getMin(numWndVals) / 1000.0 * radToDeg, wdHstry->getMax(numWndVals) / 1000.0 * radToDeg); } } chrtScl = float(width) / float(chrtRng) / 2.0; // Chart scale: pixels per degree @@ -285,7 +350,7 @@ public: char sWndLbl[4]; // char buffer for Wind angle label epd->setFont(&Ubuntu_Bold12pt8b); epd->setCursor(xCenter - 88, yOffset - 3); - epd->print("TWD"); // Wind data name + epd->print(wdName); // Wind data name snprintf(sWndLbl, 4, "%03d", (wndCenter < 0) ? (wndCenter + 360) : wndCenter); drawTextCenter(xCenter, yOffset - 11, sWndLbl); epd->drawCircle(xCenter + 25, yOffset - 17, 2, commonData->fgcolor); // symbol @@ -301,11 +366,11 @@ public: epd->drawCircle(width - 5, yOffset - 17, 2, commonData->fgcolor); // symbol epd->drawCircle(width - 5, yOffset - 17, 3, commonData->fgcolor); // symbol - if (pageData.boatHstry.twdHstry->getMax() == pageData.boatHstry.twdHstry->getMinVal()) { + if (wdHstry->getMax() == wdHstry->getMinVal()) { // only values in buffer -> no valid wind data available wndDataValid = false; - } else if (!BDataValid[0]) { - // currently no valid TWD data available + } else if (!BDataValid[0] && !simulation) { + // currently no valid TWD data available and no simulation mode numNoData++; wndDataValid = true; if (numNoData > 3) { @@ -320,7 +385,7 @@ public: //*********************************************************************** if (wndDataValid) { for (int i = 0; i < (numWndVals / dataIntv); i++) { - chrtVal = static_cast(pageData.boatHstry.twdHstry->get(bufStart + (i * dataIntv))); // show the latest wind values in buffer; keep 1st value constant in a rolling buffer + chrtVal = static_cast(wdHstry->get(bufStart + (i * dataIntv))); // show the latest wind values in buffer; keep 1st value constant in a rolling buffer if (chrtVal == INT16_MIN) { chrtPrevVal = INT16_MIN; } else { @@ -328,8 +393,7 @@ public: x = ((chrtVal - wndLeft + 360) % 360) * chrtScl; y = yOffset + cHeight - i; // Position in chart area -// if (i >= (numWndVals / dataIntv) - 10) - if (i >= (numWndVals / dataIntv) - 1) + if (i >= (numWndVals / dataIntv) - 1) // log chart data of 1 line (adjust for test purposes) logger->logDebug(GwLog::DEBUG, "PageWindPlot Chart: i: %d, chrtVal: %d, bufStart: %d, count: %d, linesToShow: %d", i, chrtVal, bufStart, count, (numWndVals / dataIntv)); if ((i == 0) || (chrtPrevVal == INT16_MIN)) { @@ -361,41 +425,27 @@ public: if (i >= (cHeight - 1)) { oldDataIntv = 0; // force reset of buffer start and number of values to show in next display loop - int minWndDir = pageData.boatHstry.twdHstry->getMin(numWndVals) / 1000.0 * radToDeg; - int maxWndDir = pageData.boatHstry.twdHstry->getMax(numWndVals) / 1000.0 * radToDeg; + int minWndDir = wdHstry->getMin(numWndVals) / 1000.0 * radToDeg; + int maxWndDir = wdHstry->getMax(numWndVals) / 1000.0 * radToDeg; logger->logDebug(GwLog::DEBUG, "PageWindPlot FreeTop: Minimum: %d, Maximum: %d, OldwndCenter: %d", minWndDir, maxWndDir, wndCenter); -// if ((minWndDir + 540 >= wndCenter + 540) || (maxWndDir + 540 <= wndCenter + 540)) { - if (((minWndDir - wndCenter >= 0) && (minWndDir - wndCenter < 180)) || ((maxWndDir - wndCenter <= 0) && (maxWndDir - wndCenter >=180))) { - // Check if all wind value are left or right of center value -> optimize chart range - midWndDir = pageData.boatHstry.twdHstry->getMid(numWndVals) / 1000.0 * radToDeg; - if (midWndDir != INT16_MIN) { - wndCenter = int((midWndDir + (midWndDir >= 0 ? 5 : -5)) / 10) * 10; // Set new center value; round to nearest 10 degree value - } + // if (((minWndDir - wndCenter >= 0) && (minWndDir - wndCenter < 180)) || ((maxWndDir - wndCenter <= 0) && (maxWndDir - wndCenter >=180))) { + if ((wndRight > wndCenter && (minWndDir >= wndCenter && minWndDir <= wndRight)) || (wndRight <= wndCenter && (minWndDir >= wndCenter || minWndDir <= wndRight)) || (wndLeft < wndCenter && (maxWndDir <= wndCenter && maxWndDir >= wndLeft)) || (wndLeft >= wndCenter && (maxWndDir <= wndCenter || maxWndDir >= wndLeft))) { + // Check if all wind value are left or right of center value -> optimize chart center + wndCenter = getCntr(*wdHstry, numWndVals); } logger->logDebug(GwLog::DEBUG, "PageWindPlot FreeTop: cHeight: %d, bufStart: %d, numWndVals: %d, wndCenter: %d", cHeight, bufStart, numWndVals, wndCenter); break; } } - } else { - // No valid data available - logger->logDebug(GwLog::LOG, "PageWindPlot: No valid data available"); - epd->setFont(&Ubuntu_Bold10pt8b); - epd->fillRect(xCenter - 33, height / 2 - 20, 66, 24, commonData->bgcolor); // Clear area for message - drawTextCenter(xCenter, height / 2 - 10, "No data"); - } - - // Print TWS value - if (showTWS) { + // Print wind speed value int currentZone; static int lastZone = 0; static bool flipTws = false; int xPosTws; static const int yPosTws = yOffset + 40; - twsValue = pageData.boatHstry.twsHstry->getLast() / 10.0 * 1.94384; // TWS value in knots - - xPosTws = flipTws ? 20 : width - 138; + xPosTws = flipTws ? 20 : width - 145; currentZone = (y >= yPosTws - 38) && (y <= yPosTws + 6) && (x >= xPosTws - 4) && (x <= xPosTws + 146) ? 1 : 0; // Define current zone for TWS value if (currentZone != lastZone) { // Only flip when x moves to a different zone @@ -406,28 +456,36 @@ public: } lastZone = currentZone; + wsValue = wsHstry->getLast(); + wsBVal->value = wsValue; // temp variable to retreive data unit from OBP60Formater + wsBVal->valid = (static_cast(wsValue) != wsHstry->getMinVal()); + wsUnit = formatValue(wsBVal, *commonData).unit; // Unit of value epd->fillRect(xPosTws - 4, yPosTws - 38, 142, 44, commonData->bgcolor); // Clear area for TWS value epd->setFont(&DSEG7Classic_BoldItalic16pt7b); epd->setCursor(xPosTws, yPosTws); - if (!BDataValid[1]) { + if (!wsBVal->valid) { epd->print("--.-"); } else { - double dbl = BDataValue[1] * 3.6 / 1.852; - if (dbl < 10.0) { - epd->printf("!%3.1f", dbl); // Value, round to 1 decimal + wsValue = wsValue / 10.0 * 1.94384; // Wind speed value in knots + if (wsValue < 10.0) { + epd->printf("!%3.1f", wsValue); // Value, round to 1 decimal } else { - epd->printf("%4.1f", dbl); // Value, round to 1 decimal + epd->printf("%4.1f", wsValue); // Value, round to 1 decimal } } epd->setFont(&Ubuntu_Bold12pt8b); epd->setCursor(xPosTws + 82, yPosTws - 14); -// epd->print("TWS"); // Name - epd->print(BDataName[1]); // Name + epd->print(wsName); // Name epd->setFont(&Ubuntu_Bold8pt8b); -// epd->setCursor(xPosTws + 78, yPosTws + 1); epd->setCursor(xPosTws + 82, yPosTws + 1); -// epd->printf(" kn"); // Unit - epd->print(BDataUnit[1]); // Unit + epd->print(wsUnit); // Unit + + } else { + // No valid data available + LOG_DEBUG(GwLog::LOG, "PageWindPlot: No valid data available"); + epd->setFont(&Ubuntu_Bold10pt8b); + epd->fillRect(xCenter - 33, height / 2 - 20, 66, 24, commonData->bgcolor); // Clear area for message + drawTextCenter(xCenter, height / 2 - 10, "No data"); } // chart Y axis labels; print at last to overwrite potential chart lines in label area @@ -457,19 +515,17 @@ static Page* createPage(CommonData& common) { return new PageWindPlot(common); } -/** - * with the code below we make this page known to the PageTask + +/* 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 * we define which function is to be called * and we provide the number of user parameters we expect (0 here) - * and will will provide the names of the fixed values we need - */ + * and will will provide the names of the fixed values we need */ PageDescription registerPageWindPlot( "WindPlot", // Page name createPage, // Action 0, // Number of bus values depends on selection in Web configuration - { "TWD", "TWS" }, // Bus values we need in the page -// {}, // Bus values we need in the page + { "TWD", "TWS", "AWD", "AWS" }, // Bus values we need in the page true // Show display header on/off ); diff --git a/lib/obp60task/PageWindRoseFlex.cpp b/lib/obp60task/PageWindRoseFlex.cpp index 85a4424..576bc42 100644 --- a/lib/obp60task/PageWindRoseFlex.cpp +++ b/lib/obp60task/PageWindRoseFlex.cpp @@ -9,7 +9,9 @@ class PageWindRoseFlex : public Page { private: String lengthformat; - int16_t lp = 80; // Pointer length + int16_t lp = 80; // Pointer length + char source = 'A'; // data source (A)pparent | (T)rue + String ssource="App."; // String for Data Source public: PageWindRoseFlex(CommonData &common) : Page(common) @@ -20,6 +22,11 @@ public: lengthformat = config->getString(config->lengthFormat); } + void setupKeys() { + Page::setupKeys(); + commonData->keydata[1].label = "SRC"; + } + // Key functions int handleKey(int key) { // Code for keylock @@ -55,37 +62,50 @@ public: static String svalue6old = ""; static String unit6old = ""; - // 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 + GwApi::BoatValue *bvalue1; // Value 1 for angle + GwApi::BoatValue *bvalue2; // Value 2 for speed + + // Get boat value for wind angle (AWA/TWA), shown by pointer + if (source == 'A') { + bvalue1 = pageData.values[4]; + } else { + bvalue1 = pageData.values[6]; + } + String name1 = bvalue1->getName().c_str(); // 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 - value1 = formatValue(bvalue1, *commonData).value;// Format only nesaccery for simulation data for pointer 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 - if(valid1 == true){ + if (valid1 == true) { svalue1old = svalue1; // Save old value unit1old = unit1; // Save old unit } - // Get boat values #2 - GwApi::BoatValue *bvalue2 = pageData.values[1]; // Second element in list - String name2 = xdrDelete(bvalue2->getName()); // Value name + // Get boat value for wind speed (AWS/TWS), shown in top left corner + if (source == 'A') { + bvalue2 =pageData.values[5]; + } else { + bvalue2 = pageData.values[7]; + } + String name2 = bvalue2->getName().c_str(); // 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 + if (simulation) { + value2 = 0.62731; // some random value + } 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 - if(valid2 == true){ + if (valid2 == true) { svalue2old = svalue2; // Save old value unit2old = unit2; // Save old unit } - // Get boat values #3 - GwApi::BoatValue *bvalue3 = pageData.values[2]; // Third element in list + // Get boat value for bottom left corner + GwApi::BoatValue *bvalue3 = pageData.values[0]; String name3 = xdrDelete(bvalue3->getName()); // Value name name3 = name3.substring(0, 6); // String length limit for value name calibrationData.calibrateInstance(bvalue3, logger); // Check if boat data value is to be calibrated @@ -98,8 +118,8 @@ public: unit3old = unit3; // Save old unit } - // Get boat values #4 - GwApi::BoatValue *bvalue4 = pageData.values[3]; // Fourth element in list + // Get boat value for top right corner + GwApi::BoatValue *bvalue4 = pageData.values[1]; String name4 = xdrDelete(bvalue4->getName()); // Value name name4 = name4.substring(0, 6); // String length limit for value name calibrationData.calibrateInstance(bvalue4, logger); // Check if boat data value is to be calibrated @@ -112,8 +132,8 @@ public: unit4old = unit4; // Save old unit } - // Get boat values #5 - GwApi::BoatValue *bvalue5 = pageData.values[4]; // Fifth element in list + // Get boat value for bottom right corner + GwApi::BoatValue *bvalue5 = pageData.values[2]; String name5 = xdrDelete(bvalue5->getName()); // Value name name5 = name5.substring(0, 6); // String length limit for value name calibrationData.calibrateInstance(bvalue5, logger); // Check if boat data value is to be calibrated @@ -126,8 +146,8 @@ public: unit5old = unit5; // Save old unit } - // Get boat values #5 - GwApi::BoatValue *bvalue6 = pageData.values[5]; // Sixth element in list + // Get boat value for center + GwApi::BoatValue *bvalue6 = pageData.values[3]; String name6 = xdrDelete(bvalue6->getName()); // Value name name6 = name6.substring(0, 6); // String length limit for value name calibrationData.calibrateInstance(bvalue6, logger); // Check if boat data value is to be calibrated @@ -152,7 +172,7 @@ public: epd->setTextColor(commonData->fgcolor); - // Show value 2 at position of value 1 (top left) + // Show AWS or TWS top left epd->setFont(&DSEG7Classic_BoldItalic20pt7b); epd->setCursor(10, 65); epd->print(svalue2); // Value @@ -167,7 +187,7 @@ public: // Horizintal separator left epd->fillRect(0, 149, 60, 3, commonData->fgcolor); - // Show value 3 at bottom left + // Show value 3 (=first user-configured parameter) at bottom left epd->setFont(&DSEG7Classic_BoldItalic20pt7b); epd->setCursor(10, 270); epd->print(svalue3); // Value @@ -180,11 +200,10 @@ public: epd->print(holdvalues ? unit3old : unit3); - // Show value 4 at top right + // Show value 4 (=second user-configured parameter) at top right epd->setFont(&DSEG7Classic_BoldItalic20pt7b); epd->setCursor(295, 65); if(valid3 == true){ - // epd->print(abs(value3 * 180 / M_PI), 0); // Value epd->print(svalue4); // Value } else{ @@ -202,7 +221,7 @@ public: // Horizintal separator right epd->fillRect(340, 149, 80, 3, commonData->fgcolor); - // Show value 5 at bottom right + // Show value 5 (=third user-configured parameter) at bottom right epd->setFont(&DSEG7Classic_BoldItalic20pt7b); epd->setCursor(295, 270); epd->print(svalue5); // Value @@ -311,21 +330,37 @@ public: //******************************************************************************************* - // Show value6, so that it does not collide with the wind pointer + // Show value6 (=fourth user-configured parameter) and ssource, so that they do not collide with the wind pointer + if (cos(value1) > 0) { epd->setFont(&DSEG7Classic_BoldItalic16pt7b); - if (cos(value1) > 0){ - epd->setCursor(160, 200); - epd->print(svalue6); // Value - epd->setFont(&Ubuntu_Bold8pt8b); - epd->setCursor(190, 215); - } else{ - epd->setCursor(160, 130); - epd->print(svalue6); // Value - epd->setFont(&Ubuntu_Bold8pt8b); - epd->setCursor(190, 90); - } + epd->setCursor(160, 200); + epd->print(svalue6); // Value + epd->setFont(&Ubuntu_Bold8pt8b); + epd->setCursor(190, 215); epd->print(" "); epd->print(holdvalues ? unit6old : unit6); + if (sin(value1) > 0) { + epd->setCursor(160, 130); + } else { + epd->setCursor(220, 130); + } + epd->print(ssource); // true or app. + } + else { + epd->setFont(&DSEG7Classic_BoldItalic16pt7b); + epd->setCursor(160, 130); + epd->print(svalue6); + epd->setFont(&Ubuntu_Bold8pt8b); + epd->setCursor(190, 90); + epd->print(" "); + epd->print(holdvalues ? unit6old : unit6); + if (sin(value1) > 0) { + epd->setCursor(160, 130); + } else { + epd->setCursor(220, 130); + } + epd->print(ssource); //true or app. + } return PAGE_UPDATE; }; @@ -338,13 +373,14 @@ static Page *createPage(CommonData &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 * we define which function is to be called - * and we provide the number of user parameters we expect (0 here) + * and we provide the number of user parameters we expect (4 here) * and will will provide the names of the fixed values we need */ PageDescription registerPageWindRoseFlex( "WindRoseFlex", // Page name createPage, // Action - 6, // Number of bus values depends on selection in Web configuration; was zero + 4, // Number of bus values depends on selection in Web configuration + {"AWA", "AWS", "TWA", "TWS"}, // fixed values we need in the page. They are inserted AFTER the web-configured values. true // Show display header on/off ); diff --git a/lib/obp60task/config_obp40.json b/lib/obp60task/config_obp40.json index 3c8c306..9ad5e51 100644 --- a/lib/obp60task/config_obp40.json +++ b/lib/obp60task/config_obp40.json @@ -1674,6 +1674,26 @@ } ] }, + { + "name": "page1wndsrc", + "label": "Wind source", + "type": "list", + "default": "True wind", + "description": "Wind source for page 1: [true|apparant]", + "list": [ + "True wind", + "Apparant wind" + ], + "category": "OBP40 Page 1", + "capabilities": { + "obp40": "true" + }, + "condition": [ + { + "page1type": "WindPlot" + } + ] + }, { "name": "page2type", "label": "Type", @@ -1976,6 +1996,26 @@ } ] }, + { + "name": "page2wndsrc", + "label": "Wind source", + "type": "list", + "default": "True wind", + "description": "Wind source for page 2: [true|apparant]", + "list": [ + "True wind", + "Apparant wind" + ], + "category": "OBP40 Page 2", + "capabilities": { + "obp40": "true" + }, + "condition": [ + { + "page2type": "WindPlot" + } + ] + }, { "name": "page3type", "label": "Type", @@ -2269,6 +2309,26 @@ } ] }, + { + "name": "page3wndsrc", + "label": "Wind source", + "type": "list", + "default": "True wind", + "description": "Wind source for page 3: [true|apparant]", + "list": [ + "True wind", + "Apparant wind" + ], + "category": "OBP40 Page 3", + "capabilities": { + "obp40": "true" + }, + "condition": [ + { + "page3type": "WindPlot" + } + ] + }, { "name": "page4type", "label": "Type", @@ -2553,6 +2613,26 @@ } ] }, + { + "name": "page4wndsrc", + "label": "Wind source", + "type": "list", + "default": "True wind", + "description": "Wind source for page 4: [true|apparant]", + "list": [ + "True wind", + "Apparant wind" + ], + "category": "OBP40 Page 4", + "capabilities": { + "obp40": "true" + }, + "condition": [ + { + "page4type": "WindPlot" + } + ] + }, { "name": "page5type", "label": "Type", @@ -2828,6 +2908,26 @@ } ] }, + { + "name": "page5wndsrc", + "label": "Wind source", + "type": "list", + "default": "True wind", + "description": "Wind source for page 5: [true|apparant]", + "list": [ + "True wind", + "Apparant wind" + ], + "category": "OBP40 Page 5", + "capabilities": { + "obp40": "true" + }, + "condition": [ + { + "page5type": "WindPlot" + } + ] + }, { "name": "page6type", "label": "Type", @@ -3094,6 +3194,26 @@ } ] }, + { + "name": "page6wndsrc", + "label": "Wind source", + "type": "list", + "default": "True wind", + "description": "Wind source for page 6: [true|apparant]", + "list": [ + "True wind", + "Apparant wind" + ], + "category": "OBP40 Page 6", + "capabilities": { + "obp40": "true" + }, + "condition": [ + { + "page6type": "WindPlot" + } + ] + }, { "name": "page7type", "label": "Type", @@ -3351,6 +3471,26 @@ } ] }, + { + "name": "page7wndsrc", + "label": "Wind source", + "type": "list", + "default": "True wind", + "description": "Wind source for page 7: [true|apparant]", + "list": [ + "True wind", + "Apparant wind" + ], + "category": "OBP40 Page 7", + "capabilities": { + "obp40": "true" + }, + "condition": [ + { + "page7type": "WindPlot" + } + ] + }, { "name": "page8type", "label": "Type", @@ -3599,6 +3739,26 @@ } ] }, + { + "name": "page8wndsrc", + "label": "Wind source", + "type": "list", + "default": "True wind", + "description": "Wind source for page 8: [true|apparant]", + "list": [ + "True wind", + "Apparant wind" + ], + "category": "OBP40 Page 8", + "capabilities": { + "obp40": "true" + }, + "condition": [ + { + "page8type": "WindPlot" + } + ] + }, { "name": "page9type", "label": "Type", @@ -3838,6 +3998,26 @@ } ] }, + { + "name": "page9wndsrc", + "label": "Wind source", + "type": "list", + "default": "True wind", + "description": "Wind source for page 9: [true|apparant]", + "list": [ + "True wind", + "Apparant wind" + ], + "category": "OBP40 Page 9", + "capabilities": { + "obp40": "true" + }, + "condition": [ + { + "page9type": "WindPlot" + } + ] + }, { "name": "page10type", "label": "Type", @@ -4067,5 +4247,25 @@ "page10type": "Fluid" } ] + }, + { + "name": "page10wndsrc", + "label": "Wind source", + "type": "list", + "default": "True wind", + "description": "Wind source for page 10: [true|apparant]", + "list": [ + "True wind", + "Apparant wind" + ], + "category": "OBP40 Page 10", + "capabilities": { + "obp40": "true" + }, + "condition": [ + { + "page10type": "WindPlot" + } + ] } ] diff --git a/lib/obp60task/index.js b/lib/obp60task/index.js new file mode 100644 index 0000000..54b3cf2 --- /dev/null +++ b/lib/obp60task/index.js @@ -0,0 +1,12 @@ +(function(){ + const api=window.esp32nmea2k; + if (! api) return; + const tabName="OBP60"; + api.registerListener((id, data) => { + // if (!data.testboard) return; //do nothing if we are not active + let page = api.addTabPage(tabName, "OBP60"); + api.addEl('button', '', page, 'Screenshot').addEventListener('click', function (ev) { + window.open('/api/user/OBP60Task/screenshot', 'screenshot'); + }) + }, api.EVENTS.init); +})(); diff --git a/lib/obp60task/obp60task.cpp b/lib/obp60task/obp60task.cpp index 2785cef..84a2085 100644 --- a/lib/obp60task/obp60task.cpp +++ b/lib/obp60task/obp60task.cpp @@ -339,9 +339,8 @@ void underVoltageDetection(GwApi *api, CommonData &common){ } } -//bool addTrueWind(GwApi* api, BoatValueList* boatValues, double *twd, double *tws, double *twa) { +// Calculate true wind data and add to obp60task boat data list bool addTrueWind(GwApi* api, BoatValueList* boatValues) { - // Calculate true wind data and add to obp60task boat data list double awaVal, awsVal, cogVal, stwVal, sogVal, hdtVal, hdmVal, varVal; double twd, tws, twa; @@ -367,7 +366,7 @@ bool addTrueWind(GwApi* api, BoatValueList* boatValues) { hdtVal = hdtBVal->valid ? hdtBVal->value : DBL_MIN; hdmVal = hdmBVal->valid ? hdmBVal->value : DBL_MIN; varVal = varBVal->valid ? varBVal->value : DBL_MIN; - api->getLogger()->logDebug(GwLog::DEBUG,"obp60task addTrueWind: AWA %.1f, AWS %.1f, COG %.1f, STW %.1f, SOG %.1f, HDT %.1f, HDM %.1f, VAR %.1f", awaBVal->value * RAD_TO_DEG, awsBVal->value * 3.6 / 1.852, + api->getLogger()->logDebug(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 = WindUtils::calcTrueWind(&awaVal, &awsVal, &cogVal, &stwVal, &sogVal, &hdtVal, &hdmVal, &varVal, &twd, &tws, &twa); @@ -386,31 +385,42 @@ bool addTrueWind(GwApi* api, BoatValueList* boatValues) { twaBVal->valid = true; } } - api->getLogger()->logDebug(GwLog::DEBUG,"obp60task addTrueWind: TWD_Valid %d, isCalculated %d, TWD %.1f, TWA %.1f, TWS %.1f", twdBVal->valid, isCalculated, twdBVal->value * RAD_TO_DEG, + api->getLogger()->logDebug(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); return isCalculated; } +// Init history buffers for selected boat data void initHstryBuf(GwApi* api, BoatValueList* boatValues, tBoatHstryData hstryBufList) { - // Init history buffers for TWD, TWS GwApi::BoatValue *calBVal; // temp variable just for data calibration -> we don't want to calibrate the original data here + const double DBL_MIN = std::numeric_limits::lowest(); int hstryUpdFreq = 1000; // Update frequency for history buffers in ms int hstryMinVal = 0; // Minimum value for these history buffers - int twdHstryMax = 6283; // Max value for wind direction (TWD) in rad (0...2*PI), shifted by 1000 for 3 decimals - int twsHstryMax = 1000; // Max value for wind speed (TWS) in m/s, shifted by 10 for 1 decimal + int twdHstryMax = 6283; // Max value for wind direction (TWD, AWD) in rad (0...2*PI), shifted by 1000 for 3 decimals + int twsHstryMax = 1000; // Max value for wind speed (TWS, AWS) in m/s, shifted by 10 for 1 decimal // Initialize history buffers with meta data hstryBufList.twdHstry->setMetaData("TWD", "formatCourse", hstryUpdFreq, hstryMinVal, twdHstryMax); hstryBufList.twsHstry->setMetaData("TWS", "formatKnots", hstryUpdFreq, hstryMinVal, twsHstryMax); + hstryBufList.awdHstry->setMetaData("AWD", "formatCourse", hstryUpdFreq, hstryMinVal, twdHstryMax); + hstryBufList.awsHstry->setMetaData("AWS", "formatKnots", hstryUpdFreq, hstryMinVal, twsHstryMax); + // create boat values for history data types, if they don't exist yet GwApi::BoatValue *twdBVal = boatValues->findValueOrCreate(hstryBufList.twdHstry->getName()); GwApi::BoatValue *twsBVal = boatValues->findValueOrCreate(hstryBufList.twsHstry->getName()); GwApi::BoatValue *twaBVal = boatValues->findValueOrCreate("TWA"); + GwApi::BoatValue *awdBVal = boatValues->findValueOrCreate(hstryBufList.awdHstry->getName()); + GwApi::BoatValue *awsBVal = boatValues->findValueOrCreate(hstryBufList.awsHstry->getName()); + + if (!awdBVal->valid) { // AWD usually does not exist + awdBVal->setFormat(hstryBufList.awdHstry->getFormat()); + awdBVal->value = DBL_MIN; + } } -void handleHstryBuf(GwApi* api, BoatValueList* boatValues, tBoatHstryData hstryBufList) { +void handleHstryBuf(GwApi* api, BoatValueList* boatValues, tBoatHstryData hstryBufList, bool useSimuData) { // Handle history buffers for TWD, TWS GwLog *logger = api->getLogger(); @@ -419,42 +429,110 @@ void handleHstryBuf(GwApi* api, BoatValueList* boatValues, tBoatHstryData hstryB int16_t twdHstryMax = hstryBufList.twdHstry->getMaxVal(); int16_t twsHstryMin = hstryBufList.twsHstry->getMinVal(); int16_t twsHstryMax = hstryBufList.twsHstry->getMaxVal(); - int16_t twdBuf, twsBuf; + int16_t awdHstryMin = hstryBufList.awdHstry->getMinVal(); + int16_t awdHstryMax = hstryBufList.awdHstry->getMaxVal(); + int16_t awsHstryMin = hstryBufList.awsHstry->getMinVal(); + int16_t awsHstryMax = hstryBufList.awsHstry->getMaxVal(); + static int16_t twd, tws = 20; //initial value only relevant if we use simulation data + static double awd, aws, hdt = 20; //initial value only relevant if we use simulation data GwApi::BoatValue *calBVal; // temp variable just for data calibration -> we don't want to calibrate the original data here GwApi::BoatValue *twdBVal = boatValues->findValueOrCreate(hstryBufList.twdHstry->getName()); GwApi::BoatValue *twsBVal = boatValues->findValueOrCreate(hstryBufList.twsHstry->getName()); GwApi::BoatValue *twaBVal = boatValues->findValueOrCreate("TWA"); + GwApi::BoatValue *awdBVal = boatValues->findValueOrCreate(hstryBufList.awdHstry->getName()); + GwApi::BoatValue *awsBVal = boatValues->findValueOrCreate(hstryBufList.awsHstry->getName()); + GwApi::BoatValue *awaBVal = boatValues->findValueOrCreate("AWA"); + GwApi::BoatValue *hdtBVal = boatValues->findValueOrCreate("HDT"); + GwApi::BoatValue *hdmBVal = boatValues->findValueOrCreate("HDM"); + GwApi::BoatValue *varBVal = boatValues->findValueOrCreate("VAR"); + GwApi::BoatValue *cogBVal = boatValues->findValueOrCreate("COG"); + GwApi::BoatValue *sogBVal = boatValues->findValueOrCreate("SOG"); + + api->getLogger()->logDebug(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); - api->getLogger()->logDebug(GwLog::DEBUG,"obp60task handleHstryBuf: twdBVal: %.1f, twaBVal: %.1f, twsBVal: %.1f, TWD_isValid? %d", twdBVal->value * RAD_TO_DEG, - twaBVal->value * RAD_TO_DEG, twsBVal->value * 3.6 / 1.852, twdBVal->valid); - calBVal = new GwApi::BoatValue("TWD"); // temporary solution for calibration of history buffer values - calBVal->setFormat(twdBVal->getFormat()); if (twdBVal->valid) { + 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 - twdBuf = static_cast(std::round(calBVal->value * 1000)); - if (twdBuf >= twdHstryMin && twdBuf <= twdHstryMax) { - hstryBufList.twdHstry->add(twdBuf); + twd = static_cast(std::round(calBVal->value * 1000)); + if (twd >= twdHstryMin && twd <= twdHstryMax) { + hstryBufList.twdHstry->add(twd); } + delete calBVal; + calBVal = nullptr; + } else if (useSimuData) { + twd += random(-20, 20); + twd = WindUtils::to360(twd); + hstryBufList.twdHstry->add(static_cast(DegToRad(twd) * 1000.0)); } - delete calBVal; - calBVal = nullptr; - calBVal = new GwApi::BoatValue("TWS"); // temporary solution for calibration of history buffer values - calBVal->setFormat(twsBVal->getFormat()); 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 - twsBuf = static_cast(std::round(calBVal->value * 10)); - if (twsBuf >= twsHstryMin && twsBuf <= twsHstryMax) { - hstryBufList.twsHstry->add(twsBuf); + tws = static_cast(std::round(calBVal->value * 10)); + if (tws >= twsHstryMin && tws <= twsHstryMax) { + hstryBufList.twsHstry->add(tws); } + delete calBVal; + calBVal = nullptr; + } else if (useSimuData) { + tws += random(-50, 50); // TWS value in m/s; expands to 1 decimal + tws = constrain(tws, 0, 250); // Limit TWS to [0..25] m/s + hstryBufList.twsHstry->add(tws); + } + + 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); + } + + 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 = std::round(calBVal->value * 1000); + if (awd >= awdHstryMin && awd <= awdHstryMax) { + hstryBufList.awdHstry->add(static_cast(awd)); + } + delete calBVal; + calBVal = nullptr; + } else if (useSimuData) { + awd += random(-20, 20); + awd = WindUtils::to360(awd); + hstryBufList.awdHstry->add(static_cast(DegToRad(awd) * 1000.0)); + } + + 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 = std::round(calBVal->value * 10); + if (aws >= awsHstryMin && aws <= awsHstryMax) { + hstryBufList.awsHstry->add(static_cast(aws)); + } + delete calBVal; + calBVal = nullptr; + } else if (useSimuData) { + aws += random(-50, 50); // TWS value in m/s; expands to 1 decimal + aws = constrain(aws, 0, 250); // Limit TWS to [0..25] m/s + hstryBufList.awsHstry->add(aws); } - delete calBVal; - calBVal = nullptr; } // OBP60 Task @@ -572,9 +650,11 @@ void OBP60Task(GwApi *api){ //add all necessary data to common data // Create ring buffers for history storage of some boat data - RingBuffer twdHstry(960); // Circular buffer to store wind direction values; store 960 TWD values for 16 minutes history - RingBuffer twsHstry(960); // Circular buffer to store wind speed values (TWS) - tBoatHstryData hstryBufList = {&twdHstry, &twsHstry}; + RingBuffer twdHstry(960); // Circular buffer to store true wind direction values; store 960 TWD values for 16 minutes history + RingBuffer twsHstry(960); // Circular buffer to store true wind speed values (TWS) + RingBuffer awdHstry(960); // Circular buffer to store appearant wind direction values; store 960 AWD values for 16 minutes history + RingBuffer awsHstry(960); // Circular buffer to store appearant xwind speed values (AWS) + tBoatHstryData hstryBufList = {&twdHstry, &twsHstry, &awdHstry, &awsHstry}; //fill the page data from config numPages=config->getInt(config->visiblePages,1); @@ -627,7 +707,7 @@ void OBP60Task(GwApi *api){ // Check user setting for true wind calculation bool calcTrueWnds = api->getConfig()->getBool(api->getConfig()->calcTrueWnds, false); - // bool simulation = api->getConfig()->getBool(api->getConfig()->useSimuData, false); + bool simulation = api->getConfig()->getBool(api->getConfig()->useSimuData, false); // Initialize history buffer for certain boat data initHstryBuf(api, &boatValues, hstryBufList); @@ -940,7 +1020,7 @@ void OBP60Task(GwApi *api){ addTrueWind(api, &boatValues); } // Handle history buffers for TWD, TWS for wind plot page and other usage - handleHstryBuf(api, &boatValues, hstryBufList); + handleHstryBuf(api, &boatValues, hstryBufList, simulation); // Clear display // epd->fillRect(0, 0, epd->width(), epd->height(), commonData.bgcolor);