From 7d66ec91da05e546bc76425d631b9cbdb618b282 Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Tue, 3 Jun 2025 22:52:06 +0200 Subject: [PATCH 01/26] Principle working; several bugs incl. --- lib/obp60task/PageWindPlot.cpp | 314 +++++++++++++++++++++++++++++++++ lib/obp60task/config.json | 1 + lib/obp60task/gen_set.py | 1 + lib/obp60task/obp60task.cpp | 2 + 4 files changed, 318 insertions(+) create mode 100644 lib/obp60task/PageWindPlot.cpp diff --git a/lib/obp60task/PageWindPlot.cpp b/lib/obp60task/PageWindPlot.cpp new file mode 100644 index 0000000..4c738d6 --- /dev/null +++ b/lib/obp60task/PageWindPlot.cpp @@ -0,0 +1,314 @@ +#if defined BOARD_OBP60S3 || defined BOARD_OBP40S3 + +#include "BoatDataCalibration.h" +#include "OBP60Extensions.h" +#include "Pagedata.h" + +class CircularBuffer { + // provides a circular buffer to store wind history values +private: + static const int SIZE = 202; + int buffer[SIZE]; + int head = 0; // points to the next insertion index + int count = 0; // number of valid elements + +public: + bool begin(int size) + // Constructor specifies buffer size + { + if (size <= 0 || size > 1000) { + return false; + } +// SIZE = size; +// buffer = new int[SIZE]; // allocate buffer + if (!buffer) { + return false; + } else { + return true; + } + } + + ~CircularBuffer() + { +// delete[] buffer; // Free allocated memory + } + + void add(int value) + // Add a new value + { + buffer[head] = value; + head = (head + 1) % SIZE; + if (count < SIZE) + count++; + } + + int get(int index) const + // Get value by index (0 = oldest, count-1 = newest) + { + if (index < 0 || index >= count) { + return -1; // Invalid index + } + int realIndex = (head + SIZE - count + index) % SIZE; + return buffer[realIndex]; + } + + int size() const + // Get number of valid elements + { + return count; + } +}; + +class PageWindPlot : public Page { + // int16_t lp = 80; // Pointer length + +public: + PageWindPlot(CommonData& common) + { + commonData = &common; + common.logger->logDebug(GwLog::LOG, "Instantiate PageWindPlot"); + } + + // Key functions + virtual int handleKey(int key) + { + // Code for keylock + if (key == 11) { + commonData->keylock = !commonData->keylock; + return 0; // Commit the key + } + return key; + } + + virtual void displayPage(PageData& pageData) + { + GwConfigHandler* config = commonData->config; + GwLog* logger = commonData->logger; + + static String svalue1old = ""; + static String unit1old = ""; + static String svalue2old = ""; + static String unit2old = ""; + static String svalue3old = ""; + static String unit3old = ""; + static String svalue4old = ""; + static String unit4old = ""; + static String svalue5old = ""; + static String unit5old = ""; + + int width = getdisplay().width(); // Get screen width + int height = getdisplay().height(); // Get screen height + int cHeight = height - 98; // height of chart area + int xCenter = width / 2; // Center of screen in x direction +// int yOffset = 76; // Offset for y coordinate to center chart area vertically + int yOffset = 275; // Offset for y coordinate to center chart area vertically + int x, y, lastX, lastY; // x and y coordinates for drawing + static const float radToDeg = 180.0 / M_PI; // Conversion factor from radians to degrees + + static int chartRng = 40; // Range of wind values from mid wind value to min/max wind value in degrees + static int chartMidVal; // Mid wind value in degrees + int chartScl; // Scale for wind values in pixels per degree + int chartVal; // Current wind value + int count; // index for next wind value in buffer + + static CircularBuffer windValues; // Circular buffer to store wind values +/* if (!windValues.begin(cHeight)) { // buffer holds as many values as the height of the chart area + logger->logDebug(GwLog::ERROR, "Failed to initialize wind values buffer"); + return; + } +*/ + LOG_DEBUG(GwLog::LOG, "Display page WindPlot"); + + // 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 for TWD + 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(name1, 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) { + svalue1old = svalue1; // Save old value + unit1old = unit1; // Save old unit + } + + // Get boat values for TWS + GwApi::BoatValue* bvalue2 = pageData.values[1]; // First element in list (only one value by PageOneValue) + String name2 = xdrDelete(bvalue2->getName()); // Value name + name2 = name2.substring(0, 6); // String length limit for value name + calibrationData.calibrateInstance(name2, 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 + if (valid2 == true) { + svalue2old = svalue2; // Save old value + unit2old = unit2; // Save old unit + } + + // Get boat values TWA + GwApi::BoatValue* bvalue3 = pageData.values[2]; // Second element in list (only one value by PageOneValue) + String name3 = xdrDelete(bvalue3->getName()); // Value name + name3 = name3.substring(0, 6); // String length limit for value name + calibrationData.calibrateInstance(name3, bvalue3, logger); // Check if boat data value is to be calibrated + double value3 = bvalue3->value; // Value as double in SI unit + bool valid3 = bvalue3->valid; // Valid information + String svalue3 = formatValue(bvalue3, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places + String unit3 = formatValue(bvalue3, *commonData).unit; // Unit of value + if (valid3 == true) { + svalue3old = svalue3; // Save old value + unit3old = unit3; // Save old unit + } + + // Get boat values AWA + GwApi::BoatValue* bvalue4 = pageData.values[3]; // Second element in list (only one value by PageOneValue) + String name4 = xdrDelete(bvalue4->getName()); // Value name + name4 = name4.substring(0, 6); // String length limit for value name + calibrationData.calibrateInstance(name4, bvalue4, logger); // Check if boat data value is to be calibrated + double value4 = bvalue4->value; // Value as double in SI unit + bool valid4 = bvalue4->valid; // Valid information + String svalue4 = formatValue(bvalue4, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places + String unit4 = formatValue(bvalue4, *commonData).unit; // Unit of value + if (valid4 == true) { + svalue4old = svalue4; // Save old value + unit4old = unit4; // Save old unit + } + + // Get boat values HDM + GwApi::BoatValue* bvalue5 = pageData.values[4]; // Second element in list (only one value by PageOneValue) + String name5 = xdrDelete(bvalue5->getName()); // Value name + name5 = name5.substring(0, 6); // String length limit for value name + calibrationData.calibrateInstance(name5, bvalue5, logger); // Check if boat data value is to be calibrated + double value5 = bvalue5->value; // Value as double in SI unit + bool valid5 = bvalue5->valid; // Valid information + String svalue5 = formatValue(bvalue5, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places + String unit5 = formatValue(bvalue5, *commonData).unit; // Unit of value + if (valid5 == true) { + svalue5old = svalue5; // Save old value + unit5old = unit5; // Save old unit + } + + // Store wind value in buffer + windValues.add(int(value3 * radToDeg)); // Store TWA value (degree) in buffer + count = windValues.size(); // Get number of valid elements in buffer; maximum is cHeight + + // Optical warning by limit violation (unused) + if (String(flashLED) == "Limit Violation") { + setBlinkingLED(false); + setFlashLED(false); + } + + // Logging boat values + if (bvalue1 == NULL) + return; + LOG_DEBUG(GwLog::LOG, "PageWindPlot, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, cnt: %d, wind: %f", name1.c_str(), value1, name2.c_str(), + value2, name3.c_str(), value3, name4.c_str(), value4, name5.c_str(), value5, count, windValues.get(count-1)); + + // Draw page + //*********************************************************** + + // Set display in partial refresh mode + getdisplay().setPartialWindow(0, 0, width, height); // Set partial update + getdisplay().setTextColor(commonData->fgcolor); + + // getdisplay().fillRect(0, 20, width, 1, commonData->fgcolor); // Horizontal top line for orientation -> to be deleted + + // Show TWS value on top right + getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b); + getdisplay().setCursor(262, 58); + getdisplay().print(svalue2); // Value + getdisplay().setFont(&Ubuntu_Bold12pt7b); + getdisplay().setCursor(344, 48); + getdisplay().print(name2); // Name + getdisplay().setFont(&Ubuntu_Bold8pt7b); + getdisplay().setCursor(340, 59); + // getdisplay().print(" "); + if (holdvalues == false) { + getdisplay().print(unit2); // Unit + } else { + getdisplay().print(unit2old); // Unit + } + + // chart lines + getdisplay().fillRect(0, yOffset, width, 2, commonData->fgcolor); + getdisplay().fillRect(xCenter - 1, yOffset, 2, cHeight, commonData->fgcolor); + + // initial chart labels + int twdCenter = windValues.get(0); // TWD center value position + int twdLeft = twdCenter - 40; // TWD left value position + int twdRight = twdCenter + 40; // TWD right value position + getdisplay().setFont(&Ubuntu_Bold10pt7b); + getdisplay().setCursor(xCenter - 15, 73); + getdisplay().print(twdCenter); // TWD center value + getdisplay().drawCircle(xCenter + 22, 63, 2, commonData->fgcolor); // symbol + getdisplay().drawCircle(xCenter + 22, 63, 3, commonData->fgcolor); // symbol + getdisplay().setCursor(2, 73); + getdisplay().print(twdLeft); // TWD left value + getdisplay().drawCircle(40, 63, 2, commonData->fgcolor); // symbol + getdisplay().drawCircle(40, 63, 3, commonData->fgcolor); // symbol + getdisplay().setCursor(width - 35, 73); + getdisplay().print(twdRight); // TWD right value + getdisplay().drawCircle(width - 5, 63, 2, commonData->fgcolor); // symbol + getdisplay().drawCircle(width - 5, 63, 3, commonData->fgcolor); // symbol + + // Draw wind values in chart + //*********************************************************** + + chartMidVal = windValues.get(0); // Get 1st value from buffer for specifcation of chart middle value + lastX = xCenter; + lastY = 275 + count; + LOG_DEBUG(GwLog::LOG, "PageWindPlot Start: lastX: %d, lastY: %d", lastX, lastY); + + for (int i = 0; i < count; i++) { + chartVal = windValues.get(i); // Get value from buffer + chartScl = xCenter / chartRng; // current scale + // Calculate x and y position for the pointer + x = xCenter + ((chartVal - chartMidVal) * chartScl); // Scale to chart width +// y = yOffset + (count - i); // Position in chart area + y = 275 - i; // Position in chart area + // Draw the pointer + getdisplay().drawLine(lastX, lastY, x, y, commonData->fgcolor); + getdisplay().drawLine(lastX, lastY-1, x, y-1, commonData->fgcolor); + lastX = x; + lastY = y; +// LOG_DEBUG(GwLog::LOG, "PageWindPlot: loop-Counter: %d, X: %d, Y: %d", count, x, y); + if (count = 200) { + count -= 40; // move plotting area down by 40 pixels + } + } + LOG_DEBUG(GwLog::LOG, "PageWindPlot End: lastX: %d, lastY: %d, loop-Counter: %d", lastX, lastY, count); + + // Update display + getdisplay().nextPage(); // Partial update (fast) + }; +}; + +static Page* createPage(CommonData& common) +{ + return new PageWindPlot(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 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", "TWA", "AWA", "HDM" }, // Bus values we need in the page + true // Show display header on/off +); + +#endif diff --git a/lib/obp60task/config.json b/lib/obp60task/config.json index c1801af..0cfba1b 100644 --- a/lib/obp60task/config.json +++ b/lib/obp60task/config.json @@ -1734,6 +1734,7 @@ "Voltage", "WhitePage", "Wind", + "WindPlot", "WindRose", "WindRoseFlex", "XTETrack" diff --git a/lib/obp60task/gen_set.py b/lib/obp60task/gen_set.py index 69809fa..d3d8703 100755 --- a/lib/obp60task/gen_set.py +++ b/lib/obp60task/gen_set.py @@ -29,6 +29,7 @@ no_of_fields_per_page = { "TwoValues": 2, "Voltage": 0, "WhitePage": 0, + "WindPlot": 0, "WindRose": 0, "WindRoseFlex": 6, "SixValues" : 6, diff --git a/lib/obp60task/obp60task.cpp b/lib/obp60task/obp60task.cpp index bdc28e0..091c771 100644 --- a/lib/obp60task/obp60task.cpp +++ b/lib/obp60task/obp60task.cpp @@ -275,6 +275,8 @@ void registerAllPages(PageList &list){ list.add(®isterPageFourValues2); extern PageDescription registerPageWind; list.add(®isterPageWind); + extern PageDescription registerPageWindPlot; + list.add(®isterPageWindPlot); extern PageDescription registerPageWindRose; list.add(®isterPageWindRose); extern PageDescription registerPageWindRoseFlex; From da06f3e7918b73ccb389d76d8217ee07f477723e Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Thu, 5 Jun 2025 01:04:07 +0200 Subject: [PATCH 02/26] Automatic scale adjustment + plot shift --- lib/obp60task/PageWindPlot.cpp | 156 ++++++++++++++++++++------------- 1 file changed, 96 insertions(+), 60 deletions(-) diff --git a/lib/obp60task/PageWindPlot.cpp b/lib/obp60task/PageWindPlot.cpp index 4c738d6..b8c731d 100644 --- a/lib/obp60task/PageWindPlot.cpp +++ b/lib/obp60task/PageWindPlot.cpp @@ -4,33 +4,33 @@ #include "OBP60Extensions.h" #include "Pagedata.h" +#include + class CircularBuffer { // provides a circular buffer to store wind history values private: - static const int SIZE = 202; - int buffer[SIZE]; + // static const int SIZE = 202; + int SIZE; + // int buffer[SIZE]; + std::vector buffer; int head = 0; // points to the next insertion index int count = 0; // number of valid elements public: bool begin(int size) - // Constructor specifies buffer size + // specifies buffer size { if (size <= 0 || size > 1000) { return false; } -// SIZE = size; -// buffer = new int[SIZE]; // allocate buffer - if (!buffer) { - return false; - } else { - return true; - } + SIZE = size; + buffer.resize(size); // allocate buffer + return true; } ~CircularBuffer() { -// delete[] buffer; // Free allocated memory + // delete[] buffer; // Free allocated memory } void add(int value) @@ -57,6 +57,16 @@ public: { return count; } + + void mvStart(int start) + // Move the start index for the buffer forward by positions + { + head = (head + start) % SIZE; + if (count > start) + count -= start; + else + count = 0; + } }; class PageWindPlot : public Page { @@ -99,24 +109,29 @@ public: int width = getdisplay().width(); // Get screen width int height = getdisplay().height(); // Get screen height int cHeight = height - 98; // height of chart area + // int cHeight = 50; // height of chart area int xCenter = width / 2; // Center of screen in x direction -// int yOffset = 76; // Offset for y coordinate to center chart area vertically - int yOffset = 275; // Offset for y coordinate to center chart area vertically - int x, y, lastX, lastY; // x and y coordinates for drawing + static const int yOffset = 76; // Offset for y coordinate to center chart area vertically static const float radToDeg = 180.0 / M_PI; // Conversion factor from radians to degrees - static int chartRng = 40; // Range of wind values from mid wind value to min/max wind value in degrees - static int chartMidVal; // Mid wind value in degrees - int chartScl; // Scale for wind values in pixels per degree - int chartVal; // Current wind value + static int wndCenter = -400; // chart wind center value position; init value indicates that wndCenter is not set yet + 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 + int x, y; // x and y coordinates for drawing + static int lastX, lastY; // Last x and y coordinates for drawing + int chrtScl; // Scale for wind values in pixels per degree + int chrtVal; // Current wind value int count; // index for next wind value in buffer static CircularBuffer windValues; // Circular buffer to store wind values -/* if (!windValues.begin(cHeight)) { // buffer holds as many values as the height of the chart area + // CircularBuffer::SIZE = 202; // Set buffer size to 202 values + if (!windValues.begin(cHeight)) { // buffer holds as many values as the height of the chart area logger->logDebug(GwLog::ERROR, "Failed to initialize wind values buffer"); return; } -*/ + LOG_DEBUG(GwLog::LOG, "Display page WindPlot"); // Get config data @@ -198,9 +213,37 @@ public: } // Store wind value in buffer - windValues.add(int(value3 * radToDeg)); // Store TWA value (degree) in buffer + windValues.add(int((value3 * radToDeg) + 0.5)); // Store TWA value (degree) in buffer (rounded to integer) count = windValues.size(); // Get number of valid elements in buffer; maximum is cHeight + // specify and check chart border values + if (wndCenter == -400) { + wndCenter = windValues.get(0); + chrtRng = 20; + wndLeft = wndCenter - chrtRng; + if (wndLeft < 0) + wndLeft += 360; + wndRight = wndCenter + chrtRng; + if (wndRight >= 360) + wndRight -= 360; + + LOG_DEBUG(GwLog::LOG, "PageWindPlot: wndCenter + chrtRng initialized"); + } + diffRng = abs(windValues.get(count - 1) - wndCenter); + if (diffRng > chrtRng) { + chrtRng = int(ceil(diffRng / 10.0) * 10); // Round to next 10 degree value + wndLeft = wndCenter - chrtRng; + if (wndLeft < 0) + wndLeft += 360; + wndRight = wndCenter + chrtRng; + if (wndRight >= 360) + wndRight -= 360; + } + LOG_DEBUG(GwLog::LOG, "PageWindPlot Value3: %f, windValue: %d, count: %d, Range: %d, ChartRng: %d", float(value3 * radToDeg), windValues.get(count - 1), count, diffRng, chrtRng); + + // was, wenn alle Werte kleiner als current wind range sind? + // passe wndCenter an, wenn chrtRng > std und alle Werte > oder < wndCenter sind + // Optical warning by limit violation (unused) if (String(flashLED) == "Limit Violation") { setBlinkingLED(false); @@ -210,8 +253,8 @@ public: // Logging boat values if (bvalue1 == NULL) return; - LOG_DEBUG(GwLog::LOG, "PageWindPlot, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, cnt: %d, wind: %f", name1.c_str(), value1, name2.c_str(), - value2, name3.c_str(), value3, name4.c_str(), value4, name5.c_str(), value5, count, windValues.get(count-1)); + LOG_DEBUG(GwLog::LOG, "PageWindPlot, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, cnt: %d, wind: %f", name1.c_str(), value1, name2.c_str(), + value2, name3.c_str(), value3, name4.c_str(), value4, name5.c_str(), value5, count, windValues.get(count - 1)); // Draw page //*********************************************************** @@ -220,18 +263,18 @@ public: getdisplay().setPartialWindow(0, 0, width, height); // Set partial update getdisplay().setTextColor(commonData->fgcolor); - // getdisplay().fillRect(0, 20, width, 1, commonData->fgcolor); // Horizontal top line for orientation -> to be deleted + getdisplay().fillRect(0, 20, width, 1, commonData->fgcolor); // Horizontal top line for orientation -> to be deleted // Show TWS value on top right getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b); - getdisplay().setCursor(262, 58); + getdisplay().setCursor(252, 58); getdisplay().print(svalue2); // Value getdisplay().setFont(&Ubuntu_Bold12pt7b); - getdisplay().setCursor(344, 48); + getdisplay().setCursor(334, 48); getdisplay().print(name2); // Name getdisplay().setFont(&Ubuntu_Bold8pt7b); - getdisplay().setCursor(340, 59); - // getdisplay().print(" "); + getdisplay().setCursor(330, 59); + getdisplay().print(" "); if (holdvalues == false) { getdisplay().print(unit2); // Unit } else { @@ -242,47 +285,40 @@ public: getdisplay().fillRect(0, yOffset, width, 2, commonData->fgcolor); getdisplay().fillRect(xCenter - 1, yOffset, 2, cHeight, commonData->fgcolor); - // initial chart labels - int twdCenter = windValues.get(0); // TWD center value position - int twdLeft = twdCenter - 40; // TWD left value position - int twdRight = twdCenter + 40; // TWD right value position + // chart labels getdisplay().setFont(&Ubuntu_Bold10pt7b); - getdisplay().setCursor(xCenter - 15, 73); - getdisplay().print(twdCenter); // TWD center value - getdisplay().drawCircle(xCenter + 22, 63, 2, commonData->fgcolor); // symbol - getdisplay().drawCircle(xCenter + 22, 63, 3, commonData->fgcolor); // symbol - getdisplay().setCursor(2, 73); - getdisplay().print(twdLeft); // TWD left value - getdisplay().drawCircle(40, 63, 2, commonData->fgcolor); // symbol - getdisplay().drawCircle(40, 63, 3, commonData->fgcolor); // symbol - getdisplay().setCursor(width - 35, 73); - getdisplay().print(twdRight); // TWD right value - getdisplay().drawCircle(width - 5, 63, 2, commonData->fgcolor); // symbol - getdisplay().drawCircle(width - 5, 63, 3, commonData->fgcolor); // symbol + getdisplay().setCursor(xCenter - 68, yOffset - 3); + getdisplay().print(name3); // Wind name + getdisplay().setCursor(xCenter - 18, yOffset - 3); + getdisplay().print(wndCenter); // Wind center value + getdisplay().drawCircle(xCenter + 19, 63, 2, commonData->fgcolor); // symbol + getdisplay().drawCircle(xCenter + 19, 63, 3, commonData->fgcolor); // symbol + getdisplay().setCursor(5, yOffset - 3); + getdisplay().print(wndLeft); // Wind left value + getdisplay().drawCircle(44, 63, 2, commonData->fgcolor); // symbol + getdisplay().drawCircle(44, 63, 3, commonData->fgcolor); // symbol + getdisplay().setCursor(width - 45, yOffset - 3); + getdisplay().print(wndRight); // Wind right value + getdisplay().drawCircle(width - 6, 63, 2, commonData->fgcolor); // symbol + getdisplay().drawCircle(width - 6, 63, 3, commonData->fgcolor); // symbol // Draw wind values in chart //*********************************************************** - - chartMidVal = windValues.get(0); // Get 1st value from buffer for specifcation of chart middle value lastX = xCenter; - lastY = 275 + count; - LOG_DEBUG(GwLog::LOG, "PageWindPlot Start: lastX: %d, lastY: %d", lastX, lastY); - + lastY = yOffset + cHeight; for (int i = 0; i < count; i++) { - chartVal = windValues.get(i); // Get value from buffer - chartScl = xCenter / chartRng; // current scale - // Calculate x and y position for the pointer - x = xCenter + ((chartVal - chartMidVal) * chartScl); // Scale to chart width -// y = yOffset + (count - i); // Position in chart area - y = 275 - i; // Position in chart area - // Draw the pointer + chrtVal = windValues.get(i); // Get value from buffer + chrtScl = xCenter / chrtRng; // current scale + x = xCenter + ((chrtVal - wndCenter) * chrtScl); // Scale to chart width + y = yOffset + cHeight - i; // Position in chart area getdisplay().drawLine(lastX, lastY, x, y, commonData->fgcolor); - getdisplay().drawLine(lastX, lastY-1, x, y-1, commonData->fgcolor); + getdisplay().drawLine(lastX, lastY - 1, x, y - 1, commonData->fgcolor); lastX = x; lastY = y; -// LOG_DEBUG(GwLog::LOG, "PageWindPlot: loop-Counter: %d, X: %d, Y: %d", count, x, y); - if (count = 200) { - count -= 40; // move plotting area down by 40 pixels + // LOG_DEBUG(GwLog::LOG, "PageWindPlot: loop-Counter: %d, X: %d, Y: %d", count, x, y); + if (i == (cHeight - 1)) { // Reaching chart area top end + windValues.mvStart(40); // virtually delete 40 values from buffer + continue; } } LOG_DEBUG(GwLog::LOG, "PageWindPlot End: lastX: %d, lastY: %d, loop-Counter: %d", lastX, lastY, count); From f153d8282573ed3a44fa291af7188474b0bb0da7 Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Thu, 5 Jun 2025 22:51:25 +0200 Subject: [PATCH 03/26] PlotShift --- lib/obp60task/PageWindPlot.cpp | 40 +++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/lib/obp60task/PageWindPlot.cpp b/lib/obp60task/PageWindPlot.cpp index b8c731d..e1fd13a 100644 --- a/lib/obp60task/PageWindPlot.cpp +++ b/lib/obp60task/PageWindPlot.cpp @@ -9,9 +9,7 @@ class CircularBuffer { // provides a circular buffer to store wind history values private: - // static const int SIZE = 202; int SIZE; - // int buffer[SIZE]; std::vector buffer; int head = 0; // points to the next insertion index int count = 0; // number of valid elements @@ -28,11 +26,6 @@ public: return true; } - ~CircularBuffer() - { - // delete[] buffer; // Free allocated memory - } - void add(int value) // Add a new value { @@ -58,6 +51,12 @@ public: return count; } + int getSIZE() const + // Get number of SIZE + { + return SIZE; + } + void mvStart(int start) // Move the start index for the buffer forward by positions { @@ -70,7 +69,6 @@ public: }; class PageWindPlot : public Page { - // int16_t lp = 80; // Pointer length public: PageWindPlot(CommonData& common) @@ -109,7 +107,6 @@ public: int width = getdisplay().width(); // Get screen width int height = getdisplay().height(); // Get screen height int cHeight = height - 98; // height of chart area - // int cHeight = 50; // height of chart area int xCenter = width / 2; // Center of screen in x direction static const int yOffset = 76; // Offset for y coordinate to center chart area vertically static const float radToDeg = 180.0 / M_PI; // Conversion factor from radians to degrees @@ -119,6 +116,8 @@ public: 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 bool plotShift = false; // Flag to indicate if the plot has been shifted + int x, y; // x and y coordinates for drawing static int lastX, lastY; // Last x and y coordinates for drawing int chrtScl; // Scale for wind values in pixels per degree @@ -126,13 +125,14 @@ public: int count; // index for next wind value in buffer static CircularBuffer windValues; // Circular buffer to store wind values - // CircularBuffer::SIZE = 202; // Set buffer size to 202 values - if (!windValues.begin(cHeight)) { // buffer holds as many values as the height of the chart area - logger->logDebug(GwLog::ERROR, "Failed to initialize wind values buffer"); - return; + if (windValues.size() == 0) { + if (!windValues.begin(cHeight)) { // buffer holds as many values as the height of the chart area + logger->logDebug(GwLog::ERROR, "Failed to initialize wind values buffer"); + return; + } } - LOG_DEBUG(GwLog::LOG, "Display page WindPlot"); + LOG_DEBUG(GwLog::LOG, "Display page WindPlot, SIZE = %d", windValues.getSIZE()); // Get config data String lengthformat = config->getString(config->lengthFormat); @@ -226,7 +226,6 @@ public: wndRight = wndCenter + chrtRng; if (wndRight >= 360) wndRight -= 360; - LOG_DEBUG(GwLog::LOG, "PageWindPlot: wndCenter + chrtRng initialized"); } diffRng = abs(windValues.get(count - 1) - wndCenter); @@ -304,8 +303,14 @@ public: // Draw wind values in chart //*********************************************************** - lastX = xCenter; - lastY = yOffset + cHeight; + if (plotShift) { // If plot was shifted, set lastX to 1st chart wind value + lastX = windValues.get(0); // set to new start of buffer + plotShift = false; + } else { + lastX = xCenter; + } + lastY = yOffset + cHeight; // Reset lastY to bottom of chart + for (int i = 0; i < count; i++) { chrtVal = windValues.get(i); // Get value from buffer chrtScl = xCenter / chrtRng; // current scale @@ -318,6 +323,7 @@ public: // LOG_DEBUG(GwLog::LOG, "PageWindPlot: loop-Counter: %d, X: %d, Y: %d", count, x, y); if (i == (cHeight - 1)) { // Reaching chart area top end windValues.mvStart(40); // virtually delete 40 values from buffer + plotShift = true; // Set flag to shift plot continue; } } From bf4dff45b4c46919ae66e63d6fd18a3978c03f4c Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Thu, 5 Jun 2025 23:42:36 +0200 Subject: [PATCH 04/26] Compact config reading code --- lib/obp60task/PageWindPlot.cpp | 131 +++++++++------------------------ 1 file changed, 35 insertions(+), 96 deletions(-) diff --git a/lib/obp60task/PageWindPlot.cpp b/lib/obp60task/PageWindPlot.cpp index e1fd13a..1959d9d 100644 --- a/lib/obp60task/PageWindPlot.cpp +++ b/lib/obp60task/PageWindPlot.cpp @@ -51,12 +51,6 @@ public: return count; } - int getSIZE() const - // Get number of SIZE - { - return SIZE; - } - void mvStart(int start) // Move the start index for the buffer forward by positions { @@ -93,16 +87,16 @@ public: GwConfigHandler* config = commonData->config; GwLog* logger = commonData->logger; - static String svalue1old = ""; - static String unit1old = ""; - static String svalue2old = ""; - static String unit2old = ""; - static String svalue3old = ""; - static String unit3old = ""; - static String svalue4old = ""; - static String unit4old = ""; - static String svalue5old = ""; - static String unit5old = ""; + const int numCfgValues = 5; + GwApi::BoatValue* bvalue; + String dataName[numCfgValues]; + double dataValue[numCfgValues]; + bool dataValid[numCfgValues]; + String dataSValue[numCfgValues]; + String dataUnit[numCfgValues]; + String dataSValueOld[numCfgValues]; + String dataUnitOld[numCfgValues]; + // String dataFormat[numCfgValues]; int width = getdisplay().width(); // Get screen width int height = getdisplay().height(); // Get screen height @@ -132,7 +126,7 @@ public: } } - LOG_DEBUG(GwLog::LOG, "Display page WindPlot, SIZE = %d", windValues.getSIZE()); + LOG_DEBUG(GwLog::LOG, "Display page WindPlot"); // Get config data String lengthformat = config->getString(config->lengthFormat); @@ -141,79 +135,24 @@ public: String flashLED = config->getString(config->flashLED); String backlightMode = config->getString(config->backlight); - // Get boat values for TWD - 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(name1, 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) { - svalue1old = svalue1; // Save old value - unit1old = unit1; // Save old unit - } - - // Get boat values for TWS - GwApi::BoatValue* bvalue2 = pageData.values[1]; // First element in list (only one value by PageOneValue) - String name2 = xdrDelete(bvalue2->getName()); // Value name - name2 = name2.substring(0, 6); // String length limit for value name - calibrationData.calibrateInstance(name2, 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 - if (valid2 == true) { - svalue2old = svalue2; // Save old value - unit2old = unit2; // Save old unit - } - - // Get boat values TWA - GwApi::BoatValue* bvalue3 = pageData.values[2]; // Second element in list (only one value by PageOneValue) - String name3 = xdrDelete(bvalue3->getName()); // Value name - name3 = name3.substring(0, 6); // String length limit for value name - calibrationData.calibrateInstance(name3, bvalue3, logger); // Check if boat data value is to be calibrated - double value3 = bvalue3->value; // Value as double in SI unit - bool valid3 = bvalue3->valid; // Valid information - String svalue3 = formatValue(bvalue3, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places - String unit3 = formatValue(bvalue3, *commonData).unit; // Unit of value - if (valid3 == true) { - svalue3old = svalue3; // Save old value - unit3old = unit3; // Save old unit - } - - // Get boat values AWA - GwApi::BoatValue* bvalue4 = pageData.values[3]; // Second element in list (only one value by PageOneValue) - String name4 = xdrDelete(bvalue4->getName()); // Value name - name4 = name4.substring(0, 6); // String length limit for value name - calibrationData.calibrateInstance(name4, bvalue4, logger); // Check if boat data value is to be calibrated - double value4 = bvalue4->value; // Value as double in SI unit - bool valid4 = bvalue4->valid; // Valid information - String svalue4 = formatValue(bvalue4, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places - String unit4 = formatValue(bvalue4, *commonData).unit; // Unit of value - if (valid4 == true) { - svalue4old = svalue4; // Save old value - unit4old = unit4; // Save old unit - } - - // Get boat values HDM - GwApi::BoatValue* bvalue5 = pageData.values[4]; // Second element in list (only one value by PageOneValue) - String name5 = xdrDelete(bvalue5->getName()); // Value name - name5 = name5.substring(0, 6); // String length limit for value name - calibrationData.calibrateInstance(name5, bvalue5, logger); // Check if boat data value is to be calibrated - double value5 = bvalue5->value; // Value as double in SI unit - bool valid5 = bvalue5->valid; // Valid information - String svalue5 = formatValue(bvalue5, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places - String unit5 = formatValue(bvalue5, *commonData).unit; // Unit of value - if (valid5 == true) { - svalue5old = svalue5; // Save old value - unit5old = unit5; // Save old unit + for (int i = 0; i < numCfgValues; i++) { + bvalue = pageData.values[i]; + dataName[i] = xdrDelete(bvalue->getName()); + dataName[i] = dataName[i].substring(0, 6); // String length limit for value name + dataValue[i] = bvalue->value; // Value as double in SI unit + calibrationData.calibrateInstance(dataName[i], bvalue, logger); // Check if boat data value is to be calibrated + dataValid[i] = bvalue->valid; + dataSValue[i] = formatValue(bvalue, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places + dataUnit[i] = formatValue(bvalue, *commonData).unit; + // dataFormat[i] = bvalue->getFormat(); // Unit of value + if (dataValid[i]) { + dataSValueOld[i] = dataSValue[i]; // Save old value + dataUnitOld[i] = dataUnit[i]; // Save old unit + } } // Store wind value in buffer - windValues.add(int((value3 * radToDeg) + 0.5)); // Store TWA value (degree) in buffer (rounded to integer) + windValues.add(int((dataValue[2] * radToDeg) + 0.5)); // Store TWA value (degree) in buffer (rounded to integer) count = windValues.size(); // Get number of valid elements in buffer; maximum is cHeight // specify and check chart border values @@ -238,7 +177,7 @@ public: if (wndRight >= 360) wndRight -= 360; } - LOG_DEBUG(GwLog::LOG, "PageWindPlot Value3: %f, windValue: %d, count: %d, Range: %d, ChartRng: %d", float(value3 * radToDeg), windValues.get(count - 1), count, diffRng, chrtRng); + LOG_DEBUG(GwLog::LOG, "PageWindPlot dataValue[2]: %f, windValue: %d, count: %d, Range: %d, ChartRng: %d", float(dataValue[2] * radToDeg), windValues.get(count - 1), count, diffRng, chrtRng); // was, wenn alle Werte kleiner als current wind range sind? // passe wndCenter an, wenn chrtRng > std und alle Werte > oder < wndCenter sind @@ -250,10 +189,10 @@ public: } // Logging boat values - if (bvalue1 == NULL) + if (bvalue == NULL) return; - LOG_DEBUG(GwLog::LOG, "PageWindPlot, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, cnt: %d, wind: %f", name1.c_str(), value1, name2.c_str(), - value2, name3.c_str(), value3, name4.c_str(), value4, name5.c_str(), value5, count, windValues.get(count - 1)); +// LOG_DEBUG(GwLog::LOG, "PageWindPlot, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, cnt: %d, wind: %f", dataName[0].c_str(), dataValue[0], name2.c_str(), +// value2, name3.c_str(), value3, name4.c_str(), value4, name5.c_str(), value5, count, windValues.get(count - 1)); // Draw page //*********************************************************** @@ -267,17 +206,17 @@ public: // Show TWS value on top right getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b); getdisplay().setCursor(252, 58); - getdisplay().print(svalue2); // Value + getdisplay().print(dataSValue[1]); // Value getdisplay().setFont(&Ubuntu_Bold12pt7b); getdisplay().setCursor(334, 48); - getdisplay().print(name2); // Name + getdisplay().print(dataName[1]); // Name getdisplay().setFont(&Ubuntu_Bold8pt7b); getdisplay().setCursor(330, 59); getdisplay().print(" "); if (holdvalues == false) { - getdisplay().print(unit2); // Unit + getdisplay().print(dataUnit[1]); // Unit } else { - getdisplay().print(unit2old); // Unit + getdisplay().print(dataUnitOld[1]); // Unit } // chart lines @@ -287,7 +226,7 @@ public: // chart labels getdisplay().setFont(&Ubuntu_Bold10pt7b); getdisplay().setCursor(xCenter - 68, yOffset - 3); - getdisplay().print(name3); // Wind name + getdisplay().print(dataName[2]); // Wind name getdisplay().setCursor(xCenter - 18, yOffset - 3); getdisplay().print(wndCenter); // Wind center value getdisplay().drawCircle(xCenter + 19, 63, 2, commonData->fgcolor); // symbol From 9f79a7d4bc505ae5f07adb9df47a032bd04dc342 Mon Sep 17 00:00:00 2001 From: Scorgan01 <145987006+Scorgan01@users.noreply.github.com> Date: Fri, 6 Jun 2025 10:17:11 +0200 Subject: [PATCH 05/26] Switch to TWD --- lib/obp60task/PageWindPlot.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/obp60task/PageWindPlot.cpp b/lib/obp60task/PageWindPlot.cpp index 1959d9d..a116e66 100644 --- a/lib/obp60task/PageWindPlot.cpp +++ b/lib/obp60task/PageWindPlot.cpp @@ -151,8 +151,8 @@ public: } } - // Store wind value in buffer - windValues.add(int((dataValue[2] * radToDeg) + 0.5)); // Store TWA value (degree) in buffer (rounded to integer) + // Store TWD wind value in buffer + windValues.add(int((dataValue[0] * radToDeg) + 0.5)); // Store TWD value (degree) in buffer (rounded to integer) count = windValues.size(); // Get number of valid elements in buffer; maximum is cHeight // specify and check chart border values @@ -226,7 +226,7 @@ public: // chart labels getdisplay().setFont(&Ubuntu_Bold10pt7b); getdisplay().setCursor(xCenter - 68, yOffset - 3); - getdisplay().print(dataName[2]); // Wind name + getdisplay().print(dataName[0]); // Wind name getdisplay().setCursor(xCenter - 18, yOffset - 3); getdisplay().print(wndCenter); // Wind center value getdisplay().drawCircle(xCenter + 19, 63, 2, commonData->fgcolor); // symbol From 62aef176d3858a3c78b0131464cc4ffe1138c9c1 Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Fri, 6 Jun 2025 23:06:04 +0200 Subject: [PATCH 06/26] Chart + plotshift working --- lib/obp60task/PageWindPlot.cpp | 98 ++++++++++++++++++++++------------ 1 file changed, 63 insertions(+), 35 deletions(-) diff --git a/lib/obp60task/PageWindPlot.cpp b/lib/obp60task/PageWindPlot.cpp index a116e66..383bbc2 100644 --- a/lib/obp60task/PageWindPlot.cpp +++ b/lib/obp60task/PageWindPlot.cpp @@ -11,6 +11,7 @@ class CircularBuffer { private: int SIZE; std::vector buffer; + int first = 0; // points to the first valid element int head = 0; // points to the next insertion index int count = 0; // number of valid elements @@ -31,17 +32,22 @@ public: { buffer[head] = value; head = (head + 1) % SIZE; - if (count < SIZE) + if (count < SIZE) { count++; + } else { + first = head - 1; // When buffer is full, first points to the oldest value + } } int get(int index) const // Get value by index (0 = oldest, count-1 = newest) { + int realIndex; + if (index < 0 || index >= count) { return -1; // Invalid index } - int realIndex = (head + SIZE - count + index) % SIZE; + realIndex = (first + index) % SIZE; return buffer[realIndex]; } @@ -52,9 +58,9 @@ public: } void mvStart(int start) - // Move the start index for the buffer forward by positions + // Move the start index of buffer forward by positions { - head = (head + start) % SIZE; + first = (first + start) % SIZE; if (count > start) count -= start; else @@ -63,7 +69,6 @@ public: }; class PageWindPlot : public Page { - public: PageWindPlot(CommonData& common) { @@ -102,7 +107,7 @@ public: int height = getdisplay().height(); // Get screen height int cHeight = height - 98; // height of chart area int xCenter = width / 2; // Center of screen in x direction - static const int yOffset = 76; // Offset for y coordinate to center chart area vertically + static const int yOffset = 76; // Offset for y coordinates of chart area static const float radToDeg = 180.0 / M_PI; // Conversion factor from radians to degrees static int wndCenter = -400; // chart wind center value position; init value indicates that wndCenter is not set yet @@ -110,17 +115,18 @@ public: 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 bool plotShift = false; // Flag to indicate if the plot has been shifted int x, y; // x and y coordinates for drawing static int lastX, lastY; // Last x and y coordinates for drawing - int chrtScl; // Scale for wind values in pixels per degree + static int chrtScl; // Scale for wind values in pixels per degree int chrtVal; // Current wind value int count; // index for next wind value in buffer - static CircularBuffer windValues; // Circular buffer to store wind values + // Circular buffer to store wind values + static CircularBuffer windValues; if (windValues.size() == 0) { if (!windValues.begin(cHeight)) { // buffer holds as many values as the height of the chart area + // if (!windValues.begin(60)) { // buffer holds as many values as the height of the chart area logger->logDebug(GwLog::ERROR, "Failed to initialize wind values buffer"); return; } @@ -128,6 +134,30 @@ public: LOG_DEBUG(GwLog::LOG, "Display page WindPlot"); + /* // Fill windValues buffer with sequential values from 1 to 60 and back to 1 for cHeight values + int num = 60; + windValues.begin(num + 20); + int seq = 1; + int dir = 1; + for (int i = 0; i < num; i++) { + windValues.add(seq); + seq += dir; + if (seq == 60) + dir = -1; + if (seq == 1) + dir = 1; + } + + for (int i = 0; i < num; i += 4) { + LOG_DEBUG(GwLog::LOG, "WindValues[%d]: %d; [%d]: %d; [%d]: %d [%d]: %d", i, windValues.get(i), i + 1, windValues.get(i + 1), i + 2, windValues.get(i + 2), i + 3, windValues.get(i + 3)); + } + windValues.mvStart(20); + for (int i = 0; i < num; i += 4) { + LOG_DEBUG(GwLog::LOG, "Shifted WindValues[%d]: %d; [%d]: %d; [%d]: %d [%d]: %d", i, windValues.get(i), i + 1, windValues.get(i + 1), i + 2, windValues.get(i + 2), i + 3, windValues.get(i + 3)); + } + return; // Return early for testing purposes + */ + // Get config data String lengthformat = config->getString(config->lengthFormat); bool simulation = config->getBool(config->useSimuData); @@ -177,7 +207,7 @@ public: if (wndRight >= 360) wndRight -= 360; } - LOG_DEBUG(GwLog::LOG, "PageWindPlot dataValue[2]: %f, windValue: %d, count: %d, Range: %d, ChartRng: %d", float(dataValue[2] * radToDeg), windValues.get(count - 1), count, diffRng, chrtRng); + LOG_DEBUG(GwLog::LOG, "PageWindPlot dataValue[0]: %f, windValue: %d, count: %d, Range: %d, ChartRng: %d", float(dataValue[0] * radToDeg), windValues.get(count - 1), count, diffRng, chrtRng); // was, wenn alle Werte kleiner als current wind range sind? // passe wndCenter an, wenn chrtRng > std und alle Werte > oder < wndCenter sind @@ -191,8 +221,8 @@ public: // Logging boat values if (bvalue == NULL) return; -// LOG_DEBUG(GwLog::LOG, "PageWindPlot, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, cnt: %d, wind: %f", dataName[0].c_str(), dataValue[0], name2.c_str(), -// value2, name3.c_str(), value3, name4.c_str(), value4, name5.c_str(), value5, count, windValues.get(count - 1)); + // LOG_DEBUG(GwLog::LOG, "PageWindPlot, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, cnt: %d, wind: %f", dataName[0].c_str(), dataValue[0], name2.c_str(), + // value2, name3.c_str(), value3, name4.c_str(), value4, name5.c_str(), value5, count, windValues.get(count - 1)); // Draw page //*********************************************************** @@ -201,17 +231,17 @@ public: getdisplay().setPartialWindow(0, 0, width, height); // Set partial update getdisplay().setTextColor(commonData->fgcolor); - getdisplay().fillRect(0, 20, width, 1, commonData->fgcolor); // Horizontal top line for orientation -> to be deleted - + // Horizontal top line for orientation -> to be deleted + // getdisplay().fillRect(0, 20, width, 1, commonData->fgcolor); // Show TWS value on top right getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b); - getdisplay().setCursor(252, 58); + getdisplay().setCursor(252, 52); getdisplay().print(dataSValue[1]); // Value getdisplay().setFont(&Ubuntu_Bold12pt7b); - getdisplay().setCursor(334, 48); + getdisplay().setCursor(334, 38); getdisplay().print(dataName[1]); // Name getdisplay().setFont(&Ubuntu_Bold8pt7b); - getdisplay().setCursor(330, 59); + getdisplay().setCursor(330, 53); getdisplay().print(" "); if (holdvalues == false) { getdisplay().print(dataUnit[1]); // Unit @@ -224,30 +254,29 @@ public: getdisplay().fillRect(xCenter - 1, yOffset, 2, cHeight, commonData->fgcolor); // chart labels + char sWndLbl[4]; // Wind label getdisplay().setFont(&Ubuntu_Bold10pt7b); getdisplay().setCursor(xCenter - 68, yOffset - 3); getdisplay().print(dataName[0]); // Wind name - getdisplay().setCursor(xCenter - 18, yOffset - 3); - getdisplay().print(wndCenter); // Wind center value - getdisplay().drawCircle(xCenter + 19, 63, 2, commonData->fgcolor); // symbol - getdisplay().drawCircle(xCenter + 19, 63, 3, commonData->fgcolor); // symbol - getdisplay().setCursor(5, yOffset - 3); - getdisplay().print(wndLeft); // Wind left value - getdisplay().drawCircle(44, 63, 2, commonData->fgcolor); // symbol - getdisplay().drawCircle(44, 63, 3, commonData->fgcolor); // symbol - getdisplay().setCursor(width - 45, yOffset - 3); - getdisplay().print(wndRight); // Wind right value + getdisplay().setCursor(xCenter - 16, yOffset - 3); + snprintf(sWndLbl, 4, "%03d", wndCenter); + getdisplay().print(sWndLbl); // Wind center value + getdisplay().drawCircle(xCenter + 21, 63, 2, commonData->fgcolor); // symbol + getdisplay().drawCircle(xCenter + 21, 63, 3, commonData->fgcolor); // symbol + getdisplay().setCursor(2, yOffset - 3); + snprintf(sWndLbl, 4, "%03d", wndLeft); + getdisplay().print(sWndLbl); // Wind left value + getdisplay().drawCircle(39, 63, 2, commonData->fgcolor); // symbol + getdisplay().drawCircle(39, 63, 3, commonData->fgcolor); // symbol + getdisplay().setCursor(width - 43, yOffset - 3); + snprintf(sWndLbl, 4, "%03d", wndRight); + getdisplay().print(sWndLbl); // Wind right value getdisplay().drawCircle(width - 6, 63, 2, commonData->fgcolor); // symbol getdisplay().drawCircle(width - 6, 63, 3, commonData->fgcolor); // symbol // Draw wind values in chart //*********************************************************** - if (plotShift) { // If plot was shifted, set lastX to 1st chart wind value - lastX = windValues.get(0); // set to new start of buffer - plotShift = false; - } else { - lastX = xCenter; - } + lastX = xCenter + ((windValues.get(0) - wndCenter) * chrtScl); lastY = yOffset + cHeight; // Reset lastY to bottom of chart for (int i = 0; i < count; i++) { @@ -259,10 +288,9 @@ public: getdisplay().drawLine(lastX, lastY - 1, x, y - 1, commonData->fgcolor); lastX = x; lastY = y; - // LOG_DEBUG(GwLog::LOG, "PageWindPlot: loop-Counter: %d, X: %d, Y: %d", count, x, y); + // LOG_DEBUG(GwLog::LOG, "PageWindPlot: loop-Counter: %d, X: %d, Y: %d, lastX: %d, lastY: %d", count, x, y, lastX, lastY); if (i == (cHeight - 1)) { // Reaching chart area top end windValues.mvStart(40); // virtually delete 40 values from buffer - plotShift = true; // Set flag to shift plot continue; } } From aa70c34a963e3a0446907d4eb0228f0b1d8ce6fa Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Sun, 8 Jun 2025 13:45:20 +0200 Subject: [PATCH 07/26] Fix getMin/Max + wndCenter rounding; --- lib/obp60task/PageWindPlot.cpp | 205 +++++++++++++++++++++++---------- 1 file changed, 141 insertions(+), 64 deletions(-) diff --git a/lib/obp60task/PageWindPlot.cpp b/lib/obp60task/PageWindPlot.cpp index 383bbc2..5f13f52 100644 --- a/lib/obp60task/PageWindPlot.cpp +++ b/lib/obp60task/PageWindPlot.cpp @@ -6,8 +6,8 @@ #include -class CircularBuffer { - // provides a circular buffer to store wind history values +class wndHistory { + // provides a FiFo circular buffer to store wind history values private: int SIZE; std::vector buffer; @@ -30,6 +30,9 @@ public: void add(int value) // Add a new value { + if (value > 180) { + value -= 360; // Normalize value to -180..180 to make min/max calculations working + } buffer[head] = value; head = (head + 1) % SIZE; if (count < SIZE) { @@ -40,7 +43,7 @@ public: } int get(int index) const - // Get value by index (0 = oldest, count-1 = newest) + // Get value by index in [-180..180 deg] format (0 = oldest, count-1 = newest) { int realIndex; @@ -49,14 +52,77 @@ public: } realIndex = (first + index) % SIZE; return buffer[realIndex]; + // } } - int size() const + int get(int index, int deg) const + // Get value by index in [-180..180 deg] or [0..360 deg] format (0 = oldest, count-1 = newest) + { + switch (deg) { + case 180: + // Return value in [-180..180 deg] format + return get(index); + case 360: { + // Return value in [0..360 deg] format + int value = get(index); + if (value < 0) { + value += 360; + }; + return value; + } + default: + // Return value in [-180..180 deg] format + return -1; + } + } + + int getSize() const // Get number of valid elements { return count; } + int getMin() const + // Get minimum value in the buffer + { + if (count == 0) { + return -1; // Buffer is empty + } else if (first + count <= SIZE) { + // No wrap-around + return *std::min_element(buffer.begin() + first, buffer.begin() + first + count); + } else { + // Wrap-around: check [first, end) and [begin, (first+count)%SIZE) + int min1 = *std::min_element(buffer.begin() + first, buffer.end()); + int min2 = *std::min_element(buffer.begin(), buffer.begin() + ((first + count) % SIZE)); + return std::min(min1, min2); + } + } + + int getMax() const + // Get maximum value in the buffer + { + if (count == 0) { + return -1; // Buffer is empty + } else if (first + count <= SIZE) { + // No wrap-around + return *std::max_element(buffer.begin() + first, buffer.begin() + first + count); + } else { + // Wrap-around: check [first, end) and [begin, (first+count)%SIZE) + int max1 = *std::max_element(buffer.begin() + first, buffer.end()); + int max2 = *std::max_element(buffer.begin(), buffer.begin() + ((first + count) % SIZE)); + return std::max(max1, max2); + } + } + + int getMid() const + // Get middle value in the buffer + { + if (count == 0) { + return -1; // Buffer is empty + } + return (getMin() + getMax()) / 2; + } + void mvStart(int start) // Move the start index of buffer forward by positions { @@ -105,7 +171,8 @@ public: int width = getdisplay().width(); // Get screen width int height = getdisplay().height(); // Get screen height - int cHeight = height - 98; // height of chart area + // int cHeight = height - 98; // height of chart area + int cHeight = 80; // height of chart area int xCenter = width / 2; // Center of screen in x direction static const int yOffset = 76; // Offset for y coordinates of chart area static const float radToDeg = 180.0 / M_PI; // Conversion factor from radians to degrees @@ -115,6 +182,7 @@ public: 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 bool plotShift = false; // Flag to indicate if chartplot data have been shifted int x, y; // x and y coordinates for drawing static int lastX, lastY; // Last x and y coordinates for drawing @@ -123,8 +191,8 @@ public: int count; // index for next wind value in buffer // Circular buffer to store wind values - static CircularBuffer windValues; - if (windValues.size() == 0) { + static wndHistory windValues; + if (windValues.getSize() == 0) { if (!windValues.begin(cHeight)) { // buffer holds as many values as the height of the chart area // if (!windValues.begin(60)) { // buffer holds as many values as the height of the chart area logger->logDebug(GwLog::ERROR, "Failed to initialize wind values buffer"); @@ -174,55 +242,47 @@ public: dataValid[i] = bvalue->valid; dataSValue[i] = formatValue(bvalue, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places dataUnit[i] = formatValue(bvalue, *commonData).unit; - // dataFormat[i] = bvalue->getFormat(); // Unit of value if (dataValid[i]) { dataSValueOld[i] = dataSValue[i]; // Save old value dataUnitOld[i] = dataUnit[i]; // Save old unit } } - // Store TWD wind value in buffer - windValues.add(int((dataValue[0] * radToDeg) + 0.5)); // Store TWD value (degree) in buffer (rounded to integer) - count = windValues.size(); // Get number of valid elements in buffer; maximum is cHeight - - // specify and check chart border values - if (wndCenter == -400) { - wndCenter = windValues.get(0); - chrtRng = 20; - wndLeft = wndCenter - chrtRng; - if (wndLeft < 0) - wndLeft += 360; - wndRight = wndCenter + chrtRng; - if (wndRight >= 360) - wndRight -= 360; - LOG_DEBUG(GwLog::LOG, "PageWindPlot: wndCenter + chrtRng initialized"); - } - diffRng = abs(windValues.get(count - 1) - wndCenter); - if (diffRng > chrtRng) { - chrtRng = int(ceil(diffRng / 10.0) * 10); // Round to next 10 degree value - wndLeft = wndCenter - chrtRng; - if (wndLeft < 0) - wndLeft += 360; - wndRight = wndCenter + chrtRng; - if (wndRight >= 360) - wndRight -= 360; - } - LOG_DEBUG(GwLog::LOG, "PageWindPlot dataValue[0]: %f, windValue: %d, count: %d, Range: %d, ChartRng: %d", float(dataValue[0] * radToDeg), windValues.get(count - 1), count, diffRng, chrtRng); - - // was, wenn alle Werte kleiner als current wind range sind? - // passe wndCenter an, wenn chrtRng > std und alle Werte > oder < wndCenter sind - // Optical warning by limit violation (unused) if (String(flashLED) == "Limit Violation") { setBlinkingLED(false); setFlashLED(false); } - // Logging boat values - if (bvalue == NULL) - return; - // LOG_DEBUG(GwLog::LOG, "PageWindPlot, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, cnt: %d, wind: %f", dataName[0].c_str(), dataValue[0], name2.c_str(), - // value2, name3.c_str(), value3, name4.c_str(), value4, name5.c_str(), value5, count, windValues.get(count - 1)); + // Store TWD wind value in buffer + windValues.add(int((dataValue[0] * radToDeg) + 0.5)); // Store TWD value (degree) in buffer (rounded to integer) + count = windValues.getSize(); // Get number of valid elements in buffer; maximum is cHeight + + // initialize chart range values + if (wndCenter == -400) { + wndCenter = windValues.get(0); + wndCenter = int((wndCenter + (wndCenter >= 0 ? 5 : -5)) / 10) * 10; // Set new center value; round to nearest 10 degree value +// wndCenter = int((windValues.get(0) + 5) / 10) * 10; // Round to nearest 10 degree value + diffRng = 30; + chrtRng = 30; + LOG_DEBUG(GwLog::LOG, "PageWindPlot initialized. wndCenter: %d, chrtRng: %d ", wndCenter, chrtRng); + } else { + diffRng = max(abs(((windValues.getMax() - wndCenter + 540) % 360) - 180), abs(((windValues.getMin() - wndCenter + 540) % 360) - 180)); // check necessary range size + if (diffRng > chrtRng) { + chrtRng = int((diffRng + (diffRng >= 0 ? 9 : -1)) / 10) * 10; // Round up to next 10 degree value +// chrtRng = int(ceil(diffRng / 10.0) * 10); // Round to next 10 degree value + } else if (diffRng + 10 < chrtRng) { + chrtRng = max(30, int((diffRng + (diffRng >= 0 ? 9 : -1)) / 10) * 10); // Round up to next 10 degree value +// chrtRng = max(30, int(ceil(diffRng / 10.0) * 10)); // Round to next 10 degree value, but mimimum range is 30 degrees + } + } + wndLeft = wndCenter - chrtRng; + if (wndLeft < -180) + wndLeft += 360; + wndRight = wndCenter + chrtRng; + if (wndRight >= 180) + wndRight -= 360; + LOG_DEBUG(GwLog::LOG, "PageWindPlot dataValue[0]: %f, windValue: %d, count: %d, diffRng: %d, chartRng: %d", float(dataValue[0] * radToDeg), windValues.get(count - 1), count, diffRng, chrtRng); // Draw page //*********************************************************** @@ -256,43 +316,60 @@ public: // chart labels char sWndLbl[4]; // Wind label getdisplay().setFont(&Ubuntu_Bold10pt7b); - getdisplay().setCursor(xCenter - 68, yOffset - 3); - getdisplay().print(dataName[0]); // Wind name + getdisplay().setCursor(xCenter - 80, yOffset - 3); + getdisplay().print("TWD"); // Wind name getdisplay().setCursor(xCenter - 16, yOffset - 3); - snprintf(sWndLbl, 4, "%03d", wndCenter); + snprintf(sWndLbl, 4, "%03d", (wndCenter < 0) ? (wndCenter + 360) : wndCenter); getdisplay().print(sWndLbl); // Wind center value getdisplay().drawCircle(xCenter + 21, 63, 2, commonData->fgcolor); // symbol getdisplay().drawCircle(xCenter + 21, 63, 3, commonData->fgcolor); // symbol getdisplay().setCursor(2, yOffset - 3); - snprintf(sWndLbl, 4, "%03d", wndLeft); + snprintf(sWndLbl, 4, "%03d", (wndLeft < 0) ? (wndLeft + 360) : wndLeft); getdisplay().print(sWndLbl); // Wind left value getdisplay().drawCircle(39, 63, 2, commonData->fgcolor); // symbol getdisplay().drawCircle(39, 63, 3, commonData->fgcolor); // symbol getdisplay().setCursor(width - 43, yOffset - 3); - snprintf(sWndLbl, 4, "%03d", wndRight); + snprintf(sWndLbl, 4, "%03d", (wndRight < 0) ? (wndRight + 360) : wndRight); getdisplay().print(sWndLbl); // Wind right value getdisplay().drawCircle(width - 6, 63, 2, commonData->fgcolor); // symbol getdisplay().drawCircle(width - 6, 63, 3, commonData->fgcolor); // symbol // Draw wind values in chart //*********************************************************** - lastX = xCenter + ((windValues.get(0) - wndCenter) * chrtScl); - lastY = yOffset + cHeight; // Reset lastY to bottom of chart + if (dataValid[0] || holdvalues || simulation == true) { - for (int i = 0; i < count; i++) { - chrtVal = windValues.get(i); // Get value from buffer - chrtScl = xCenter / chrtRng; // current scale - x = xCenter + ((chrtVal - wndCenter) * chrtScl); // Scale to chart width - y = yOffset + cHeight - i; // Position in chart area - getdisplay().drawLine(lastX, lastY, x, y, commonData->fgcolor); - getdisplay().drawLine(lastX, lastY - 1, x, y - 1, commonData->fgcolor); - lastX = x; - lastY = y; - // LOG_DEBUG(GwLog::LOG, "PageWindPlot: loop-Counter: %d, X: %d, Y: %d, lastX: %d, lastY: %d", count, x, y, lastX, lastY); - if (i == (cHeight - 1)) { // Reaching chart area top end - windValues.mvStart(40); // virtually delete 40 values from buffer - continue; + lastX = xCenter + ((windValues.get(0) - wndCenter) * chrtScl); + lastY = yOffset + cHeight; // Reset lastY to bottom of chart + + for (int i = 0; i < count; i++) { + chrtVal = windValues.get(i); // Get value from buffer + chrtScl = xCenter / chrtRng; // current scale: pixels per degree + x = xCenter + ((chrtVal - wndCenter) * chrtScl); // Scale to chart width + // x = xCenter + ((((chrtVal - wndCenter + 540) % 360) - 180) * chrtScl); // Scale to chart width + y = yOffset + cHeight - i; // Position in chart area + // Draw line with 2 pixels width; make sure vertical line are drawn correctly + getdisplay().drawLine(lastX, lastY, x, y, commonData->fgcolor); + getdisplay().drawLine(lastX, lastY - 1, (x != lastX) ? x : x-1, (x != lastX) ? y - 1 : y, commonData->fgcolor); + lastX = x; + lastY = y; + // LOG_DEBUG(GwLog::LOG, "PageWindPlot: loop-Counter: %d, X: %d, Y: %d, lastX: %d, lastY: %d", count, x, y, lastX, lastY); + LOG_DEBUG(GwLog::LOG, "PageWindPlot Shift: Min: %d, Max: %d, Mid: %d", windValues.getMin(), windValues.getMax(), windValues.getMid()); + if (i == (cHeight - 1)) { // Reaching chart area top end + windValues.mvStart(40); // virtually delete 40 values from buffer + LOG_DEBUG(GwLog::LOG, "PageWindPlot Shift: Min: %d, Max: %d, Mid: %d", windValues.getMin(), windValues.getMax(), windValues.getMid()); + if ((windValues.getMin() > wndCenter) || (windValues.getMax() < wndCenter)) { + int mid = windValues.getMid(); + wndCenter = int((mid + (mid >= 0 ? 5 : -5)) / 10) * 10; // Set new center value; round to nearest 10 degree value + LOG_DEBUG(GwLog::LOG, "PageWindPlot Shift: Min: %d, Max: %d, new Center: %d", windValues.getMin(), windValues.getMax(), wndCenter); + } + continue; // Will leave loop + } } + } else { + getdisplay().setFont(&Ubuntu_Bold8pt7b); + getdisplay().setCursor(xCenter - 55, height / 2); + getdisplay().print("No sensor data"); + return; } LOG_DEBUG(GwLog::LOG, "PageWindPlot End: lastX: %d, lastY: %d, loop-Counter: %d", lastX, lastY, count); From 235188dfb28f0540cf468e0154f43ca731dcc510 Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Mon, 9 Jun 2025 13:35:54 +0200 Subject: [PATCH 08/26] Added update interval + no sens data msg; corrected rounding --- lib/obp60task/PageWindPlot.cpp | 203 +++++++++++++++++++++++---------- 1 file changed, 140 insertions(+), 63 deletions(-) diff --git a/lib/obp60task/PageWindPlot.cpp b/lib/obp60task/PageWindPlot.cpp index 5f13f52..e0b6cc0 100644 --- a/lib/obp60task/PageWindPlot.cpp +++ b/lib/obp60task/PageWindPlot.cpp @@ -6,6 +6,7 @@ #include +// **************************************************************** class wndHistory { // provides a FiFo circular buffer to store wind history values private: @@ -132,9 +133,33 @@ public: else count = 0; } + + // TWA, TWS, HDM, AWA, AWS, STW + bool calcTWD(int* twd, float twa, float tws, float hdm, float awa, float aws, float stw) + // Calculate TWD based on other boat data values + { + if (count == 0) { + return false; // Buffer is empty + } + + int twdMin = getMin(); + int twdMax = getMax(); + int twdMid = getMid(); + + // Calculate TWD based on TWA and HDM + return true; + } }; +// **************************************************************** class PageWindPlot : public Page { + + bool keylock = false; // Keylock +// int16_t lp = 80; // Pointer length + char mode = 'D'; // Chart mode: 'D' for TWD, 'S' for TWS + int updTime = 1; // Update interval for wind history chart: + // (1)|(2)|(3)|(5) seconds for 3, 7, 10, 15 min. history chart + public: PageWindPlot(CommonData& common) { @@ -142,11 +167,44 @@ public: common.logger->logDebug(GwLog::LOG, "Instantiate PageWindPlot"); } + virtual void setupKeys() + { + Page::setupKeys(); + commonData->keydata[0].label = "MODE"; + commonData->keydata[1].label = "INTV"; + } + // Key functions virtual int handleKey(int key) { - // Code for keylock - if (key == 11) { + // Set chart mode TWD | TWS + if (key == 1) { + if (mode == 'D') { + mode = 'S'; + } else { + mode = 'D'; + } + // setupKeys(); // Update key labels + return 0; // Commit the key + } + + // Set interval for wind history chart update time + if (key == 2) { + if (updTime == 1) { + updTime = 2; + } else if (updTime == 2) { + updTime = 3; + } else if (updTime == 3) { + updTime = 5; + } else { + updTime = 1; + } + setupKeys(); // Update key labels + return 0; // Commit the key + } + + // Keylock function + if (key == 11) { // Code for keylock commonData->keylock = !commonData->keylock; return 0; // Commit the key } @@ -158,8 +216,10 @@ public: GwConfigHandler* config = commonData->config; GwLog* logger = commonData->logger; - const int numCfgValues = 5; + static wndHistory windValues; // Circular buffer to store wind values + GwApi::BoatValue* bvalue; + const int numCfgValues = 9; String dataName[numCfgValues]; double dataValue[numCfgValues]; bool dataValid[numCfgValues]; @@ -167,12 +227,11 @@ public: String dataUnit[numCfgValues]; String dataSValueOld[numCfgValues]; String dataUnitOld[numCfgValues]; - // String dataFormat[numCfgValues]; int width = getdisplay().width(); // Get screen width int height = getdisplay().height(); // Get screen height - // int cHeight = height - 98; // height of chart area - int cHeight = 80; // height of chart area + int cHeight = height - 98; // height of chart area + // int cHeight = 80; // height of chart area int xCenter = width / 2; // Center of screen in x direction static const int yOffset = 76; // Offset for y coordinates of chart area static const float radToDeg = 180.0 / M_PI; // Conversion factor from radians to degrees @@ -185,16 +244,17 @@ public: static bool plotShift = false; // Flag to indicate if chartplot data have been shifted int x, y; // x and y coordinates for drawing - static int lastX, lastY; // Last x and y coordinates for drawing + static int prevX, prevY; // Last x and y coordinates for drawing static int chrtScl; // Scale for wind values in pixels per degree int chrtVal; // Current wind value + static int chrtPrevVal; // Last wind value in chart area for check if value crosses 180 degree line int count; // index for next wind value in buffer - // Circular buffer to store wind values - static wndHistory windValues; + static int updCnt = 0; // update counter for wind history chart in seconds + bool isTimeforUpd = true; // Flag to indicate if it is time for chart update + if (windValues.getSize() == 0) { - if (!windValues.begin(cHeight)) { // buffer holds as many values as the height of the chart area - // if (!windValues.begin(60)) { // buffer holds as many values as the height of the chart area + if (!windValues.begin(cHeight)) { logger->logDebug(GwLog::ERROR, "Failed to initialize wind values buffer"); return; } @@ -202,29 +262,14 @@ public: LOG_DEBUG(GwLog::LOG, "Display page WindPlot"); - /* // Fill windValues buffer with sequential values from 1 to 60 and back to 1 for cHeight values - int num = 60; - windValues.begin(num + 20); - int seq = 1; - int dir = 1; - for (int i = 0; i < num; i++) { - windValues.add(seq); - seq += dir; - if (seq == 60) - dir = -1; - if (seq == 1) - dir = 1; - } - - for (int i = 0; i < num; i += 4) { - LOG_DEBUG(GwLog::LOG, "WindValues[%d]: %d; [%d]: %d; [%d]: %d [%d]: %d", i, windValues.get(i), i + 1, windValues.get(i + 1), i + 2, windValues.get(i + 2), i + 3, windValues.get(i + 3)); - } - windValues.mvStart(20); - for (int i = 0; i < num; i += 4) { - LOG_DEBUG(GwLog::LOG, "Shifted WindValues[%d]: %d; [%d]: %d; [%d]: %d [%d]: %d", i, windValues.get(i), i + 1, windValues.get(i + 1), i + 2, windValues.get(i + 2), i + 3, windValues.get(i + 3)); - } - return; // Return early for testing purposes - */ + if (updCnt < updTime) { + // Next update interval not reached yet + updCnt++; + isTimeforUpd = false; + } else { + isTimeforUpd = true; + updCnt = 1; // Data update is now; reset counter + } // Get config data String lengthformat = config->getString(config->lengthFormat); @@ -233,6 +278,7 @@ public: String flashLED = config->getString(config->flashLED); String backlightMode = config->getString(config->backlight); + // Read boatdata values for TWD, TWA, TWS, HDM, AWA, AWS, STW, if available for (int i = 0; i < numCfgValues; i++) { bvalue = pageData.values[i]; dataName[i] = xdrDelete(bvalue->getName()); @@ -254,26 +300,48 @@ public: setFlashLED(false); } + // Logging boat values + if (bvalue == NULL) + return; + LOG_DEBUG(GwLog::DEBUG, "PageWindPlot, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, cnt: %d, valid0: %d", dataName[0].c_str(), dataValue[0], + dataName[1].c_str(), dataValue[1], dataName[2].c_str(), dataValue[2], dataName[3].c_str(), dataValue[3], dataName[4].c_str(), dataValue[4], + dataName[5].c_str(), dataValue[5], dataName[6].c_str(), dataValue[6], dataName[7].c_str(), dataValue[7], dataName[8].c_str(), dataValue[8], count, dataValid[0]); + // Store TWD wind value in buffer - windValues.add(int((dataValue[0] * radToDeg) + 0.5)); // Store TWD value (degree) in buffer (rounded to integer) - count = windValues.getSize(); // Get number of valid elements in buffer; maximum is cHeight + int twdValue = 0; + if (dataValid[0]) { // TWD data existing + twdValue = int((dataValue[0] * radToDeg) + 0.5); // Read TWD value in degrees and round to integer + } else { + // Try to calculate TWD value from other data, if available + // dataValid[0] = windValues.calcTWD(&twdValue, dataValue[1], dataValue[2], dataValue[3], dataValue[4], dataValue[5], dataValue[6]); + } + if (dataValid[0]) { + windValues.add(twdValue); + count = windValues.getSize(); // Get number of valid elements in buffer; maximum is cHeight + LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: Data 0 valid - dataValue[0]: %f, TWD: %d, cnt: %d, valid0: %d", dataValue[0] * radToDeg, twdValue, count, dataValid[0]); + } // initialize chart range values if (wndCenter == -400) { wndCenter = windValues.get(0); wndCenter = int((wndCenter + (wndCenter >= 0 ? 5 : -5)) / 10) * 10; // Set new center value; round to nearest 10 degree value -// wndCenter = int((windValues.get(0) + 5) / 10) * 10; // Round to nearest 10 degree value diffRng = 30; chrtRng = 30; - LOG_DEBUG(GwLog::LOG, "PageWindPlot initialized. wndCenter: %d, chrtRng: %d ", wndCenter, chrtRng); + LOG_DEBUG(GwLog::DEBUG, "PageWindPlot initialized. wndCenter: %d, chrtRng: %d ", wndCenter, chrtRng); } else { diffRng = max(abs(((windValues.getMax() - wndCenter + 540) % 360) - 180), abs(((windValues.getMin() - wndCenter + 540) % 360) - 180)); // check necessary range size + + if (diffRng < -180) { + // wind value crosses 180 degree line, so we need to adjust the chart range + // ********************** hier an der Skalierung arbeiten ******************** + + } + if (diffRng > chrtRng) { chrtRng = int((diffRng + (diffRng >= 0 ? 9 : -1)) / 10) * 10; // Round up to next 10 degree value -// chrtRng = int(ceil(diffRng / 10.0) * 10); // Round to next 10 degree value } else if (diffRng + 10 < chrtRng) { + // Reduce chart range for higher resolution if possible chrtRng = max(30, int((diffRng + (diffRng >= 0 ? 9 : -1)) / 10) * 10); // Round up to next 10 degree value -// chrtRng = max(30, int(ceil(diffRng / 10.0) * 10)); // Round to next 10 degree value, but mimimum range is 30 degrees } } wndLeft = wndCenter - chrtRng; @@ -293,20 +361,21 @@ public: // Horizontal top line for orientation -> to be deleted // getdisplay().fillRect(0, 20, width, 1, commonData->fgcolor); + // Show TWS value on top right getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b); getdisplay().setCursor(252, 52); - getdisplay().print(dataSValue[1]); // Value + getdisplay().print(dataSValue[2]); // Value getdisplay().setFont(&Ubuntu_Bold12pt7b); getdisplay().setCursor(334, 38); - getdisplay().print(dataName[1]); // Name + getdisplay().print(dataName[2]); // Name getdisplay().setFont(&Ubuntu_Bold8pt7b); getdisplay().setCursor(330, 53); getdisplay().print(" "); if (holdvalues == false) { - getdisplay().print(dataUnit[1]); // Unit + getdisplay().print(dataUnit[2]); // Unit } else { - getdisplay().print(dataUnitOld[1]); // Unit + getdisplay().print(dataUnitOld[2]); // Unit } // chart lines @@ -336,42 +405,50 @@ public: // Draw wind values in chart //*********************************************************** - if (dataValid[0] || holdvalues || simulation == true) { + if ((dataValid[0] || holdvalues || simulation == true) && isTimeforUpd) { - lastX = xCenter + ((windValues.get(0) - wndCenter) * chrtScl); - lastY = yOffset + cHeight; // Reset lastY to bottom of chart + prevX = xCenter + ((windValues.get(0) - wndCenter) * chrtScl); + prevY = yOffset + cHeight; // Reset lastY to bottom of chart for (int i = 0; i < count; i++) { chrtVal = windValues.get(i); // Get value from buffer + if (abs(chrtVal - chrtPrevVal) > 180) { + // Large value jump, so probably crosses -180°/180° degree boundary } + break; // Stop drawing and adjust chart center at next page update + } chrtScl = xCenter / chrtRng; // current scale: pixels per degree x = xCenter + ((chrtVal - wndCenter) * chrtScl); // Scale to chart width // x = xCenter + ((((chrtVal - wndCenter + 540) % 360) - 180) * chrtScl); // Scale to chart width y = yOffset + cHeight - i; // Position in chart area + // LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: chrtVal: %d, wndCenter: %d, chrtScl: %d, x: %d, y: %d", chrtVal, wndCenter, chrtScl, x, y); + // Draw line with 2 pixels width; make sure vertical line are drawn correctly - getdisplay().drawLine(lastX, lastY, x, y, commonData->fgcolor); - getdisplay().drawLine(lastX, lastY - 1, (x != lastX) ? x : x-1, (x != lastX) ? y - 1 : y, commonData->fgcolor); - lastX = x; - lastY = y; - // LOG_DEBUG(GwLog::LOG, "PageWindPlot: loop-Counter: %d, X: %d, Y: %d, lastX: %d, lastY: %d", count, x, y, lastX, lastY); - LOG_DEBUG(GwLog::LOG, "PageWindPlot Shift: Min: %d, Max: %d, Mid: %d", windValues.getMin(), windValues.getMax(), windValues.getMid()); - if (i == (cHeight - 1)) { // Reaching chart area top end - windValues.mvStart(40); // virtually delete 40 values from buffer - LOG_DEBUG(GwLog::LOG, "PageWindPlot Shift: Min: %d, Max: %d, Mid: %d", windValues.getMin(), windValues.getMax(), windValues.getMid()); + getdisplay().drawLine(prevX, prevY, x, y, commonData->fgcolor); + getdisplay().drawLine(prevX, prevY - 1, (x != prevX) ? x : x - 1, (x != prevX) ? y - 1 : y, commonData->fgcolor); + chrtPrevVal = chrtVal; + prevX = x; + prevY = y; + if (i == (cHeight - 1)) { + // Reaching chart area top end + windValues.mvStart(40); + // virtually delete 40 values from buffer if ((windValues.getMin() > wndCenter) || (windValues.getMax() < wndCenter)) { int mid = windValues.getMid(); wndCenter = int((mid + (mid >= 0 ? 5 : -5)) / 10) * 10; // Set new center value; round to nearest 10 degree value - LOG_DEBUG(GwLog::LOG, "PageWindPlot Shift: Min: %d, Max: %d, new Center: %d", windValues.getMin(), windValues.getMax(), wndCenter); } - continue; // Will leave loop + LOG_DEBUG(GwLog::DEBUG, "PageWindPlot Shift: Min: %d, Max: %d, Mid: %d, new Center: %d", windValues.getMin(), windValues.getMax(), windValues.getMid(), wndCenter); + break; } } + LOG_DEBUG(GwLog::DEBUG, "PageWindPlot End: prevX: %d, prevY: %d, loop-Counter: %d", prevX, prevY, count); + } else { - getdisplay().setFont(&Ubuntu_Bold8pt7b); - getdisplay().setCursor(xCenter - 55, height / 2); + // No valid data available + LOG_DEBUG(GwLog::LOG, "PageWindPlot: No valid data available"); + getdisplay().setFont(&Ubuntu_Bold10pt7b); + getdisplay().setCursor(xCenter - 60, height / 2); getdisplay().print("No sensor data"); - return; } - LOG_DEBUG(GwLog::LOG, "PageWindPlot End: lastX: %d, lastY: %d, loop-Counter: %d", lastX, lastY, count); // Update display getdisplay().nextPage(); // Partial update (fast) @@ -393,7 +470,7 @@ PageDescription registerPageWindPlot( "WindPlot", // Page name createPage, // Action 0, // Number of bus values depends on selection in Web configuration - { "TWD", "TWS", "TWA", "AWA", "HDM" }, // Bus values we need in the page + { "TWD", "TWA", "TWS", "HDM", "AWA", "AWS", "STW", "COG", "SOG" }, // Bus values we need in the page true // Show display header on/off ); From fe095a971646b527371934ee74a0ddd90430387a Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Mon, 9 Jun 2025 17:58:57 +0200 Subject: [PATCH 09/26] Y Axis label; some interval bug fixing --- lib/obp60task/PageWindPlot.cpp | 132 +++++--- lib/obp60task/PageWindPlot.cpp.txt | 477 +++++++++++++++++++++++++++++ 2 files changed, 563 insertions(+), 46 deletions(-) create mode 100644 lib/obp60task/PageWindPlot.cpp.txt diff --git a/lib/obp60task/PageWindPlot.cpp b/lib/obp60task/PageWindPlot.cpp index e0b6cc0..3dba067 100644 --- a/lib/obp60task/PageWindPlot.cpp +++ b/lib/obp60task/PageWindPlot.cpp @@ -31,9 +31,9 @@ public: void add(int value) // Add a new value { - if (value > 180) { - value -= 360; // Normalize value to -180..180 to make min/max calculations working - } + // if (value > 180) { + // value -= 360; // Normalize value to -180..180 to make min/max calculations working + // } buffer[head] = value; head = (head + 1) % SIZE; if (count < SIZE) { @@ -44,7 +44,8 @@ public: } int get(int index) const - // Get value by index in [-180..180 deg] format (0 = oldest, count-1 = newest) + // Get value by index in [0..360 deg] format (0 = oldest, count-1 = newest) + // **** Get value by index in [-180..180 deg] format (0 = oldest, count-1 = newest) { int realIndex; @@ -124,6 +125,22 @@ public: return (getMin() + getMax()) / 2; } + int getRng(int center) const + // Get range of values in the buffer relative to a center value + { + if (count == 0) { + return -1; // Buffer is empty + } + int min = getMin(); + int max = getMax(); + int rng = std::max(abs((min - center + 540) % 360 - 180), abs((max - center + 540) % 360 - 180)); + // if (rng < -180) { + // wind value crosses 180 degree line, so we need to adjust the chart range + // ********************** hier an der Skalierung arbeiten ******************** + // } + return rng; + } + void mvStart(int start) // Move the start index of buffer forward by positions { @@ -155,7 +172,7 @@ public: class PageWindPlot : public Page { bool keylock = false; // Keylock -// int16_t lp = 80; // Pointer length + // int16_t lp = 80; // Pointer length char mode = 'D'; // Chart mode: 'D' for TWD, 'S' for TWS int updTime = 1; // Update interval for wind history chart: // (1)|(2)|(3)|(5) seconds for 3, 7, 10, 15 min. history chart @@ -227,6 +244,9 @@ public: String dataUnit[numCfgValues]; String dataSValueOld[numCfgValues]; String dataUnitOld[numCfgValues]; + bool wndDataValid = false; // Flag to indicate if wind data is valid + bool simulation = false; + bool holdValues = false; int width = getdisplay().width(); // Get screen width int height = getdisplay().height(); // Get screen height @@ -234,6 +254,7 @@ public: // int cHeight = 80; // height of chart area int xCenter = width / 2; // Center of screen in x direction static const int yOffset = 76; // Offset for y coordinates of chart area + // static bool plotShift = false; // Flag to indicate if chartplot data have been shifted static const float radToDeg = 180.0 / M_PI; // Conversion factor from radians to degrees static int wndCenter = -400; // chart wind center value position; init value indicates that wndCenter is not set yet @@ -241,7 +262,9 @@ public: 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 bool plotShift = false; // Flag to indicate if chartplot data have been shifted + static int simWnd = 0; // Simulation value for wind data + static float simTWS = 0; // Simulation value for TWS data + static const int simStep = 10; // Simulation step for wind data int x, y; // x and y coordinates for drawing static int prevX, prevY; // Last x and y coordinates for drawing @@ -273,8 +296,8 @@ public: // Get config data String lengthformat = config->getString(config->lengthFormat); - bool simulation = config->getBool(config->useSimuData); - bool holdvalues = config->getBool(config->holdvalues); + simulation = config->getBool(config->useSimuData); + holdValues = config->getBool(config->holdvalues); String flashLED = config->getString(config->flashLED); String backlightMode = config->getString(config->backlight); @@ -294,33 +317,45 @@ public: } } + // Store TWD wind value in buffer + int twdValue = 0; + if (dataValid[0]) { // TWD data existing + twdValue = int((dataValue[0] * radToDeg) + 0.5); // Read TWD value in degrees and round to integer + wndDataValid = true; + } else { + // Try to calculate TWD value from other data, if available + // wndDataValid = windValues.calcTWD(&twdValue, dataValue[1], dataValue[2], dataValue[3], dataValue[4], dataValue[5], dataValue[6]); + } + if (isTimeforUpd) { + if (wndDataValid) { + windValues.add(twdValue); + LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: Data 0 valid - dataValue[0]: %f, TWD: %d, cnt: %d, valid0: %d", dataValue[0] * radToDeg, twdValue, count, dataValid[0]); + } + + if (simulation) { + // Simulate data if simulation is enabled; use default simulation values for TWS + simWnd += random(simStep * -1, simStep); // random value between -simStep and +simStep + if (simWnd < 0) + simWnd += 360; + simWnd = simWnd % 360; + windValues.add(simWnd); + LOG_DEBUG(GwLog::DEBUG, "PageWindPlot simulation data: windValue: %d, windSpeed: %s", simWnd, dataSValue[2].c_str()); + } + } + count = windValues.getSize(); // Get number of valid elements in buffer; maximum is cHeight + // Optical warning by limit violation (unused) if (String(flashLED) == "Limit Violation") { setBlinkingLED(false); setFlashLED(false); } - // Logging boat values - if (bvalue == NULL) - return; - LOG_DEBUG(GwLog::DEBUG, "PageWindPlot, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, cnt: %d, valid0: %d", dataName[0].c_str(), dataValue[0], + // if (bvalue == NULL) + // return; + LOG_DEBUG(GwLog::LOG, "PageWindPlot, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, cnt: %d, valid0: %d", dataName[0].c_str(), dataValue[0], dataName[1].c_str(), dataValue[1], dataName[2].c_str(), dataValue[2], dataName[3].c_str(), dataValue[3], dataName[4].c_str(), dataValue[4], dataName[5].c_str(), dataValue[5], dataName[6].c_str(), dataValue[6], dataName[7].c_str(), dataValue[7], dataName[8].c_str(), dataValue[8], count, dataValid[0]); - // Store TWD wind value in buffer - int twdValue = 0; - if (dataValid[0]) { // TWD data existing - twdValue = int((dataValue[0] * radToDeg) + 0.5); // Read TWD value in degrees and round to integer - } else { - // Try to calculate TWD value from other data, if available - // dataValid[0] = windValues.calcTWD(&twdValue, dataValue[1], dataValue[2], dataValue[3], dataValue[4], dataValue[5], dataValue[6]); - } - if (dataValid[0]) { - windValues.add(twdValue); - count = windValues.getSize(); // Get number of valid elements in buffer; maximum is cHeight - LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: Data 0 valid - dataValue[0]: %f, TWD: %d, cnt: %d, valid0: %d", dataValue[0] * radToDeg, twdValue, count, dataValid[0]); - } - // initialize chart range values if (wndCenter == -400) { wndCenter = windValues.get(0); @@ -329,14 +364,8 @@ public: chrtRng = 30; LOG_DEBUG(GwLog::DEBUG, "PageWindPlot initialized. wndCenter: %d, chrtRng: %d ", wndCenter, chrtRng); } else { - diffRng = max(abs(((windValues.getMax() - wndCenter + 540) % 360) - 180), abs(((windValues.getMin() - wndCenter + 540) % 360) - 180)); // check necessary range size - - if (diffRng < -180) { - // wind value crosses 180 degree line, so we need to adjust the chart range - // ********************** hier an der Skalierung arbeiten ******************** - - } - + diffRng = windValues.getRng(wndCenter); + // diffRng = max(abs(((windValues.getMax() - wndCenter + 540) % 360) - 180), abs(((windValues.getMin() - wndCenter + 540) % 360) - 180)); // check necessary range size if (diffRng > chrtRng) { chrtRng = int((diffRng + (diffRng >= 0 ? 9 : -1)) / 10) * 10; // Round up to next 10 degree value } else if (diffRng + 10 < chrtRng) { @@ -372,7 +401,7 @@ public: getdisplay().setFont(&Ubuntu_Bold8pt7b); getdisplay().setCursor(330, 53); getdisplay().print(" "); - if (holdvalues == false) { + if (holdValues == false) { getdisplay().print(dataUnit[2]); // Unit } else { getdisplay().print(dataUnitOld[2]); // Unit @@ -383,7 +412,7 @@ public: getdisplay().fillRect(xCenter - 1, yOffset, 2, cHeight, commonData->fgcolor); // chart labels - char sWndLbl[4]; // Wind label + char sWndLbl[4]; // char buffer for Wind angle label getdisplay().setFont(&Ubuntu_Bold10pt7b); getdisplay().setCursor(xCenter - 80, yOffset - 3); getdisplay().print("TWD"); // Wind name @@ -405,20 +434,17 @@ public: // Draw wind values in chart //*********************************************************** - if ((dataValid[0] || holdvalues || simulation == true) && isTimeforUpd) { + if (wndDataValid || holdValues || simulation) { prevX = xCenter + ((windValues.get(0) - wndCenter) * chrtScl); prevY = yOffset + cHeight; // Reset lastY to bottom of chart for (int i = 0; i < count; i++) { chrtVal = windValues.get(i); // Get value from buffer - if (abs(chrtVal - chrtPrevVal) > 180) { - // Large value jump, so probably crosses -180°/180° degree boundary } - break; // Stop drawing and adjust chart center at next page update - } - chrtScl = xCenter / chrtRng; // current scale: pixels per degree - x = xCenter + ((chrtVal - wndCenter) * chrtScl); // Scale to chart width - // x = xCenter + ((((chrtVal - wndCenter + 540) % 360) - 180) * chrtScl); // Scale to chart width + // chrtScl = xCenter / chrtRng; // current scale: pixels per degree + chrtScl = width / chrtRng / 2; // current scale: pixels per degree + // x = xCenter + ((chrtVal - wndCenter) * chrtScl); // Scale to chart width + x = ((chrtVal - wndLeft + 360) % 360) * chrtScl; // Scale to chart width y = yOffset + cHeight - i; // Position in chart area // LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: chrtVal: %d, wndCenter: %d, chrtScl: %d, x: %d, y: %d", chrtVal, wndCenter, chrtScl, x, y); @@ -433,6 +459,7 @@ public: windValues.mvStart(40); // virtually delete 40 values from buffer if ((windValues.getMin() > wndCenter) || (windValues.getMax() < wndCenter)) { + // Check if all wind value are left or right of center value -> optimize chart range int mid = windValues.getMid(); wndCenter = int((mid + (mid >= 0 ? 5 : -5)) / 10) * 10; // Set new center value; round to nearest 10 degree value } @@ -442,14 +469,27 @@ public: } LOG_DEBUG(GwLog::DEBUG, "PageWindPlot End: prevX: %d, prevY: %d, loop-Counter: %d", prevX, prevY, count); - } else { + } else if (!wndDataValid) { // No valid data available LOG_DEBUG(GwLog::LOG, "PageWindPlot: No valid data available"); getdisplay().setFont(&Ubuntu_Bold10pt7b); - getdisplay().setCursor(xCenter - 60, height / 2); + getdisplay().setCursor(xCenter - 54, height / 2); getdisplay().print("No sensor data"); } + // chart Y axis labels + char sWndYAx[3]; // char buffer for wind Y axis labels + int yPos; // Y position for label + getdisplay().setFont(&Ubuntu_Bold8pt7b); + for (int i = 3; i > 0; i--) { + yPos = yOffset + cHeight - (i * 60); // Y position for label + getdisplay().fillRect(0, yPos - 7, 28, 15, commonData->bgcolor); // Clear small area to remove potential chart lines + getdisplay().fillRect(0, yPos, 8, 2, commonData->fgcolor); + getdisplay().setCursor(9, yPos + 5); + snprintf(sWndYAx, 4, "%2d", i * updTime); + getdisplay().print(sWndYAx); // Wind value label + } + // Update display getdisplay().nextPage(); // Partial update (fast) }; diff --git a/lib/obp60task/PageWindPlot.cpp.txt b/lib/obp60task/PageWindPlot.cpp.txt new file mode 100644 index 0000000..e0b6cc0 --- /dev/null +++ b/lib/obp60task/PageWindPlot.cpp.txt @@ -0,0 +1,477 @@ +#if defined BOARD_OBP60S3 || defined BOARD_OBP40S3 + +#include "BoatDataCalibration.h" +#include "OBP60Extensions.h" +#include "Pagedata.h" + +#include + +// **************************************************************** +class wndHistory { + // provides a FiFo circular buffer to store wind history values +private: + int SIZE; + std::vector buffer; + int first = 0; // points to the first valid element + int head = 0; // points to the next insertion index + int count = 0; // number of valid elements + +public: + bool begin(int size) + // specifies buffer size + { + if (size <= 0 || size > 1000) { + return false; + } + SIZE = size; + buffer.resize(size); // allocate buffer + return true; + } + + void add(int value) + // Add a new value + { + if (value > 180) { + value -= 360; // Normalize value to -180..180 to make min/max calculations working + } + buffer[head] = value; + head = (head + 1) % SIZE; + if (count < SIZE) { + count++; + } else { + first = head - 1; // When buffer is full, first points to the oldest value + } + } + + int get(int index) const + // Get value by index in [-180..180 deg] format (0 = oldest, count-1 = newest) + { + int realIndex; + + if (index < 0 || index >= count) { + return -1; // Invalid index + } + realIndex = (first + index) % SIZE; + return buffer[realIndex]; + // } + } + + int get(int index, int deg) const + // Get value by index in [-180..180 deg] or [0..360 deg] format (0 = oldest, count-1 = newest) + { + switch (deg) { + case 180: + // Return value in [-180..180 deg] format + return get(index); + case 360: { + // Return value in [0..360 deg] format + int value = get(index); + if (value < 0) { + value += 360; + }; + return value; + } + default: + // Return value in [-180..180 deg] format + return -1; + } + } + + int getSize() const + // Get number of valid elements + { + return count; + } + + int getMin() const + // Get minimum value in the buffer + { + if (count == 0) { + return -1; // Buffer is empty + } else if (first + count <= SIZE) { + // No wrap-around + return *std::min_element(buffer.begin() + first, buffer.begin() + first + count); + } else { + // Wrap-around: check [first, end) and [begin, (first+count)%SIZE) + int min1 = *std::min_element(buffer.begin() + first, buffer.end()); + int min2 = *std::min_element(buffer.begin(), buffer.begin() + ((first + count) % SIZE)); + return std::min(min1, min2); + } + } + + int getMax() const + // Get maximum value in the buffer + { + if (count == 0) { + return -1; // Buffer is empty + } else if (first + count <= SIZE) { + // No wrap-around + return *std::max_element(buffer.begin() + first, buffer.begin() + first + count); + } else { + // Wrap-around: check [first, end) and [begin, (first+count)%SIZE) + int max1 = *std::max_element(buffer.begin() + first, buffer.end()); + int max2 = *std::max_element(buffer.begin(), buffer.begin() + ((first + count) % SIZE)); + return std::max(max1, max2); + } + } + + int getMid() const + // Get middle value in the buffer + { + if (count == 0) { + return -1; // Buffer is empty + } + return (getMin() + getMax()) / 2; + } + + void mvStart(int start) + // Move the start index of buffer forward by positions + { + first = (first + start) % SIZE; + if (count > start) + count -= start; + else + count = 0; + } + + // TWA, TWS, HDM, AWA, AWS, STW + bool calcTWD(int* twd, float twa, float tws, float hdm, float awa, float aws, float stw) + // Calculate TWD based on other boat data values + { + if (count == 0) { + return false; // Buffer is empty + } + + int twdMin = getMin(); + int twdMax = getMax(); + int twdMid = getMid(); + + // Calculate TWD based on TWA and HDM + return true; + } +}; + +// **************************************************************** +class PageWindPlot : public Page { + + bool keylock = false; // Keylock +// int16_t lp = 80; // Pointer length + char mode = 'D'; // Chart mode: 'D' for TWD, 'S' for TWS + int updTime = 1; // Update interval for wind history chart: + // (1)|(2)|(3)|(5) seconds for 3, 7, 10, 15 min. history chart + +public: + PageWindPlot(CommonData& common) + { + commonData = &common; + common.logger->logDebug(GwLog::LOG, "Instantiate PageWindPlot"); + } + + virtual void setupKeys() + { + Page::setupKeys(); + commonData->keydata[0].label = "MODE"; + commonData->keydata[1].label = "INTV"; + } + + // Key functions + virtual int handleKey(int key) + { + // Set chart mode TWD | TWS + if (key == 1) { + if (mode == 'D') { + mode = 'S'; + } else { + mode = 'D'; + } + // setupKeys(); // Update key labels + return 0; // Commit the key + } + + // Set interval for wind history chart update time + if (key == 2) { + if (updTime == 1) { + updTime = 2; + } else if (updTime == 2) { + updTime = 3; + } else if (updTime == 3) { + updTime = 5; + } else { + updTime = 1; + } + setupKeys(); // Update key labels + return 0; // Commit the key + } + + // Keylock function + if (key == 11) { // Code for keylock + commonData->keylock = !commonData->keylock; + return 0; // Commit the key + } + return key; + } + + virtual void displayPage(PageData& pageData) + { + GwConfigHandler* config = commonData->config; + GwLog* logger = commonData->logger; + + static wndHistory windValues; // Circular buffer to store wind values + + GwApi::BoatValue* bvalue; + const int numCfgValues = 9; + String dataName[numCfgValues]; + double dataValue[numCfgValues]; + bool dataValid[numCfgValues]; + String dataSValue[numCfgValues]; + String dataUnit[numCfgValues]; + String dataSValueOld[numCfgValues]; + String dataUnitOld[numCfgValues]; + + int width = getdisplay().width(); // Get screen width + int height = getdisplay().height(); // Get screen height + int cHeight = height - 98; // height of chart area + // int cHeight = 80; // height of chart area + int xCenter = width / 2; // Center of screen in x direction + static const int yOffset = 76; // Offset for y coordinates of chart area + static const float radToDeg = 180.0 / M_PI; // Conversion factor from radians to degrees + + static int wndCenter = -400; // chart wind center value position; init value indicates that wndCenter is not set yet + 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 bool plotShift = false; // Flag to indicate if chartplot data have been shifted + + int x, y; // x and y coordinates for drawing + static int prevX, prevY; // Last x and y coordinates for drawing + static int chrtScl; // Scale for wind values in pixels per degree + int chrtVal; // Current wind value + static int chrtPrevVal; // Last wind value in chart area for check if value crosses 180 degree line + int count; // index for next wind value in buffer + + static int updCnt = 0; // update counter for wind history chart in seconds + bool isTimeforUpd = true; // Flag to indicate if it is time for chart update + + if (windValues.getSize() == 0) { + if (!windValues.begin(cHeight)) { + logger->logDebug(GwLog::ERROR, "Failed to initialize wind values buffer"); + return; + } + } + + LOG_DEBUG(GwLog::LOG, "Display page WindPlot"); + + if (updCnt < updTime) { + // Next update interval not reached yet + updCnt++; + isTimeforUpd = false; + } else { + isTimeforUpd = true; + updCnt = 1; // Data update is now; reset counter + } + + // 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); + + // Read boatdata values for TWD, TWA, TWS, HDM, AWA, AWS, STW, if available + for (int i = 0; i < numCfgValues; i++) { + bvalue = pageData.values[i]; + dataName[i] = xdrDelete(bvalue->getName()); + dataName[i] = dataName[i].substring(0, 6); // String length limit for value name + dataValue[i] = bvalue->value; // Value as double in SI unit + calibrationData.calibrateInstance(dataName[i], bvalue, logger); // Check if boat data value is to be calibrated + dataValid[i] = bvalue->valid; + dataSValue[i] = formatValue(bvalue, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places + dataUnit[i] = formatValue(bvalue, *commonData).unit; + if (dataValid[i]) { + dataSValueOld[i] = dataSValue[i]; // Save old value + dataUnitOld[i] = dataUnit[i]; // Save old unit + } + } + + // Optical warning by limit violation (unused) + if (String(flashLED) == "Limit Violation") { + setBlinkingLED(false); + setFlashLED(false); + } + + // Logging boat values + if (bvalue == NULL) + return; + LOG_DEBUG(GwLog::DEBUG, "PageWindPlot, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, cnt: %d, valid0: %d", dataName[0].c_str(), dataValue[0], + dataName[1].c_str(), dataValue[1], dataName[2].c_str(), dataValue[2], dataName[3].c_str(), dataValue[3], dataName[4].c_str(), dataValue[4], + dataName[5].c_str(), dataValue[5], dataName[6].c_str(), dataValue[6], dataName[7].c_str(), dataValue[7], dataName[8].c_str(), dataValue[8], count, dataValid[0]); + + // Store TWD wind value in buffer + int twdValue = 0; + if (dataValid[0]) { // TWD data existing + twdValue = int((dataValue[0] * radToDeg) + 0.5); // Read TWD value in degrees and round to integer + } else { + // Try to calculate TWD value from other data, if available + // dataValid[0] = windValues.calcTWD(&twdValue, dataValue[1], dataValue[2], dataValue[3], dataValue[4], dataValue[5], dataValue[6]); + } + if (dataValid[0]) { + windValues.add(twdValue); + count = windValues.getSize(); // Get number of valid elements in buffer; maximum is cHeight + LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: Data 0 valid - dataValue[0]: %f, TWD: %d, cnt: %d, valid0: %d", dataValue[0] * radToDeg, twdValue, count, dataValid[0]); + } + + // initialize chart range values + if (wndCenter == -400) { + wndCenter = windValues.get(0); + wndCenter = int((wndCenter + (wndCenter >= 0 ? 5 : -5)) / 10) * 10; // Set new center value; round to nearest 10 degree value + diffRng = 30; + chrtRng = 30; + LOG_DEBUG(GwLog::DEBUG, "PageWindPlot initialized. wndCenter: %d, chrtRng: %d ", wndCenter, chrtRng); + } else { + diffRng = max(abs(((windValues.getMax() - wndCenter + 540) % 360) - 180), abs(((windValues.getMin() - wndCenter + 540) % 360) - 180)); // check necessary range size + + if (diffRng < -180) { + // wind value crosses 180 degree line, so we need to adjust the chart range + // ********************** hier an der Skalierung arbeiten ******************** + + } + + 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(30, int((diffRng + (diffRng >= 0 ? 9 : -1)) / 10) * 10); // Round up to next 10 degree value + } + } + wndLeft = wndCenter - chrtRng; + if (wndLeft < -180) + wndLeft += 360; + wndRight = wndCenter + chrtRng; + if (wndRight >= 180) + wndRight -= 360; + LOG_DEBUG(GwLog::LOG, "PageWindPlot dataValue[0]: %f, windValue: %d, count: %d, diffRng: %d, chartRng: %d", float(dataValue[0] * radToDeg), windValues.get(count - 1), count, diffRng, chrtRng); + + // Draw page + //*********************************************************** + + // Set display in partial refresh mode + getdisplay().setPartialWindow(0, 0, width, height); // Set partial update + getdisplay().setTextColor(commonData->fgcolor); + + // Horizontal top line for orientation -> to be deleted + // getdisplay().fillRect(0, 20, width, 1, commonData->fgcolor); + + // Show TWS value on top right + getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b); + getdisplay().setCursor(252, 52); + getdisplay().print(dataSValue[2]); // Value + getdisplay().setFont(&Ubuntu_Bold12pt7b); + getdisplay().setCursor(334, 38); + getdisplay().print(dataName[2]); // Name + getdisplay().setFont(&Ubuntu_Bold8pt7b); + getdisplay().setCursor(330, 53); + getdisplay().print(" "); + if (holdvalues == false) { + getdisplay().print(dataUnit[2]); // Unit + } else { + getdisplay().print(dataUnitOld[2]); // Unit + } + + // chart lines + getdisplay().fillRect(0, yOffset, width, 2, commonData->fgcolor); + getdisplay().fillRect(xCenter - 1, yOffset, 2, cHeight, commonData->fgcolor); + + // chart labels + char sWndLbl[4]; // Wind label + getdisplay().setFont(&Ubuntu_Bold10pt7b); + getdisplay().setCursor(xCenter - 80, yOffset - 3); + getdisplay().print("TWD"); // Wind name + getdisplay().setCursor(xCenter - 16, yOffset - 3); + snprintf(sWndLbl, 4, "%03d", (wndCenter < 0) ? (wndCenter + 360) : wndCenter); + getdisplay().print(sWndLbl); // Wind center value + getdisplay().drawCircle(xCenter + 21, 63, 2, commonData->fgcolor); // symbol + getdisplay().drawCircle(xCenter + 21, 63, 3, commonData->fgcolor); // symbol + getdisplay().setCursor(2, yOffset - 3); + snprintf(sWndLbl, 4, "%03d", (wndLeft < 0) ? (wndLeft + 360) : wndLeft); + getdisplay().print(sWndLbl); // Wind left value + getdisplay().drawCircle(39, 63, 2, commonData->fgcolor); // symbol + getdisplay().drawCircle(39, 63, 3, commonData->fgcolor); // symbol + getdisplay().setCursor(width - 43, yOffset - 3); + snprintf(sWndLbl, 4, "%03d", (wndRight < 0) ? (wndRight + 360) : wndRight); + getdisplay().print(sWndLbl); // Wind right value + getdisplay().drawCircle(width - 6, 63, 2, commonData->fgcolor); // symbol + getdisplay().drawCircle(width - 6, 63, 3, commonData->fgcolor); // symbol + + // Draw wind values in chart + //*********************************************************** + if ((dataValid[0] || holdvalues || simulation == true) && isTimeforUpd) { + + prevX = xCenter + ((windValues.get(0) - wndCenter) * chrtScl); + prevY = yOffset + cHeight; // Reset lastY to bottom of chart + + for (int i = 0; i < count; i++) { + chrtVal = windValues.get(i); // Get value from buffer + if (abs(chrtVal - chrtPrevVal) > 180) { + // Large value jump, so probably crosses -180°/180° degree boundary } + break; // Stop drawing and adjust chart center at next page update + } + chrtScl = xCenter / chrtRng; // current scale: pixels per degree + x = xCenter + ((chrtVal - wndCenter) * chrtScl); // Scale to chart width + // x = xCenter + ((((chrtVal - wndCenter + 540) % 360) - 180) * chrtScl); // Scale to chart width + y = yOffset + cHeight - i; // Position in chart area + // LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: chrtVal: %d, wndCenter: %d, chrtScl: %d, x: %d, y: %d", chrtVal, wndCenter, chrtScl, x, y); + + // Draw line with 2 pixels width; make sure vertical line are drawn correctly + getdisplay().drawLine(prevX, prevY, x, y, commonData->fgcolor); + getdisplay().drawLine(prevX, prevY - 1, (x != prevX) ? x : x - 1, (x != prevX) ? y - 1 : y, commonData->fgcolor); + chrtPrevVal = chrtVal; + prevX = x; + prevY = y; + if (i == (cHeight - 1)) { + // Reaching chart area top end + windValues.mvStart(40); + // virtually delete 40 values from buffer + if ((windValues.getMin() > wndCenter) || (windValues.getMax() < wndCenter)) { + int mid = windValues.getMid(); + wndCenter = int((mid + (mid >= 0 ? 5 : -5)) / 10) * 10; // Set new center value; round to nearest 10 degree value + } + LOG_DEBUG(GwLog::DEBUG, "PageWindPlot Shift: Min: %d, Max: %d, Mid: %d, new Center: %d", windValues.getMin(), windValues.getMax(), windValues.getMid(), wndCenter); + break; + } + } + LOG_DEBUG(GwLog::DEBUG, "PageWindPlot End: prevX: %d, prevY: %d, loop-Counter: %d", prevX, prevY, count); + + } else { + // No valid data available + LOG_DEBUG(GwLog::LOG, "PageWindPlot: No valid data available"); + getdisplay().setFont(&Ubuntu_Bold10pt7b); + getdisplay().setCursor(xCenter - 60, height / 2); + getdisplay().print("No sensor data"); + } + + // Update display + getdisplay().nextPage(); // Partial update (fast) + }; +}; + +static Page* createPage(CommonData& common) +{ + return new PageWindPlot(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 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", "TWA", "TWS", "HDM", "AWA", "AWS", "STW", "COG", "SOG" }, // Bus values we need in the page + true // Show display header on/off +); + +#endif From f0aba8930168e66296e80a1f98980b5e2b45921f Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Mon, 9 Jun 2025 22:32:34 +0200 Subject: [PATCH 10/26] Simulation data; ext. chart area; flexible TWS position --- lib/obp60task/PageWindPlot.cpp | 76 +++++++++++++++++++--------------- 1 file changed, 43 insertions(+), 33 deletions(-) diff --git a/lib/obp60task/PageWindPlot.cpp b/lib/obp60task/PageWindPlot.cpp index 3dba067..b2e2ab6 100644 --- a/lib/obp60task/PageWindPlot.cpp +++ b/lib/obp60task/PageWindPlot.cpp @@ -250,10 +250,10 @@ public: int width = getdisplay().width(); // Get screen width int height = getdisplay().height(); // Get screen height - int cHeight = height - 98; // height of chart area + static const int yOffset = 48; // Offset for y coordinates of chart area 76 + int cHeight = height - yOffset - 22; // height of chart area 98 // int cHeight = 80; // height of chart area int xCenter = width / 2; // Center of screen in x direction - static const int yOffset = 76; // Offset for y coordinates of chart area // static bool plotShift = false; // Flag to indicate if chartplot data have been shifted static const float radToDeg = 180.0 / M_PI; // Conversion factor from radians to degrees @@ -326,6 +326,9 @@ public: // Try to calculate TWD value from other data, if available // wndDataValid = windValues.calcTWD(&twdValue, dataValue[1], dataValue[2], dataValue[3], dataValue[4], dataValue[5], dataValue[6]); } + + +// ************* falsche Position **************** if (isTimeforUpd) { if (wndDataValid) { windValues.add(twdValue); @@ -391,46 +394,30 @@ public: // Horizontal top line for orientation -> to be deleted // getdisplay().fillRect(0, 20, width, 1, commonData->fgcolor); - // Show TWS value on top right - getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b); - getdisplay().setCursor(252, 52); - getdisplay().print(dataSValue[2]); // Value - getdisplay().setFont(&Ubuntu_Bold12pt7b); - getdisplay().setCursor(334, 38); - getdisplay().print(dataName[2]); // Name - getdisplay().setFont(&Ubuntu_Bold8pt7b); - getdisplay().setCursor(330, 53); - getdisplay().print(" "); - if (holdValues == false) { - getdisplay().print(dataUnit[2]); // Unit - } else { - getdisplay().print(dataUnitOld[2]); // Unit - } - // chart lines getdisplay().fillRect(0, yOffset, width, 2, commonData->fgcolor); getdisplay().fillRect(xCenter - 1, yOffset, 2, cHeight, commonData->fgcolor); // chart labels char sWndLbl[4]; // char buffer for Wind angle label - getdisplay().setFont(&Ubuntu_Bold10pt7b); - getdisplay().setCursor(xCenter - 80, yOffset - 3); + getdisplay().setFont(&Ubuntu_Bold12pt7b); + getdisplay().setCursor(xCenter - 88, yOffset - 3); getdisplay().print("TWD"); // Wind name - getdisplay().setCursor(xCenter - 16, yOffset - 3); + getdisplay().setCursor(xCenter - 20, yOffset - 3); snprintf(sWndLbl, 4, "%03d", (wndCenter < 0) ? (wndCenter + 360) : wndCenter); getdisplay().print(sWndLbl); // Wind center value - getdisplay().drawCircle(xCenter + 21, 63, 2, commonData->fgcolor); // symbol - getdisplay().drawCircle(xCenter + 21, 63, 3, commonData->fgcolor); // symbol + getdisplay().drawCircle(xCenter + 25, yOffset - 16, 2, commonData->fgcolor); // symbol 63 + getdisplay().drawCircle(xCenter + 25, yOffset - 16, 3, commonData->fgcolor); // symbol getdisplay().setCursor(2, yOffset - 3); snprintf(sWndLbl, 4, "%03d", (wndLeft < 0) ? (wndLeft + 360) : wndLeft); getdisplay().print(sWndLbl); // Wind left value - getdisplay().drawCircle(39, 63, 2, commonData->fgcolor); // symbol - getdisplay().drawCircle(39, 63, 3, commonData->fgcolor); // symbol - getdisplay().setCursor(width - 43, yOffset - 3); + getdisplay().drawCircle(47, yOffset - 16, 2, commonData->fgcolor); // symbol + getdisplay().drawCircle(47, yOffset - 16, 3, commonData->fgcolor); // symbol + getdisplay().setCursor(width - 51, yOffset - 3); snprintf(sWndLbl, 4, "%03d", (wndRight < 0) ? (wndRight + 360) : wndRight); getdisplay().print(sWndLbl); // Wind right value - getdisplay().drawCircle(width - 6, 63, 2, commonData->fgcolor); // symbol - getdisplay().drawCircle(width - 6, 63, 3, commonData->fgcolor); // symbol + getdisplay().drawCircle(width - 5, yOffset - 16, 2, commonData->fgcolor); // symbol + getdisplay().drawCircle(width - 5, yOffset - 16, 3, commonData->fgcolor); // symbol // Draw wind values in chart //*********************************************************** @@ -473,17 +460,40 @@ public: // No valid data available LOG_DEBUG(GwLog::LOG, "PageWindPlot: No valid data available"); getdisplay().setFont(&Ubuntu_Bold10pt7b); - getdisplay().setCursor(xCenter - 54, height / 2); + getdisplay().setCursor(xCenter - 66, height / 2); getdisplay().print("No sensor data"); } + // Print TWS value + int yPosTws = yOffset + 40; // Y position for TWS value + int xPosTws = width - 145; // X position for TWS value + if ((prevY < yPosTws) && prevX > xPosTws) { + // If chart line enters TWS value area, move TWS value to the left side + xPosTws = 20 ; + } + getdisplay().fillRect(xPosTws - 3, yPosTws - 35, 138, 40, commonData->bgcolor); // Clear area for TWS value + getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b); + getdisplay().setCursor(xPosTws, yPosTws); // 252, 52 + getdisplay().print(dataSValue[2]); // Value + getdisplay().setFont(&Ubuntu_Bold12pt7b); + getdisplay().setCursor(xPosTws + 82, yPosTws - 14); // 334, 38 + getdisplay().print(dataName[2]); // Name + getdisplay().setFont(&Ubuntu_Bold8pt7b); + getdisplay().setCursor(xPosTws + 78, yPosTws + 1); // 330, 53 + getdisplay().print(" "); + if (holdValues == false) { + getdisplay().print(dataUnit[2]); // Unit + } else { + getdisplay().print(dataUnitOld[2]); // Unit + } + // chart Y axis labels - char sWndYAx[3]; // char buffer for wind Y axis labels + char sWndYAx[4]; // char buffer for wind Y axis labels int yPos; // Y position for label getdisplay().setFont(&Ubuntu_Bold8pt7b); - for (int i = 3; i > 0; i--) { - yPos = yOffset + cHeight - (i * 60); // Y position for label - getdisplay().fillRect(0, yPos - 7, 28, 15, commonData->bgcolor); // Clear small area to remove potential chart lines + for (int i = 4; i > 0; i--) { + yPos = yOffset + cHeight - (i * 59) + 14; // Y position for label + getdisplay().fillRect(0, yPos - 6, 28, 15, commonData->bgcolor); // Clear small area to remove potential chart lines getdisplay().fillRect(0, yPos, 8, 2, commonData->fgcolor); getdisplay().setCursor(9, yPos + 5); snprintf(sWndYAx, 4, "%2d", i * updTime); From 9b504469bcf6a3a5e22282582fd6a9a68e250f58 Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Thu, 12 Jun 2025 23:41:15 +0200 Subject: [PATCH 11/26] Fixes for TWS flip, scale calculation, chart range overflow; add axis lines --- lib/obp60task/PageWindPlot.cpp | 169 ++++++++++++++++++--------------- 1 file changed, 94 insertions(+), 75 deletions(-) diff --git a/lib/obp60task/PageWindPlot.cpp b/lib/obp60task/PageWindPlot.cpp index b2e2ab6..d08bd3a 100644 --- a/lib/obp60task/PageWindPlot.cpp +++ b/lib/obp60task/PageWindPlot.cpp @@ -24,16 +24,13 @@ public: return false; } SIZE = size; - buffer.resize(size); // allocate buffer + buffer.resize(size, INT_MIN); // allocate buffer return true; } void add(int value) - // Add a new value + // Add a new value; store in [0..360 deg] format { - // if (value > 180) { - // value -= 360; // Normalize value to -180..180 to make min/max calculations working - // } buffer[head] = value; head = (head + 1) % SIZE; if (count < SIZE) { @@ -45,7 +42,6 @@ public: int get(int index) const // Get value by index in [0..360 deg] format (0 = oldest, count-1 = newest) - // **** Get value by index in [-180..180 deg] format (0 = oldest, count-1 = newest) { int realIndex; @@ -54,7 +50,6 @@ public: } realIndex = (first + index) % SIZE; return buffer[realIndex]; - // } } int get(int index, int deg) const @@ -125,19 +120,20 @@ public: return (getMin() + getMax()) / 2; } - int getRng(int center) const + int getRng(int center) // Get range of values in the buffer relative to a center value { if (count == 0) { return -1; // Buffer is empty } + int min = getMin(); int max = getMax(); - int rng = std::max(abs((min - center + 540) % 360 - 180), abs((max - center + 540) % 360 - 180)); - // if (rng < -180) { - // wind value crosses 180 degree line, so we need to adjust the chart range - // ********************** hier an der Skalierung arbeiten ******************** - // } + // int rng = std::max(abs((min - center + 540) % 360 - 180), abs((max - center + 540) % 360 - 180)); + int rng = std::max(abs(min - center), abs(max - center)); + if (rng > 180) { // should never happen, but just in case + rng = 180; + } return rng; } @@ -176,6 +172,7 @@ class PageWindPlot : public Page { char mode = 'D'; // Chart mode: 'D' for TWD, 'S' for TWS int updTime = 1; // Update interval for wind history chart: // (1)|(2)|(3)|(5) seconds for 3, 7, 10, 15 min. history chart + bool showTWS = true; // Show TWS value in chart area public: PageWindPlot(CommonData& common) @@ -189,6 +186,7 @@ public: Page::setupKeys(); commonData->keydata[0].label = "MODE"; commonData->keydata[1].label = "INTV"; + commonData->keydata[4].label = "TWS"; } // Key functions @@ -216,7 +214,12 @@ public: } else { updTime = 1; } - setupKeys(); // Update key labels + return 0; // Commit the key + } + + // Switch TWS on/off + if (key == 5) { + showTWS = !showTWS; return 0; // Commit the key } @@ -250,9 +253,9 @@ public: int width = getdisplay().width(); // Get screen width int height = getdisplay().height(); // Get screen height - static const int yOffset = 48; // Offset for y coordinates of chart area 76 - int cHeight = height - yOffset - 22; // height of chart area 98 - // int cHeight = 80; // height of chart area + static const int yOffset = 48; // Offset for y coordinates of chart area + int cHeight = height - yOffset - 22; // height of chart area + // int cHeight = 60; // height of chart area int xCenter = width / 2; // Center of screen in x direction // static bool plotShift = false; // Flag to indicate if chartplot data have been shifted static const float radToDeg = 180.0 / M_PI; // Conversion factor from radians to degrees @@ -262,19 +265,23 @@ public: 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 + bool rngFlipped = false; // Flag to indicate if range exceeds 180 degrees static int simWnd = 0; // Simulation value for wind data static float simTWS = 0; // Simulation value for TWS data static const int simStep = 10; // Simulation step for wind data int x, y; // x and y coordinates for drawing static int prevX, prevY; // Last x and y coordinates for drawing - static int chrtScl; // Scale for wind values in pixels per degree + static float chrtScl; // Scale for wind values in pixels per degree int chrtVal; // Current wind value static int chrtPrevVal; // Last wind value in chart area for check if value crosses 180 degree line int count; // index for next wind value in buffer static int updCnt = 0; // update counter for wind history chart in seconds bool isTimeforUpd = true; // Flag to indicate if it is time for chart update + bool TwsFlipped = false; // Flag to indicate if TWS value flipped + + LOG_DEBUG(GwLog::LOG, "Display page WindPlot"); if (windValues.getSize() == 0) { if (!windValues.begin(cHeight)) { @@ -283,8 +290,6 @@ public: } } - LOG_DEBUG(GwLog::LOG, "Display page WindPlot"); - if (updCnt < updTime) { // Next update interval not reached yet updCnt++; @@ -327,12 +332,10 @@ public: // wndDataValid = windValues.calcTWD(&twdValue, dataValue[1], dataValue[2], dataValue[3], dataValue[4], dataValue[5], dataValue[6]); } - -// ************* falsche Position **************** + // ************* falsche Position **************** if (isTimeforUpd) { if (wndDataValid) { windValues.add(twdValue); - LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: Data 0 valid - dataValue[0]: %f, TWD: %d, cnt: %d, valid0: %d", dataValue[0] * radToDeg, twdValue, count, dataValid[0]); } if (simulation) { @@ -346,6 +349,7 @@ public: } } count = windValues.getSize(); // Get number of valid elements in buffer; maximum is cHeight + LOG_DEBUG(GwLog::ERROR, "PageWindPlot: Data 0 valid - dataValue[0]: %f, TWD: %d, cnt: %d, valid0: %d", dataValue[0] * radToDeg, twdValue, count, dataValid[0]); // Optical warning by limit violation (unused) if (String(flashLED) == "Limit Violation") { @@ -361,28 +365,31 @@ public: // initialize chart range values if (wndCenter == -400) { - wndCenter = windValues.get(0); + wndCenter = (windValues.get(0) < 0 ? 0 : windValues.get(0)); wndCenter = int((wndCenter + (wndCenter >= 0 ? 5 : -5)) / 10) * 10; // Set new center value; round to nearest 10 degree value diffRng = 30; chrtRng = 30; LOG_DEBUG(GwLog::DEBUG, "PageWindPlot initialized. wndCenter: %d, chrtRng: %d ", wndCenter, chrtRng); + } else { diffRng = windValues.getRng(wndCenter); - // diffRng = max(abs(((windValues.getMax() - wndCenter + 540) % 360) - 180), abs(((windValues.getMin() - wndCenter + 540) % 360) - 180)); // check necessary range size + diffRng = (diffRng < 0 ? 0 : diffRng); // If no data in buffer, set range to 0 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(30, 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(30, int((diffRng + (diffRng >= 0 ? 9 : -1)) / 10) * 10); } + LOG_DEBUG(GwLog::ERROR, "PageWindPlot range adjusted. wndCenter: %d, chrtRng: %d ", wndCenter, chrtRng); } + chrtScl = float(width) / float(chrtRng) / 2.0; // chart scale: pixels per degree wndLeft = wndCenter - chrtRng; - if (wndLeft < -180) + if (wndLeft < 0) wndLeft += 360; wndRight = wndCenter + chrtRng; - if (wndRight >= 180) + if (wndRight >= 360) wndRight -= 360; - LOG_DEBUG(GwLog::LOG, "PageWindPlot dataValue[0]: %f, windValue: %d, count: %d, diffRng: %d, chartRng: %d", float(dataValue[0] * radToDeg), windValues.get(count - 1), count, diffRng, chrtRng); + LOG_DEBUG(GwLog::ERROR, "PageWindPlot dataValue[0]: %f, windValue: %d, count: %d, diffRng: %d, chartRng: %d, Center: %d, scale: %f", double(dataValue[0] * radToDeg), + (!windValues.get(count - 1) < 0 ? 0 : windValues.get(count - 1)), count, diffRng, chrtRng, wndCenter, chrtScl); // Draw page //*********************************************************** @@ -422,80 +429,92 @@ public: // Draw wind values in chart //*********************************************************** if (wndDataValid || holdValues || simulation) { - - prevX = xCenter + ((windValues.get(0) - wndCenter) * chrtScl); - prevY = yOffset + cHeight; // Reset lastY to bottom of chart + LOG_DEBUG(GwLog::ERROR, "PageWindPlot Draw: prevX: %d, chrtPrevVal: %d, wndLeft: %d, chrtScl: %f, count: %d", prevX, chrtPrevVal, wndLeft, chrtScl, count); for (int i = 0; i < count; i++) { - chrtVal = windValues.get(i); // Get value from buffer - // chrtScl = xCenter / chrtRng; // current scale: pixels per degree - chrtScl = width / chrtRng / 2; // current scale: pixels per degree - // x = xCenter + ((chrtVal - wndCenter) * chrtScl); // Scale to chart width - x = ((chrtVal - wndLeft + 360) % 360) * chrtScl; // Scale to chart width + chrtVal = windValues.get(i); + x = ((chrtVal - wndLeft + 360) % 360) * chrtScl; y = yOffset + cHeight - i; // Position in chart area - // LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: chrtVal: %d, wndCenter: %d, chrtScl: %d, x: %d, y: %d", chrtVal, wndCenter, chrtScl, x, y); - // Draw line with 2 pixels width; make sure vertical line are drawn correctly + if ((abs(chrtVal - wndCenter) > 180) && !rngFlipped) { // If range exceeds 180 degrees, value plotted on other side of chart + rngFlipped = true; + prevX = x; // don't print connecting line to previous value + prevY = y; + } + if (i == 0) { // just a dot for 1st chart point + prevX = x; + prevY = y; + } +// if (i < 30) +// LOG_DEBUG(GwLog::ERROR, "PageWindPlot Chart: x: %d, y: %d prevX: %d, prevY: %d, loop-Counter: %d", x, y, prevX, prevY, count); + // Draw line with 2 pixels width + make sure vertical line are drawn correctly getdisplay().drawLine(prevX, prevY, x, y, commonData->fgcolor); - getdisplay().drawLine(prevX, prevY - 1, (x != prevX) ? x : x - 1, (x != prevX) ? y - 1 : y, commonData->fgcolor); - chrtPrevVal = chrtVal; + getdisplay().drawLine(prevX, prevY - 1, ((x != prevX) ? x : x - 1), ((x != prevX) ? y - 1 : y), commonData->fgcolor); + // chrtPrevVal = chrtVal; prevX = x; prevY = y; - if (i == (cHeight - 1)) { - // Reaching chart area top end - windValues.mvStart(40); - // virtually delete 40 values from buffer + + if (i == (cHeight - 1)) { // Reaching chart area top end + windValues.mvStart(40); // virtually delete 40 values from buffer if ((windValues.getMin() > wndCenter) || (windValues.getMax() < wndCenter)) { // Check if all wind value are left or right of center value -> optimize chart range int mid = windValues.getMid(); wndCenter = int((mid + (mid >= 0 ? 5 : -5)) / 10) * 10; // Set new center value; round to nearest 10 degree value + rngFlipped = false; // chart value within standard 180 degree range + chrtPrevVal = (windValues.get(0) < 0 ? 0 : windValues.get(0)); } - LOG_DEBUG(GwLog::DEBUG, "PageWindPlot Shift: Min: %d, Max: %d, Mid: %d, new Center: %d", windValues.getMin(), windValues.getMax(), windValues.getMid(), wndCenter); + LOG_DEBUG(GwLog::ERROR, "PageWindPlot Shift: Min: %d, Max: %d, Mid: %d, new Center: %d", windValues.getMin(), windValues.getMax(), windValues.getMid(), wndCenter); break; } } - LOG_DEBUG(GwLog::DEBUG, "PageWindPlot End: prevX: %d, prevY: %d, loop-Counter: %d", prevX, prevY, count); + LOG_DEBUG(GwLog::ERROR, "PageWindPlot chart end: x: %d, y: %d prevX: %d, prevY: %d, chrtPrevVal: %d, loop-Counter: %d", x, y, prevX, prevY, chrtPrevVal, count); } else if (!wndDataValid) { // No valid data available LOG_DEBUG(GwLog::LOG, "PageWindPlot: No valid data available"); getdisplay().setFont(&Ubuntu_Bold10pt7b); + getdisplay().fillRect(xCenter - 66, height / 2 - 18, 146, 24, commonData->bgcolor); // Clear area for TWS value getdisplay().setCursor(xCenter - 66, height / 2); getdisplay().print("No sensor data"); } // Print TWS value - int yPosTws = yOffset + 40; // Y position for TWS value - int xPosTws = width - 145; // X position for TWS value - if ((prevY < yPosTws) && prevX > xPosTws) { - // If chart line enters TWS value area, move TWS value to the left side - xPosTws = 20 ; - } - getdisplay().fillRect(xPosTws - 3, yPosTws - 35, 138, 40, commonData->bgcolor); // Clear area for TWS value - getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b); - getdisplay().setCursor(xPosTws, yPosTws); // 252, 52 - getdisplay().print(dataSValue[2]); // Value - getdisplay().setFont(&Ubuntu_Bold12pt7b); - getdisplay().setCursor(xPosTws + 82, yPosTws - 14); // 334, 38 - getdisplay().print(dataName[2]); // Name - getdisplay().setFont(&Ubuntu_Bold8pt7b); - getdisplay().setCursor(xPosTws + 78, yPosTws + 1); // 330, 53 - getdisplay().print(" "); - if (holdValues == false) { - getdisplay().print(dataUnit[2]); // Unit - } else { - getdisplay().print(dataUnitOld[2]); // Unit + if (showTWS) { + int xPosTws = width - 145; + int yPosTws = yOffset + 40; + if ((prevY > yPosTws - 36) && (prevY < yPosTws) && (prevX > xPosTws) && !TwsFlipped) { + // If chart line enters TWS value area, move TWS value to the other side + xPosTws = (xPosTws == width - 145) ? 30 : width - 145; + TwsFlipped = true; + } + TwsFlipped = false; // Reset flag for next display update + getdisplay().fillRect(xPosTws - 4, yPosTws - 38, 142, 44, commonData->bgcolor); // Clear area for TWS value + getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b); + getdisplay().setCursor(xPosTws, yPosTws); + getdisplay().print(dataSValue[2]); // Value + getdisplay().setFont(&Ubuntu_Bold12pt7b); + getdisplay().setCursor(xPosTws + 82, yPosTws - 14); + getdisplay().print(dataName[2]); // Name + getdisplay().setFont(&Ubuntu_Bold8pt7b); + getdisplay().setCursor(xPosTws + 78, yPosTws + 1); + getdisplay().print(" "); + if (holdValues == false) { + getdisplay().print(dataUnit[2]); // Unit + } else { + getdisplay().print(dataUnitOld[2]); // Unit + } } - // chart Y axis labels + // chart Y axis labels; print last to overwrite potential chart lines in label area char sWndYAx[4]; // char buffer for wind Y axis labels - int yPos; // Y position for label + int yPos; getdisplay().setFont(&Ubuntu_Bold8pt7b); - for (int i = 4; i > 0; i--) { - yPos = yOffset + cHeight - (i * 59) + 14; // Y position for label - getdisplay().fillRect(0, yPos - 6, 28, 15, commonData->bgcolor); // Clear small area to remove potential chart lines + for (int i = 3; i > 0; i--) { + yPos = yOffset + cHeight - (i * 60) + 14; // Y position for label + getdisplay().fillRect(0, yPos, width, 1, commonData->fgcolor); + getdisplay().fillRect(0, yPos - 6, 26, 15, commonData->bgcolor); // Clear small area to remove potential chart lines getdisplay().fillRect(0, yPos, 8, 2, commonData->fgcolor); - getdisplay().setCursor(9, yPos + 5); + getdisplay().setCursor(9, yPos + 4); snprintf(sWndYAx, 4, "%2d", i * updTime); getdisplay().print(sWndYAx); // Wind value label } From 13c85adad2b23bbd561b9fac231f2cfb0e750769 Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Fri, 13 Jun 2025 17:43:23 +0200 Subject: [PATCH 12/26] completed config.json; modified TWS flipping; almost fully fixed chart rng overflow --- lib/obp60task/PageWindPlot.cpp | 175 +++++++++++++++++---------------- lib/obp60task/config.json | 9 ++ 2 files changed, 97 insertions(+), 87 deletions(-) diff --git a/lib/obp60task/PageWindPlot.cpp b/lib/obp60task/PageWindPlot.cpp index d08bd3a..be6356c 100644 --- a/lib/obp60task/PageWindPlot.cpp +++ b/lib/obp60task/PageWindPlot.cpp @@ -20,7 +20,7 @@ public: bool begin(int size) // specifies buffer size { - if (size <= 0 || size > 1000) { + if (size <= 0 || size > 10000) { return false; } SIZE = size; @@ -155,10 +155,6 @@ public: return false; // Buffer is empty } - int twdMin = getMin(); - int twdMax = getMax(); - int twdMid = getMid(); - // Calculate TWD based on TWA and HDM return true; } @@ -169,9 +165,9 @@ class PageWindPlot : public Page { bool keylock = false; // Keylock // int16_t lp = 80; // Pointer length - char mode = 'D'; // Chart mode: 'D' for TWD, 'S' for TWS - int updTime = 1; // Update interval for wind history chart: - // (1)|(2)|(3)|(5) seconds for 3, 7, 10, 15 min. history chart + char chrtMode = 'D'; // Chart mode: 'D' for TWD, 'S' for TWS, 'B' for both06121990 + int dataInterv = 1; // Update interval for wind history chart: + // (1)|(2)|(3)|(4) seconds for 4, 8, 12, 16 min. history chart bool showTWS = true; // Show TWS value in chart area public: @@ -194,25 +190,26 @@ public: { // Set chart mode TWD | TWS if (key == 1) { - if (mode == 'D') { - mode = 'S'; + if (chrtMode == 'D') { + chrtMode = 'S'; + } else if (chrtMode == 'S') { + chrtMode = 'B'; } else { - mode = 'D'; + chrtMode = 'D'; } - // setupKeys(); // Update key labels return 0; // Commit the key } // Set interval for wind history chart update time if (key == 2) { - if (updTime == 1) { - updTime = 2; - } else if (updTime == 2) { - updTime = 3; - } else if (updTime == 3) { - updTime = 5; + if (dataInterv == 1) { + dataInterv = 2; + } else if (dataInterv == 2) { + dataInterv = 3; + } else if (dataInterv == 3) { + dataInterv = 4; } else { - updTime = 1; + dataInterv = 1; } return 0; // Commit the key } @@ -236,7 +233,8 @@ public: GwConfigHandler* config = commonData->config; GwLog* logger = commonData->logger; - static wndHistory windValues; // Circular buffer to store wind values + static wndHistory windDirHstry; // Circular buffer to store wind direction values + static wndHistory windSpdHstry; // Circular buffer to store wind speed values GwApi::BoatValue* bvalue; const int numCfgValues = 9; @@ -247,6 +245,7 @@ public: String dataUnit[numCfgValues]; String dataSValueOld[numCfgValues]; String dataUnitOld[numCfgValues]; + static const int bufMinutes = 16; // Buffer size in minutes for wind history chart bool wndDataValid = false; // Flag to indicate if wind data is valid bool simulation = false; bool holdValues = false; @@ -255,12 +254,10 @@ public: int height = getdisplay().height(); // Get screen height static const int yOffset = 48; // Offset for y coordinates of chart area int cHeight = height - yOffset - 22; // height of chart area - // int cHeight = 60; // height of chart area int xCenter = width / 2; // Center of screen in x direction - // static bool plotShift = false; // Flag to indicate if chartplot data have been shifted static const float radToDeg = 180.0 / M_PI; // Conversion factor from radians to degrees - static int wndCenter = -400; // chart wind center value position; init value indicates that wndCenter is not set yet + static int wndCenter = INT_MIN; // chart wind center value position; init value indicates that wndCenter is not set yet 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 @@ -279,26 +276,26 @@ public: static int updCnt = 0; // update counter for wind history chart in seconds bool isTimeforUpd = true; // Flag to indicate if it is time for chart update - bool TwsFlipped = false; // Flag to indicate if TWS value flipped LOG_DEBUG(GwLog::LOG, "Display page WindPlot"); - if (windValues.getSize() == 0) { - if (!windValues.begin(cHeight)) { + if (windDirHstry.getSize() == 0) { +// if (!windDirValues.begin(bufMinutes * 60)) { + if (!windDirHstry.begin(cHeight)) { logger->logDebug(GwLog::ERROR, "Failed to initialize wind values buffer"); return; } } - if (updCnt < updTime) { - // Next update interval not reached yet - updCnt++; - isTimeforUpd = false; - } else { - isTimeforUpd = true; - updCnt = 1; // Data update is now; reset counter - } - + /* if (updCnt < updTime) { + // Next update interval not reached yet + updCnt++; + isTimeforUpd = false; + } else { + isTimeforUpd = true; + updCnt = 1; // Data update is now; reset counter + } + */ // Get config data String lengthformat = config->getString(config->lengthFormat); simulation = config->getBool(config->useSimuData); @@ -332,24 +329,20 @@ public: // wndDataValid = windValues.calcTWD(&twdValue, dataValue[1], dataValue[2], dataValue[3], dataValue[4], dataValue[5], dataValue[6]); } - // ************* falsche Position **************** - if (isTimeforUpd) { - if (wndDataValid) { - windValues.add(twdValue); - } - - if (simulation) { - // Simulate data if simulation is enabled; use default simulation values for TWS - simWnd += random(simStep * -1, simStep); // random value between -simStep and +simStep - if (simWnd < 0) - simWnd += 360; - simWnd = simWnd % 360; - windValues.add(simWnd); - LOG_DEBUG(GwLog::DEBUG, "PageWindPlot simulation data: windValue: %d, windSpeed: %s", simWnd, dataSValue[2].c_str()); - } + if (simulation) { + // Simulate data if simulation is enabled; use default simulation values for TWS + simWnd += random(simStep * -1, simStep); // random value between -simStep and +simStep + if (simWnd < 0) + simWnd += 360; + simWnd = simWnd % 360; + windDirHstry.add(simWnd); + LOG_DEBUG(GwLog::DEBUG, "PageWindPlot simulation data: windValue: %d, windSpeed: %s", simWnd, dataSValue[2].c_str()); + } else if (wndDataValid) { + windDirHstry.add(twdValue); } - count = windValues.getSize(); // Get number of valid elements in buffer; maximum is cHeight - LOG_DEBUG(GwLog::ERROR, "PageWindPlot: Data 0 valid - dataValue[0]: %f, TWD: %d, cnt: %d, valid0: %d", dataValue[0] * radToDeg, twdValue, count, dataValid[0]); + + count = windDirHstry.getSize(); // Get number of valid elements in buffer; maximum is cHeight + LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: Data 0 valid - dataValue[0]: %f, TWD: %d, cnt: %d, valid0: %d", dataValue[0] * radToDeg, twdValue, count, dataValid[0]); // Optical warning by limit violation (unused) if (String(flashLED) == "Limit Violation") { @@ -357,39 +350,39 @@ public: setFlashLED(false); } - // if (bvalue == NULL) - // return; + if (bvalue == NULL) + return; LOG_DEBUG(GwLog::LOG, "PageWindPlot, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, cnt: %d, valid0: %d", dataName[0].c_str(), dataValue[0], dataName[1].c_str(), dataValue[1], dataName[2].c_str(), dataValue[2], dataName[3].c_str(), dataValue[3], dataName[4].c_str(), dataValue[4], dataName[5].c_str(), dataValue[5], dataName[6].c_str(), dataValue[6], dataName[7].c_str(), dataValue[7], dataName[8].c_str(), dataValue[8], count, dataValid[0]); // initialize chart range values - if (wndCenter == -400) { - wndCenter = (windValues.get(0) < 0 ? 0 : windValues.get(0)); + if (wndCenter == INT_MIN) { + wndCenter = (windDirHstry.get(0) < 0 ? 0 : windDirHstry.get(0)); wndCenter = int((wndCenter + (wndCenter >= 0 ? 5 : -5)) / 10) * 10; // Set new center value; round to nearest 10 degree value diffRng = 30; chrtRng = 30; LOG_DEBUG(GwLog::DEBUG, "PageWindPlot initialized. wndCenter: %d, chrtRng: %d ", wndCenter, chrtRng); } else { - diffRng = windValues.getRng(wndCenter); + diffRng = windDirHstry.getRng(wndCenter); diffRng = (diffRng < 0 ? 0 : diffRng); // If no data in buffer, set range to 0 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(30, int((diffRng + (diffRng >= 0 ? 9 : -1)) / 10) * 10); } - LOG_DEBUG(GwLog::ERROR, "PageWindPlot range adjusted. wndCenter: %d, chrtRng: %d ", wndCenter, chrtRng); + LOG_DEBUG(GwLog::DEBUG, "PageWindPlot range adjusted. wndCenter: %d, chrtRng: %d ", wndCenter, chrtRng); } chrtScl = float(width) / float(chrtRng) / 2.0; // chart scale: pixels per degree wndLeft = wndCenter - chrtRng; if (wndLeft < 0) wndLeft += 360; - wndRight = wndCenter + chrtRng; + wndRight = wndCenter + chrtRng - 1; if (wndRight >= 360) wndRight -= 360; - LOG_DEBUG(GwLog::ERROR, "PageWindPlot dataValue[0]: %f, windValue: %d, count: %d, diffRng: %d, chartRng: %d, Center: %d, scale: %f", double(dataValue[0] * radToDeg), - (!windValues.get(count - 1) < 0 ? 0 : windValues.get(count - 1)), count, diffRng, chrtRng, wndCenter, chrtScl); + LOG_DEBUG(GwLog::DEBUG, "PageWindPlot dataValue[0]: %f, windValue: %d, count: %d, diffRng: %d, chartRng: %d, Center: %d, scale: %f", double(dataValue[0] * radToDeg), + (!windDirHstry.get(count - 1) < 0 ? 0 : windDirHstry.get(count - 1)), count, diffRng, chrtRng, wndCenter, chrtScl); // Draw page //*********************************************************** @@ -413,7 +406,7 @@ public: getdisplay().setCursor(xCenter - 20, yOffset - 3); snprintf(sWndLbl, 4, "%03d", (wndCenter < 0) ? (wndCenter + 360) : wndCenter); getdisplay().print(sWndLbl); // Wind center value - getdisplay().drawCircle(xCenter + 25, yOffset - 16, 2, commonData->fgcolor); // symbol 63 + getdisplay().drawCircle(xCenter + 25, yOffset - 16, 2, commonData->fgcolor); // symbol getdisplay().drawCircle(xCenter + 25, yOffset - 16, 3, commonData->fgcolor); // symbol getdisplay().setCursor(2, yOffset - 3); snprintf(sWndLbl, 4, "%03d", (wndLeft < 0) ? (wndLeft + 360) : wndLeft); @@ -429,46 +422,46 @@ public: // Draw wind values in chart //*********************************************************** if (wndDataValid || holdValues || simulation) { - LOG_DEBUG(GwLog::ERROR, "PageWindPlot Draw: prevX: %d, chrtPrevVal: %d, wndLeft: %d, chrtScl: %f, count: %d", prevX, chrtPrevVal, wndLeft, chrtScl, count); + LOG_DEBUG(GwLog::DEBUG, "PageWindPlot Draw: prevX: %d, chrtPrevVal: %d, wndLeft: %d, chrtScl: %f, count: %d", prevX, chrtPrevVal, wndLeft, chrtScl, count); for (int i = 0; i < count; i++) { - chrtVal = windValues.get(i); + chrtVal = windDirHstry.get(i); + // if (i == 0) + // chrtPrevVal = chrtVal; x = ((chrtVal - wndLeft + 360) % 360) * chrtScl; y = yOffset + cHeight - i; // Position in chart area - if ((abs(chrtVal - wndCenter) > 180) && !rngFlipped) { // If range exceeds 180 degrees, value plotted on other side of chart - rngFlipped = true; - prevX = x; // don't print connecting line to previous value - prevY = y; - } if (i == 0) { // just a dot for 1st chart point prevX = x; prevY = y; + chrtPrevVal = chrtVal; + } else if (((chrtPrevVal >= wndLeft) && (chrtVal <= wndLeft)) || ((chrtPrevVal <= wndRight) && (chrtVal >= wndRight)) && !((chrtPrevVal < wndCenter && chrtVal >= wndCenter) || (chrtPrevVal >= wndCenter && chrtVal <= wndCenter))) { + // If current value crosses chart edges, compared to previous value, and does not cross "0" line, draw a dot only, no line + prevX = x; // don't print connecting line to previous value + prevY = y; } -// if (i < 30) -// LOG_DEBUG(GwLog::ERROR, "PageWindPlot Chart: x: %d, y: %d prevX: %d, prevY: %d, loop-Counter: %d", x, y, prevX, prevY, count); + if ((chrtVal > 350 || chrtVal < 10) || (chrtVal > 170 && chrtVal < 190)) // data debugging + LOG_DEBUG(GwLog::ERROR, "PageWindPlot Chart: chrtVal: %d, x: %d, y: %d prevX: %d, prevY: %d, loop-Counter: %d, Flipped: %d", chrtVal, x, y, prevX, prevY, count, rngFlipped); + // Draw line with 2 pixels width + make sure vertical line are drawn correctly getdisplay().drawLine(prevX, prevY, x, y, commonData->fgcolor); getdisplay().drawLine(prevX, prevY - 1, ((x != prevX) ? x : x - 1), ((x != prevX) ? y - 1 : y), commonData->fgcolor); - // chrtPrevVal = chrtVal; + chrtPrevVal = chrtVal; prevX = x; prevY = y; if (i == (cHeight - 1)) { // Reaching chart area top end - windValues.mvStart(40); // virtually delete 40 values from buffer - if ((windValues.getMin() > wndCenter) || (windValues.getMax() < wndCenter)) { + windDirHstry.mvStart(40); // virtually delete 40 values from buffer + if ((windDirHstry.getMin() > wndCenter) || (windDirHstry.getMax() < wndCenter)) { // Check if all wind value are left or right of center value -> optimize chart range - int mid = windValues.getMid(); + int mid = windDirHstry.getMid(); wndCenter = int((mid + (mid >= 0 ? 5 : -5)) / 10) * 10; // Set new center value; round to nearest 10 degree value rngFlipped = false; // chart value within standard 180 degree range - chrtPrevVal = (windValues.get(0) < 0 ? 0 : windValues.get(0)); } - LOG_DEBUG(GwLog::ERROR, "PageWindPlot Shift: Min: %d, Max: %d, Mid: %d, new Center: %d", windValues.getMin(), windValues.getMax(), windValues.getMid(), wndCenter); break; } } - LOG_DEBUG(GwLog::ERROR, "PageWindPlot chart end: x: %d, y: %d prevX: %d, prevY: %d, chrtPrevVal: %d, loop-Counter: %d", x, y, prevX, prevY, chrtPrevVal, count); - + LOG_DEBUG(GwLog::DEBUG, "PageWindPlot chart end: chrtVal: %d, x: %d, y: %d prevX: %d, prevY: %d, loop-Counter: %d", chrtVal, x, y, prevX, prevY, count); } else if (!wndDataValid) { // No valid data available LOG_DEBUG(GwLog::LOG, "PageWindPlot: No valid data available"); @@ -480,14 +473,22 @@ public: // Print TWS value if (showTWS) { - int xPosTws = width - 145; - int yPosTws = yOffset + 40; - if ((prevY > yPosTws - 36) && (prevY < yPosTws) && (prevX > xPosTws) && !TwsFlipped) { - // If chart line enters TWS value area, move TWS value to the other side - xPosTws = (xPosTws == width - 145) ? 30 : width - 145; - TwsFlipped = true; + static bool flipTws = false; + static int lastZone = -1; + int xPosTws = flipTws ? 30 : width - 145; + static const int yPosTws = yOffset + 40; + + int currentZone = (y > yPosTws - 36 && y < yPosTws) ? 1 : 0; // Define zone for TWS value + + if (currentZone != lastZone) { + // Only flip when y moves to a different zone + if ((y > yPosTws - 36) && (y < yPosTws) && ((!flipTws && (x > xPosTws)) || (flipTws && (x > xPosTws) && (x < (xPosTws + 145))))) { + flipTws = !flipTws; + xPosTws = flipTws ? 30 : width - 145; + } } - TwsFlipped = false; // Reset flag for next display update + lastZone = currentZone; + getdisplay().fillRect(xPosTws - 4, yPosTws - 38, 142, 44, commonData->bgcolor); // Clear area for TWS value getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b); getdisplay().setCursor(xPosTws, yPosTws); @@ -515,7 +516,7 @@ public: getdisplay().fillRect(0, yPos - 6, 26, 15, commonData->bgcolor); // Clear small area to remove potential chart lines getdisplay().fillRect(0, yPos, 8, 2, commonData->fgcolor); getdisplay().setCursor(9, yPos + 4); - snprintf(sWndYAx, 4, "%2d", i * updTime); + snprintf(sWndYAx, 4, "%2d", i * dataInterv); getdisplay().print(sWndYAx); // Wind value label } diff --git a/lib/obp60task/config.json b/lib/obp60task/config.json index 0cfba1b..6f3032c 100644 --- a/lib/obp60task/config.json +++ b/lib/obp60task/config.json @@ -1179,6 +1179,7 @@ "Voltage", "WhitePage", "Wind", + "WindPlot", "WindRose", "WindRoseFlex", "XTETrack" @@ -1458,6 +1459,7 @@ "Voltage", "WhitePage", "Wind", + "WindPlot", "WindRose", "WindRoseFlex", "XTETrack" @@ -2008,6 +2010,7 @@ "Voltage", "WhitePage", "Wind", + "WindPlot", "WindRose", "WindRoseFlex", "XTETrack" @@ -2278,6 +2281,7 @@ "Voltage", "WhitePage", "Wind", + "WindPlot", "WindRose", "WindRoseFlex", "XTETrack" @@ -2545,6 +2549,7 @@ "Voltage", "WhitePage", "Wind", + "WindPlot", "WindRose", "WindRoseFlex", "XTETrack" @@ -2809,6 +2814,7 @@ "Voltage", "WhitePage", "Wind", + "WindPlot", "WindRose", "WindRoseFlex", "XTETrack" @@ -3070,6 +3076,7 @@ "Voltage", "WhitePage", "Wind", + "WindPlot", "WindRose", "WindRoseFlex", "XTETrack" @@ -3328,6 +3335,7 @@ "Voltage", "WhitePage", "Wind", + "WindPlot", "WindRose", "WindRoseFlex", "XTETrack" @@ -3583,6 +3591,7 @@ "Voltage", "WhitePage", "Wind", + "WindPlot", "WindRose", "WindRoseFlex", "XTETrack" From bd9741d851744de899da0c1bb895be3da652c21b Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Sat, 14 Jun 2025 02:19:52 +0200 Subject: [PATCH 13/26] buffer extension; still some errors --- lib/obp60task/PageWindPlot.cpp | 182 +++++++++++++++++++++++---------- 1 file changed, 130 insertions(+), 52 deletions(-) diff --git a/lib/obp60task/PageWindPlot.cpp b/lib/obp60task/PageWindPlot.cpp index be6356c..4224497 100644 --- a/lib/obp60task/PageWindPlot.cpp +++ b/lib/obp60task/PageWindPlot.cpp @@ -12,19 +12,24 @@ class wndHistory { private: int SIZE; std::vector buffer; - int first = 0; // points to the first valid element - int head = 0; // points to the next insertion index - int count = 0; // number of valid elements + int first; // points to the first (oldest) valid element + int last; // points to the last (newest) valid element + int head; // points to the next insertion index + int count; // number of valid elements public: bool begin(int size) - // specifies buffer size + // start buffer { if (size <= 0 || size > 10000) { return false; } SIZE = size; buffer.resize(size, INT_MIN); // allocate buffer + head = 0; + first = 0; + last = 0; + count = 0; return true; } @@ -32,6 +37,7 @@ public: // Add a new value; store in [0..360 deg] format { buffer[head] = value; + last = head; head = (head + 1) % SIZE; if (count < SIZE) { count++; @@ -80,7 +86,7 @@ public: } int getMin() const - // Get minimum value in the buffer + // Get minimum value of buffer { if (count == 0) { return -1; // Buffer is empty @@ -95,8 +101,46 @@ public: } } + /* int getMin(int amount) const + // Get minimum value of the last values of buffer + { + if (count == 0 || amount <= 0) { + return -1; + } else if (amount > count) { + amount = count; // Limit to available values + } + + if (last + amount <= SIZE) { + // No wrap-around + return *std::min_element(buffer.begin() + last, buffer.begin() + (last + amount)); + } else { + // Wrap-around + int min1 = *std::min_element(buffer.begin() + ((last - amount) % last), buffer.begin() + last); + int min2 = *std::min_element(buffer.end() - (count - amount - last > 0 ? amount - last : 0), buffer.end()); + return std::min(min1, min2); + } + } */ + + int getMin(int amount) const + // Get minimum value of the last values of buffer + { + if (count == 0 || amount <= 0) + return -1; + if (amount > count) + amount = count; + + int minVal = INT_MAX; + // Start from the newest value (last) and go backwards x times + for (int i = 0; i < amount; ++i) { + int idx = (last - i + SIZE) % SIZE; + if (buffer[idx] < minVal) + minVal = buffer[idx]; + } + return minVal; + } + int getMax() const - // Get maximum value in the buffer + // Get maximum value of buffer { if (count == 0) { return -1; // Buffer is empty @@ -111,6 +155,44 @@ public: } } + int getMax(int amount) const + // Get minimum value of the last values of buffer + { + if (count == 0 || amount <= 0) + return -1; + if (amount > count) + amount = count; + + int maxVal = INT_MIN; + // Start from the newest value (last) and go backwards x times + for (int i = 0; i < amount; ++i) { + int idx = (last - i + SIZE) % SIZE; + if (buffer[idx] > maxVal) + maxVal = buffer[idx]; + } + return maxVal; + } + + /* int getMax(int amount) const + // Get maximum value of the last values of buffer + { + if (count == 0 || amount <= 0) { + return -1; + } else if (amount > count) { + amount = count; // Limit to available values + } + + if (first + count <= SIZE) { + // No wrap-around + return *std::max_element(buffer.begin() + last, buffer.begin() + (last + amount)); + } else { + // Wrap-around + int max1 = *std::max_element(buffer.begin() + ((last - amount) % last), buffer.begin() + last); + int max2 = *std::max_element(buffer.end(), buffer.end() - (amount - last > 0 ? amount - last : 0); + return std::max(max1, max2); + } + } */ + int getMid() const // Get middle value in the buffer { @@ -120,16 +202,15 @@ public: return (getMin() + getMax()) / 2; } - int getRng(int center) - // Get range of values in the buffer relative to a center value + int getRng(int center, int amount) const + // Get maximum difference of last of buffer values to center value { if (count == 0) { return -1; // Buffer is empty } - int min = getMin(); - int max = getMax(); - // int rng = std::max(abs((min - center + 540) % 360 - 180), abs((max - center + 540) % 360 - 180)); + int min = getMin(amount); + int max = getMax(amount); int rng = std::max(abs(min - center), abs(max - center)); if (rng > 180) { // should never happen, but just in case rng = 180; @@ -145,6 +226,10 @@ public: count -= start; else count = 0; + if (first == 0) + last = count; + else + last = first - 1; } // TWA, TWS, HDM, AWA, AWS, STW @@ -167,7 +252,7 @@ class PageWindPlot : public Page { // int16_t lp = 80; // Pointer length char chrtMode = 'D'; // Chart mode: 'D' for TWD, 'S' for TWS, 'B' for both06121990 int dataInterv = 1; // Update interval for wind history chart: - // (1)|(2)|(3)|(4) seconds for 4, 8, 12, 16 min. 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: @@ -245,16 +330,18 @@ public: String dataUnit[numCfgValues]; String dataSValueOld[numCfgValues]; String dataUnitOld[numCfgValues]; - static const int bufMinutes = 16; // Buffer size in minutes for wind history chart bool wndDataValid = false; // Flag to indicate if wind data is valid bool simulation = false; bool holdValues = false; int width = getdisplay().width(); // Get screen width int height = getdisplay().height(); // Get screen height + int xCenter = width / 2; // Center of screen in x direction static const int yOffset = 48; // Offset for y coordinates of chart area int cHeight = height - yOffset - 22; // height of chart area - int xCenter = width / 2; // Center of screen in x direction + static int bufSize = cHeight * 4; // Buffer size: 920 values for appox. 16 min. history chart + int intvBufSize; // Buffer size currently used for user selected time interval + int count; // current size of buffer static const float radToDeg = 180.0 / M_PI; // Conversion factor from radians to degrees static int wndCenter = INT_MIN; // chart wind center value position; init value indicates that wndCenter is not set yet @@ -272,32 +359,19 @@ public: static float chrtScl; // Scale for wind values in pixels per degree int chrtVal; // Current wind value static int chrtPrevVal; // Last wind value in chart area for check if value crosses 180 degree line - int count; // index for next wind value in buffer - - static int updCnt = 0; // update counter for wind history chart in seconds - bool isTimeforUpd = true; // Flag to indicate if it is time for chart update + int numWndValues; // number of wind values available for current interval selection LOG_DEBUG(GwLog::LOG, "Display page WindPlot"); if (windDirHstry.getSize() == 0) { -// if (!windDirValues.begin(bufMinutes * 60)) { - if (!windDirHstry.begin(cHeight)) { - logger->logDebug(GwLog::ERROR, "Failed to initialize wind values buffer"); + if (!windDirHstry.begin(bufSize)) { + logger->logDebug(GwLog::ERROR, "Failed to initialize wind direction history buffer"); return; } } - /* if (updCnt < updTime) { - // Next update interval not reached yet - updCnt++; - isTimeforUpd = false; - } else { - isTimeforUpd = true; - updCnt = 1; // Data update is now; reset counter - } - */ // Get config data - String lengthformat = config->getString(config->lengthFormat); + // String lengthformat = config->getString(config->lengthFormat); simulation = config->getBool(config->useSimuData); holdValues = config->getBool(config->holdvalues); String flashLED = config->getString(config->flashLED); @@ -341,8 +415,10 @@ public: windDirHstry.add(twdValue); } - count = windDirHstry.getSize(); // Get number of valid elements in buffer; maximum is cHeight - LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: Data 0 valid - dataValue[0]: %f, TWD: %d, cnt: %d, valid0: %d", dataValue[0] * radToDeg, twdValue, count, dataValid[0]); + intvBufSize = cHeight * dataInterv; // Get buffer size for current interval selection + count = windDirHstry.getSize(); + numWndValues = min(count, intvBufSize); + LOG_DEBUG(GwLog::ERROR, "PageWindPlot: User Interval: %d, intvBufSize: %d, count: %d, TWD: %d", dataInterv, intvBufSize, count, twdValue); // Optical warning by limit violation (unused) if (String(flashLED) == "Limit Violation") { @@ -358,14 +434,14 @@ public: // initialize chart range values if (wndCenter == INT_MIN) { - wndCenter = (windDirHstry.get(0) < 0 ? 0 : windDirHstry.get(0)); + wndCenter = windDirHstry.get(count - intvBufSize < 0 ? 0 : count - intvBufSize); wndCenter = int((wndCenter + (wndCenter >= 0 ? 5 : -5)) / 10) * 10; // Set new center value; round to nearest 10 degree value diffRng = 30; chrtRng = 30; LOG_DEBUG(GwLog::DEBUG, "PageWindPlot initialized. wndCenter: %d, chrtRng: %d ", wndCenter, chrtRng); } else { - diffRng = windDirHstry.getRng(wndCenter); + diffRng = windDirHstry.getRng(wndCenter, numWndValues); diffRng = (diffRng < 0 ? 0 : diffRng); // If no data in buffer, set range to 0 if (diffRng > chrtRng) { chrtRng = int((diffRng + (diffRng >= 0 ? 9 : -1)) / 10) * 10; // Round up to next 10 degree value @@ -422,26 +498,27 @@ public: // Draw wind values in chart //*********************************************************** if (wndDataValid || holdValues || simulation) { - LOG_DEBUG(GwLog::DEBUG, "PageWindPlot Draw: prevX: %d, chrtPrevVal: %d, wndLeft: %d, chrtScl: %f, count: %d", prevX, chrtPrevVal, wndLeft, chrtScl, count); + int threshold = 90; - for (int i = 0; i < count; i++) { - chrtVal = windDirHstry.get(i); - // if (i == 0) - // chrtPrevVal = chrtVal; + for (int i = 0; i < numWndValues; i++) { + chrtVal = windDirHstry.get(count - numWndValues + (i * dataInterv)); + if (i < 12) + LOG_DEBUG(GwLog::ERROR, "PageWindPlot Draw: i: %d, numWndValues: %d, chrtVal: %d, count: %d", i, numWndValues, chrtVal, count); x = ((chrtVal - wndLeft + 360) % 360) * chrtScl; y = yOffset + cHeight - i; // Position in chart area if (i == 0) { // just a dot for 1st chart point prevX = x; prevY = y; - chrtPrevVal = chrtVal; - } else if (((chrtPrevVal >= wndLeft) && (chrtVal <= wndLeft)) || ((chrtPrevVal <= wndRight) && (chrtVal >= wndRight)) && !((chrtPrevVal < wndCenter && chrtVal >= wndCenter) || (chrtPrevVal >= wndCenter && chrtVal <= wndCenter))) { - // If current value crosses chart edges, compared to previous value, and does not cross "0" line, draw a dot only, no line - prevX = x; // don't print connecting line to previous value - prevY = y; + } else if (((chrtPrevVal >= wndLeft) && (chrtVal <= wndLeft)) || ((chrtPrevVal <= wndRight) && (chrtVal >= wndRight))) { +// if (!((chrtPrevVal > 180) && (chrtVal < 180)) || ((chrtPrevVal < 180) && (chrtVal > 180))) { + if (!((chrtPrevVal >= 360 - threshold) && (chrtVal < threshold)) || ((chrtPrevVal < threshold) && (chrtVal >= 360 - threshold))) { + // If current value crosses chart edges, compared to previous value, and does not cross "0" line, draw a dot only, no line + prevX = x; // don't print connecting line to previous value + prevY = y; + LOG_DEBUG(GwLog::ERROR, "PageWindPlot Chart: chrtVal: %d, chrtPrevVal: %d, 0-Crossing: %d", chrtVal, chrtPrevVal, ((chrtPrevVal > 180 && chrtVal < 180) || (chrtPrevVal < 180 && chrtVal > 180))); + } } - if ((chrtVal > 350 || chrtVal < 10) || (chrtVal > 170 && chrtVal < 190)) // data debugging - LOG_DEBUG(GwLog::ERROR, "PageWindPlot Chart: chrtVal: %d, x: %d, y: %d prevX: %d, prevY: %d, loop-Counter: %d, Flipped: %d", chrtVal, x, y, prevX, prevY, count, rngFlipped); // Draw line with 2 pixels width + make sure vertical line are drawn correctly getdisplay().drawLine(prevX, prevY, x, y, commonData->fgcolor); @@ -452,7 +529,7 @@ public: if (i == (cHeight - 1)) { // Reaching chart area top end windDirHstry.mvStart(40); // virtually delete 40 values from buffer - if ((windDirHstry.getMin() > wndCenter) || (windDirHstry.getMax() < wndCenter)) { + if ((windDirHstry.getMin(numWndValues) > wndCenter) || (windDirHstry.getMax(numWndValues) < wndCenter)) { // Check if all wind value are left or right of center value -> optimize chart range int mid = windDirHstry.getMid(); wndCenter = int((mid + (mid >= 0 ? 5 : -5)) / 10) * 10; // Set new center value; round to nearest 10 degree value @@ -478,11 +555,12 @@ public: int xPosTws = flipTws ? 30 : width - 145; static const int yPosTws = yOffset + 40; - int currentZone = (y > yPosTws - 36 && y < yPosTws) ? 1 : 0; // Define zone for TWS value - + // int currentZone = (y > yPosTws - 36 && y < yPosTws) ? 1 : 0; // Define zone for TWS value + int currentZone = (x > xPosTws) && (x < (xPosTws + 145)) ? 1 : 0; // Define zone for TWS value if (currentZone != lastZone) { // Only flip when y moves to a different zone - if ((y > yPosTws - 36) && (y < yPosTws) && ((!flipTws && (x > xPosTws)) || (flipTws && (x > xPosTws) && (x < (xPosTws + 145))))) { + // if ((y > yPosTws - 36) && (y < yPosTws) && ((!flipTws && (x > xPosTws)) || (flipTws && (x > xPosTws) && (x < (xPosTws + 145))))) { + if ((y > yPosTws - 36) && (y < yPosTws) && (x > xPosTws) && (x < (xPosTws + 145))) { flipTws = !flipTws; xPosTws = flipTws ? 30 : width - 145; } @@ -513,7 +591,7 @@ public: for (int i = 3; i > 0; i--) { yPos = yOffset + cHeight - (i * 60) + 14; // Y position for label getdisplay().fillRect(0, yPos, width, 1, commonData->fgcolor); - getdisplay().fillRect(0, yPos - 6, 26, 15, commonData->bgcolor); // Clear small area to remove potential chart lines + getdisplay().fillRect(0, yPos - 9, 26, 16, commonData->bgcolor); // Clear small area to remove potential chart lines getdisplay().fillRect(0, yPos, 8, 2, commonData->fgcolor); getdisplay().setCursor(9, yPos + 4); snprintf(sWndYAx, 4, "%2d", i * dataInterv); From 73656e7d141b40abc3b80c67d49fe99e5b21e431 Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Wed, 18 Jun 2025 23:40:57 +0200 Subject: [PATCH 14/26] Buffer and interval stuff --- lib/obp60task/PageWindPlot.cpp | 105 ++++++++++++++++++++------------- 1 file changed, 65 insertions(+), 40 deletions(-) diff --git a/lib/obp60task/PageWindPlot.cpp b/lib/obp60task/PageWindPlot.cpp index 4224497..5c740a4 100644 --- a/lib/obp60task/PageWindPlot.cpp +++ b/lib/obp60task/PageWindPlot.cpp @@ -36,6 +36,8 @@ public: void add(int value) // Add a new value; store in [0..360 deg] format { + if (value < 0 || value > 360) + value = INT_MIN; buffer[head] = value; last = head; head = (head + 1) % SIZE; @@ -43,6 +45,8 @@ public: count++; } else { first = head - 1; // When buffer is full, first points to the oldest value + if (first < 0) + first += SIZE; } } @@ -193,13 +197,13 @@ public: } } */ - int getMid() const + int getMid(int amount) const // Get middle value in the buffer { if (count == 0) { return -1; // Buffer is empty } - return (getMin() + getMax()) / 2; + return (getMin(amount) + getMax(amount)) / 2; } int getRng(int center, int amount) const @@ -218,12 +222,12 @@ public: return rng; } - void mvStart(int start) - // Move the start index of buffer forward by positions + void mvStart(int delta) + // Move the start index of buffer forward by positions -> virtually delete oldest data entries { - first = (first + start) % SIZE; - if (count > start) - count -= start; + first = (first + delta) % SIZE; + if (count > delta) + count -= delta; else count = 0; if (first == 0) @@ -330,6 +334,8 @@ public: String dataUnit[numCfgValues]; String dataSValueOld[numCfgValues]; String dataUnitOld[numCfgValues]; + int twdValue; + bool wndDataValid = false; // Flag to indicate if wind data is valid bool simulation = false; bool holdValues = false; @@ -339,9 +345,12 @@ public: int xCenter = width / 2; // Center of screen in x direction static const int yOffset = 48; // Offset for y coordinates of chart area int cHeight = height - yOffset - 22; // height of chart area - static int bufSize = cHeight * 4; // Buffer size: 920 values for appox. 16 min. history chart - int intvBufSize; // Buffer size currently used for user selected time interval + int bufSize = cHeight * 4; // Buffer size: 920 values for appox. 16 min. history chart + int intvBufSize; // Buffer size used for currently selected time interval int count; // current size of buffer + int numWndValues; // number of wind values available for current interval selection + static int linesToShow; // current number of lines to display on chart + static int bufStart; // 1st data value in buffer to show static const float radToDeg = 180.0 / M_PI; // Conversion factor from radians to degrees static int wndCenter = INT_MIN; // chart wind center value position; init value indicates that wndCenter is not set yet @@ -349,7 +358,6 @@ public: 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 - bool rngFlipped = false; // Flag to indicate if range exceeds 180 degrees static int simWnd = 0; // Simulation value for wind data static float simTWS = 0; // Simulation value for TWS data static const int simStep = 10; // Simulation step for wind data @@ -359,7 +367,6 @@ public: static float chrtScl; // Scale for wind values in pixels per degree int chrtVal; // Current wind value static int chrtPrevVal; // Last wind value in chart area for check if value crosses 180 degree line - int numWndValues; // number of wind values available for current interval selection LOG_DEBUG(GwLog::LOG, "Display page WindPlot"); @@ -368,6 +375,10 @@ public: logger->logDebug(GwLog::ERROR, "Failed to initialize wind direction history buffer"); return; } + bufStart = 0; + simWnd = 0; + simTWS = 0; + twdValue = 0; } // Get config data @@ -378,10 +389,12 @@ public: String backlightMode = config->getString(config->backlight); // Read boatdata values for TWD, TWA, TWS, HDM, AWA, AWS, STW, if available + // Read boatdata values for TWD, TWA, TWS, HDM, AWA, AWS, STW, COG, SOG, if available for (int i = 0; i < numCfgValues; i++) { bvalue = pageData.values[i]; dataName[i] = xdrDelete(bvalue->getName()); dataName[i] = dataName[i].substring(0, 6); // String length limit for value name + calibrationData.calibrateInstance(dataName[i], bvalue, logger); // Check if boat data value is to be calibrated dataValue[i] = bvalue->value; // Value as double in SI unit calibrationData.calibrateInstance(dataName[i], bvalue, logger); // Check if boat data value is to be calibrated dataValid[i] = bvalue->valid; @@ -393,10 +406,9 @@ public: } } - // Store TWD wind value in buffer - int twdValue = 0; + // Store TWD wind value in buffer, regardless of validity -> one value per second (if delivered in that frequency) + twdValue = int((dataValue[0] * radToDeg) + 0.5); // Read TWD value in degrees and round to integer if (dataValid[0]) { // TWD data existing - twdValue = int((dataValue[0] * radToDeg) + 0.5); // Read TWD value in degrees and round to integer wndDataValid = true; } else { // Try to calculate TWD value from other data, if available @@ -415,10 +427,16 @@ public: windDirHstry.add(twdValue); } - intvBufSize = cHeight * dataInterv; // Get buffer size for current interval selection + intvBufSize = cHeight * dataInterv; count = windDirHstry.getSize(); numWndValues = min(count, intvBufSize); - LOG_DEBUG(GwLog::ERROR, "PageWindPlot: User Interval: %d, intvBufSize: %d, count: %d, TWD: %d", dataInterv, intvBufSize, count, twdValue); + if (numWndValues / dataInterv < cHeight) { + linesToShow = numWndValues / dataInterv; + } else { + linesToShow++; + } + + LOG_DEBUG(GwLog::ERROR, "PageWindPlot: User Interval: %d, intvBufSize: %d, count: %d, numWndValues: %d, TWD: %d", dataInterv, intvBufSize, count, numWndValues, twdValue); // Optical warning by limit violation (unused) if (String(flashLED) == "Limit Violation") { @@ -434,10 +452,10 @@ public: // initialize chart range values if (wndCenter == INT_MIN) { - wndCenter = windDirHstry.get(count - intvBufSize < 0 ? 0 : count - intvBufSize); + wndCenter = windDirHstry.get(max(0, numWndValues - intvBufSize)); // get 1st value of current data interval wndCenter = int((wndCenter + (wndCenter >= 0 ? 5 : -5)) / 10) * 10; // Set new center value; round to nearest 10 degree value - diffRng = 30; - chrtRng = 30; + diffRng = 40; + chrtRng = 40; LOG_DEBUG(GwLog::DEBUG, "PageWindPlot initialized. wndCenter: %d, chrtRng: %d ", wndCenter, chrtRng); } else { @@ -450,7 +468,9 @@ public: } LOG_DEBUG(GwLog::DEBUG, "PageWindPlot range adjusted. wndCenter: %d, chrtRng: %d ", wndCenter, chrtRng); } - chrtScl = float(width) / float(chrtRng) / 2.0; // chart scale: pixels per degree + chrtScl = float(width) / float(chrtRng) / 2.0; // Chart scale: pixels per degree + // wndCenter = windDirHstry.get(max(0, numWndValues - intvBufSize)); // Get 1st value of current data interval + // wndCenter = int((wndCenter + (wndCenter >= 0 ? 5 : -5)) / 10) * 10; // Round to nearest 10 degree value wndLeft = wndCenter - chrtRng; if (wndLeft < 0) wndLeft += 360; @@ -498,26 +518,28 @@ public: // Draw wind values in chart //*********************************************************** if (wndDataValid || holdValues || simulation) { - int threshold = 90; + for (int i = 0; i < linesToShow; i++) { + chrtVal = windDirHstry.get(bufStart + (i * dataInterv)); // show the latest wind values in buffer + // if (i < 12) + // LOG_DEBUG(GwLog::ERROR, "PageWindPlot Draw: i: %d, numWndValues: %d, chrtVal: %d, count: %d", i, numWndValues, chrtVal, count); + if (chrtVal<> INT_MIN) { // value has valid data + x = ((chrtVal - wndLeft + 360) % 360) * chrtScl; + y = yOffset + cHeight - i; // Position in chart area + if (i > linesToShow - 20) + LOG_DEBUG(GwLog::ERROR, "PageWindPlot Chart: i: %d, y; %d, linesToShow; %d, chrtVal: %d, chrtPrevVal: %d, bufStart: %d", i, y, linesToShow, chrtVal, chrtPrevVal, bufStart); - for (int i = 0; i < numWndValues; i++) { - chrtVal = windDirHstry.get(count - numWndValues + (i * dataInterv)); - if (i < 12) - LOG_DEBUG(GwLog::ERROR, "PageWindPlot Draw: i: %d, numWndValues: %d, chrtVal: %d, count: %d", i, numWndValues, chrtVal, count); - x = ((chrtVal - wndLeft + 360) % 360) * chrtScl; - y = yOffset + cHeight - i; // Position in chart area - - if (i == 0) { // just a dot for 1st chart point - prevX = x; - prevY = y; - } else if (((chrtPrevVal >= wndLeft) && (chrtVal <= wndLeft)) || ((chrtPrevVal <= wndRight) && (chrtVal >= wndRight))) { -// if (!((chrtPrevVal > 180) && (chrtVal < 180)) || ((chrtPrevVal < 180) && (chrtVal > 180))) { - if (!((chrtPrevVal >= 360 - threshold) && (chrtVal < threshold)) || ((chrtPrevVal < threshold) && (chrtVal >= 360 - threshold))) { + if (i == 0) { + prevX = x; // just a dot for 1st chart point + prevY = y; + // } else if ((((chrtPrevVal >= wndLeft) && (chrtVal < wndLeft)) && ((chrtPrevVal <= 0) && (chrtVal >= 0))) || (((chrtPrevVal <= wndRight) && (chrtVal > wndRight)) && ((chrtPrevVal >= 0) && (chrtVal <= 0)))) { + } else if ((((chrtPrevVal >= wndLeft) && (chrtVal < wndLeft)) && (abs(chrtPrevVal - chrtVal) > 0)) || (((chrtPrevVal <= wndRight) && (chrtVal > wndRight)) && (abs(chrtPrevVal - chrtVal) > 0))) { // If current value crosses chart edges, compared to previous value, and does not cross "0" line, draw a dot only, no line prevX = x; // don't print connecting line to previous value prevY = y; - LOG_DEBUG(GwLog::ERROR, "PageWindPlot Chart: chrtVal: %d, chrtPrevVal: %d, 0-Crossing: %d", chrtVal, chrtPrevVal, ((chrtPrevVal > 180 && chrtVal < 180) || (chrtPrevVal < 180 && chrtVal > 180))); + // LOG_DEBUG(GwLog::ERROR, "PageWindPlot Chart: chrtVal: %d, chrtPrevVal: %d, 0-Crossing: %d", chrtVal, chrtPrevVal, ((chrtPrevVal > 180 && chrtVal < 180) || (chrtPrevVal < 180 && chrtVal > 180))); } + } else { // invalid data -> print 'invisible' dot + x, y, prevX, prevY = 0; // -> to be corrected for next dot !!!!!!!!!!!!!!!!!!!!!!!!!!!! } // Draw line with 2 pixels width + make sure vertical line are drawn correctly @@ -527,18 +549,21 @@ public: prevX = x; prevY = y; - if (i == (cHeight - 1)) { // Reaching chart area top end - windDirHstry.mvStart(40); // virtually delete 40 values from buffer + if (i == (cHeight - 1)) { // Reaching chart area top end () + linesToShow -= 40; // free top 40 lines of chart for new values + bufStart = count - (linesToShow * dataInterv); // next start value in buffer to show + // windDirHstry.mvStart(); // virtually delete 40 values from buffer if ((windDirHstry.getMin(numWndValues) > wndCenter) || (windDirHstry.getMax(numWndValues) < wndCenter)) { // Check if all wind value are left or right of center value -> optimize chart range - int mid = windDirHstry.getMid(); + int mid = windDirHstry.getMid(numWndValues); wndCenter = int((mid + (mid >= 0 ? 5 : -5)) / 10) * 10; // Set new center value; round to nearest 10 degree value - rngFlipped = false; // chart value within standard 180 degree range } + LOG_DEBUG(GwLog::ERROR, "PageWindPlot FreeTop: cHeight: %d, LinesToShow: %d, numWndValues: %d, wndCenter: %d", cHeight, linesToShow, numWndValues, wndCenter); break; } } LOG_DEBUG(GwLog::DEBUG, "PageWindPlot chart end: chrtVal: %d, x: %d, y: %d prevX: %d, prevY: %d, loop-Counter: %d", chrtVal, x, y, prevX, prevY, count); + } else if (!wndDataValid) { // No valid data available LOG_DEBUG(GwLog::LOG, "PageWindPlot: No valid data available"); @@ -551,7 +576,7 @@ public: // Print TWS value if (showTWS) { static bool flipTws = false; - static int lastZone = -1; + static int lastZone = 0; int xPosTws = flipTws ? 30 : width - 145; static const int yPosTws = yOffset + 40; From 03d83391700f0636cd83c994a940fab261f4bc43 Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Sun, 22 Jun 2025 00:11:54 +0200 Subject: [PATCH 15/26] fix interval, border cross, TWS flip, range; add config_obp40; adjust axis legend --- lib/obp60task/PageWindPlot.cpp | 216 ++++++++++++++++++-------------- lib/obp60task/config_obp40.json | 10 ++ 2 files changed, 131 insertions(+), 95 deletions(-) diff --git a/lib/obp60task/PageWindPlot.cpp b/lib/obp60task/PageWindPlot.cpp index 5c740a4..dc4b97a 100644 --- a/lib/obp60task/PageWindPlot.cpp +++ b/lib/obp60task/PageWindPlot.cpp @@ -8,7 +8,7 @@ // **************************************************************** class wndHistory { - // provides a FiFo circular buffer to store wind history values + // provides a circular buffer to store wind history values private: int SIZE; std::vector buffer; @@ -136,9 +136,11 @@ public: int minVal = INT_MAX; // Start from the newest value (last) and go backwards x times for (int i = 0; i < amount; ++i) { - int idx = (last - i + SIZE) % SIZE; - if (buffer[idx] < minVal) - minVal = buffer[idx]; +// int idx = (last - i + SIZE) % SIZE; +// if (buffer[idx] < minVal) +// minVal = buffer[idx]; + if (get(i) > minVal) + minVal = get(i); } return minVal; } @@ -160,7 +162,7 @@ public: } int getMax(int amount) const - // Get minimum value of the last values of buffer + // Get maximum value of the last values of buffer { if (count == 0 || amount <= 0) return -1; @@ -170,9 +172,11 @@ public: int maxVal = INT_MIN; // Start from the newest value (last) and go backwards x times for (int i = 0; i < amount; ++i) { - int idx = (last - i + SIZE) % SIZE; - if (buffer[idx] > maxVal) - maxVal = buffer[idx]; +// int idx = (last - i + SIZE) % SIZE; +// if (buffer[idx] > maxVal) +// maxVal = buffer[idx]; + if (get(i) > maxVal) + maxVal = get(i); } return maxVal; } @@ -209,17 +213,23 @@ public: int getRng(int center, int amount) const // Get maximum difference of last of buffer values to center value { - if (count == 0) { - return -1; // Buffer is empty - } + if (count == 0 || amount <= 0) + return -1; + if (amount > count) + amount = count; - int min = getMin(amount); - int max = getMax(amount); - int rng = std::max(abs(min - center), abs(max - center)); - if (rng > 180) { // should never happen, but just in case - rng = 180; + int maxRng = INT_MIN; + int rng = 0; + // Start from the newest value (last) and go backwards x times + for (int i = 0; i < amount; ++i) { + rng = abs(((get(i) - center + 540) % 360) - 180); + if (rng > maxRng) + maxRng = rng; } - return rng; + if (maxRng > 180) { + maxRng = 180; + } + return maxRng; } void mvStart(int delta) @@ -235,18 +245,6 @@ public: else last = first - 1; } - - // TWA, TWS, HDM, AWA, AWS, STW - bool calcTWD(int* twd, float twa, float tws, float hdm, float awa, float aws, float stw) - // Calculate TWD based on other boat data values - { - if (count == 0) { - return false; // Buffer is empty - } - - // Calculate TWD based on TWA and HDM - return true; - } }; // **************************************************************** @@ -335,6 +333,7 @@ public: String dataSValueOld[numCfgValues]; String dataUnitOld[numCfgValues]; int twdValue; + static const float radToDeg = 180.0 / M_PI; // Conversion factor from radians to degrees bool wndDataValid = false; // Flag to indicate if wind data is valid bool simulation = false; @@ -345,19 +344,22 @@ public: int xCenter = width / 2; // Center of screen in x direction static const int yOffset = 48; // Offset for y coordinates of chart area int cHeight = height - yOffset - 22; // height of chart area + // cHeight = 60; int bufSize = cHeight * 4; // Buffer size: 920 values for appox. 16 min. history chart int intvBufSize; // Buffer size used for currently selected time interval int count; // current size of buffer int numWndValues; // number of wind values available for current interval selection static int linesToShow; // current number of lines to display on chart static int bufStart; // 1st data value in buffer to show - static const float radToDeg = 180.0 / M_PI; // Conversion factor from radians to degrees + static int oldDataInterv; // remember recent user selection of data interval + static int newDate; // indicates for higher time intervals that new date is available static int wndCenter = INT_MIN; // chart wind center value position; init value indicates that wndCenter is not set yet 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 int simWnd = 0; // Simulation value for wind data static float simTWS = 0; // Simulation value for TWS data static const int simStep = 10; // Simulation step for wind data @@ -367,18 +369,25 @@ public: static float chrtScl; // Scale for wind values in pixels per degree int chrtVal; // Current wind value static int chrtPrevVal; // Last wind value in chart area for check if value crosses 180 degree line + int distVals; // helper to check wndCenter crossing + int distMid; // helper to check wndCenter crossing LOG_DEBUG(GwLog::LOG, "Display page WindPlot"); + unsigned long start = millis(); + // Data initialization if (windDirHstry.getSize() == 0) { if (!windDirHstry.begin(bufSize)) { logger->logDebug(GwLog::ERROR, "Failed to initialize wind direction history buffer"); return; } - bufStart = 0; simWnd = 0; simTWS = 0; twdValue = 0; + bufStart = 0; + linesToShow = 0; + oldDataInterv = dataInterv; + newDate = 0; } // Get config data @@ -388,7 +397,12 @@ public: String flashLED = config->getString(config->flashLED); String backlightMode = config->getString(config->backlight); - // Read boatdata values for TWD, TWA, TWS, HDM, AWA, AWS, STW, if available + // Optical warning by limit violation (unused) + if (String(flashLED) == "Limit Violation") { + setBlinkingLED(false); + setFlashLED(false); + } + // Read boatdata values for TWD, TWA, TWS, HDM, AWA, AWS, STW, COG, SOG, if available for (int i = 0; i < numCfgValues; i++) { bvalue = pageData.values[i]; @@ -422,27 +436,27 @@ public: simWnd += 360; simWnd = simWnd % 360; windDirHstry.add(simWnd); - LOG_DEBUG(GwLog::DEBUG, "PageWindPlot simulation data: windValue: %d, windSpeed: %s", simWnd, dataSValue[2].c_str()); + // LOG_DEBUG(GwLog::DEBUG, "PageWindPlot simulation data: windValue: %d, windSpeed: %s", simWnd, dataSValue[2].c_str()); } else if (wndDataValid) { windDirHstry.add(twdValue); } + // Identify buffer sizes and buffer position to print on the chart intvBufSize = cHeight * dataInterv; count = windDirHstry.getSize(); numWndValues = min(count, intvBufSize); - if (numWndValues / dataInterv < cHeight) { - linesToShow = numWndValues / dataInterv; - } else { - linesToShow++; - } - - LOG_DEBUG(GwLog::ERROR, "PageWindPlot: User Interval: %d, intvBufSize: %d, count: %d, numWndValues: %d, TWD: %d", dataInterv, intvBufSize, count, numWndValues, twdValue); - - // Optical warning by limit violation (unused) - if (String(flashLED) == "Limit Violation") { - setBlinkingLED(false); - setFlashLED(false); + newDate++; + if (dataInterv != oldDataInterv) { + linesToShow = min(numWndValues / dataInterv, max(0, cHeight - 40)); + bufStart = max(0, (count - (linesToShow * dataInterv))); + oldDataInterv = dataInterv; + } else if (newDate >= dataInterv) { + linesToShow = min(numWndValues / dataInterv, linesToShow + 1); + newDate = 0; } + if (count == bufSize) + bufStart--; // show the latest wind values in buffer; keep 1st value constant in a rolling buffer when new data is added + LOG_DEBUG(GwLog::ERROR, "PageWindPlot DateSet: TWD: %d, count: %d, bufStart: %d, linesToShow: %d, newDate: %d", twdValue, count, bufStart, linesToShow, newDate); if (bvalue == NULL) return; @@ -453,20 +467,21 @@ public: // initialize chart range values if (wndCenter == INT_MIN) { wndCenter = windDirHstry.get(max(0, numWndValues - intvBufSize)); // get 1st value of current data interval - wndCenter = int((wndCenter + (wndCenter >= 0 ? 5 : -5)) / 10) * 10; // Set new center value; round to nearest 10 degree value - diffRng = 40; - chrtRng = 40; - LOG_DEBUG(GwLog::DEBUG, "PageWindPlot initialized. wndCenter: %d, chrtRng: %d ", wndCenter, chrtRng); - + wndCenter = (int((wndCenter + (wndCenter >= 0 ? 5 : -5)) / 10) * 10) % 360; // Set new center value; round to nearest 10 degree value; 360° -> 0° + diffRng = dfltRng; + chrtRng = dfltRng; } else { + // check and adjust range between left, center, and right chart limit diffRng = windDirHstry.getRng(wndCenter, numWndValues); diffRng = (diffRng < 0 ? 0 : diffRng); // If no data in buffer, set range to 0 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(30, int((diffRng + (diffRng >= 0 ? 9 : -1)) / 10) * 10); + chrtRng = max(dfltRng, int((diffRng + (diffRng >= 0 ? 9 : -1)) / 10) * 10); } - LOG_DEBUG(GwLog::DEBUG, "PageWindPlot range adjusted. wndCenter: %d, chrtRng: %d ", wndCenter, chrtRng); + int debugMin = windDirHstry.getMin(numWndValues); + int debugMax = windDirHstry.getMax(numWndValues); + LOG_DEBUG(GwLog::ERROR, "PageWindPlot Range. wndCenter: %d, numWndValues: %d, min: %d, max: %d, diffrng: %d, chrtRng: %d ", wndCenter, numWndValues, debugMin, debugMax, diffRng, chrtRng); } chrtScl = float(width) / float(chrtRng) / 2.0; // Chart scale: pixels per degree // wndCenter = windDirHstry.get(max(0, numWndValues - intvBufSize)); // Get 1st value of current data interval @@ -477,8 +492,8 @@ public: wndRight = wndCenter + chrtRng - 1; if (wndRight >= 360) wndRight -= 360; - LOG_DEBUG(GwLog::DEBUG, "PageWindPlot dataValue[0]: %f, windValue: %d, count: %d, diffRng: %d, chartRng: %d, Center: %d, scale: %f", double(dataValue[0] * radToDeg), - (!windDirHstry.get(count - 1) < 0 ? 0 : windDirHstry.get(count - 1)), count, diffRng, chrtRng, wndCenter, chrtScl); + // LOG_DEBUG(GwLog::DEBUG, "PageWindPlot dataValue[0]: %f, windValue: %d, count: %d, diffRng: %d, chartRng: %d, Center: %d, scale: %f", double(dataValue[0] * radToDeg), + // (!windDirHstry.get(count - 1) < 0 ? 0 : windDirHstry.get(count - 1)), count, diffRng, chrtRng, wndCenter, chrtScl); // Draw page //*********************************************************** @@ -492,7 +507,7 @@ public: // chart lines getdisplay().fillRect(0, yOffset, width, 2, commonData->fgcolor); - getdisplay().fillRect(xCenter - 1, yOffset, 2, cHeight, commonData->fgcolor); + getdisplay().fillRect(xCenter, yOffset, 1, cHeight, commonData->fgcolor); // chart labels char sWndLbl[4]; // char buffer for Wind angle label @@ -504,11 +519,11 @@ public: getdisplay().print(sWndLbl); // Wind center value getdisplay().drawCircle(xCenter + 25, yOffset - 16, 2, commonData->fgcolor); // symbol getdisplay().drawCircle(xCenter + 25, yOffset - 16, 3, commonData->fgcolor); // symbol - getdisplay().setCursor(2, yOffset - 3); + getdisplay().setCursor(1, yOffset - 3); snprintf(sWndLbl, 4, "%03d", (wndLeft < 0) ? (wndLeft + 360) : wndLeft); getdisplay().print(sWndLbl); // Wind left value - getdisplay().drawCircle(47, yOffset - 16, 2, commonData->fgcolor); // symbol - getdisplay().drawCircle(47, yOffset - 16, 3, commonData->fgcolor); // symbol + getdisplay().drawCircle(46, yOffset - 16, 2, commonData->fgcolor); // symbol + getdisplay().drawCircle(46, yOffset - 16, 3, commonData->fgcolor); // symbol getdisplay().setCursor(width - 51, yOffset - 3); snprintf(sWndLbl, 4, "%03d", (wndRight < 0) ? (wndRight + 360) : wndRight); getdisplay().print(sWndLbl); // Wind right value @@ -518,28 +533,32 @@ public: // Draw wind values in chart //*********************************************************** if (wndDataValid || holdValues || simulation) { + // if (count == bufSize) + // bufStart--; // show the latest wind values in buffer; keep 1st value constant in a rolling buffer for (int i = 0; i < linesToShow; i++) { - chrtVal = windDirHstry.get(bufStart + (i * dataInterv)); // show the latest wind values in buffer - // if (i < 12) - // LOG_DEBUG(GwLog::ERROR, "PageWindPlot Draw: i: %d, numWndValues: %d, chrtVal: %d, count: %d", i, numWndValues, chrtVal, count); - if (chrtVal<> INT_MIN) { // value has valid data - x = ((chrtVal - wndLeft + 360) % 360) * chrtScl; - y = yOffset + cHeight - i; // Position in chart area - if (i > linesToShow - 20) - LOG_DEBUG(GwLog::ERROR, "PageWindPlot Chart: i: %d, y; %d, linesToShow; %d, chrtVal: %d, chrtPrevVal: %d, bufStart: %d", i, y, linesToShow, chrtVal, chrtPrevVal, bufStart); + chrtVal = windDirHstry.get(bufStart + (i * dataInterv)); // show the latest wind values in buffer; keep 1st value constant in a rolling buffer + x = ((chrtVal - wndLeft + 360) % 360) * chrtScl; + y = yOffset + cHeight - i; // Position in chart area + LOG_DEBUG(GwLog::ERROR, "PageWindPlot Chart: i: %d, chrtVal: %d, chrtPrevVal: %d, bufStart: %d count: %d, linesToShow: %d", i, chrtVal, chrtPrevVal, bufStart, count, linesToShow); - if (i == 0) { - prevX = x; // just a dot for 1st chart point - prevY = y; - // } else if ((((chrtPrevVal >= wndLeft) && (chrtVal < wndLeft)) && ((chrtPrevVal <= 0) && (chrtVal >= 0))) || (((chrtPrevVal <= wndRight) && (chrtVal > wndRight)) && ((chrtPrevVal >= 0) && (chrtVal <= 0)))) { - } else if ((((chrtPrevVal >= wndLeft) && (chrtVal < wndLeft)) && (abs(chrtPrevVal - chrtVal) > 0)) || (((chrtPrevVal <= wndRight) && (chrtVal > wndRight)) && (abs(chrtPrevVal - chrtVal) > 0))) { - // If current value crosses chart edges, compared to previous value, and does not cross "0" line, draw a dot only, no line - prevX = x; // don't print connecting line to previous value - prevY = y; - // LOG_DEBUG(GwLog::ERROR, "PageWindPlot Chart: chrtVal: %d, chrtPrevVal: %d, 0-Crossing: %d", chrtVal, chrtPrevVal, ((chrtPrevVal > 180 && chrtVal < 180) || (chrtPrevVal < 180 && chrtVal > 180))); + if (i == 0) { + prevX = x; // just a dot for 1st chart point + prevY = y; + } else { + // cross borders check; shift values to [-180..0..180]; when crossing borders, range is 2x 180 degrees + int wndLeftDlt = -180 - ((wndLeft >= 180) ? (wndLeft - 360) : wndLeft); + int chrtVal180 = ((chrtVal + wndLeftDlt + 180) % 360 + 360) % 360 - 180; + int chrtPrevVal180 = ((chrtPrevVal + wndLeftDlt + 180) % 360 + 360) % 360 - 180; + // if (i > linesToShow - 15) + // LOG_DEBUG(GwLog::ERROR, "PageWindPlot Chart: i: %d, chrtVal: %d, chrtVal180: %d, chrtPrevVal: %d, chrtPrevVal180: %d, wndLeftDlt: %d", i, chrtVal, chrtVal180, chrtPrevVal, chrtPrevVal180, wndLeftDlt); + if (((chrtPrevVal180 >= -180) && (chrtPrevVal180 < -90) && (chrtVal180 > 90)) || ((chrtPrevVal180 <= 179) && (chrtPrevVal180 > 90) && chrtVal180 <= -90)) { + // If current value crosses chart borders, compared to previous value, split line + int xSplit = (((chrtPrevVal180 > 0 ? wndRight : wndLeft) - wndLeft + 360) % 360) * chrtScl; + getdisplay().drawLine(prevX, prevY, xSplit, y, commonData->fgcolor); + getdisplay().drawLine(prevX, prevY - 1, ((xSplit != prevX) ? xSplit : xSplit - 1), ((xSplit != prevX) ? y - 1 : y), commonData->fgcolor); + prevX = (((chrtVal180 > 0 ? wndRight : wndLeft) - wndLeft + 360) % 360) * chrtScl; + LOG_DEBUG(GwLog::ERROR, "PageWindPlot Cross: i: %d, chrtVal: %d, chrtPrevVal: %d, wndLeft: %d wndRight: %d, curr:{%d,%d} prev:{%d,%d}", i, chrtVal, chrtPrevVal, wndLeft, wndRight, x, y, prevX, prevY); } - } else { // invalid data -> print 'invisible' dot - x, y, prevX, prevY = 0; // -> to be corrected for next dot !!!!!!!!!!!!!!!!!!!!!!!!!!!! } // Draw line with 2 pixels width + make sure vertical line are drawn correctly @@ -550,21 +569,22 @@ public: prevY = y; if (i == (cHeight - 1)) { // Reaching chart area top end () - linesToShow -= 40; // free top 40 lines of chart for new values - bufStart = count - (linesToShow * dataInterv); // next start value in buffer to show + linesToShow -= min(40, cHeight); // free top 40 lines of chart for new values + bufStart = max(0, count - (linesToShow * dataInterv)); // next start value in buffer to show // windDirHstry.mvStart(); // virtually delete 40 values from buffer if ((windDirHstry.getMin(numWndValues) > wndCenter) || (windDirHstry.getMax(numWndValues) < wndCenter)) { // Check if all wind value are left or right of center value -> optimize chart range int mid = windDirHstry.getMid(numWndValues); wndCenter = int((mid + (mid >= 0 ? 5 : -5)) / 10) * 10; // Set new center value; round to nearest 10 degree value } - LOG_DEBUG(GwLog::ERROR, "PageWindPlot FreeTop: cHeight: %d, LinesToShow: %d, numWndValues: %d, wndCenter: %d", cHeight, linesToShow, numWndValues, wndCenter); + LOG_DEBUG(GwLog::ERROR, "PageWindPlot FreeTop: cHeight: %d, LinesToShow: %d, numWndValues: %d, wndCenter: %d, bufStart: %d", cHeight, linesToShow, numWndValues, wndCenter, bufStart); break; } } - LOG_DEBUG(GwLog::DEBUG, "PageWindPlot chart end: chrtVal: %d, x: %d, y: %d prevX: %d, prevY: %d, loop-Counter: %d", chrtVal, x, y, prevX, prevY, count); + // LOG_DEBUG(GwLog::DEBUG, "PageWindPlot chart end: chrtVal: %d, x: %d, y: %d prevX: %d, prevY: %d, loop-Counter: %d", chrtVal, x, y, prevX, prevY, count); + } - } else if (!wndDataValid) { + else if (!wndDataValid) { // No valid data available LOG_DEBUG(GwLog::LOG, "PageWindPlot: No valid data available"); getdisplay().setFont(&Ubuntu_Bold10pt7b); @@ -575,19 +595,22 @@ public: // Print TWS value if (showTWS) { - static bool flipTws = false; + int currentZone; static int lastZone = 0; - int xPosTws = flipTws ? 30 : width - 145; + static bool flipTws = false; + int xPosTws; static const int yPosTws = yOffset + 40; - // int currentZone = (y > yPosTws - 36 && y < yPosTws) ? 1 : 0; // Define zone for TWS value - int currentZone = (x > xPosTws) && (x < (xPosTws + 145)) ? 1 : 0; // Define zone for TWS value + // xPosTws = flipTws ? 30 : width - 145; + xPosTws = flipTws ? 20 : width - 138; + currentZone = (y >= yPosTws - 38) && (y <= yPosTws + 6) && (x >= xPosTws - 4) && (x <= xPosTws + 146) ? 1 : 0; // Define current zone for TWS value + // currentZone = (x >= xPosTws - 4) && (x <= xPosTws + 142) ? 1 : 0; // Define current zone for TWS value + // LOG_DEBUG(GwLog::DEBUG, "PageWindPlot TWS: xPos: %d, yPos: %d, x: %d y: %d, currZone: %d, lastZone: %d", xPosTws, yPosTws, x, y, currentZone, lastZone); if (currentZone != lastZone) { - // Only flip when y moves to a different zone - // if ((y > yPosTws - 36) && (y < yPosTws) && ((!flipTws && (x > xPosTws)) || (flipTws && (x > xPosTws) && (x < (xPosTws + 145))))) { - if ((y > yPosTws - 36) && (y < yPosTws) && (x > xPosTws) && (x < (xPosTws + 145))) { + // Only flip when x moves to a different zone + if ((y >= yPosTws - 38) && (y <= yPosTws + 6) && (x >= xPosTws - 4) && (x <= xPosTws + 146)) { flipTws = !flipTws; - xPosTws = flipTws ? 30 : width - 145; + xPosTws = flipTws ? 20 : width - 145; } } lastZone = currentZone; @@ -613,18 +636,21 @@ public: char sWndYAx[4]; // char buffer for wind Y axis labels int yPos; getdisplay().setFont(&Ubuntu_Bold8pt7b); - for (int i = 3; i > 0; i--) { - yPos = yOffset + cHeight - (i * 60) + 14; // Y position for label + for (int i = 1; i <= 3; i++) { + yPos = yOffset + (i * 60); // Y position for label +// yPos = yOffset + cHeight - (i * 60); // Y position for label getdisplay().fillRect(0, yPos, width, 1, commonData->fgcolor); getdisplay().fillRect(0, yPos - 9, 26, 16, commonData->bgcolor); // Clear small area to remove potential chart lines - getdisplay().fillRect(0, yPos, 8, 2, commonData->fgcolor); - getdisplay().setCursor(9, yPos + 4); - snprintf(sWndYAx, 4, "%2d", i * dataInterv); + // getdisplay().fillRect(0, yPos, 8, 2, commonData->fgcolor); + getdisplay().setCursor(1, yPos + 4); + snprintf(sWndYAx, 4, "%3d", i * dataInterv * -1); getdisplay().print(sWndYAx); // Wind value label } // Update display getdisplay().nextPage(); // Partial update (fast) + unsigned long finish = millis() - start; + LOG_DEBUG(GwLog::ERROR, "PageWindPlot Time: %lu", finish); }; }; diff --git a/lib/obp60task/config_obp40.json b/lib/obp60task/config_obp40.json index e24e9e9..b3080a4 100644 --- a/lib/obp60task/config_obp40.json +++ b/lib/obp60task/config_obp40.json @@ -1021,6 +1021,7 @@ "Voltage", "WhitePage", "Wind", + "WindPlot", "WindRose", "WindRoseFlex", "XTETrack" @@ -1281,6 +1282,7 @@ "Voltage", "WhitePage", "Wind", + "WindPlot", "WindRose", "WindRoseFlex", "XTETrack" @@ -1539,6 +1541,7 @@ "Voltage", "WhitePage", "Wind", + "WindPlot", "WindRose", "WindRoseFlex", "XTETrack" @@ -1793,6 +1796,7 @@ "Voltage", "WhitePage", "Wind", + "WindPlot", "WindRose", "WindRoseFlex", "XTETrack" @@ -2044,6 +2048,7 @@ "Voltage", "WhitePage", "Wind", + "WindPlot", "WindRose", "WindRoseFlex", "XTETrack" @@ -2292,6 +2297,7 @@ "Voltage", "WhitePage", "Wind", + "WindPlot", "WindRose", "WindRoseFlex", "XTETrack" @@ -2537,6 +2543,7 @@ "Voltage", "WhitePage", "Wind", + "WindPlot", "WindRose", "WindRoseFlex", "XTETrack" @@ -2779,6 +2786,7 @@ "Voltage", "WhitePage", "Wind", + "WindPlot", "WindRose", "WindRoseFlex", "XTETrack" @@ -3018,6 +3026,7 @@ "Voltage", "WhitePage", "Wind", + "WindPlot", "WindRose", "WindRoseFlex", "XTETrack" @@ -3254,6 +3263,7 @@ "Voltage", "WhitePage", "Wind", + "WindPlot", "WindRose", "WindRoseFlex", "XTETrack" From 9ada5be7cb84107bb1f452fc78b0aba1473cb5a9 Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Sun, 22 Jun 2025 14:29:03 +0200 Subject: [PATCH 16/26] Handling of missing data --- lib/obp60task/PageWindPlot.cpp | 188 +++++------- lib/obp60task/PageWindPlot.cpp.txt | 477 ----------------------------- 2 files changed, 72 insertions(+), 593 deletions(-) delete mode 100644 lib/obp60task/PageWindPlot.cpp.txt diff --git a/lib/obp60task/PageWindPlot.cpp b/lib/obp60task/PageWindPlot.cpp index dc4b97a..b1b17dd 100644 --- a/lib/obp60task/PageWindPlot.cpp +++ b/lib/obp60task/PageWindPlot.cpp @@ -52,11 +52,12 @@ public: int get(int index) const // Get value by index in [0..360 deg] format (0 = oldest, count-1 = newest) + // INT_MIN indicates missing value or wrong index { int realIndex; if (index < 0 || index >= count) { - return -1; // Invalid index + return INT_MIN; // Invalid index } realIndex = (first + index) % SIZE; return buffer[realIndex]; @@ -78,7 +79,6 @@ public: return value; } default: - // Return value in [-180..180 deg] format return -1; } } @@ -105,42 +105,23 @@ public: } } - /* int getMin(int amount) const - // Get minimum value of the last values of buffer - { - if (count == 0 || amount <= 0) { - return -1; - } else if (amount > count) { - amount = count; // Limit to available values - } - - if (last + amount <= SIZE) { - // No wrap-around - return *std::min_element(buffer.begin() + last, buffer.begin() + (last + amount)); - } else { - // Wrap-around - int min1 = *std::min_element(buffer.begin() + ((last - amount) % last), buffer.begin() + last); - int min2 = *std::min_element(buffer.end() - (count - amount - last > 0 ? amount - last : 0), buffer.end()); - return std::min(min1, min2); - } - } */ - int getMin(int amount) const // Get minimum value of the last values of buffer + // INT_MIN indicates missing value or wrong index + { if (count == 0 || amount <= 0) - return -1; + return INT_MIN; if (amount > count) amount = count; int minVal = INT_MAX; + int value = 0; // Start from the newest value (last) and go backwards x times for (int i = 0; i < amount; ++i) { -// int idx = (last - i + SIZE) % SIZE; -// if (buffer[idx] < minVal) -// minVal = buffer[idx]; - if (get(i) > minVal) - minVal = get(i); + value = get(i); + if (value < minVal) + minVal = value; } return minVal; } @@ -163,6 +144,8 @@ public: int getMax(int amount) const // Get maximum value of the last values of buffer + // INT_MIN indicates missing value or wrong index + { if (count == 0 || amount <= 0) return -1; @@ -170,42 +153,21 @@ public: amount = count; int maxVal = INT_MIN; + int value = 0; // Start from the newest value (last) and go backwards x times for (int i = 0; i < amount; ++i) { -// int idx = (last - i + SIZE) % SIZE; -// if (buffer[idx] > maxVal) -// maxVal = buffer[idx]; - if (get(i) > maxVal) - maxVal = get(i); + value = get(i); + if (value > maxVal) + maxVal = value; } return maxVal; } - /* int getMax(int amount) const - // Get maximum value of the last values of buffer - { - if (count == 0 || amount <= 0) { - return -1; - } else if (amount > count) { - amount = count; // Limit to available values - } - - if (first + count <= SIZE) { - // No wrap-around - return *std::max_element(buffer.begin() + last, buffer.begin() + (last + amount)); - } else { - // Wrap-around - int max1 = *std::max_element(buffer.begin() + ((last - amount) % last), buffer.begin() + last); - int max2 = *std::max_element(buffer.end(), buffer.end() - (amount - last > 0 ? amount - last : 0); - return std::max(max1, max2); - } - } */ - int getMid(int amount) const // Get middle value in the buffer { if (count == 0) { - return -1; // Buffer is empty + return INT_MIN; // Buffer is empty } return (getMin(amount) + getMax(amount)) / 2; } @@ -214,15 +176,20 @@ public: // Get maximum difference of last of buffer values to center value { if (count == 0 || amount <= 0) - return -1; + return INT_MIN; if (amount > count) amount = count; + int value = 0; int maxRng = INT_MIN; int rng = 0; // Start from the newest value (last) and go backwards x times for (int i = 0; i < amount; ++i) { - rng = abs(((get(i) - center + 540) % 360) - 180); + value = get(i); + if (value == INT_MIN) { + continue; + } + rng = abs(((value - center + 540) % 360) - 180); if (rng > maxRng) maxRng = rng; } @@ -231,20 +198,6 @@ public: } return maxRng; } - - void mvStart(int delta) - // Move the start index of buffer forward by positions -> virtually delete oldest data entries - { - first = (first + delta) % SIZE; - if (count > delta) - count -= delta; - else - count = 0; - if (first == 0) - last = count; - else - last = first - 1; - } }; // **************************************************************** @@ -253,8 +206,8 @@ class PageWindPlot : public Page { bool keylock = false; // Keylock // int16_t lp = 80; // Pointer length char chrtMode = 'D'; // Chart mode: 'D' for TWD, 'S' for TWS, 'B' for both06121990 - int dataInterv = 1; // Update interval for wind history chart: - // (1)|(2)|(3)|(4) seconds for approx. 4, 8, 12, 16 min. history chart + 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: @@ -289,14 +242,14 @@ public: // Set interval for wind history chart update time if (key == 2) { - if (dataInterv == 1) { - dataInterv = 2; - } else if (dataInterv == 2) { - dataInterv = 3; - } else if (dataInterv == 3) { - dataInterv = 4; + if (dataIntv == 1) { + dataIntv = 2; + } else if (dataIntv == 2) { + dataIntv = 3; + } else if (dataIntv == 3) { + dataIntv = 4; } else { - dataInterv = 1; + dataIntv = 1; } return 0; // Commit the key } @@ -351,7 +304,7 @@ public: int numWndValues; // number of wind values available for current interval selection static int linesToShow; // current number of lines to display on chart static int bufStart; // 1st data value in buffer to show - static int oldDataInterv; // remember recent user selection of data interval + static int oldDataIntv; // remember recent user selection of data interval static int newDate; // indicates for higher time intervals that new date is available static int wndCenter = INT_MIN; // chart wind center value position; init value indicates that wndCenter is not set yet @@ -386,7 +339,7 @@ public: twdValue = 0; bufStart = 0; linesToShow = 0; - oldDataInterv = dataInterv; + oldDataIntv = dataIntv; newDate = 0; } @@ -442,21 +395,22 @@ public: } // Identify buffer sizes and buffer position to print on the chart - intvBufSize = cHeight * dataInterv; + intvBufSize = cHeight * dataIntv; count = windDirHstry.getSize(); numWndValues = min(count, intvBufSize); newDate++; - if (dataInterv != oldDataInterv) { - linesToShow = min(numWndValues / dataInterv, max(0, cHeight - 40)); - bufStart = max(0, (count - (linesToShow * dataInterv))); - oldDataInterv = dataInterv; - } else if (newDate >= dataInterv) { - linesToShow = min(numWndValues / dataInterv, linesToShow + 1); + if (dataIntv != oldDataIntv) { + linesToShow = min(numWndValues / dataIntv, max(0, cHeight - 40)); + bufStart = max(0, (count - (linesToShow * dataIntv))); + oldDataIntv = dataIntv; + } else if (newDate >= dataIntv) { + linesToShow = min(numWndValues / dataIntv, linesToShow + 1); newDate = 0; } - if (count == bufSize) + if (count == bufSize) { bufStart--; // show the latest wind values in buffer; keep 1st value constant in a rolling buffer when new data is added - LOG_DEBUG(GwLog::ERROR, "PageWindPlot DateSet: TWD: %d, count: %d, bufStart: %d, linesToShow: %d, newDate: %d", twdValue, count, bufStart, linesToShow, newDate); + } + LOG_DEBUG(GwLog::ERROR, "PageWindPlot Dataset: TWD: %d, count: %d, intvBufSize: %d, numWndValues: %d, bufStart: %d, linesToShow: %d, newDate: %d", twdValue, count, intvBufSize, numWndValues, bufStart, linesToShow, newDate); if (bvalue == NULL) return; @@ -466,14 +420,14 @@ public: // initialize chart range values if (wndCenter == INT_MIN) { - wndCenter = windDirHstry.get(max(0, numWndValues - intvBufSize)); // get 1st value of current data interval + wndCenter = max(0, windDirHstry.get(numWndValues - intvBufSize)); // get 1st value of current data interval wndCenter = (int((wndCenter + (wndCenter >= 0 ? 5 : -5)) / 10) * 10) % 360; // Set new center value; round to nearest 10 degree value; 360° -> 0° diffRng = dfltRng; chrtRng = dfltRng; } else { // check and adjust range between left, center, and right chart limit diffRng = windDirHstry.getRng(wndCenter, numWndValues); - diffRng = (diffRng < 0 ? 0 : diffRng); // If no data in buffer, set range to 0 + diffRng = (diffRng == INT_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 @@ -484,12 +438,10 @@ public: LOG_DEBUG(GwLog::ERROR, "PageWindPlot Range. wndCenter: %d, numWndValues: %d, min: %d, max: %d, diffrng: %d, chrtRng: %d ", wndCenter, numWndValues, debugMin, debugMax, diffRng, chrtRng); } chrtScl = float(width) / float(chrtRng) / 2.0; // Chart scale: pixels per degree - // wndCenter = windDirHstry.get(max(0, numWndValues - intvBufSize)); // Get 1st value of current data interval - // wndCenter = int((wndCenter + (wndCenter >= 0 ? 5 : -5)) / 10) * 10; // Round to nearest 10 degree value wndLeft = wndCenter - chrtRng; if (wndLeft < 0) wndLeft += 360; - wndRight = wndCenter + chrtRng - 1; + wndRight = (chrtRng < 180 ? wndCenter + chrtRng : wndCenter + chrtRng - 1); if (wndRight >= 360) wndRight -= 360; // LOG_DEBUG(GwLog::DEBUG, "PageWindPlot dataValue[0]: %f, windValue: %d, count: %d, diffRng: %d, chartRng: %d, Center: %d, scale: %f", double(dataValue[0] * radToDeg), @@ -502,9 +454,6 @@ public: getdisplay().setPartialWindow(0, 0, width, height); // Set partial update getdisplay().setTextColor(commonData->fgcolor); - // Horizontal top line for orientation -> to be deleted - // getdisplay().fillRect(0, 20, width, 1, commonData->fgcolor); - // chart lines getdisplay().fillRect(0, yOffset, width, 2, commonData->fgcolor); getdisplay().fillRect(xCenter, yOffset, 1, cHeight, commonData->fgcolor); @@ -514,21 +463,22 @@ public: getdisplay().setFont(&Ubuntu_Bold12pt7b); getdisplay().setCursor(xCenter - 88, yOffset - 3); getdisplay().print("TWD"); // Wind name - getdisplay().setCursor(xCenter - 20, yOffset - 3); + // getdisplay().setCursor(xCenter - 20, yOffset - 3); snprintf(sWndLbl, 4, "%03d", (wndCenter < 0) ? (wndCenter + 360) : wndCenter); - getdisplay().print(sWndLbl); // Wind center value - getdisplay().drawCircle(xCenter + 25, yOffset - 16, 2, commonData->fgcolor); // symbol - getdisplay().drawCircle(xCenter + 25, yOffset - 16, 3, commonData->fgcolor); // symbol + drawTextCenter(xCenter, yOffset - 11, sWndLbl); + // getdisplay().print(sWndLbl); // Wind center value + getdisplay().drawCircle(xCenter + 25, yOffset - 17, 2, commonData->fgcolor); // symbol + getdisplay().drawCircle(xCenter + 25, yOffset - 17, 3, commonData->fgcolor); // symbol getdisplay().setCursor(1, yOffset - 3); snprintf(sWndLbl, 4, "%03d", (wndLeft < 0) ? (wndLeft + 360) : wndLeft); getdisplay().print(sWndLbl); // Wind left value - getdisplay().drawCircle(46, yOffset - 16, 2, commonData->fgcolor); // symbol - getdisplay().drawCircle(46, yOffset - 16, 3, commonData->fgcolor); // symbol - getdisplay().setCursor(width - 51, yOffset - 3); + getdisplay().drawCircle(46, yOffset - 17, 2, commonData->fgcolor); // symbol + getdisplay().drawCircle(46, yOffset - 17, 3, commonData->fgcolor); // symbol + getdisplay().setCursor(width - 50, yOffset - 3); snprintf(sWndLbl, 4, "%03d", (wndRight < 0) ? (wndRight + 360) : wndRight); getdisplay().print(sWndLbl); // Wind right value - getdisplay().drawCircle(width - 5, yOffset - 16, 2, commonData->fgcolor); // symbol - getdisplay().drawCircle(width - 5, yOffset - 16, 3, commonData->fgcolor); // symbol + getdisplay().drawCircle(width - 5, yOffset - 17, 2, commonData->fgcolor); // symbol + getdisplay().drawCircle(width - 5, yOffset - 17, 3, commonData->fgcolor); // symbol // Draw wind values in chart //*********************************************************** @@ -536,10 +486,11 @@ public: // if (count == bufSize) // bufStart--; // show the latest wind values in buffer; keep 1st value constant in a rolling buffer for (int i = 0; i < linesToShow; i++) { - chrtVal = windDirHstry.get(bufStart + (i * dataInterv)); // show the latest wind values in buffer; keep 1st value constant in a rolling buffer + chrtVal = windDirHstry.get(bufStart + (i * dataIntv)); // show the latest wind values in buffer; keep 1st value constant in a rolling buffer x = ((chrtVal - wndLeft + 360) % 360) * chrtScl; y = yOffset + cHeight - i; // Position in chart area - LOG_DEBUG(GwLog::ERROR, "PageWindPlot Chart: i: %d, chrtVal: %d, chrtPrevVal: %d, bufStart: %d count: %d, linesToShow: %d", i, chrtVal, chrtPrevVal, bufStart, count, linesToShow); + if (i > linesToShow - 15) + LOG_DEBUG(GwLog::ERROR, "PageWindPlot Chart: i: %d, chrtVal: %d, chrtPrevVal: %d, bufStart: %d count: %d, linesToShow: %d", i, chrtVal, chrtPrevVal, bufStart, count, linesToShow); if (i == 0) { prevX = x; // just a dot for 1st chart point @@ -570,12 +521,14 @@ public: if (i == (cHeight - 1)) { // Reaching chart area top end () linesToShow -= min(40, cHeight); // free top 40 lines of chart for new values - bufStart = max(0, count - (linesToShow * dataInterv)); // next start value in buffer to show + bufStart = max(0, count - (linesToShow * dataIntv)); // next start value in buffer to show // windDirHstry.mvStart(); // virtually delete 40 values from buffer if ((windDirHstry.getMin(numWndValues) > wndCenter) || (windDirHstry.getMax(numWndValues) < wndCenter)) { // Check if all wind value are left or right of center value -> optimize chart range int mid = windDirHstry.getMid(numWndValues); - wndCenter = int((mid + (mid >= 0 ? 5 : -5)) / 10) * 10; // Set new center value; round to nearest 10 degree value + if (mid != INT_MIN) { + wndCenter = int((mid + (mid >= 0 ? 5 : -5)) / 10) * 10; // Set new center value; round to nearest 10 degree value + } } LOG_DEBUG(GwLog::ERROR, "PageWindPlot FreeTop: cHeight: %d, LinesToShow: %d, numWndValues: %d, wndCenter: %d, bufStart: %d", cHeight, linesToShow, numWndValues, wndCenter, bufStart); break; @@ -637,13 +590,16 @@ public: int yPos; getdisplay().setFont(&Ubuntu_Bold8pt7b); for (int i = 1; i <= 3; i++) { - yPos = yOffset + (i * 60); // Y position for label -// yPos = yOffset + cHeight - (i * 60); // Y position for label + if (numWndValues < intvBufSize) { + // chart initially filled from botton to top -> revert minute labels + yPos = yOffset + cHeight - (i * 60); + } else { + yPos = yOffset + (i * 60); + } getdisplay().fillRect(0, yPos, width, 1, commonData->fgcolor); - getdisplay().fillRect(0, yPos - 9, 26, 16, commonData->bgcolor); // Clear small area to remove potential chart lines - // getdisplay().fillRect(0, yPos, 8, 2, commonData->fgcolor); + getdisplay().fillRect(0, yPos - 8, 26, 16, commonData->bgcolor); // Clear small area to remove potential chart lines getdisplay().setCursor(1, yPos + 4); - snprintf(sWndYAx, 4, "%3d", i * dataInterv * -1); + snprintf(sWndYAx, 4, "%3d", i * dataIntv * -1); getdisplay().print(sWndYAx); // Wind value label } diff --git a/lib/obp60task/PageWindPlot.cpp.txt b/lib/obp60task/PageWindPlot.cpp.txt deleted file mode 100644 index e0b6cc0..0000000 --- a/lib/obp60task/PageWindPlot.cpp.txt +++ /dev/null @@ -1,477 +0,0 @@ -#if defined BOARD_OBP60S3 || defined BOARD_OBP40S3 - -#include "BoatDataCalibration.h" -#include "OBP60Extensions.h" -#include "Pagedata.h" - -#include - -// **************************************************************** -class wndHistory { - // provides a FiFo circular buffer to store wind history values -private: - int SIZE; - std::vector buffer; - int first = 0; // points to the first valid element - int head = 0; // points to the next insertion index - int count = 0; // number of valid elements - -public: - bool begin(int size) - // specifies buffer size - { - if (size <= 0 || size > 1000) { - return false; - } - SIZE = size; - buffer.resize(size); // allocate buffer - return true; - } - - void add(int value) - // Add a new value - { - if (value > 180) { - value -= 360; // Normalize value to -180..180 to make min/max calculations working - } - buffer[head] = value; - head = (head + 1) % SIZE; - if (count < SIZE) { - count++; - } else { - first = head - 1; // When buffer is full, first points to the oldest value - } - } - - int get(int index) const - // Get value by index in [-180..180 deg] format (0 = oldest, count-1 = newest) - { - int realIndex; - - if (index < 0 || index >= count) { - return -1; // Invalid index - } - realIndex = (first + index) % SIZE; - return buffer[realIndex]; - // } - } - - int get(int index, int deg) const - // Get value by index in [-180..180 deg] or [0..360 deg] format (0 = oldest, count-1 = newest) - { - switch (deg) { - case 180: - // Return value in [-180..180 deg] format - return get(index); - case 360: { - // Return value in [0..360 deg] format - int value = get(index); - if (value < 0) { - value += 360; - }; - return value; - } - default: - // Return value in [-180..180 deg] format - return -1; - } - } - - int getSize() const - // Get number of valid elements - { - return count; - } - - int getMin() const - // Get minimum value in the buffer - { - if (count == 0) { - return -1; // Buffer is empty - } else if (first + count <= SIZE) { - // No wrap-around - return *std::min_element(buffer.begin() + first, buffer.begin() + first + count); - } else { - // Wrap-around: check [first, end) and [begin, (first+count)%SIZE) - int min1 = *std::min_element(buffer.begin() + first, buffer.end()); - int min2 = *std::min_element(buffer.begin(), buffer.begin() + ((first + count) % SIZE)); - return std::min(min1, min2); - } - } - - int getMax() const - // Get maximum value in the buffer - { - if (count == 0) { - return -1; // Buffer is empty - } else if (first + count <= SIZE) { - // No wrap-around - return *std::max_element(buffer.begin() + first, buffer.begin() + first + count); - } else { - // Wrap-around: check [first, end) and [begin, (first+count)%SIZE) - int max1 = *std::max_element(buffer.begin() + first, buffer.end()); - int max2 = *std::max_element(buffer.begin(), buffer.begin() + ((first + count) % SIZE)); - return std::max(max1, max2); - } - } - - int getMid() const - // Get middle value in the buffer - { - if (count == 0) { - return -1; // Buffer is empty - } - return (getMin() + getMax()) / 2; - } - - void mvStart(int start) - // Move the start index of buffer forward by positions - { - first = (first + start) % SIZE; - if (count > start) - count -= start; - else - count = 0; - } - - // TWA, TWS, HDM, AWA, AWS, STW - bool calcTWD(int* twd, float twa, float tws, float hdm, float awa, float aws, float stw) - // Calculate TWD based on other boat data values - { - if (count == 0) { - return false; // Buffer is empty - } - - int twdMin = getMin(); - int twdMax = getMax(); - int twdMid = getMid(); - - // Calculate TWD based on TWA and HDM - return true; - } -}; - -// **************************************************************** -class PageWindPlot : public Page { - - bool keylock = false; // Keylock -// int16_t lp = 80; // Pointer length - char mode = 'D'; // Chart mode: 'D' for TWD, 'S' for TWS - int updTime = 1; // Update interval for wind history chart: - // (1)|(2)|(3)|(5) seconds for 3, 7, 10, 15 min. history chart - -public: - PageWindPlot(CommonData& common) - { - commonData = &common; - common.logger->logDebug(GwLog::LOG, "Instantiate PageWindPlot"); - } - - virtual void setupKeys() - { - Page::setupKeys(); - commonData->keydata[0].label = "MODE"; - commonData->keydata[1].label = "INTV"; - } - - // Key functions - virtual int handleKey(int key) - { - // Set chart mode TWD | TWS - if (key == 1) { - if (mode == 'D') { - mode = 'S'; - } else { - mode = 'D'; - } - // setupKeys(); // Update key labels - return 0; // Commit the key - } - - // Set interval for wind history chart update time - if (key == 2) { - if (updTime == 1) { - updTime = 2; - } else if (updTime == 2) { - updTime = 3; - } else if (updTime == 3) { - updTime = 5; - } else { - updTime = 1; - } - setupKeys(); // Update key labels - return 0; // Commit the key - } - - // Keylock function - if (key == 11) { // Code for keylock - commonData->keylock = !commonData->keylock; - return 0; // Commit the key - } - return key; - } - - virtual void displayPage(PageData& pageData) - { - GwConfigHandler* config = commonData->config; - GwLog* logger = commonData->logger; - - static wndHistory windValues; // Circular buffer to store wind values - - GwApi::BoatValue* bvalue; - const int numCfgValues = 9; - String dataName[numCfgValues]; - double dataValue[numCfgValues]; - bool dataValid[numCfgValues]; - String dataSValue[numCfgValues]; - String dataUnit[numCfgValues]; - String dataSValueOld[numCfgValues]; - String dataUnitOld[numCfgValues]; - - int width = getdisplay().width(); // Get screen width - int height = getdisplay().height(); // Get screen height - int cHeight = height - 98; // height of chart area - // int cHeight = 80; // height of chart area - int xCenter = width / 2; // Center of screen in x direction - static const int yOffset = 76; // Offset for y coordinates of chart area - static const float radToDeg = 180.0 / M_PI; // Conversion factor from radians to degrees - - static int wndCenter = -400; // chart wind center value position; init value indicates that wndCenter is not set yet - 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 bool plotShift = false; // Flag to indicate if chartplot data have been shifted - - int x, y; // x and y coordinates for drawing - static int prevX, prevY; // Last x and y coordinates for drawing - static int chrtScl; // Scale for wind values in pixels per degree - int chrtVal; // Current wind value - static int chrtPrevVal; // Last wind value in chart area for check if value crosses 180 degree line - int count; // index for next wind value in buffer - - static int updCnt = 0; // update counter for wind history chart in seconds - bool isTimeforUpd = true; // Flag to indicate if it is time for chart update - - if (windValues.getSize() == 0) { - if (!windValues.begin(cHeight)) { - logger->logDebug(GwLog::ERROR, "Failed to initialize wind values buffer"); - return; - } - } - - LOG_DEBUG(GwLog::LOG, "Display page WindPlot"); - - if (updCnt < updTime) { - // Next update interval not reached yet - updCnt++; - isTimeforUpd = false; - } else { - isTimeforUpd = true; - updCnt = 1; // Data update is now; reset counter - } - - // 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); - - // Read boatdata values for TWD, TWA, TWS, HDM, AWA, AWS, STW, if available - for (int i = 0; i < numCfgValues; i++) { - bvalue = pageData.values[i]; - dataName[i] = xdrDelete(bvalue->getName()); - dataName[i] = dataName[i].substring(0, 6); // String length limit for value name - dataValue[i] = bvalue->value; // Value as double in SI unit - calibrationData.calibrateInstance(dataName[i], bvalue, logger); // Check if boat data value is to be calibrated - dataValid[i] = bvalue->valid; - dataSValue[i] = formatValue(bvalue, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places - dataUnit[i] = formatValue(bvalue, *commonData).unit; - if (dataValid[i]) { - dataSValueOld[i] = dataSValue[i]; // Save old value - dataUnitOld[i] = dataUnit[i]; // Save old unit - } - } - - // Optical warning by limit violation (unused) - if (String(flashLED) == "Limit Violation") { - setBlinkingLED(false); - setFlashLED(false); - } - - // Logging boat values - if (bvalue == NULL) - return; - LOG_DEBUG(GwLog::DEBUG, "PageWindPlot, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, cnt: %d, valid0: %d", dataName[0].c_str(), dataValue[0], - dataName[1].c_str(), dataValue[1], dataName[2].c_str(), dataValue[2], dataName[3].c_str(), dataValue[3], dataName[4].c_str(), dataValue[4], - dataName[5].c_str(), dataValue[5], dataName[6].c_str(), dataValue[6], dataName[7].c_str(), dataValue[7], dataName[8].c_str(), dataValue[8], count, dataValid[0]); - - // Store TWD wind value in buffer - int twdValue = 0; - if (dataValid[0]) { // TWD data existing - twdValue = int((dataValue[0] * radToDeg) + 0.5); // Read TWD value in degrees and round to integer - } else { - // Try to calculate TWD value from other data, if available - // dataValid[0] = windValues.calcTWD(&twdValue, dataValue[1], dataValue[2], dataValue[3], dataValue[4], dataValue[5], dataValue[6]); - } - if (dataValid[0]) { - windValues.add(twdValue); - count = windValues.getSize(); // Get number of valid elements in buffer; maximum is cHeight - LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: Data 0 valid - dataValue[0]: %f, TWD: %d, cnt: %d, valid0: %d", dataValue[0] * radToDeg, twdValue, count, dataValid[0]); - } - - // initialize chart range values - if (wndCenter == -400) { - wndCenter = windValues.get(0); - wndCenter = int((wndCenter + (wndCenter >= 0 ? 5 : -5)) / 10) * 10; // Set new center value; round to nearest 10 degree value - diffRng = 30; - chrtRng = 30; - LOG_DEBUG(GwLog::DEBUG, "PageWindPlot initialized. wndCenter: %d, chrtRng: %d ", wndCenter, chrtRng); - } else { - diffRng = max(abs(((windValues.getMax() - wndCenter + 540) % 360) - 180), abs(((windValues.getMin() - wndCenter + 540) % 360) - 180)); // check necessary range size - - if (diffRng < -180) { - // wind value crosses 180 degree line, so we need to adjust the chart range - // ********************** hier an der Skalierung arbeiten ******************** - - } - - 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(30, int((diffRng + (diffRng >= 0 ? 9 : -1)) / 10) * 10); // Round up to next 10 degree value - } - } - wndLeft = wndCenter - chrtRng; - if (wndLeft < -180) - wndLeft += 360; - wndRight = wndCenter + chrtRng; - if (wndRight >= 180) - wndRight -= 360; - LOG_DEBUG(GwLog::LOG, "PageWindPlot dataValue[0]: %f, windValue: %d, count: %d, diffRng: %d, chartRng: %d", float(dataValue[0] * radToDeg), windValues.get(count - 1), count, diffRng, chrtRng); - - // Draw page - //*********************************************************** - - // Set display in partial refresh mode - getdisplay().setPartialWindow(0, 0, width, height); // Set partial update - getdisplay().setTextColor(commonData->fgcolor); - - // Horizontal top line for orientation -> to be deleted - // getdisplay().fillRect(0, 20, width, 1, commonData->fgcolor); - - // Show TWS value on top right - getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b); - getdisplay().setCursor(252, 52); - getdisplay().print(dataSValue[2]); // Value - getdisplay().setFont(&Ubuntu_Bold12pt7b); - getdisplay().setCursor(334, 38); - getdisplay().print(dataName[2]); // Name - getdisplay().setFont(&Ubuntu_Bold8pt7b); - getdisplay().setCursor(330, 53); - getdisplay().print(" "); - if (holdvalues == false) { - getdisplay().print(dataUnit[2]); // Unit - } else { - getdisplay().print(dataUnitOld[2]); // Unit - } - - // chart lines - getdisplay().fillRect(0, yOffset, width, 2, commonData->fgcolor); - getdisplay().fillRect(xCenter - 1, yOffset, 2, cHeight, commonData->fgcolor); - - // chart labels - char sWndLbl[4]; // Wind label - getdisplay().setFont(&Ubuntu_Bold10pt7b); - getdisplay().setCursor(xCenter - 80, yOffset - 3); - getdisplay().print("TWD"); // Wind name - getdisplay().setCursor(xCenter - 16, yOffset - 3); - snprintf(sWndLbl, 4, "%03d", (wndCenter < 0) ? (wndCenter + 360) : wndCenter); - getdisplay().print(sWndLbl); // Wind center value - getdisplay().drawCircle(xCenter + 21, 63, 2, commonData->fgcolor); // symbol - getdisplay().drawCircle(xCenter + 21, 63, 3, commonData->fgcolor); // symbol - getdisplay().setCursor(2, yOffset - 3); - snprintf(sWndLbl, 4, "%03d", (wndLeft < 0) ? (wndLeft + 360) : wndLeft); - getdisplay().print(sWndLbl); // Wind left value - getdisplay().drawCircle(39, 63, 2, commonData->fgcolor); // symbol - getdisplay().drawCircle(39, 63, 3, commonData->fgcolor); // symbol - getdisplay().setCursor(width - 43, yOffset - 3); - snprintf(sWndLbl, 4, "%03d", (wndRight < 0) ? (wndRight + 360) : wndRight); - getdisplay().print(sWndLbl); // Wind right value - getdisplay().drawCircle(width - 6, 63, 2, commonData->fgcolor); // symbol - getdisplay().drawCircle(width - 6, 63, 3, commonData->fgcolor); // symbol - - // Draw wind values in chart - //*********************************************************** - if ((dataValid[0] || holdvalues || simulation == true) && isTimeforUpd) { - - prevX = xCenter + ((windValues.get(0) - wndCenter) * chrtScl); - prevY = yOffset + cHeight; // Reset lastY to bottom of chart - - for (int i = 0; i < count; i++) { - chrtVal = windValues.get(i); // Get value from buffer - if (abs(chrtVal - chrtPrevVal) > 180) { - // Large value jump, so probably crosses -180°/180° degree boundary } - break; // Stop drawing and adjust chart center at next page update - } - chrtScl = xCenter / chrtRng; // current scale: pixels per degree - x = xCenter + ((chrtVal - wndCenter) * chrtScl); // Scale to chart width - // x = xCenter + ((((chrtVal - wndCenter + 540) % 360) - 180) * chrtScl); // Scale to chart width - y = yOffset + cHeight - i; // Position in chart area - // LOG_DEBUG(GwLog::DEBUG, "PageWindPlot: chrtVal: %d, wndCenter: %d, chrtScl: %d, x: %d, y: %d", chrtVal, wndCenter, chrtScl, x, y); - - // Draw line with 2 pixels width; make sure vertical line are drawn correctly - getdisplay().drawLine(prevX, prevY, x, y, commonData->fgcolor); - getdisplay().drawLine(prevX, prevY - 1, (x != prevX) ? x : x - 1, (x != prevX) ? y - 1 : y, commonData->fgcolor); - chrtPrevVal = chrtVal; - prevX = x; - prevY = y; - if (i == (cHeight - 1)) { - // Reaching chart area top end - windValues.mvStart(40); - // virtually delete 40 values from buffer - if ((windValues.getMin() > wndCenter) || (windValues.getMax() < wndCenter)) { - int mid = windValues.getMid(); - wndCenter = int((mid + (mid >= 0 ? 5 : -5)) / 10) * 10; // Set new center value; round to nearest 10 degree value - } - LOG_DEBUG(GwLog::DEBUG, "PageWindPlot Shift: Min: %d, Max: %d, Mid: %d, new Center: %d", windValues.getMin(), windValues.getMax(), windValues.getMid(), wndCenter); - break; - } - } - LOG_DEBUG(GwLog::DEBUG, "PageWindPlot End: prevX: %d, prevY: %d, loop-Counter: %d", prevX, prevY, count); - - } else { - // No valid data available - LOG_DEBUG(GwLog::LOG, "PageWindPlot: No valid data available"); - getdisplay().setFont(&Ubuntu_Bold10pt7b); - getdisplay().setCursor(xCenter - 60, height / 2); - getdisplay().print("No sensor data"); - } - - // Update display - getdisplay().nextPage(); // Partial update (fast) - }; -}; - -static Page* createPage(CommonData& common) -{ - return new PageWindPlot(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 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", "TWA", "TWS", "HDM", "AWA", "AWS", "STW", "COG", "SOG" }, // Bus values we need in the page - true // Show display header on/off -); - -#endif From 1f90cefbd612ed9dc8473275fc491fcb6e179884 Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Tue, 24 Jun 2025 00:05:15 +0200 Subject: [PATCH 17/26] Implement OBPRingBuffer class and adjust PageWindPlot accordingly --- .vscode/settings.json | 7 +- lib/obp60task/OBPRingBuffer.h | 52 +++++ lib/obp60task/OBPRingBuffer.tpp | 356 ++++++++++++++++++++++++++++++++ lib/obp60task/OBPSensorTask.cpp | 1 + lib/obp60task/PageWindPlot.cpp | 19 +- 5 files changed, 424 insertions(+), 11 deletions(-) create mode 100644 lib/obp60task/OBPRingBuffer.h create mode 100644 lib/obp60task/OBPRingBuffer.tpp diff --git a/.vscode/settings.json b/.vscode/settings.json index cad7657..2ef566a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,8 @@ { - "cmake.configureOnOpen": false + "cmake.configureOnOpen": false, + "files.associations": { + "stdexcept": "cpp", + "limits": "cpp", + "functional": "cpp" + } } \ No newline at end of file diff --git a/lib/obp60task/OBPRingBuffer.h b/lib/obp60task/OBPRingBuffer.h new file mode 100644 index 0000000..d373b0c --- /dev/null +++ b/lib/obp60task/OBPRingBuffer.h @@ -0,0 +1,52 @@ +#pragma once +#include +#include +#include +#include + +template +class RingBuffer { +private: + std::vector buffer; + size_t capacity; + size_t head; // Points to the next insertion position + size_t first; // Points to the first (oldest) valid element + size_t last; // Points to the last (newest) valid element + size_t count; // Number of valid elements currently in buffer + bool is_Full; // Indicates that all buffer elements are used and ringing is in use + T MIN_VAL; // lowest possible value of buffer + T MAX_VAL; // highest possible value of buffer of type + + // metadata for buffer + int updFreq; // Update frequency in milliseconds + int smallest; // Value range of buffer: smallest value + int biggest; // Value range of buffer: biggest value + +public: + RingBuffer(size_t size); + void add(const T& value); // Add a new value to buffer + T get(size_t index) const; // Get value at specific position (0-based index from oldest to newest) + T getFirst() const; // Get the first (oldest) value in buffer + T getLast() const; // Get the last (newest) value in buffer + T getMin() const; // Get the lowest value in buffer + T getMin(size_t amount) const; // Get minimum value of the last values of buffer + T getMax() const; // Get the highest value in buffer + T getMax(size_t amount) const; // Get maximum value of the last values of buffer + T getMid() const; // Get mid value between and value in buffer + T getMid(size_t amount) const; // Get mid value between and value of the last values of buffer + T getRng(T center, size_t amount) const; // Get maximum difference of last of buffer values to center value + T getMedian() const; // Get the median value in buffer + T getMedian(size_t amount) const; // Get the median value of the last values of buffer + size_t getCapacity() const; // Get the buffer capacity (maximum size) + size_t getCurrentSize() const; // Get the current number of elements in buffer + bool isEmpty() const; // Check if buffer is empty + bool isFull() const; // Check if buffer is full + T getMinVal(); // Get lowest possible value for buffer; used for initialized buffer data + T getMaxVal(); // Get highest possible value for buffer + void clear(); // Clear buffer + T operator[](size_t index) const; + std::vector getAllValues() const; // Operator[] for convenient access (same as get()) + +}; + +#include "OBPRingBuffer.tpp" \ No newline at end of file diff --git a/lib/obp60task/OBPRingBuffer.tpp b/lib/obp60task/OBPRingBuffer.tpp new file mode 100644 index 0000000..b966bec --- /dev/null +++ b/lib/obp60task/OBPRingBuffer.tpp @@ -0,0 +1,356 @@ +#include "OBPRingBuffer.h" + +template +RingBuffer::RingBuffer(size_t size) + : capacity(size) + , head(0) + , first(0) + , last(0) + , count(0) + , is_Full(false) +{ + if (size == 0) { + // return false; + } + + MIN_VAL = std::numeric_limits::lowest(); + MAX_VAL = std::numeric_limits::max(); + buffer.resize(size, MIN_VAL); + + // return true; +} + +// Add a new value to the buffer +template +void RingBuffer::add(const T& value) +{ + buffer[head] = value; + last = head; + + if (is_Full) { + first = (first + 1) % capacity; // Move pointer to oldest element when overwriting + } else { + count++; + if (count == capacity) { + is_Full = true; + } + } + + head = (head + 1) % capacity; +} + +// Get value at specific position (0-based index from oldest to newest) +template +T RingBuffer::get(size_t index) const +{ + if (isEmpty()) { + throw std::runtime_error("Buffer is empty"); + } + if (index < 0 || index >= count) { + return MIN_VAL; + // throw std::out_of_range("Index out of range"); + } + + size_t realIndex = (first + index) % capacity; + return buffer[realIndex]; +} + +// Operator[] for convenient access (same as get()) +template +T RingBuffer::operator[](size_t index) const +{ + return get(index); +} + +// Get the first (oldest) value in the buffer +template +T RingBuffer::getFirst() const +{ + if (isEmpty()) { + return MIN_VAL; + // throw std::runtime_error("Buffer is empty"); + } + return buffer[first]; +} + +// Get the last (newest) value in the buffer +template +T RingBuffer::getLast() const +{ + if (isEmpty()) { + return MIN_VAL; + // throw std::runtime_error("Buffer is empty"); + } + return buffer[last]; +} + +// Get the lowest value in the buffer +template +T RingBuffer::getMin() const +{ + if (isEmpty()) { + return MIN_VAL; + // throw std::runtime_error("Buffer is empty"); + } + + T minVal = get(first); + T value; + for (size_t i = 0; i < count; i++) { + value = get(i); + if (value < minVal) { + minVal = value; + } + } + return minVal; +} + +// Get minimum value of the last values of buffer +template +T RingBuffer::getMin(size_t amount) const +{ + if (isEmpty() || amount <= 0) { + return MIN_VAL; + // throw std::runtime_error("Buffer is empty"); + } + if (amount > count) + amount = count; + + T minVal = get(last); + T value; + for (size_t i = 0; i < amount; i++) { + value = get((last + capacity - i) % capacity); + if (value < minVal && value != MIN_VAL) { + minVal = value; + } + } + return minVal; +} + +// Get the highest value in the buffer +template +T RingBuffer::getMax() const +{ + if (isEmpty()) { + return MIN_VAL; + // throw std::runtime_error("Buffer is empty"); + } + + T maxVal = get(first); + T value; + for (size_t i = 0; i < count; i++) { + value = get(i); + if (value > maxVal) { + maxVal = value; + } + } + return maxVal; +} + +// Get maximum value of the last values of buffer +template +T RingBuffer::getMax(size_t amount) const +{ + if (isEmpty() || amount <= 0) { + return MIN_VAL; + // throw std::runtime_error("Buffer is empty"); + } + if (amount > count) + amount = count; + + T maxVal = get(last); + T value; + for (size_t i = 0; i < amount; i++) { + value = get((last + capacity - i) % capacity); + if (value > maxVal && value != MIN_VAL) { + maxVal = value; + } + } + return maxVal; +} + +// Get mid value between and value in the buffer +template +T RingBuffer::getMid() const +{ + if (isEmpty()) { + return MIN_VAL; + // throw std::runtime_error("Buffer is empty"); + } + + return (getMin() + getMax()) / static_cast(2); +} + +// Get mid value between and value of the last values of buffer +template +T RingBuffer::getMid(size_t amount) const +{ + if (isEmpty() || amount <= 0) { + return MIN_VAL; + // throw std::runtime_error("Buffer is empty"); + } + + if (amount > count) + amount = count; + + return (getMin(amount) + getMax(amount)) / static_cast(2); +} + +// ******************* works for wind direction only -> move out of here ******************************* +// Get maximum difference of last of buffer values to center value +template +T RingBuffer::getRng(T center, size_t amount) const +{ + if (isEmpty() || amount <= 0) { + return MIN_VAL; + // throw std::runtime_error("Buffer is empty"); + } + if (amount > count) + amount = count; + + T value = 0; + T rng = 0; + T maxRng = MIN_VAL; + // Start from the newest value (last) and go backwards x times + for (size_t i = 0; i < amount; i++) { + value = get((last + capacity - i) % capacity); + if (value == MIN_VAL) { + continue; + } + rng = abs(((value - center + 540) % 360) - 180); + if (rng > maxRng) + maxRng = rng; + } + if (maxRng > 180) { + maxRng = 180; + } + return maxRng; +} + +// Get the median value in the buffer +template +T RingBuffer::getMedian() const +{ + if (isEmpty()) { + return MIN_VAL; + // throw std::runtime_error("Buffer is empty"); + } + + // Create a temporary vector with current valid elements + std::vector temp; + temp.reserve(count); + + for (size_t i = 0; i < count; i++) { + temp.push_back(get(i)); + } + + // Sort to find median + std::sort(temp.begin(), temp.end()); + + if (count % 2 == 1) { + // Odd number of elements + return temp[count / 2]; + } else { + // Even number of elements - return average of middle two + // Note: For integer types, this truncates. For floating point, it's exact. + return (temp[count / 2 - 1] + temp[count / 2]) / 2; + } +} + +// Get the median value of the last values of buffer +template +T RingBuffer::getMedian(size_t amount) const +{ + if (isEmpty() || amount <= 0) { + return MIN_VAL; + // throw std::runtime_error("Buffer is empty"); + } + if (amount > count) + amount = count; + + // Create a temporary vector with current valid elements + std::vector temp; + temp.reserve(amount); + + for (size_t i = 0; i < amount; i++) { + temp.push_back(get(i)); + } + + // Sort to find median + std::sort(temp.begin(), temp.end()); + + if (amount % 2 == 1) { + // Odd number of elements + return temp[amount / 2]; + } else { + // Even number of elements - return average of middle two + // Note: For integer types, this truncates. For floating point, it's exact. + return (temp[amount / 2 - 1] + temp[amount / 2]) / 2; + } +} + +// Get the buffer capacity (maximum size) +template +size_t RingBuffer::getCapacity() const +{ + return capacity; +} + +// Get the current number of elements in the buffer +template +size_t RingBuffer::getCurrentSize() const +{ + return count; +} + +// Check if buffer is empty +template +bool RingBuffer::isEmpty() const +{ + return count == 0; +} + +// Check if buffer is full +template +bool RingBuffer::isFull() const +{ + return is_Full; +} + +// Get lowest possible value for buffer; used for initialized buffer data +template +T RingBuffer::getMinVal() +{ + return MIN_VAL; +} + +// Get highest possible value for buffer +template +T RingBuffer::getMaxVal() +{ + return MAX_VAL; +} + +// Clear buffer +template +void RingBuffer::clear() +{ + head = 0; + first = 0; + last = 0; + count = 0; + is_Full = false; +} + +// Get all current values as a vector +template +std::vector RingBuffer::getAllValues() const +{ + std::vector result; + result.reserve(count); + + for (size_t i = 0; i < count; i++) { + result.push_back(get(i)); + } + + return result; +} \ No newline at end of file diff --git a/lib/obp60task/OBPSensorTask.cpp b/lib/obp60task/OBPSensorTask.cpp index 500389e..c2f1ac8 100644 --- a/lib/obp60task/OBPSensorTask.cpp +++ b/lib/obp60task/OBPSensorTask.cpp @@ -17,6 +17,7 @@ #include "ObpNmea0183.h" // Check NMEA0183 sentence for uncorrect content #include "OBP60Extensions.h" // Lib for hardware extensions #include "movingAvg.h" // Lib for moving average building +#include "OBPRingBuffer.h" // Lib with ring buffer for history storage of some boat data #include "time.h" // For getting NTP time #include // Internal ESP32 RTC clock diff --git a/lib/obp60task/PageWindPlot.cpp b/lib/obp60task/PageWindPlot.cpp index b1b17dd..80365aa 100644 --- a/lib/obp60task/PageWindPlot.cpp +++ b/lib/obp60task/PageWindPlot.cpp @@ -3,11 +3,12 @@ #include "BoatDataCalibration.h" #include "OBP60Extensions.h" #include "Pagedata.h" +#include "OBPRingBuffer.h" #include // **************************************************************** -class wndHistory { +class OldwndHistory { // provides a circular buffer to store wind history values private: int SIZE; @@ -273,9 +274,6 @@ public: GwConfigHandler* config = commonData->config; GwLog* logger = commonData->logger; - static wndHistory windDirHstry; // Circular buffer to store wind direction values - static wndHistory windSpdHstry; // Circular buffer to store wind speed values - GwApi::BoatValue* bvalue; const int numCfgValues = 9; String dataName[numCfgValues]; @@ -325,15 +323,18 @@ public: int distVals; // helper to check wndCenter crossing int distMid; // helper to check wndCenter crossing + static RingBuffer windDirHstry(bufSize); // Circular buffer to store wind direction values + static RingBufferwindSpdHstry(bufSize); // Circular buffer to store wind speed values + LOG_DEBUG(GwLog::LOG, "Display page WindPlot"); unsigned long start = millis(); // Data initialization - if (windDirHstry.getSize() == 0) { - if (!windDirHstry.begin(bufSize)) { + if (windDirHstry.getCurrentSize() == 0) { + /* if (!windDirHstry.begin(bufSize)) { logger->logDebug(GwLog::ERROR, "Failed to initialize wind direction history buffer"); return; - } + } */ simWnd = 0; simTWS = 0; twdValue = 0; @@ -363,7 +364,6 @@ public: dataName[i] = dataName[i].substring(0, 6); // String length limit for value name calibrationData.calibrateInstance(dataName[i], bvalue, logger); // Check if boat data value is to be calibrated dataValue[i] = bvalue->value; // Value as double in SI unit - calibrationData.calibrateInstance(dataName[i], bvalue, logger); // Check if boat data value is to be calibrated dataValid[i] = bvalue->valid; dataSValue[i] = formatValue(bvalue, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places dataUnit[i] = formatValue(bvalue, *commonData).unit; @@ -396,7 +396,7 @@ public: // Identify buffer sizes and buffer position to print on the chart intvBufSize = cHeight * dataIntv; - count = windDirHstry.getSize(); + count = windDirHstry.getCurrentSize(); numWndValues = min(count, intvBufSize); newDate++; if (dataIntv != oldDataIntv) { @@ -522,7 +522,6 @@ public: if (i == (cHeight - 1)) { // Reaching chart area top end () linesToShow -= min(40, cHeight); // free top 40 lines of chart for new values bufStart = max(0, count - (linesToShow * dataIntv)); // next start value in buffer to show - // windDirHstry.mvStart(); // virtually delete 40 values from buffer if ((windDirHstry.getMin(numWndValues) > wndCenter) || (windDirHstry.getMax(numWndValues) < wndCenter)) { // Check if all wind value are left or right of center value -> optimize chart range int mid = windDirHstry.getMid(numWndValues); From 2729ef9cb634cf0fabb9708d548b32ef3446e1ea Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Wed, 25 Jun 2025 23:14:09 +0200 Subject: [PATCH 18/26] Implement v1 history data storage at OBPSensorTask --- .vscode/settings.json | 3 +- lib/obp60task/OBPRingBuffer.h | 14 ++++++--- lib/obp60task/OBPRingBuffer.tpp | 52 ++++++++++++++++++++++++++++--- lib/obp60task/OBPSensorTask.cpp | 55 +++++++++++++++++++++++++++++++++ lib/obp60task/PageWindPlot.cpp | 54 +++++++++++++++++++++++++------- 5 files changed, 157 insertions(+), 21 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 2ef566a..eb2cff6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,5 +4,6 @@ "stdexcept": "cpp", "limits": "cpp", "functional": "cpp" - } + }, + "github.copilot.nextEditSuggestions.enabled": false } \ No newline at end of file diff --git a/lib/obp60task/OBPRingBuffer.h b/lib/obp60task/OBPRingBuffer.h index d373b0c..1a57266 100644 --- a/lib/obp60task/OBPRingBuffer.h +++ b/lib/obp60task/OBPRingBuffer.h @@ -3,6 +3,7 @@ #include #include #include +#include "WString.h" template class RingBuffer { @@ -18,12 +19,16 @@ private: T MAX_VAL; // highest possible value of buffer of type // metadata for buffer + String dataName; // Name of boat data in buffer + String dataFmt; // Format of boat data in buffer int updFreq; // Update frequency in milliseconds int smallest; // Value range of buffer: smallest value - int biggest; // Value range of buffer: biggest value + int largest; // Value range of buffer: biggest value public: RingBuffer(size_t size); + void setMetaData(String name, String format, int updateFrequency, int minValue, int maxValue); // Set meta data for buffer + bool getMetaData(String& name, String& format, int& updateFrequency, int& minValue, int& maxValue); // Get meta data of buffer 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 @@ -39,12 +44,13 @@ public: T getMedian(size_t amount) const; // Get the median value of the last values of buffer size_t getCapacity() const; // Get the buffer capacity (maximum size) size_t getCurrentSize() const; // Get the current number of elements in buffer + size_t getLastIdx() const; // Get the last index of buffer bool isEmpty() const; // Check if buffer is empty bool isFull() const; // Check if buffer is full - T getMinVal(); // Get lowest possible value for buffer; used for initialized buffer data - T getMaxVal(); // Get highest possible value for buffer + T getMinVal() const; // Get lowest possible value for buffer; used for initialized buffer data + T getMaxVal() const; // Get highest possible value for buffer void clear(); // Clear buffer - T operator[](size_t index) const; + T operator[](size_t index); std::vector getAllValues() const; // Operator[] for convenient access (same as get()) }; diff --git a/lib/obp60task/OBPRingBuffer.tpp b/lib/obp60task/OBPRingBuffer.tpp index b966bec..c3814b8 100644 --- a/lib/obp60task/OBPRingBuffer.tpp +++ b/lib/obp60task/OBPRingBuffer.tpp @@ -15,15 +15,50 @@ RingBuffer::RingBuffer(size_t size) MIN_VAL = std::numeric_limits::lowest(); MAX_VAL = std::numeric_limits::max(); + dataName = ""; + dataFmt = ""; + updFreq = MIN_VAL; + smallest = MIN_VAL; + largest = MAX_VAL; buffer.resize(size, MIN_VAL); // return true; } -// Add a new value to the buffer +// Specify meta data of buffer content +template +void RingBuffer::setMetaData(String name, String format, int updateFrequency, int minValue, int maxValue) +{ + dataName = name; + dataFmt = format; + updFreq = updateFrequency; + smallest = minValue; + largest = maxValue; +} + +// Get meta data of buffer content +template +bool RingBuffer::getMetaData(String& name, String& format, int& updateFrequency, int& minValue, int& maxValue) +{ + if (updFreq == MIN_VAL || smallest == MIN_VAL || largest == MAX_VAL) { + return false; // Meta data not set + } + + name = dataName; + format = dataFmt; + updFreq = updFreq; + smallest = smallest; + largest = largest; + return true; // Meta data successfully retrieved +} + +// Add a new value to buffer template void RingBuffer::add(const T& value) { + if (value < smallest || value > largest) { + buffer[head] = MIN_VAL; // Store MIN_VAL if value is out of range + } buffer[head] = value; last = head; @@ -48,7 +83,7 @@ T RingBuffer::get(size_t index) const } if (index < 0 || index >= count) { return MIN_VAL; - // throw std::out_of_range("Index out of range"); + // throw std::out_of_range("Index out of range"); } size_t realIndex = (first + index) % capacity; @@ -57,7 +92,7 @@ T RingBuffer::get(size_t index) const // Operator[] for convenient access (same as get()) template -T RingBuffer::operator[](size_t index) const +T RingBuffer::operator[](size_t index) { return get(index); } @@ -302,6 +337,13 @@ size_t RingBuffer::getCurrentSize() const return count; } +// Get the last index of buffer +template +size_t RingBuffer::getLastIdx() const +{ + return last; +} + // Check if buffer is empty template bool RingBuffer::isEmpty() const @@ -318,14 +360,14 @@ bool RingBuffer::isFull() const // Get lowest possible value for buffer; used for initialized buffer data template -T RingBuffer::getMinVal() +T RingBuffer::getMinVal() const { return MIN_VAL; } // Get highest possible value for buffer template -T RingBuffer::getMaxVal() +T RingBuffer::getMaxVal() const { return MAX_VAL; } diff --git a/lib/obp60task/OBPSensorTask.cpp b/lib/obp60task/OBPSensorTask.cpp index c2f1ac8..90ce62a 100644 --- a/lib/obp60task/OBPSensorTask.cpp +++ b/lib/obp60task/OBPSensorTask.cpp @@ -70,6 +70,12 @@ void sensorTask(void *param){ batV.begin(); batC.begin(); + // Create ring buffers for history storage of some boat data + // later read data types from config and specify buffers accordingly + 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) + RingBuffer dbtHstry(960); // Circular buffer to store water depth values (DBT) + // Start timer Timer1.start(); // Start Timer1 for blinking LED @@ -361,6 +367,7 @@ void sensorTask(void *param){ long starttime11 = millis(); // Copy GPS data to RTC all 5min long starttime12 = millis(); // Get RTC data all 500ms long starttime13 = millis(); // Get 1Wire sensor data all 2s + unsigned long starttime20 = millis(); // Get TWD and TWS data every 1000ms tN2kMsg N2kMsg; shared->setSensorData(sensors); //set initially read values @@ -370,6 +377,21 @@ void sensorTask(void *param){ GwApi::BoatValue *hdop=new GwApi::BoatValue(GwBoatData::_HDOP); GwApi::BoatValue *valueList[]={gpsdays, gpsseconds, hdop}; + // Prepare boat data values for history storage + // later read data types from config and specify hstryvalList accordingly + GwApi::BoatValue *twdBVal=new GwApi::BoatValue(GwBoatData::_TWD); + GwApi::BoatValue *twsBVal=new GwApi::BoatValue(GwBoatData::_TWS); + GwApi::BoatValue *dbtBVal=new GwApi::BoatValue(GwBoatData::_DBT); + GwApi::BoatValue *hstryValList[]={twdBVal, twsBVal, dbtBVal}; // List of boat values for history storage + int twdHstryMin = 0; + int twsHstryMin = 0; + int dbtHstryMin = 0; + // Initialize history buffers with meta data + api->getBoatDataValues(3,hstryValList); + twdHstry.setMetaData(twdBVal->getName(), twdBVal->getFormat(), 1000, twdHstryMin, 360); // Set meta data for TWD buffer: update frequency 1000ms, min value 0, max value 360 + twsHstry.setMetaData(twsBVal->getName(), twsBVal->getFormat(), 1000, twsHstryMin, 100); // Set meta data for TWS buffer: update frequency 1000ms, min value 0, max value 100 + dbtHstry.setMetaData(dbtBVal->getName(), dbtBVal->getFormat(), 1000, dbtHstryMin, 10928); // Set meta data for TWS buffer: update frequency 1000ms, min value 0, max value 10,928 + // Internal RTC with NTP init ESP32Time rtc(0); if (api->getConfig()->getString(api->getConfig()->timeSource) == "iRTC") { @@ -775,6 +797,39 @@ void sensorTask(void *param){ } } + // Read TWD, TWS, DBT data from boatData every 1000ms for history and windplot display + if(millis() > starttime20 + 1000){ + starttime20 = millis(); + api->getBoatDataValues(3,hstryValList); + int16_t bValue; + if (twdBVal->valid) { + bValue = int16_t(RadToDeg(twdBVal->value)); + twdHstry.add(bValue); + } else { + twdHstry.add(INT16_MIN); // Add invalid value + } + if (twsBVal->valid) { + bValue = int16_t(twsBVal->value); + twsHstry.add(bValue); + } else { + twsHstry.add(INT16_MIN); // Add invalid value + } + if (dbtBVal->valid) { + bValue = int16_t(dbtBVal->value); + dbtHstry.add(bValue); + } else { + dbtHstry.add(INT16_MIN); // Add invalid value + } + String TmpName; + String TmpFormat; + int TmpUpdFreq; + int TmpSmallest; + int TmpBiggest; + twdHstry.getMetaData(TmpName, TmpFormat, TmpUpdFreq, TmpSmallest, TmpBiggest); + api->getLogger()->logDebug(GwLog::ERROR,"History buffer TWD: name:%s format:%s Freq:%d, Min: %d, Max: %d", TmpName.c_str(), TmpFormat.c_str(), TmpUpdFreq, TmpSmallest, TmpBiggest); + api->getLogger()->logDebug(GwLog::ERROR,"History buffers: TWD:%d TWS:%d DBT:%d", twdHstry.getLast(), twsHstry.getLast(), dbtHstry.getLast()); + } + shared->setSensorData(sensors); } vTaskDelete(NULL); diff --git a/lib/obp60task/PageWindPlot.cpp b/lib/obp60task/PageWindPlot.cpp index 80365aa..cc50b58 100644 --- a/lib/obp60task/PageWindPlot.cpp +++ b/lib/obp60task/PageWindPlot.cpp @@ -2,8 +2,8 @@ #include "BoatDataCalibration.h" #include "OBP60Extensions.h" -#include "Pagedata.h" #include "OBPRingBuffer.h" +#include "Pagedata.h" #include @@ -201,12 +201,44 @@ public: } }; +// 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; + } + if (amount > count) + amount = count; + + int value = 0; + int rng = 0; + int maxRng = minVal; + // Start from the newest value (last) and go backwards x times + for (size_t i = 0; i < amount; i++) { + value = windDirHstry.get((last + capacity - i) % capacity); + if (value == minVal) { + continue; + } + rng = abs(((value - center + 540) % 360) - 180); + if (rng > maxRng) + maxRng = rng; + } + if (maxRng > 180) { + maxRng = 180; + } + return maxRng; +} + // **************************************************************** class PageWindPlot : public Page { bool keylock = false; // Keylock - // int16_t lp = 80; // Pointer length - char chrtMode = 'D'; // Chart mode: 'D' for TWD, 'S' for TWS, 'B' for both06121990 + char chrtMode = 'D'; // Chart mode: 'D' for TWD, 'S' for TWS, 'B' for both 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 @@ -323,18 +355,18 @@ public: int distVals; // helper to check wndCenter crossing int distMid; // helper to check wndCenter crossing - static RingBuffer windDirHstry(bufSize); // Circular buffer to store wind direction values - static RingBufferwindSpdHstry(bufSize); // Circular buffer to store wind speed values + static RingBuffer windDirHstry(bufSize); // Circular buffer to store wind direction values + static RingBuffer windSpdHstry(bufSize); // Circular buffer to store wind speed values LOG_DEBUG(GwLog::LOG, "Display page WindPlot"); unsigned long start = millis(); // Data initialization if (windDirHstry.getCurrentSize() == 0) { - /* if (!windDirHstry.begin(bufSize)) { - logger->logDebug(GwLog::ERROR, "Failed to initialize wind direction history buffer"); - return; - } */ + /* if (!windDirHstry.begin(bufSize)) { + logger->logDebug(GwLog::ERROR, "Failed to initialize wind direction history buffer"); + return; + } */ simWnd = 0; simTWS = 0; twdValue = 0; @@ -420,13 +452,13 @@ public: // initialize chart range values if (wndCenter == INT_MIN) { - wndCenter = max(0, windDirHstry.get(numWndValues - intvBufSize)); // get 1st value of current data interval + wndCenter = max(0, int(windDirHstry.get(numWndValues - intvBufSize))); // get 1st value of current data interval wndCenter = (int((wndCenter + (wndCenter >= 0 ? 5 : -5)) / 10) * 10) % 360; // Set new center value; round to nearest 10 degree value; 360° -> 0° diffRng = dfltRng; chrtRng = dfltRng; } else { // check and adjust range between left, center, and right chart limit - diffRng = windDirHstry.getRng(wndCenter, numWndValues); + diffRng = getRng(windDirHstry, wndCenter, numWndValues); diffRng = (diffRng == INT_MIN ? 0 : diffRng); if (diffRng > chrtRng) { chrtRng = int((diffRng + (diffRng >= 0 ? 9 : -1)) / 10) * 10; // Round up to next 10 degree value From 72ddeb3cfb2060bba893df5df2e54fa992b8acef Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Tue, 1 Jul 2025 01:27:41 +0200 Subject: [PATCH 19/26] Pointer correction -> no data copy; conc. access issues --- .vscode/settings.json | 3 +- lib/obp60task/OBPRingBuffer.h | 8 +- lib/obp60task/OBPRingBuffer.tpp | 54 +---------- lib/obp60task/OBPSensorTask.cpp | 65 +++++++------ lib/obp60task/OBPSensorTask.h | 10 ++ lib/obp60task/PageWindPlot.cpp | 156 +++++++++++++++++--------------- lib/obp60task/Pagedata.h | 9 ++ lib/obp60task/obp60task.cpp | 5 + 8 files changed, 159 insertions(+), 151 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index eb2cff6..55a7256 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,7 +3,8 @@ "files.associations": { "stdexcept": "cpp", "limits": "cpp", - "functional": "cpp" + "functional": "cpp", + "*.tpp": "cpp" }, "github.copilot.nextEditSuggestions.enabled": false } \ No newline at end of file diff --git a/lib/obp60task/OBPRingBuffer.h b/lib/obp60task/OBPRingBuffer.h index 1a57266..7d4067f 100644 --- a/lib/obp60task/OBPRingBuffer.h +++ b/lib/obp60task/OBPRingBuffer.h @@ -1,4 +1,5 @@ #pragma once +#include "GwSynchronized.h" #include #include #include @@ -8,6 +9,7 @@ template class RingBuffer { private: + SemaphoreHandle_t locker; std::vector buffer; size_t capacity; size_t head; // Points to the next insertion position @@ -39,7 +41,6 @@ public: T getMax(size_t amount) const; // Get maximum value of the last values of buffer T getMid() const; // Get mid value between and value in buffer T getMid(size_t amount) const; // Get mid value between and value of the last values of buffer - T getRng(T center, size_t amount) const; // Get maximum difference of last of buffer values to center value T getMedian() const; // Get the median value in buffer T getMedian(size_t amount) const; // Get the median value of the last values of buffer size_t getCapacity() const; // Get the buffer capacity (maximum size) @@ -50,9 +51,8 @@ public: T getMinVal() const; // Get lowest possible value for buffer; used for initialized buffer data T getMaxVal() const; // Get highest possible value for buffer void clear(); // Clear buffer - T operator[](size_t index); - std::vector getAllValues() const; // Operator[] for convenient access (same as get()) - + T operator[](size_t index); // Operator[] for convenient access (same as get()) + std::vector getAllValues() const; // Get all current values as a vector }; #include "OBPRingBuffer.tpp" \ No newline at end of file diff --git a/lib/obp60task/OBPRingBuffer.tpp b/lib/obp60task/OBPRingBuffer.tpp index c3814b8..e5589fd 100644 --- a/lib/obp60task/OBPRingBuffer.tpp +++ b/lib/obp60task/OBPRingBuffer.tpp @@ -20,6 +20,7 @@ RingBuffer::RingBuffer(size_t size) updFreq = MIN_VAL; smallest = MIN_VAL; largest = MAX_VAL; + buffer.resize(size, MIN_VAL); // return true; @@ -46,9 +47,9 @@ bool RingBuffer::getMetaData(String& name, String& format, int& updateFrequen name = dataName; format = dataFmt; - updFreq = updFreq; - smallest = smallest; - largest = largest; + updateFrequency = updFreq; + minValue = smallest; + maxValue = largest; return true; // Meta data successfully retrieved } @@ -78,12 +79,8 @@ void RingBuffer::add(const T& value) template T RingBuffer::get(size_t index) const { - if (isEmpty()) { - throw std::runtime_error("Buffer is empty"); - } - if (index < 0 || index >= count) { + if (isEmpty() || index < 0 || index >= count) { return MIN_VAL; - // throw std::out_of_range("Index out of range"); } size_t realIndex = (first + index) % capacity; @@ -103,7 +100,6 @@ T RingBuffer::getFirst() const { if (isEmpty()) { return MIN_VAL; - // throw std::runtime_error("Buffer is empty"); } return buffer[first]; } @@ -114,7 +110,6 @@ T RingBuffer::getLast() const { if (isEmpty()) { return MIN_VAL; - // throw std::runtime_error("Buffer is empty"); } return buffer[last]; } @@ -125,7 +120,6 @@ T RingBuffer::getMin() const { if (isEmpty()) { return MIN_VAL; - // throw std::runtime_error("Buffer is empty"); } T minVal = get(first); @@ -145,7 +139,6 @@ T RingBuffer::getMin(size_t amount) const { if (isEmpty() || amount <= 0) { return MIN_VAL; - // throw std::runtime_error("Buffer is empty"); } if (amount > count) amount = count; @@ -167,7 +160,6 @@ T RingBuffer::getMax() const { if (isEmpty()) { return MIN_VAL; - // throw std::runtime_error("Buffer is empty"); } T maxVal = get(first); @@ -187,7 +179,6 @@ T RingBuffer::getMax(size_t amount) const { if (isEmpty() || amount <= 0) { return MIN_VAL; - // throw std::runtime_error("Buffer is empty"); } if (amount > count) amount = count; @@ -209,7 +200,6 @@ T RingBuffer::getMid() const { if (isEmpty()) { return MIN_VAL; - // throw std::runtime_error("Buffer is empty"); } return (getMin() + getMax()) / static_cast(2); @@ -221,7 +211,6 @@ T RingBuffer::getMid(size_t amount) const { if (isEmpty() || amount <= 0) { return MIN_VAL; - // throw std::runtime_error("Buffer is empty"); } if (amount > count) @@ -230,44 +219,12 @@ T RingBuffer::getMid(size_t amount) const return (getMin(amount) + getMax(amount)) / static_cast(2); } -// ******************* works for wind direction only -> move out of here ******************************* -// Get maximum difference of last of buffer values to center value -template -T RingBuffer::getRng(T center, size_t amount) const -{ - if (isEmpty() || amount <= 0) { - return MIN_VAL; - // throw std::runtime_error("Buffer is empty"); - } - if (amount > count) - amount = count; - - T value = 0; - T rng = 0; - T maxRng = MIN_VAL; - // Start from the newest value (last) and go backwards x times - for (size_t i = 0; i < amount; i++) { - value = get((last + capacity - i) % capacity); - if (value == MIN_VAL) { - continue; - } - rng = abs(((value - center + 540) % 360) - 180); - if (rng > maxRng) - maxRng = rng; - } - if (maxRng > 180) { - maxRng = 180; - } - return maxRng; -} - // Get the median value in the buffer template T RingBuffer::getMedian() const { if (isEmpty()) { return MIN_VAL; - // throw std::runtime_error("Buffer is empty"); } // Create a temporary vector with current valid elements @@ -297,7 +254,6 @@ T RingBuffer::getMedian(size_t amount) const { if (isEmpty() || amount <= 0) { return MIN_VAL; - // throw std::runtime_error("Buffer is empty"); } if (amount > count) amount = count; diff --git a/lib/obp60task/OBPSensorTask.cpp b/lib/obp60task/OBPSensorTask.cpp index 90ce62a..b0271a1 100644 --- a/lib/obp60task/OBPSensorTask.cpp +++ b/lib/obp60task/OBPSensorTask.cpp @@ -71,7 +71,7 @@ void sensorTask(void *param){ batC.begin(); // Create ring buffers for history storage of some boat data - // later read data types from config and specify buffers accordingly + // later read additonal data types from config and specify buffers accordingly 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) RingBuffer dbtHstry(960); // Circular buffer to store water depth values (DBT) @@ -381,16 +381,18 @@ void sensorTask(void *param){ // later read data types from config and specify hstryvalList accordingly GwApi::BoatValue *twdBVal=new GwApi::BoatValue(GwBoatData::_TWD); GwApi::BoatValue *twsBVal=new GwApi::BoatValue(GwBoatData::_TWS); - GwApi::BoatValue *dbtBVal=new GwApi::BoatValue(GwBoatData::_DBT); + GwApi::BoatValue *dbtBVal=new GwApi::BoatValue(GwBoatData::_STW); // STW just for testing GwApi::BoatValue *hstryValList[]={twdBVal, twsBVal, dbtBVal}; // List of boat values for history storage - int twdHstryMin = 0; - int twsHstryMin = 0; - int dbtHstryMin = 0; // Initialize history buffers with meta data api->getBoatDataValues(3,hstryValList); - twdHstry.setMetaData(twdBVal->getName(), twdBVal->getFormat(), 1000, twdHstryMin, 360); // Set meta data for TWD buffer: update frequency 1000ms, min value 0, max value 360 - twsHstry.setMetaData(twsBVal->getName(), twsBVal->getFormat(), 1000, twsHstryMin, 100); // Set meta data for TWS buffer: update frequency 1000ms, min value 0, max value 100 - dbtHstry.setMetaData(dbtBVal->getName(), dbtBVal->getFormat(), 1000, dbtHstryMin, 10928); // Set meta data for TWS buffer: update frequency 1000ms, min value 0, max value 10,928 + int hstryUpdFreq = 1000; // Update frequency for history buffers in ms + int hstryMinVal = 0; // Minimum value for history buffers + int twdHstryMax = 360; + int twsHstryMax = 100; + int dbtHstryMax = 327; // Max value for depth (due to signed 16 bit integer and decimals shift) + twdHstry.setMetaData(twdBVal->getName(), twdBVal->getFormat(), hstryUpdFreq, hstryMinVal, twdHstryMax); + twsHstry.setMetaData(twsBVal->getName(), twsBVal->getFormat(), hstryUpdFreq, hstryMinVal, twsHstryMax); + dbtHstry.setMetaData(dbtBVal->getName(), dbtBVal->getFormat(), hstryUpdFreq, hstryMinVal, dbtHstryMax); // Internal RTC with NTP init ESP32Time rtc(0); @@ -800,37 +802,48 @@ void sensorTask(void *param){ // Read TWD, TWS, DBT data from boatData every 1000ms for history and windplot display if(millis() > starttime20 + 1000){ starttime20 = millis(); + int16_t val; api->getBoatDataValues(3,hstryValList); - int16_t bValue; if (twdBVal->valid) { - bValue = int16_t(RadToDeg(twdBVal->value)); - twdHstry.add(bValue); + val = static_cast(std::round(RadToDeg(twdBVal->value))); + if (val < hstryMinVal || val > twdHstryMax) { + val = INT16_MIN; // Add invalid value + } } else { - twdHstry.add(INT16_MIN); // Add invalid value + val = INT16_MIN; } + twdHstry.add(val); if (twsBVal->valid) { - bValue = int16_t(twsBVal->value); - twsHstry.add(bValue); + val = static_cast(twsBVal->value * 100); // Shift value to store decimals in int16_t + if (val < hstryMinVal || val > twsHstryMax) { + val = INT16_MIN; // Add invalid value + } } else { - twsHstry.add(INT16_MIN); // Add invalid value + val = INT16_MIN; } + twsHstry.add(val); if (dbtBVal->valid) { - bValue = int16_t(dbtBVal->value); - dbtHstry.add(bValue); + val = static_cast(dbtBVal->value * 100); // Shift value to store decimals in int16_t + if (val < hstryMinVal || val > dbtHstryMax) { + val = INT16_MIN; // Add invalid value + } } else { - dbtHstry.add(INT16_MIN); // Add invalid value + val = INT16_MIN; } - String TmpName; - String TmpFormat; - int TmpUpdFreq; - int TmpSmallest; - int TmpBiggest; - twdHstry.getMetaData(TmpName, TmpFormat, TmpUpdFreq, TmpSmallest, TmpBiggest); - api->getLogger()->logDebug(GwLog::ERROR,"History buffer TWD: name:%s format:%s Freq:%d, Min: %d, Max: %d", TmpName.c_str(), TmpFormat.c_str(), TmpUpdFreq, TmpSmallest, TmpBiggest); - api->getLogger()->logDebug(GwLog::ERROR,"History buffers: TWD:%d TWS:%d DBT:%d", twdHstry.getLast(), twsHstry.getLast(), dbtHstry.getLast()); + dbtHstry.add(val); + +// api->getLogger()->logDebug(GwLog::ERROR,"SensorTask pointer: TWD: %p, TWS: %p, STW: %p", twdHstry, twsHstry, dbtHstry); + api->getLogger()->logDebug(GwLog::ERROR,"SensorTask Data: TWD:%d TWS:%f DBT:%f", std::round(RadToDeg(twdBVal->value)), twsBVal->value, dbtBVal->value); + api->getLogger()->logDebug(GwLog::ERROR,"SensorTask buffers: TWD:%d TWS:%f DBT:%f", twdHstry.getLast(), twsHstry.getLast(), dbtHstry.getLast()); + } + // Add sensor and history data to shared memory for transfer to pages shared->setSensorData(sensors); + shared->setHstryBuf(twdHstry, twsHstry, dbtHstry); +// tBoatHstryData tmpHstryData = shared->getHstryBuf(); +// api->getLogger()->logDebug(GwLog::ERROR,"SensorTask tmpHstryData pointer: TWD: %p, TWS: %p, STW: %p", tmpHstryData.twdHstry, tmpHstryData.twsHstry, tmpHstryData.dbtHstry); + } vTaskDelete(NULL); } diff --git a/lib/obp60task/OBPSensorTask.h b/lib/obp60task/OBPSensorTask.h index 7dd9f24..352632a 100644 --- a/lib/obp60task/OBPSensorTask.h +++ b/lib/obp60task/OBPSensorTask.h @@ -8,6 +8,7 @@ class SharedData{ private: SemaphoreHandle_t locker; SensorData sensors; + tBoatHstryData boatHstry; public: GwApi *api=NULL; SharedData(GwApi *api){ @@ -22,6 +23,15 @@ class SharedData{ GWSYNCHRONIZED(&locker); return sensors; } + void setHstryBuf(RingBuffer twdHstry,RingBuffer twsHstry,RingBuffer dbtHstry) { + GWSYNCHRONIZED(&locker); + boatHstry={&twdHstry, &twsHstry, &dbtHstry}; +// api->getLogger()->logDebug(GwLog::ERROR, "SharedData setHstryBuf: TWD: %p, TWS: %p, STW: %p", boatHstry.twdHstry, boatHstry.twsHstry, boatHstry.dbtHstry); + } + tBoatHstryData getHstryBuf() { + GWSYNCHRONIZED(&locker); + return boatHstry; + } }; void createSensorTask(SharedData *shared); diff --git a/lib/obp60task/PageWindPlot.cpp b/lib/obp60task/PageWindPlot.cpp index cc50b58..bcd1dc3 100644 --- a/lib/obp60task/PageWindPlot.cpp +++ b/lib/obp60task/PageWindPlot.cpp @@ -4,6 +4,7 @@ #include "OBP60Extensions.h" #include "OBPRingBuffer.h" #include "Pagedata.h" +#include // just for RadToDeg function #include @@ -318,6 +319,7 @@ public: int twdValue; static const float radToDeg = 180.0 / M_PI; // Conversion factor from radians to degrees + bool isInitialized = false; // Flag to indicate that page is initialized bool wndDataValid = false; // Flag to indicate if wind data is valid bool simulation = false; bool holdValues = false; @@ -352,21 +354,21 @@ public: static float chrtScl; // Scale for wind values in pixels per degree int chrtVal; // Current wind value static int chrtPrevVal; // Last wind value in chart area for check if value crosses 180 degree line - int distVals; // helper to check wndCenter crossing - int distMid; // helper to check wndCenter crossing +// int distVals; // helper to check wndCenter crossing +// int distMid; // helper to check wndCenter crossing - static RingBuffer windDirHstry(bufSize); // Circular buffer to store wind direction values - static RingBuffer windSpdHstry(bufSize); // Circular buffer to store wind speed values + // static RingBuffer windDirHstry(bufSize); // Circular buffer to store wind direction values + // static RingBuffer windSpdHstry(bufSize); // Circular buffer to store wind speed values LOG_DEBUG(GwLog::LOG, "Display page WindPlot"); unsigned long start = millis(); // Data initialization - if (windDirHstry.getCurrentSize() == 0) { - /* if (!windDirHstry.begin(bufSize)) { - logger->logDebug(GwLog::ERROR, "Failed to initialize wind direction history buffer"); - return; - } */ + LOG_DEBUG(GwLog::ERROR, "PageWindPlot buffers: TWD:%d TWS:%f DBT:%f", pageData.boatHstry.twdHstry->getLast(), + pageData.boatHstry.twsHstry->getLast() * 0.0194384, pageData.boatHstry.dbtHstry->getLast() / 100); + + // if (windDirHstry.getCurrentSize() == 0) { + if (!isInitialized) { simWnd = 0; simTWS = 0; twdValue = 0; @@ -374,6 +376,7 @@ public: linesToShow = 0; oldDataIntv = dataIntv; newDate = 0; + isInitialized = true; // Set flag to indicate that page is now initialized } // Get config data @@ -389,46 +392,47 @@ public: setFlashLED(false); } - // Read boatdata values for TWD, TWA, TWS, HDM, AWA, AWS, STW, COG, SOG, if available - for (int i = 0; i < numCfgValues; i++) { - bvalue = pageData.values[i]; - dataName[i] = xdrDelete(bvalue->getName()); - dataName[i] = dataName[i].substring(0, 6); // String length limit for value name - calibrationData.calibrateInstance(dataName[i], bvalue, logger); // Check if boat data value is to be calibrated - dataValue[i] = bvalue->value; // Value as double in SI unit - dataValid[i] = bvalue->valid; - dataSValue[i] = formatValue(bvalue, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places - dataUnit[i] = formatValue(bvalue, *commonData).unit; - if (dataValid[i]) { - dataSValueOld[i] = dataSValue[i]; // Save old value - dataUnitOld[i] = dataUnit[i]; // Save old unit - } - } + /* // Read boatdata values for TWD, TWA, TWS, HDM, AWA, AWS, STW, COG, SOG, if available + for (int i = 0; i < numCfgValues; i++) { + bvalue = pageData.values[i]; + dataName[i] = xdrDelete(bvalue->getName()); + dataName[i] = dataName[i].substring(0, 6); // String length limit for value name + calibrationData.calibrateInstance(dataName[i], bvalue, logger); // Check if boat data value is to be calibrated + dataValue[i] = bvalue->value; // Value as double in SI unit + dataValid[i] = bvalue->valid; + dataSValue[i] = formatValue(bvalue, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places + dataUnit[i] = formatValue(bvalue, *commonData).unit; + if (dataValid[i]) { + dataSValueOld[i] = dataSValue[i]; // Save old value + dataUnitOld[i] = dataUnit[i]; // Save old unit + } + } - // Store TWD wind value in buffer, regardless of validity -> one value per second (if delivered in that frequency) - twdValue = int((dataValue[0] * radToDeg) + 0.5); // Read TWD value in degrees and round to integer - if (dataValid[0]) { // TWD data existing - wndDataValid = true; - } else { - // Try to calculate TWD value from other data, if available - // wndDataValid = windValues.calcTWD(&twdValue, dataValue[1], dataValue[2], dataValue[3], dataValue[4], dataValue[5], dataValue[6]); - } + // Store TWD wind value in buffer, regardless of validity -> one value per second (if delivered in that frequency) + twdValue = int((dataValue[0] * radToDeg) + 0.5); // Read TWD value in degrees and round to integer + if (dataValid[0]) { // TWD data existing + wndDataValid = true; + } else { + // Try to calculate TWD value from other data, if available + // wndDataValid = windValues.calcTWD(&twdValue, dataValue[1], dataValue[2], dataValue[3], dataValue[4], dataValue[5], dataValue[6]); + } - if (simulation) { - // Simulate data if simulation is enabled; use default simulation values for TWS - simWnd += random(simStep * -1, simStep); // random value between -simStep and +simStep - if (simWnd < 0) - simWnd += 360; - simWnd = simWnd % 360; - windDirHstry.add(simWnd); - // LOG_DEBUG(GwLog::DEBUG, "PageWindPlot simulation data: windValue: %d, windSpeed: %s", simWnd, dataSValue[2].c_str()); - } else if (wndDataValid) { - windDirHstry.add(twdValue); - } + if (simulation) { + // Simulate data if simulation is enabled; use default simulation values for TWS + simWnd += random(simStep * -1, simStep); // random value between -simStep and +simStep + if (simWnd < 0) + simWnd += 360; + simWnd = simWnd % 360; + windDirHstry.add(simWnd); + // LOG_DEBUG(GwLog::DEBUG, "PageWindPlot simulation data: windValue: %d, windSpeed: %s", simWnd, dataSValue[2].c_str()); + } else if (wndDataValid) { + windDirHstry.add(twdValue); + } */ // Identify buffer sizes and buffer position to print on the chart intvBufSize = cHeight * dataIntv; - count = windDirHstry.getCurrentSize(); + // count = windDirHstry.getCurrentSize(); + count = pageData.boatHstry.twdHstry->getCurrentSize(); numWndValues = min(count, intvBufSize); newDate++; if (dataIntv != oldDataIntv) { @@ -442,31 +446,34 @@ public: if (count == bufSize) { bufStart--; // show the latest wind values in buffer; keep 1st value constant in a rolling buffer when new data is added } - LOG_DEBUG(GwLog::ERROR, "PageWindPlot Dataset: TWD: %d, count: %d, intvBufSize: %d, numWndValues: %d, bufStart: %d, linesToShow: %d, newDate: %d", twdValue, count, intvBufSize, numWndValues, bufStart, linesToShow, newDate); + LOG_DEBUG(GwLog::ERROR, "PageWindPlot Dataset: TWD: %d, TWS: %f, STW: %f, count: %d, intvBufSize: %d, numWndValues: %d, bufStart: %d, linesToShow: %d, newDate: %d", pageData.boatHstry.twdHstry->getLast(), + pageData.boatHstry.twsHstry->getLast() * 0.0194384, pageData.boatHstry.dbtHstry->getLast() / 100, count, intvBufSize, numWndValues, bufStart, linesToShow, newDate); +// LOG_DEBUG(GwLog::ERROR, "PageWindPlot Pointer: TWD: %p, TWS: %p, STW: %p", pageData.boatHstry.twdHstry, pageData.boatHstry.twsHstry, pageData.boatHstry.dbtHstry); - if (bvalue == NULL) - return; - LOG_DEBUG(GwLog::LOG, "PageWindPlot, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, cnt: %d, valid0: %d", dataName[0].c_str(), dataValue[0], - dataName[1].c_str(), dataValue[1], dataName[2].c_str(), dataValue[2], dataName[3].c_str(), dataValue[3], dataName[4].c_str(), dataValue[4], - dataName[5].c_str(), dataValue[5], dataName[6].c_str(), dataValue[6], dataName[7].c_str(), dataValue[7], dataName[8].c_str(), dataValue[8], count, dataValid[0]); +// LOG_DEBUG(GwLog::LOG, "PageWindPlot, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, cnt: %d, valid0: %d", dataName[0].c_str(), dataValue[0], +// dataName[1].c_str(), dataValue[1], dataName[2].c_str(), dataValue[2], dataName[3].c_str(), dataValue[3], dataName[4].c_str(), dataValue[4], +// dataName[5].c_str(), dataValue[5], dataName[6].c_str(), dataValue[6], dataName[7].c_str(), dataValue[7], dataName[8].c_str(), dataValue[8], count, dataValid[0]); // initialize chart range values if (wndCenter == INT_MIN) { - wndCenter = max(0, int(windDirHstry.get(numWndValues - intvBufSize))); // get 1st value of current data interval + // wndCenter = max(0, int(windDirHstry.get(numWndValues - intvBufSize))); // get 1st value of current data interval + wndCenter = max(0, int(pageData.boatHstry.twdHstry->get(numWndValues - intvBufSize))); // get 1st value of current data interval wndCenter = (int((wndCenter + (wndCenter >= 0 ? 5 : -5)) / 10) * 10) % 360; // Set new center value; round to nearest 10 degree value; 360° -> 0° diffRng = dfltRng; chrtRng = dfltRng; } else { // check and adjust range between left, center, and right chart limit - diffRng = getRng(windDirHstry, wndCenter, numWndValues); + // diffRng = getRng(windDirHstry, wndCenter, numWndValues); + diffRng = getRng(*pageData.boatHstry.twdHstry, wndCenter, numWndValues); diffRng = (diffRng == INT_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); } - int debugMin = windDirHstry.getMin(numWndValues); - int debugMax = windDirHstry.getMax(numWndValues); + // int debugMin = windDirHstry.getMin(numWndValues); + int debugMin = pageData.boatHstry.twdHstry->getMin(numWndValues); + int debugMax = pageData.boatHstry.twdHstry->getMax(numWndValues); LOG_DEBUG(GwLog::ERROR, "PageWindPlot Range. wndCenter: %d, numWndValues: %d, min: %d, max: %d, diffrng: %d, chrtRng: %d ", wndCenter, numWndValues, debugMin, debugMax, diffRng, chrtRng); } chrtScl = float(width) / float(chrtRng) / 2.0; // Chart scale: pixels per degree @@ -512,20 +519,29 @@ public: getdisplay().drawCircle(width - 5, yOffset - 17, 2, commonData->fgcolor); // symbol getdisplay().drawCircle(width - 5, yOffset - 17, 3, commonData->fgcolor); // symbol + if (pageData.boatHstry.twdHstry->getMax() == INT16_MIN) { + wndDataValid = false; // only values in buffer -> no valid wind data available + } else { + wndDataValid = true; // At least some wind data available + } // Draw wind values in chart //*********************************************************** - if (wndDataValid || holdValues || simulation) { - // if (count == bufSize) - // bufStart--; // show the latest wind values in buffer; keep 1st value constant in a rolling buffer + if (wndDataValid) { for (int i = 0; i < linesToShow; i++) { - chrtVal = windDirHstry.get(bufStart + (i * dataIntv)); // show the latest wind values in buffer; keep 1st value constant in a rolling buffer + // chrtVal = windDirHstry.get(bufStart + (i * dataIntv)); // show the latest wind values in buffer; keep 1st value constant in a rolling buffer + chrtVal = pageData.boatHstry.twdHstry->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; + continue; // skip invalid values + } x = ((chrtVal - wndLeft + 360) % 360) * chrtScl; y = yOffset + cHeight - i; // Position in chart area if (i > linesToShow - 15) LOG_DEBUG(GwLog::ERROR, "PageWindPlot Chart: i: %d, chrtVal: %d, chrtPrevVal: %d, bufStart: %d count: %d, linesToShow: %d", i, chrtVal, chrtPrevVal, bufStart, count, linesToShow); - if (i == 0) { - prevX = x; // just a dot for 1st chart point +// if (i == 0) { + if ((i == 0) || (chrtPrevVal == INT16_MIN)) { + prevX = x; // just a dot for 1st chart point or after some invalid values prevY = y; } else { // cross borders check; shift values to [-180..0..180]; when crossing borders, range is 2x 180 degrees @@ -540,7 +556,7 @@ public: getdisplay().drawLine(prevX, prevY, xSplit, y, commonData->fgcolor); getdisplay().drawLine(prevX, prevY - 1, ((xSplit != prevX) ? xSplit : xSplit - 1), ((xSplit != prevX) ? y - 1 : y), commonData->fgcolor); prevX = (((chrtVal180 > 0 ? wndRight : wndLeft) - wndLeft + 360) % 360) * chrtScl; - LOG_DEBUG(GwLog::ERROR, "PageWindPlot Cross: i: %d, chrtVal: %d, chrtPrevVal: %d, wndLeft: %d wndRight: %d, curr:{%d,%d} prev:{%d,%d}", i, chrtVal, chrtPrevVal, wndLeft, wndRight, x, y, prevX, prevY); + // LOG_DEBUG(GwLog::ERROR, "PageWindPlot Cross: i: %d, chrtVal: %d, chrtPrevVal: %d, wndLeft: %d wndRight: %d, curr:{%d,%d} prev:{%d,%d}", i, chrtVal, chrtPrevVal, wndLeft, wndRight, x, y, prevX, prevY); } } @@ -554,27 +570,25 @@ public: if (i == (cHeight - 1)) { // Reaching chart area top end () linesToShow -= min(40, cHeight); // free top 40 lines of chart for new values bufStart = max(0, count - (linesToShow * dataIntv)); // next start value in buffer to show - if ((windDirHstry.getMin(numWndValues) > wndCenter) || (windDirHstry.getMax(numWndValues) < wndCenter)) { + // if ((windDirHstry.getMin(numWndValues) > wndCenter) || (windDirHstry.getMax(numWndValues) < wndCenter)) { + if ((pageData.boatHstry.twdHstry->getMin(numWndValues) > wndCenter) || (pageData.boatHstry.twdHstry->getMax(numWndValues) < wndCenter)) { // Check if all wind value are left or right of center value -> optimize chart range - int mid = windDirHstry.getMid(numWndValues); - if (mid != INT_MIN) { + int mid = pageData.boatHstry.twdHstry->getMid(numWndValues); + if (mid != INT16_MIN) { wndCenter = int((mid + (mid >= 0 ? 5 : -5)) / 10) * 10; // Set new center value; round to nearest 10 degree value } } - LOG_DEBUG(GwLog::ERROR, "PageWindPlot FreeTop: cHeight: %d, LinesToShow: %d, numWndValues: %d, wndCenter: %d, bufStart: %d", cHeight, linesToShow, numWndValues, wndCenter, bufStart); + // LOG_DEBUG(GwLog::ERROR, "PageWindPlot FreeTop: cHeight: %d, LinesToShow: %d, numWndValues: %d, wndCenter: %d, bufStart: %d", cHeight, linesToShow, numWndValues, wndCenter, bufStart); break; } } - // LOG_DEBUG(GwLog::DEBUG, "PageWindPlot chart end: chrtVal: %d, x: %d, y: %d prevX: %d, prevY: %d, loop-Counter: %d", chrtVal, x, y, prevX, prevY, count); - } - else if (!wndDataValid) { + } else { // No valid data available LOG_DEBUG(GwLog::LOG, "PageWindPlot: No valid data available"); getdisplay().setFont(&Ubuntu_Bold10pt7b); - getdisplay().fillRect(xCenter - 66, height / 2 - 18, 146, 24, commonData->bgcolor); // Clear area for TWS value - getdisplay().setCursor(xCenter - 66, height / 2); - getdisplay().print("No sensor data"); + getdisplay().fillRect(xCenter - 66, height / 2 - 20, 146, 24, commonData->bgcolor); // Clear area for TWS value + drawTextCenter(xCenter, height / 2 - 10, "No sensor data"); } // Print TWS value diff --git a/lib/obp60task/Pagedata.h b/lib/obp60task/Pagedata.h index 65bdc67..e518221 100644 --- a/lib/obp60task/Pagedata.h +++ b/lib/obp60task/Pagedata.h @@ -4,15 +4,24 @@ #include #include #include "LedSpiTask.h" +#include "OBPRingBuffer.h" #define MAX_PAGE_NUMBER 10 // Max number of pages for show data +typedef struct{ + RingBuffer* twdHstry; + RingBuffer* twsHstry; + RingBuffer* dbtHstry; +} tBoatHstryData; + typedef std::vector ValueList; + typedef struct{ String pageName; uint8_t pageNumber; // page number in sequence of visible pages //the values will always contain the user defined values first ValueList values; + tBoatHstryData boatHstry; } PageData; // Sensor data structure (only for extended sensors, not for NMEA bus sensors) diff --git a/lib/obp60task/obp60task.cpp b/lib/obp60task/obp60task.cpp index 091c771..8d14c18 100644 --- a/lib/obp60task/obp60task.cpp +++ b/lib/obp60task/obp60task.cpp @@ -837,6 +837,11 @@ void OBP60Task(GwApi *api){ currentPage->displayNew(pages[pageNumber].parameters); lastPage=pageNumber; } + pages[pageNumber].parameters.boatHstry = shared->getHstryBuf(); // Add boat history to page parameters + LOG_DEBUG(GwLog::ERROR,"obp60task buffers: TWD:%d TWS:%f DBT:%f", pages[pageNumber].parameters.boatHstry.twdHstry->getLast(), + pages[pageNumber].parameters.boatHstry.twsHstry->getLast() * 0.0194384, pages[pageNumber].parameters.boatHstry.dbtHstry->getLast() / 100); +// LOG_DEBUG(GwLog::ERROR, "obp60task pointer: TWD: %p, TWS: %p, STW: %p", pages[pageNumber].parameters.boatHstry.twdHstry, +// pages[pageNumber].parameters.boatHstry.twsHstry, pages[pageNumber].parameters.boatHstry.dbtHstry); //call the page code LOG_DEBUG(GwLog::DEBUG,"calling page %d",pageNumber); // Show footer if enabled (together with header) From 59cf52b5d2a469944f4d6f5cc0a4c576b935b222 Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Sun, 13 Jul 2025 00:26:16 +0200 Subject: [PATCH 20/26] Semaphore + chart fixes; added simulation data --- .vscode/settings.json | 9 +- lib/obp60task/OBPRingBuffer.h | 15 +- lib/obp60task/OBPRingBuffer.tpp | 37 +- lib/obp60task/OBPSensorTask.cpp | 66 ++-- lib/obp60task/OBPSensorTask.h | 3 +- lib/obp60task/PageWindPlot.cpp | 597 ++++++++++++-------------------- lib/obp60task/obp60task.cpp | 6 +- 7 files changed, 289 insertions(+), 444 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 55a7256..cad7657 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,10 +1,3 @@ { - "cmake.configureOnOpen": false, - "files.associations": { - "stdexcept": "cpp", - "limits": "cpp", - "functional": "cpp", - "*.tpp": "cpp" - }, - "github.copilot.nextEditSuggestions.enabled": false + "cmake.configureOnOpen": false } \ No newline at end of file diff --git a/lib/obp60task/OBPRingBuffer.h b/lib/obp60task/OBPRingBuffer.h index 7d4067f..3aba92b 100644 --- a/lib/obp60task/OBPRingBuffer.h +++ b/lib/obp60task/OBPRingBuffer.h @@ -9,7 +9,7 @@ template class RingBuffer { private: - SemaphoreHandle_t locker; + mutable SemaphoreHandle_t bufLocker; std::vector buffer; size_t capacity; size_t head; // Points to the next insertion position @@ -24,13 +24,13 @@ private: String dataName; // Name of boat data in buffer String dataFmt; // Format of boat data in buffer int updFreq; // Update frequency in milliseconds - int smallest; // Value range of buffer: smallest value - int largest; // Value range of buffer: biggest value + T smallest; // Value range of buffer: smallest value + T largest; // Value range of buffer: biggest value public: RingBuffer(size_t size); - void setMetaData(String name, String format, int updateFrequency, int minValue, int maxValue); // Set meta data for buffer - bool getMetaData(String& name, String& format, int& updateFrequency, int& minValue, int& maxValue); // Get meta data of buffer + void setMetaData(String name, String format, int updateFrequency, T minValue, T maxValue); // Set meta data for buffer + bool getMetaData(String& name, String& format, int& updateFrequency, T& minValue, T& maxValue); // Get meta data of buffer void 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 @@ -45,13 +45,14 @@ public: T getMedian(size_t amount) const; // Get the median value of the last values of buffer size_t getCapacity() const; // Get the buffer capacity (maximum size) size_t getCurrentSize() const; // Get the current number of elements in buffer - size_t getLastIdx() const; // Get the last index of buffer + size_t getFirstIdx() const; // Get the index of oldest value in buffer + size_t getLastIdx() const; // Get the index of newest value in buffer bool isEmpty() const; // Check if buffer is empty bool isFull() const; // Check if buffer is full T getMinVal() const; // Get lowest possible value for buffer; used for initialized buffer data T getMaxVal() const; // Get highest possible value for buffer void clear(); // Clear buffer - T operator[](size_t index); // Operator[] for convenient access (same as get()) + T operator[](size_t index) const; // Operator[] for convenient access (same as get()) std::vector getAllValues() const; // Get all current values as a vector }; diff --git a/lib/obp60task/OBPRingBuffer.tpp b/lib/obp60task/OBPRingBuffer.tpp index e5589fd..662bfcb 100644 --- a/lib/obp60task/OBPRingBuffer.tpp +++ b/lib/obp60task/OBPRingBuffer.tpp @@ -9,6 +9,8 @@ RingBuffer::RingBuffer(size_t size) , count(0) , is_Full(false) { + bufLocker=xSemaphoreCreateMutex(); + if (size == 0) { // return false; } @@ -17,7 +19,7 @@ RingBuffer::RingBuffer(size_t size) MAX_VAL = std::numeric_limits::max(); dataName = ""; dataFmt = ""; - updFreq = MIN_VAL; + updFreq = -1; smallest = MIN_VAL; largest = MAX_VAL; @@ -28,20 +30,22 @@ RingBuffer::RingBuffer(size_t size) // Specify meta data of buffer content template -void RingBuffer::setMetaData(String name, String format, int updateFrequency, int minValue, int maxValue) +void RingBuffer::setMetaData(String name, String format, int updateFrequency, T minValue, T maxValue) { + GWSYNCHRONIZED(&bufLocker); dataName = name; dataFmt = format; updFreq = updateFrequency; - smallest = minValue; - largest = maxValue; + smallest = std::max(MIN_VAL, minValue); + largest = std::min(MAX_VAL, maxValue); } // Get meta data of buffer content template -bool RingBuffer::getMetaData(String& name, String& format, int& updateFrequency, int& minValue, int& maxValue) +bool RingBuffer::getMetaData(String& name, String& format, int& updateFrequency, T& minValue, T& maxValue) { - if (updFreq == MIN_VAL || smallest == MIN_VAL || largest == MAX_VAL) { + GWSYNCHRONIZED(&bufLocker); + if (dataName == "" || dataFmt == "" || updFreq == -1) { return false; // Meta data not set } @@ -50,17 +54,19 @@ bool RingBuffer::getMetaData(String& name, String& format, int& updateFrequen updateFrequency = updFreq; minValue = smallest; maxValue = largest; - return true; // Meta data successfully retrieved + return true; } // Add a new value to buffer template void RingBuffer::add(const T& value) { + GWSYNCHRONIZED(&bufLocker); if (value < smallest || value > largest) { buffer[head] = MIN_VAL; // Store MIN_VAL if value is out of range + } else { + buffer[head] = value; } - buffer[head] = value; last = head; if (is_Full) { @@ -79,6 +85,7 @@ void RingBuffer::add(const T& value) template T RingBuffer::get(size_t index) const { + GWSYNCHRONIZED(&bufLocker); if (isEmpty() || index < 0 || index >= count) { return MIN_VAL; } @@ -89,7 +96,7 @@ T RingBuffer::get(size_t index) const // Operator[] for convenient access (same as get()) template -T RingBuffer::operator[](size_t index) +T RingBuffer::operator[](size_t index) const { return get(index); } @@ -101,7 +108,7 @@ T RingBuffer::getFirst() const if (isEmpty()) { return MIN_VAL; } - return buffer[first]; + return get(first); } // Get the last (newest) value in the buffer @@ -111,7 +118,7 @@ T RingBuffer::getLast() const if (isEmpty()) { return MIN_VAL; } - return buffer[last]; + return get(last); } // Get the lowest value in the buffer @@ -293,6 +300,13 @@ size_t RingBuffer::getCurrentSize() const return count; } +// Get the first index of buffer +template +size_t RingBuffer::getFirstIdx() const +{ + return first; +} + // Get the last index of buffer template size_t RingBuffer::getLastIdx() const @@ -332,6 +346,7 @@ T RingBuffer::getMaxVal() const template void RingBuffer::clear() { + GWSYNCHRONIZED(&bufLocker); head = 0; first = 0; last = 0; diff --git a/lib/obp60task/OBPSensorTask.cpp b/lib/obp60task/OBPSensorTask.cpp index b0271a1..fe1bcb9 100644 --- a/lib/obp60task/OBPSensorTask.cpp +++ b/lib/obp60task/OBPSensorTask.cpp @@ -75,6 +75,8 @@ void sensorTask(void *param){ 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) RingBuffer dbtHstry(960); // Circular buffer to store water depth values (DBT) + // Link ring buffer pointers to shared memory for transfer to pages + shared->setHstryBuf(twdHstry, twsHstry, dbtHstry); // Start timer Timer1.start(); // Start Timer1 for blinking LED @@ -354,6 +356,24 @@ void sensorTask(void *param){ } } + // Boat data history buffer initialization + // later, read data types from config and specify hstryValList accordingly + GwApi::BoatValue *twdBVal=new GwApi::BoatValue(GwBoatData::_TWD); + GwApi::BoatValue *twsBVal=new GwApi::BoatValue(GwBoatData::_TWS); + GwApi::BoatValue *dbtBVal=new GwApi::BoatValue(GwBoatData::_DBT); + GwApi::BoatValue *hstryValList[]={twdBVal, twsBVal, dbtBVal}; // List of boat values for history storage + api->getBoatDataValues(3, hstryValList); + 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 dbtHstryMax = 3276; // Max value for depth in m (=327), shifted by 10 for 1 decimal + // Initialize history buffers with meta data + twdHstry.setMetaData(twdBVal->getName(), twdBVal->getFormat(), hstryUpdFreq, hstryMinVal, twdHstryMax); + twsHstry.setMetaData(twsBVal->getName(), twsBVal->getFormat(), hstryUpdFreq, hstryMinVal, twsHstryMax); + dbtHstry.setMetaData(dbtBVal->getName(), dbtBVal->getFormat(), hstryUpdFreq, hstryMinVal, dbtHstryMax); + bool simulation = api->getConfig()->getBool(api->getConfig()->useSimuData, false); + int rotoffset = api->getConfig()->getConfigItem(api->getConfig()->rotOffset,true)->asInt(); static long loopCounter = 0; // Loop counter for 1Wire data transmission @@ -367,7 +387,7 @@ void sensorTask(void *param){ long starttime11 = millis(); // Copy GPS data to RTC all 5min long starttime12 = millis(); // Get RTC data all 500ms long starttime13 = millis(); // Get 1Wire sensor data all 2s - unsigned long starttime20 = millis(); // Get TWD and TWS data every 1000ms + unsigned long starttime20 = millis(); // Get history TWD, TWS, DBT data each 1s tN2kMsg N2kMsg; shared->setSensorData(sensors); //set initially read values @@ -377,23 +397,6 @@ void sensorTask(void *param){ GwApi::BoatValue *hdop=new GwApi::BoatValue(GwBoatData::_HDOP); GwApi::BoatValue *valueList[]={gpsdays, gpsseconds, hdop}; - // Prepare boat data values for history storage - // later read data types from config and specify hstryvalList accordingly - GwApi::BoatValue *twdBVal=new GwApi::BoatValue(GwBoatData::_TWD); - GwApi::BoatValue *twsBVal=new GwApi::BoatValue(GwBoatData::_TWS); - GwApi::BoatValue *dbtBVal=new GwApi::BoatValue(GwBoatData::_STW); // STW just for testing - GwApi::BoatValue *hstryValList[]={twdBVal, twsBVal, dbtBVal}; // List of boat values for history storage - // Initialize history buffers with meta data - api->getBoatDataValues(3,hstryValList); - int hstryUpdFreq = 1000; // Update frequency for history buffers in ms - int hstryMinVal = 0; // Minimum value for history buffers - int twdHstryMax = 360; - int twsHstryMax = 100; - int dbtHstryMax = 327; // Max value for depth (due to signed 16 bit integer and decimals shift) - twdHstry.setMetaData(twdBVal->getName(), twdBVal->getFormat(), hstryUpdFreq, hstryMinVal, twdHstryMax); - twsHstry.setMetaData(twsBVal->getName(), twsBVal->getFormat(), hstryUpdFreq, hstryMinVal, twsHstryMax); - dbtHstry.setMetaData(dbtBVal->getName(), dbtBVal->getFormat(), hstryUpdFreq, hstryMinVal, dbtHstryMax); - // Internal RTC with NTP init ESP32Time rtc(0); if (api->getConfig()->getString(api->getConfig()->timeSource) == "iRTC") { @@ -799,22 +802,22 @@ void sensorTask(void *param){ } } - // Read TWD, TWS, DBT data from boatData every 1000ms for history and windplot display - if(millis() > starttime20 + 1000){ - starttime20 = millis(); + // Read TWD, TWS, DBT data from boatData each 1000ms for history and windplot display + if((millis() > starttime20 + 1000) && !simulation){ int16_t val; - api->getBoatDataValues(3,hstryValList); + starttime20 = millis(); + api->getBoatDataValues(3, hstryValList); if (twdBVal->valid) { - val = static_cast(std::round(RadToDeg(twdBVal->value))); + val = static_cast(std::round(twdBVal->value * 1000)); // Shift value to store decimals in int16_t); if (val < hstryMinVal || val > twdHstryMax) { - val = INT16_MIN; // Add invalid value + val = INT16_MIN; // Add invalid value - to be fixed later } } else { val = INT16_MIN; } twdHstry.add(val); if (twsBVal->valid) { - val = static_cast(twsBVal->value * 100); // Shift value to store decimals in int16_t + val = static_cast(twsBVal->value * 10); // Shift value to store decimals in int16_t if (val < hstryMinVal || val > twsHstryMax) { val = INT16_MIN; // Add invalid value } @@ -823,7 +826,7 @@ void sensorTask(void *param){ } twsHstry.add(val); if (dbtBVal->valid) { - val = static_cast(dbtBVal->value * 100); // Shift value to store decimals in int16_t + val = static_cast(dbtBVal->value * 10); // Shift value to store decimals in int16_t if (val < hstryMinVal || val > dbtHstryMax) { val = INT16_MIN; // Add invalid value } @@ -832,18 +835,11 @@ void sensorTask(void *param){ } dbtHstry.add(val); -// api->getLogger()->logDebug(GwLog::ERROR,"SensorTask pointer: TWD: %p, TWS: %p, STW: %p", twdHstry, twsHstry, dbtHstry); - api->getLogger()->logDebug(GwLog::ERROR,"SensorTask Data: TWD:%d TWS:%f DBT:%f", std::round(RadToDeg(twdBVal->value)), twsBVal->value, dbtBVal->value); - api->getLogger()->logDebug(GwLog::ERROR,"SensorTask buffers: TWD:%d TWS:%f DBT:%f", twdHstry.getLast(), twsHstry.getLast(), dbtHstry.getLast()); - + int counttime = millis() - starttime20; + api->getLogger()->logDebug(GwLog::ERROR,"SensorTask write time: %d", counttime); } - // Add sensor and history data to shared memory for transfer to pages shared->setSensorData(sensors); - shared->setHstryBuf(twdHstry, twsHstry, dbtHstry); -// tBoatHstryData tmpHstryData = shared->getHstryBuf(); -// api->getLogger()->logDebug(GwLog::ERROR,"SensorTask tmpHstryData pointer: TWD: %p, TWS: %p, STW: %p", tmpHstryData.twdHstry, tmpHstryData.twsHstry, tmpHstryData.dbtHstry); - } vTaskDelete(NULL); } diff --git a/lib/obp60task/OBPSensorTask.h b/lib/obp60task/OBPSensorTask.h index 352632a..60a94e0 100644 --- a/lib/obp60task/OBPSensorTask.h +++ b/lib/obp60task/OBPSensorTask.h @@ -23,9 +23,10 @@ class SharedData{ GWSYNCHRONIZED(&locker); return sensors; } - void setHstryBuf(RingBuffer twdHstry,RingBuffer twsHstry,RingBuffer dbtHstry) { + void setHstryBuf(RingBuffer& twdHstry, RingBuffer& twsHstry, RingBuffer& dbtHstry) { GWSYNCHRONIZED(&locker); boatHstry={&twdHstry, &twsHstry, &dbtHstry}; +// api->getLogger()->logDebug(GwLog::ERROR, "SharedData setHstryBuf - passed object ptrs: TWD: %p, TWS: %p, STW: %p", &twdHstry, &twsHstry, &dbtHstry); // api->getLogger()->logDebug(GwLog::ERROR, "SharedData setHstryBuf: TWD: %p, TWS: %p, STW: %p", boatHstry.twdHstry, boatHstry.twsHstry, boatHstry.dbtHstry); } tBoatHstryData getHstryBuf() { diff --git a/lib/obp60task/PageWindPlot.cpp b/lib/obp60task/PageWindPlot.cpp index bcd1dc3..77d0580 100644 --- a/lib/obp60task/PageWindPlot.cpp +++ b/lib/obp60task/PageWindPlot.cpp @@ -5,202 +5,9 @@ #include "OBPRingBuffer.h" #include "Pagedata.h" #include // just for RadToDeg function - #include -// **************************************************************** -class OldwndHistory { - // provides a circular buffer to store wind history values -private: - int SIZE; - std::vector buffer; - int first; // points to the first (oldest) valid element - int last; // points to the last (newest) valid element - int head; // points to the next insertion index - int count; // number of valid elements - -public: - bool begin(int size) - // start buffer - { - if (size <= 0 || size > 10000) { - return false; - } - SIZE = size; - buffer.resize(size, INT_MIN); // allocate buffer - head = 0; - first = 0; - last = 0; - count = 0; - return true; - } - - void add(int value) - // Add a new value; store in [0..360 deg] format - { - if (value < 0 || value > 360) - value = INT_MIN; - buffer[head] = value; - last = head; - head = (head + 1) % SIZE; - if (count < SIZE) { - count++; - } else { - first = head - 1; // When buffer is full, first points to the oldest value - if (first < 0) - first += SIZE; - } - } - - int get(int index) const - // Get value by index in [0..360 deg] format (0 = oldest, count-1 = newest) - // INT_MIN indicates missing value or wrong index - { - int realIndex; - - if (index < 0 || index >= count) { - return INT_MIN; // Invalid index - } - realIndex = (first + index) % SIZE; - return buffer[realIndex]; - } - - int get(int index, int deg) const - // Get value by index in [-180..180 deg] or [0..360 deg] format (0 = oldest, count-1 = newest) - { - switch (deg) { - case 180: - // Return value in [-180..180 deg] format - return get(index); - case 360: { - // Return value in [0..360 deg] format - int value = get(index); - if (value < 0) { - value += 360; - }; - return value; - } - default: - return -1; - } - } - - int getSize() const - // Get number of valid elements - { - return count; - } - - int getMin() const - // Get minimum value of buffer - { - if (count == 0) { - return -1; // Buffer is empty - } else if (first + count <= SIZE) { - // No wrap-around - return *std::min_element(buffer.begin() + first, buffer.begin() + first + count); - } else { - // Wrap-around: check [first, end) and [begin, (first+count)%SIZE) - int min1 = *std::min_element(buffer.begin() + first, buffer.end()); - int min2 = *std::min_element(buffer.begin(), buffer.begin() + ((first + count) % SIZE)); - return std::min(min1, min2); - } - } - - int getMin(int amount) const - // Get minimum value of the last values of buffer - // INT_MIN indicates missing value or wrong index - - { - if (count == 0 || amount <= 0) - return INT_MIN; - if (amount > count) - amount = count; - - int minVal = INT_MAX; - int value = 0; - // Start from the newest value (last) and go backwards x times - for (int i = 0; i < amount; ++i) { - value = get(i); - if (value < minVal) - minVal = value; - } - return minVal; - } - - int getMax() const - // Get maximum value of buffer - { - if (count == 0) { - return -1; // Buffer is empty - } else if (first + count <= SIZE) { - // No wrap-around - return *std::max_element(buffer.begin() + first, buffer.begin() + first + count); - } else { - // Wrap-around: check [first, end) and [begin, (first+count)%SIZE) - int max1 = *std::max_element(buffer.begin() + first, buffer.end()); - int max2 = *std::max_element(buffer.begin(), buffer.begin() + ((first + count) % SIZE)); - return std::max(max1, max2); - } - } - - int getMax(int amount) const - // Get maximum value of the last values of buffer - // INT_MIN indicates missing value or wrong index - - { - if (count == 0 || amount <= 0) - return -1; - if (amount > count) - amount = count; - - int maxVal = INT_MIN; - int value = 0; - // Start from the newest value (last) and go backwards x times - for (int i = 0; i < amount; ++i) { - value = get(i); - if (value > maxVal) - maxVal = value; - } - return maxVal; - } - - int getMid(int amount) const - // Get middle value in the buffer - { - if (count == 0) { - return INT_MIN; // Buffer is empty - } - return (getMin(amount) + getMax(amount)) / 2; - } - - int getRng(int center, int amount) const - // Get maximum difference of last of buffer values to center value - { - if (count == 0 || amount <= 0) - return INT_MIN; - if (amount > count) - amount = count; - - int value = 0; - int maxRng = INT_MIN; - int rng = 0; - // Start from the newest value (last) and go backwards x times - for (int i = 0; i < amount; ++i) { - value = get(i); - if (value == INT_MIN) { - continue; - } - rng = abs(((value - center + 540) % 360) - 180); - if (rng > maxRng) - maxRng = rng; - } - if (maxRng > 180) { - maxRng = 180; - } - return maxRng; - } -}; +static const float radToDeg = 180.0 / M_PI; // Conversion factor from radians to degrees // Get maximum difference of last of TWD ringbuffer values to center chart int getRng(const RingBuffer& windDirHstry, int center, size_t amount) @@ -221,10 +28,13 @@ 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 + capacity - i) % capacity); + value = windDirHstry.get(((last - i) % capacity + capacity) % capacity); + if (value == minVal) { continue; } + + value = value / 1000.0 * radToDeg; rng = abs(((value - center + 540) % 360) - 180); if (rng > maxRng) maxRng = rng; @@ -232,9 +42,47 @@ int getRng(const RingBuffer& windDirHstry, int center, size_t amount) if (maxRng > 180) { maxRng = 180; } + return maxRng; } +void fillSimData(PageData& pageData) +// Fill the TWD history buffer with simulated data +{ + int value = 20; + int16_t value2 = 0; + for (int i = 0; i < 600; i++) { + value += random(-20, 20); + if (value < 0) + value += 360; + if (value >= 360) + value -= 360; + value2 = static_cast(DegToRad(value) * 1000.0); + pageData.boatHstry.twdHstry->add(value2); // Fill the buffer with some test data + } +} + +void fillTstBuffer(PageData& pageData) +{ + float value = 0; + int value2 = 0; + for (int i = 0; i < 60; i++) { + pageData.boatHstry.twdHstry->add(-10); // Fill the buffer with some test data + } + for (int i = 0; i < 20; i++) { + for (int j = 0; j < 20; j++) { + value += 10; + value2 = static_cast(DegToRad(value) * 1000.0); + pageData.boatHstry.twdHstry->add(value2); // Fill the buffer with some test data + } + for (int j = 0; j < 20; j++) { + value -= 10; + value2 = static_cast(DegToRad(value) * 1000.0); + pageData.boatHstry.twdHstry->add(value2); // Fill the buffer with some test data + } + } +} + // **************************************************************** class PageWindPlot : public Page { @@ -307,174 +155,151 @@ public: GwConfigHandler* config = commonData->config; GwLog* logger = commonData->logger; - GwApi::BoatValue* bvalue; - const int numCfgValues = 9; - String dataName[numCfgValues]; - double dataValue[numCfgValues]; - bool dataValid[numCfgValues]; - String dataSValue[numCfgValues]; - String dataUnit[numCfgValues]; - String dataSValueOld[numCfgValues]; - String dataUnitOld[numCfgValues]; - int twdValue; - static const float radToDeg = 180.0 / M_PI; // Conversion factor from radians to degrees + float twsValue; // TWS value in chart area + String twdName, twdUnit; // TWD name and unit + int updFreq; // Update frequency for TWD + int16_t twdLowest, twdHighest; // TWD range - bool isInitialized = false; // Flag to indicate that page is initialized - bool wndDataValid = false; // Flag to indicate if wind data is valid - bool simulation = false; - bool holdValues = false; + static bool isInitialized = false; // Flag to indicate that page is initialized + static bool wndDataValid = false; // Flag to indicate if wind data is valid + static int numNoData; // Counter for multiple invalid data values in a row + static bool simulation = false; + static bool holdValues = false; - int width = getdisplay().width(); // Get screen width - int height = getdisplay().height(); // Get screen height - int xCenter = width / 2; // Center of screen in x direction + static int width; // Screen width + static int height; // Screen height + static int xCenter; // Center of screen in x direction static const int yOffset = 48; // Offset for y coordinates of chart area - int cHeight = height - yOffset - 22; // height of chart area - // cHeight = 60; - int bufSize = cHeight * 4; // Buffer size: 920 values for appox. 16 min. history chart + static int cHeight; // height of chart area + static int bufSize; // History buffer size: 960 values for appox. 16 min. history chart int intvBufSize; // Buffer size used for currently selected time interval int count; // current size of buffer - int numWndValues; // number of wind values available for current interval selection + int numWndVals; // number of wind values available for current interval selection static int linesToShow; // current number of lines to display on chart static int bufStart; // 1st data value in buffer to show + int numAddedBufVals; // Number of values added to buffer since last display + size_t currIdx; // Current index in TWD history buffer + static size_t lastIdx; // Last index of TWD history buffer + static size_t lastAddedIdx = 0; // Last index of TWD history buffer when new data was added static int oldDataIntv; // remember recent user selection of data interval - static int newDate; // indicates for higher time intervals that new date is available - static int wndCenter = INT_MIN; // chart wind center value position; init value indicates that wndCenter is not set yet + 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 int simWnd = 0; // Simulation value for wind data - static float simTWS = 0; // Simulation value for TWS data - static const int simStep = 10; // Simulation step for wind data + 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 static float chrtScl; // Scale for wind values in pixels per degree int chrtVal; // Current wind value static int chrtPrevVal; // Last wind value in chart area for check if value crosses 180 degree line -// int distVals; // helper to check wndCenter crossing -// int distMid; // helper to check wndCenter crossing - - // static RingBuffer windDirHstry(bufSize); // Circular buffer to store wind direction values - // static RingBuffer windSpdHstry(bufSize); // Circular buffer to store wind speed values LOG_DEBUG(GwLog::LOG, "Display page WindPlot"); unsigned long start = millis(); - // Data initialization - LOG_DEBUG(GwLog::ERROR, "PageWindPlot buffers: TWD:%d TWS:%f DBT:%f", pageData.boatHstry.twdHstry->getLast(), - pageData.boatHstry.twsHstry->getLast() * 0.0194384, pageData.boatHstry.dbtHstry->getLast() / 100); - - // if (windDirHstry.getCurrentSize() == 0) { - if (!isInitialized) { - simWnd = 0; - simTWS = 0; - twdValue = 0; - bufStart = 0; - linesToShow = 0; - oldDataIntv = dataIntv; - newDate = 0; - isInitialized = true; // Set flag to indicate that page is now initialized - } - // Get config data - // String lengthformat = config->getString(config->lengthFormat); simulation = config->getBool(config->useSimuData); holdValues = config->getBool(config->holdvalues); String flashLED = config->getString(config->flashLED); String backlightMode = config->getString(config->backlight); + if (!isInitialized) { + + if (simulation) { + fillSimData(pageData); // Fill the buffer with some test data + } + + width = getdisplay().width(); + height = getdisplay().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; + linesToShow = 0; + oldDataIntv = 0; + numAddedBufVals, currIdx, lastIdx = 0; + lastAddedIdx = pageData.boatHstry.twdHstry->getLastIdx(); + pageData.boatHstry.twdHstry->getMetaData(twdName, twdUnit, updFreq, twdLowest, twdHighest); + wndCenter = INT_MIN; + isInitialized = true; // Set flag to indicate that page is now initialized + LOG_DEBUG(GwLog::ERROR, "PageWindPlot Start1: lastAddedIdx: %d, simTwd: %.1f, isInitialized: %d, SimData: %d", lastAddedIdx, simTwd / 1000 + radToDeg, isInitialized, simulation); + } + // Optical warning by limit violation (unused) if (String(flashLED) == "Limit Violation") { setBlinkingLED(false); setFlashLED(false); } - /* // Read boatdata values for TWD, TWA, TWS, HDM, AWA, AWS, STW, COG, SOG, if available - for (int i = 0; i < numCfgValues; i++) { - bvalue = pageData.values[i]; - dataName[i] = xdrDelete(bvalue->getName()); - dataName[i] = dataName[i].substring(0, 6); // String length limit for value name - calibrationData.calibrateInstance(dataName[i], bvalue, logger); // Check if boat data value is to be calibrated - dataValue[i] = bvalue->value; // Value as double in SI unit - dataValid[i] = bvalue->valid; - dataSValue[i] = formatValue(bvalue, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places - dataUnit[i] = formatValue(bvalue, *commonData).unit; - if (dataValid[i]) { - dataSValueOld[i] = dataSValue[i]; // Save old value - dataUnitOld[i] = dataUnit[i]; // Save old unit - } - } + if (simulation) { + simTwd += random(-20, 20); + if (simTwd < 0.0) + simTwd += 360.0; + if (simTwd >= 360.0) + simTwd -= 360.0; - // Store TWD wind value in buffer, regardless of validity -> one value per second (if delivered in that frequency) - twdValue = int((dataValue[0] * radToDeg) + 0.5); // Read TWD value in degrees and round to integer - if (dataValid[0]) { // TWD data existing - wndDataValid = true; - } else { - // Try to calculate TWD value from other data, if available - // wndDataValid = windValues.calcTWD(&twdValue, dataValue[1], dataValue[2], dataValue[3], dataValue[4], dataValue[5], dataValue[6]); - } + int16_t z = static_cast(DegToRad(simTwd) * 1000.0); + pageData.boatHstry.twdHstry->add(z); // Fill the buffer with some test data - if (simulation) { - // Simulate data if simulation is enabled; use default simulation values for TWS - simWnd += random(simStep * -1, simStep); // random value between -simStep and +simStep - if (simWnd < 0) - simWnd += 360; - simWnd = simWnd % 360; - windDirHstry.add(simWnd); - // LOG_DEBUG(GwLog::DEBUG, "PageWindPlot simulation data: windValue: %d, windSpeed: %s", simWnd, dataSValue[2].c_str()); - } else if (wndDataValid) { - windDirHstry.add(twdValue); - } */ + simTws += random(-20, 20); // TWS value in knots + simTws = constrain(simTws, 0.0f, 50.0f); // Ensure TWS is between 0 and 50 knots + twsValue = simTws; + LOG_DEBUG(GwLog::ERROR, "PageWindPlot Simulation: simTwd: %f, twsValue: %f", simTwd, twsValue); + } else { + twsValue = pageData.boatHstry.twsHstry->getLast() / 10.0 * 1.94384; // TWS value in knots + } - // Identify buffer sizes and buffer position to print on the chart + // Identify buffer size and buffer start position for chart intvBufSize = cHeight * dataIntv; - // count = windDirHstry.getCurrentSize(); count = pageData.boatHstry.twdHstry->getCurrentSize(); - numWndValues = min(count, intvBufSize); - newDate++; + numWndVals = min(count, intvBufSize); + currIdx = pageData.boatHstry.twdHstry->getLastIdx(); + numAddedBufVals = (currIdx - lastAddedIdx + bufSize) % bufSize; // Number of values added to buffer since last display if (dataIntv != oldDataIntv) { - linesToShow = min(numWndValues / dataIntv, max(0, cHeight - 40)); - bufStart = max(0, (count - (linesToShow * dataIntv))); + // new data interval selected by user + linesToShow = min(numWndVals / dataIntv, cHeight - 60); + bufStart = max(0, count - (linesToShow * dataIntv)); + currIdx++; // eliminate current added value for bufStart calculation oldDataIntv = dataIntv; - } else if (newDate >= dataIntv) { - linesToShow = min(numWndValues / dataIntv, linesToShow + 1); - newDate = 0; + } else { + if (numAddedBufVals >= dataIntv) { + linesToShow += (numAddedBufVals / dataIntv); // Number of lines to show on chart + LOG_DEBUG(GwLog::ERROR, "PageWindPlot bufStart: bufStart: %d, numAddedBufVals: %d, linesToShow: %d, count %d, bufSize %d, Cnt=Size: %d", bufStart, numAddedBufVals, linesToShow, count, bufSize, count == bufSize); + lastAddedIdx = currIdx; + } + if (count == bufSize && currIdx != lastIdx) { + int numVals = (currIdx - lastIdx + bufSize) % bufSize; + bufStart = ((bufStart - numVals) % bufSize + bufSize) % bufSize; // keep 1st chart value constant in a rolling buffer when new data is added + lastIdx = currIdx; + } } - if (count == bufSize) { - bufStart--; // show the latest wind values in buffer; keep 1st value constant in a rolling buffer when new data is added - } - LOG_DEBUG(GwLog::ERROR, "PageWindPlot Dataset: TWD: %d, TWS: %f, STW: %f, count: %d, intvBufSize: %d, numWndValues: %d, bufStart: %d, linesToShow: %d, newDate: %d", pageData.boatHstry.twdHstry->getLast(), - pageData.boatHstry.twsHstry->getLast() * 0.0194384, pageData.boatHstry.dbtHstry->getLast() / 100, count, intvBufSize, numWndValues, bufStart, linesToShow, newDate); -// LOG_DEBUG(GwLog::ERROR, "PageWindPlot Pointer: TWD: %p, TWS: %p, STW: %p", pageData.boatHstry.twdHstry, pageData.boatHstry.twsHstry, pageData.boatHstry.dbtHstry); - -// LOG_DEBUG(GwLog::LOG, "PageWindPlot, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, %s:%f, cnt: %d, valid0: %d", dataName[0].c_str(), dataValue[0], -// dataName[1].c_str(), dataValue[1], dataName[2].c_str(), dataValue[2], dataName[3].c_str(), dataValue[3], dataName[4].c_str(), dataValue[4], -// dataName[5].c_str(), dataValue[5], dataName[6].c_str(), dataValue[6], dataName[7].c_str(), dataValue[7], dataName[8].c_str(), dataValue[8], count, dataValid[0]); + LOG_DEBUG(GwLog::ERROR, "PageWindPlot Dataset: TWD: %.1f, TWS: %.1f, DBT: %.1f, count: %d, intvBufSize: %d, numWndVals: %d, bufStart: %d, linesToShow: %d, numAddedBufVals: %d, lastIdx: %d", + pageData.boatHstry.twdHstry->getLast() / 1000.0 * radToDeg, pageData.boatHstry.twsHstry->getLast() / 10.0 * 1.94384, pageData.boatHstry.dbtHstry->getLast() / 10.0, + count, intvBufSize, numWndVals, bufStart, linesToShow, numAddedBufVals, lastIdx); // initialize chart range values if (wndCenter == INT_MIN) { - // wndCenter = max(0, int(windDirHstry.get(numWndValues - intvBufSize))); // get 1st value of current data interval - wndCenter = max(0, int(pageData.boatHstry.twdHstry->get(numWndValues - intvBufSize))); // get 1st value of current data interval + wndCenter = max(0, int(pageData.boatHstry.twdHstry->get(numWndVals - intvBufSize) / 1000.0 * radToDeg)); // get 1st value of current data interval wndCenter = (int((wndCenter + (wndCenter >= 0 ? 5 : -5)) / 10) * 10) % 360; // Set new center value; round to nearest 10 degree value; 360° -> 0° diffRng = dfltRng; chrtRng = dfltRng; } else { // check and adjust range between left, center, and right chart limit - // diffRng = getRng(windDirHstry, wndCenter, numWndValues); - diffRng = getRng(*pageData.boatHstry.twdHstry, wndCenter, numWndValues); - diffRng = (diffRng == INT_MIN ? 0 : diffRng); + diffRng = getRng(*pageData.boatHstry.twdHstry, 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); } - // int debugMin = windDirHstry.getMin(numWndValues); - int debugMin = pageData.boatHstry.twdHstry->getMin(numWndValues); - int debugMax = pageData.boatHstry.twdHstry->getMax(numWndValues); - LOG_DEBUG(GwLog::ERROR, "PageWindPlot Range. wndCenter: %d, numWndValues: %d, min: %d, max: %d, diffrng: %d, chrtRng: %d ", wndCenter, numWndValues, debugMin, debugMax, diffRng, chrtRng); } chrtScl = float(width) / float(chrtRng) / 2.0; // Chart scale: pixels per degree wndLeft = wndCenter - chrtRng; @@ -483,11 +308,11 @@ public: wndRight = (chrtRng < 180 ? wndCenter + chrtRng : wndCenter + chrtRng - 1); if (wndRight >= 360) wndRight -= 360; - // LOG_DEBUG(GwLog::DEBUG, "PageWindPlot dataValue[0]: %f, windValue: %d, count: %d, diffRng: %d, chartRng: %d, Center: %d, scale: %f", double(dataValue[0] * radToDeg), - // (!windDirHstry.get(count - 1) < 0 ? 0 : windDirHstry.get(count - 1)), count, diffRng, chrtRng, wndCenter, chrtScl); + LOG_DEBUG(GwLog::DEBUG, "PageWindPlot FirstVal: %f, LastVal: %d, count: %d, diffRng: %d, chartRng: %d, Center: %d, scale: %f", pageData.boatHstry.twdHstry->getFirst() / 1000.0 * radToDeg, + pageData.boatHstry.twdHstry->get(linesToShow) / 1000.0 * radToDeg, count, diffRng, chrtRng, wndCenter, chrtScl); // Draw page - //*********************************************************** + //*********************************************************************** // Set display in partial refresh mode getdisplay().setPartialWindow(0, 0, width, height); // Set partial update @@ -519,66 +344,80 @@ public: getdisplay().drawCircle(width - 5, yOffset - 17, 2, commonData->fgcolor); // symbol getdisplay().drawCircle(width - 5, yOffset - 17, 3, commonData->fgcolor); // symbol - if (pageData.boatHstry.twdHstry->getMax() == INT16_MIN) { - wndDataValid = false; // only values in buffer -> no valid wind data available + if (pageData.boatHstry.twdHstry->getMax() == twdLowest) { + // only values in buffer -> no valid wind data available + wndDataValid = false; } else { wndDataValid = true; // At least some wind data available } // Draw wind values in chart - //*********************************************************** + //*********************************************************************** if (wndDataValid) { for (int i = 0; i < linesToShow; i++) { - // chrtVal = windDirHstry.get(bufStart + (i * dataIntv)); // show the latest wind values in buffer; keep 1st value constant in a rolling buffer - chrtVal = 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(pageData.boatHstry.twdHstry->get((bufStart + (i * dataIntv)) % bufSize)); // show the latest wind values in buffer; keep 1st value constant in a rolling buffer if (chrtVal == INT16_MIN) { chrtPrevVal = INT16_MIN; - continue; // skip invalid values - } - x = ((chrtVal - wndLeft + 360) % 360) * chrtScl; - y = yOffset + cHeight - i; // Position in chart area - if (i > linesToShow - 15) - LOG_DEBUG(GwLog::ERROR, "PageWindPlot Chart: i: %d, chrtVal: %d, chrtPrevVal: %d, bufStart: %d count: %d, linesToShow: %d", i, chrtVal, chrtPrevVal, bufStart, count, linesToShow); - -// if (i == 0) { - if ((i == 0) || (chrtPrevVal == INT16_MIN)) { - prevX = x; // just a dot for 1st chart point or after some invalid values - prevY = y; - } else { - // cross borders check; shift values to [-180..0..180]; when crossing borders, range is 2x 180 degrees - int wndLeftDlt = -180 - ((wndLeft >= 180) ? (wndLeft - 360) : wndLeft); - int chrtVal180 = ((chrtVal + wndLeftDlt + 180) % 360 + 360) % 360 - 180; - int chrtPrevVal180 = ((chrtPrevVal + wndLeftDlt + 180) % 360 + 360) % 360 - 180; - // if (i > linesToShow - 15) - // LOG_DEBUG(GwLog::ERROR, "PageWindPlot Chart: i: %d, chrtVal: %d, chrtVal180: %d, chrtPrevVal: %d, chrtPrevVal180: %d, wndLeftDlt: %d", i, chrtVal, chrtVal180, chrtPrevVal, chrtPrevVal180, wndLeftDlt); - if (((chrtPrevVal180 >= -180) && (chrtPrevVal180 < -90) && (chrtVal180 > 90)) || ((chrtPrevVal180 <= 179) && (chrtPrevVal180 > 90) && chrtVal180 <= -90)) { - // If current value crosses chart borders, compared to previous value, split line - int xSplit = (((chrtPrevVal180 > 0 ? wndRight : wndLeft) - wndLeft + 360) % 360) * chrtScl; - getdisplay().drawLine(prevX, prevY, xSplit, y, commonData->fgcolor); - getdisplay().drawLine(prevX, prevY - 1, ((xSplit != prevX) ? xSplit : xSplit - 1), ((xSplit != prevX) ? y - 1 : y), commonData->fgcolor); - prevX = (((chrtVal180 > 0 ? wndRight : wndLeft) - wndLeft + 360) % 360) * chrtScl; - // LOG_DEBUG(GwLog::ERROR, "PageWindPlot Cross: i: %d, chrtVal: %d, chrtPrevVal: %d, wndLeft: %d wndRight: %d, curr:{%d,%d} prev:{%d,%d}", i, chrtVal, chrtPrevVal, wndLeft, wndRight, x, y, prevX, prevY); + /* if (i == linesToShow - 1) { + numNoData++; + // If more than 4 invalid values in a row, reset chart + } else { + numNoData = 0; // Reset invalid value counter } - } + if (numNoData > 4) { + // If more than 4 invalid values in a row, send message + getdisplay().setFont(&Ubuntu_Bold10pt7b); + getdisplay().fillRect(xCenter - 66, height / 2 - 20, 146, 24, commonData->bgcolor); // Clear area for TWS value + drawTextCenter(xCenter, height / 2 - 10, "No sensor data"); + } */ + } else { + chrtVal = (chrtVal / 1000.0 * radToDeg) + 0.5; // Convert to degrees and round + x = ((chrtVal - wndLeft + 360) % 360) * chrtScl; + y = yOffset + cHeight - i; // Position in chart area + if (i > linesToShow - 30) + LOG_DEBUG(GwLog::ERROR, "PageWindPlot Chart: i: %d, chrtVal: %d, bufStart: %d count: %d, linesToShow: %d", i, chrtVal, bufStart, count, linesToShow); - // Draw line with 2 pixels width + make sure vertical line are drawn correctly - getdisplay().drawLine(prevX, prevY, x, y, commonData->fgcolor); - getdisplay().drawLine(prevX, prevY - 1, ((x != prevX) ? x : x - 1), ((x != prevX) ? y - 1 : y), commonData->fgcolor); - chrtPrevVal = chrtVal; - prevX = x; - prevY = y; - - if (i == (cHeight - 1)) { // Reaching chart area top end () - linesToShow -= min(40, cHeight); // free top 40 lines of chart for new values - bufStart = max(0, count - (linesToShow * dataIntv)); // next start value in buffer to show - // if ((windDirHstry.getMin(numWndValues) > wndCenter) || (windDirHstry.getMax(numWndValues) < wndCenter)) { - if ((pageData.boatHstry.twdHstry->getMin(numWndValues) > wndCenter) || (pageData.boatHstry.twdHstry->getMax(numWndValues) < wndCenter)) { - // Check if all wind value are left or right of center value -> optimize chart range - int mid = pageData.boatHstry.twdHstry->getMid(numWndValues); - if (mid != INT16_MIN) { - wndCenter = int((mid + (mid >= 0 ? 5 : -5)) / 10) * 10; // Set new center value; round to nearest 10 degree value + if ((i == 0) || (chrtPrevVal == INT16_MIN)) { + // just a dot for 1st chart point or after some invalid values + prevX = x; + prevY = y; + } else { + // cross borders check; shift values to [-180..0..180]; when crossing borders, range is 2x 180 degrees + int wndLeftDlt = -180 - ((wndLeft >= 180) ? (wndLeft - 360) : wndLeft); + int chrtVal180 = ((chrtVal + wndLeftDlt + 180) % 360 + 360) % 360 - 180; + int chrtPrevVal180 = ((chrtPrevVal + wndLeftDlt + 180) % 360 + 360) % 360 - 180; + if (((chrtPrevVal180 >= -180) && (chrtPrevVal180 < -90) && (chrtVal180 > 90)) || ((chrtPrevVal180 <= 179) && (chrtPrevVal180 > 90) && chrtVal180 <= -90)) { + // If current value crosses chart borders compared to previous value, split line + int xSplit = (((chrtPrevVal180 > 0 ? wndRight : wndLeft) - wndLeft + 360) % 360) * chrtScl; + getdisplay().drawLine(prevX, prevY, xSplit, y, commonData->fgcolor); + getdisplay().drawLine(prevX, prevY - 1, ((xSplit != prevX) ? xSplit : xSplit - 1), ((xSplit != prevX) ? y - 1 : y), commonData->fgcolor); + prevX = (((chrtVal180 > 0 ? wndRight : wndLeft) - wndLeft + 360) % 360) * chrtScl; } } - // LOG_DEBUG(GwLog::ERROR, "PageWindPlot FreeTop: cHeight: %d, LinesToShow: %d, numWndValues: %d, wndCenter: %d, bufStart: %d", cHeight, linesToShow, numWndValues, wndCenter, bufStart); + + // Draw line with 2 pixels width + make sure vertical line are drawn correctly + getdisplay().drawLine(prevX, prevY, x, y, commonData->fgcolor); + getdisplay().drawLine(prevX, prevY - 1, ((x != prevX) ? x : x - 1), ((x != prevX) ? y - 1 : y), commonData->fgcolor); + chrtPrevVal = chrtVal; + prevX = x; + prevY = y; + } + // Reaching chart area top end + if (i >= (cHeight - 1)) { + linesToShow -= min(60, cHeight); // free top 40 lines of chart for new values + if (count >= numWndVals) { + bufStart = (bufStart + (60 * dataIntv)) % bufSize; // next start value in buffer to show + } + int minWndDir = pageData.boatHstry.twdHstry->getMin(numWndVals) / 1000.0 * radToDeg; + int maxWndDir = pageData.boatHstry.twdHstry->getMax(numWndVals) / 1000.0 * radToDeg; + LOG_DEBUG(GwLog::ERROR, "PageWindPlot FreeTop: Minimum: %d, Maximum: %d, OldwndCenter: %d", minWndDir, maxWndDir, wndCenter); + if ((minWndDir > wndCenter) || (maxWndDir < wndCenter)) { + // Check if all wind value are left or right of center value -> optimize chart range + int 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 + } + } + LOG_DEBUG(GwLog::ERROR, "PageWindPlot FreeTop: cHeight: %d, LinesToShow: %d, bufStart: %d, numWndVals: %d, wndCenter: %d", cHeight, linesToShow, bufStart, numWndVals, wndCenter); break; } } @@ -616,42 +455,46 @@ public: getdisplay().fillRect(xPosTws - 4, yPosTws - 38, 142, 44, commonData->bgcolor); // Clear area for TWS value getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b); getdisplay().setCursor(xPosTws, yPosTws); - getdisplay().print(dataSValue[2]); // Value + // twsValue = pageData.boatHstry.twsHstry->getLast() / 10.0 * 1.94384; // TWS value in knots + if (twsValue < 0 || twsValue >= 100) { + getdisplay().print("--.-"); + } else { + getdisplay().printf("%2.1f", twsValue); // Value + } getdisplay().setFont(&Ubuntu_Bold12pt7b); getdisplay().setCursor(xPosTws + 82, yPosTws - 14); - getdisplay().print(dataName[2]); // Name + getdisplay().print("TWS"); // Name getdisplay().setFont(&Ubuntu_Bold8pt7b); getdisplay().setCursor(xPosTws + 78, yPosTws + 1); - getdisplay().print(" "); - if (holdValues == false) { - getdisplay().print(dataUnit[2]); // Unit - } else { - getdisplay().print(dataUnitOld[2]); // Unit - } + getdisplay().printf(" kn"); // Unit } - // chart Y axis labels; print last to overwrite potential chart lines in label area - char sWndYAx[4]; // char buffer for wind Y axis labels + // chart Y axis labels; print at last to overwrite potential chart lines in label area int yPos; + int chrtLbl; getdisplay().setFont(&Ubuntu_Bold8pt7b); for (int i = 1; i <= 3; i++) { - if (numWndValues < intvBufSize) { - // chart initially filled from botton to top -> revert minute labels - yPos = yOffset + cHeight - (i * 60); - } else { - yPos = yOffset + (i * 60); - } + yPos = yOffset + (i * 60); getdisplay().fillRect(0, yPos, width, 1, commonData->fgcolor); getdisplay().fillRect(0, yPos - 8, 26, 16, commonData->bgcolor); // Clear small area to remove potential chart lines getdisplay().setCursor(1, yPos + 4); - snprintf(sWndYAx, 4, "%3d", i * dataIntv * -1); - getdisplay().print(sWndYAx); // Wind value label + if (count >= intvBufSize) { + // Calculate minute value for label + chrtLbl = ((i - 1 + (prevY < yOffset + 30)) * dataIntv) * -1; // change label if last data point is more than 30 lines (= seconds) from chart line + } else { + int j = 3 - i; + // chrtLbl = (int((((count / dataIntv) - 50) * dataIntv / 60) + 1) - ((j - 1) * dataIntv)) * -1; // 50 lines left below last chart line + chrtLbl = (int(((linesToShow - 50) * dataIntv / 60) + 1) - (j * dataIntv)) * -1; // 50 lines left below last chart line + } + // if (chrtLbl <= 0) { + getdisplay().printf("%3d", chrtLbl); // Wind value label + // } } - // Update display - getdisplay().nextPage(); // Partial update (fast) unsigned long finish = millis() - start; LOG_DEBUG(GwLog::ERROR, "PageWindPlot Time: %lu", finish); + // Update display + getdisplay().nextPage(); // Partial update (fast) }; }; diff --git a/lib/obp60task/obp60task.cpp b/lib/obp60task/obp60task.cpp index 8d14c18..581c999 100644 --- a/lib/obp60task/obp60task.cpp +++ b/lib/obp60task/obp60task.cpp @@ -837,11 +837,7 @@ void OBP60Task(GwApi *api){ currentPage->displayNew(pages[pageNumber].parameters); lastPage=pageNumber; } - pages[pageNumber].parameters.boatHstry = shared->getHstryBuf(); // Add boat history to page parameters - LOG_DEBUG(GwLog::ERROR,"obp60task buffers: TWD:%d TWS:%f DBT:%f", pages[pageNumber].parameters.boatHstry.twdHstry->getLast(), - pages[pageNumber].parameters.boatHstry.twsHstry->getLast() * 0.0194384, pages[pageNumber].parameters.boatHstry.dbtHstry->getLast() / 100); -// LOG_DEBUG(GwLog::ERROR, "obp60task pointer: TWD: %p, TWS: %p, STW: %p", pages[pageNumber].parameters.boatHstry.twdHstry, -// pages[pageNumber].parameters.boatHstry.twsHstry, pages[pageNumber].parameters.boatHstry.dbtHstry); + pages[pageNumber].parameters.boatHstry = shared->getHstryBuf(); // Add boat history data to page parameters //call the page code LOG_DEBUG(GwLog::DEBUG,"calling page %d",pageNumber); // Show footer if enabled (together with header) From bb999781779d232fe4af350e711e0c3515313674 Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Mon, 14 Jul 2025 21:17:17 +0200 Subject: [PATCH 21/26] no buffer writes for invalid data; fix ringbuffer index --- lib/obp60task/OBPRingBuffer.tpp | 28 ++++---- lib/obp60task/OBPSensorTask.cpp | 14 ++-- lib/obp60task/PageWindPlot.cpp | 111 +++++++++++++++----------------- 3 files changed, 70 insertions(+), 83 deletions(-) diff --git a/lib/obp60task/OBPRingBuffer.tpp b/lib/obp60task/OBPRingBuffer.tpp index 662bfcb..4c9580f 100644 --- a/lib/obp60task/OBPRingBuffer.tpp +++ b/lib/obp60task/OBPRingBuffer.tpp @@ -9,7 +9,7 @@ RingBuffer::RingBuffer(size_t size) , count(0) , is_Full(false) { - bufLocker=xSemaphoreCreateMutex(); + bufLocker = xSemaphoreCreateMutex(); if (size == 0) { // return false; @@ -44,11 +44,11 @@ void RingBuffer::setMetaData(String name, String format, int updateFrequency, template bool RingBuffer::getMetaData(String& name, String& format, int& updateFrequency, T& minValue, T& maxValue) { - GWSYNCHRONIZED(&bufLocker); if (dataName == "" || dataFmt == "" || updFreq == -1) { return false; // Meta data not set } + GWSYNCHRONIZED(&bufLocker); name = dataName; format = dataFmt; updateFrequency = updFreq; @@ -108,7 +108,7 @@ T RingBuffer::getFirst() const if (isEmpty()) { return MIN_VAL; } - return get(first); + return get(0); } // Get the last (newest) value in the buffer @@ -118,7 +118,7 @@ T RingBuffer::getLast() const if (isEmpty()) { return MIN_VAL; } - return get(last); + return get(count - 1); } // Get the lowest value in the buffer @@ -129,11 +129,11 @@ T RingBuffer::getMin() const return MIN_VAL; } - T minVal = get(first); + T minVal = getFirst(); T value; - for (size_t i = 0; i < count; i++) { + for (size_t i = 1; i < count; i++) { value = get(i); - if (value < minVal) { + if (value < minVal && value != MIN_VAL) { minVal = value; } } @@ -150,10 +150,10 @@ T RingBuffer::getMin(size_t amount) const if (amount > count) amount = count; - T minVal = get(last); + T minVal = getLast(); T value; for (size_t i = 0; i < amount; i++) { - value = get((last + capacity - i) % capacity); + value = get(count - 1 - i); if (value < minVal && value != MIN_VAL) { minVal = value; } @@ -169,11 +169,11 @@ T RingBuffer::getMax() const return MIN_VAL; } - T maxVal = get(first); + T maxVal = getFirst(); T value; - for (size_t i = 0; i < count; i++) { + for (size_t i = 1; i < count; i++) { value = get(i); - if (value > maxVal) { + if (value > maxVal && value != MIN_VAL) { maxVal = value; } } @@ -190,10 +190,10 @@ T RingBuffer::getMax(size_t amount) const if (amount > count) amount = count; - T maxVal = get(last); + T maxVal = getLast(); T value; for (size_t i = 0; i < amount; i++) { - value = get((last + capacity - i) % capacity); + value = get(count - 1 - i); if (value > maxVal && value != MIN_VAL) { maxVal = value; } diff --git a/lib/obp60task/OBPSensorTask.cpp b/lib/obp60task/OBPSensorTask.cpp index 032b40d..9a46788 100644 --- a/lib/obp60task/OBPSensorTask.cpp +++ b/lib/obp60task/OBPSensorTask.cpp @@ -812,30 +812,24 @@ void sensorTask(void *param){ if (twdBVal->valid) { val = static_cast(std::round(twdBVal->value * 1000)); // Shift value to store decimals in int16_t); if (val < hstryMinVal || val > twdHstryMax) { - val = INT16_MIN; // Add invalid value - to be fixed later + val = INT16_MIN; // Add invalid value } - } else { - val = INT16_MIN; + twdHstry.add(val); } - twdHstry.add(val); if (twsBVal->valid) { val = static_cast(twsBVal->value * 10); // Shift value to store decimals in int16_t if (val < hstryMinVal || val > twsHstryMax) { val = INT16_MIN; // Add invalid value } - } else { - val = INT16_MIN; + twsHstry.add(val); } - twsHstry.add(val); if (dbtBVal->valid) { val = static_cast(dbtBVal->value * 10); // Shift value to store decimals in int16_t if (val < hstryMinVal || val > dbtHstryMax) { val = INT16_MIN; // Add invalid value } - } else { - val = INT16_MIN; + dbtHstry.add(val); } - dbtHstry.add(val); int counttime = millis() - starttime20; api->getLogger()->logDebug(GwLog::ERROR,"SensorTask write time: %d", counttime); diff --git a/lib/obp60task/PageWindPlot.cpp b/lib/obp60task/PageWindPlot.cpp index 77d0580..a07c03c 100644 --- a/lib/obp60task/PageWindPlot.cpp +++ b/lib/obp60task/PageWindPlot.cpp @@ -7,15 +7,17 @@ #include // just for RadToDeg function #include -static const float radToDeg = 180.0 / M_PI; // Conversion factor from radians to degrees +static const double radToDeg = 180.0 / M_PI; // Conversion factor from radians to degrees +// #define radians(a) (a * 0.017453292519943295) +// #define degrees(a) (a * 57.29577951308232) // 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(); +// size_t capacity = windDirHstry.getCapacity(); +// size_t last = windDirHstry.getLastIdx(); if (windDirHstry.isEmpty() || amount <= 0) { return minVal; @@ -28,7 +30,8 @@ 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(((last - i) % capacity + capacity) % capacity); + value = windDirHstry.get(count - 1 - i); if (value == minVal) { continue; @@ -47,18 +50,18 @@ int getRng(const RingBuffer& windDirHstry, int center, size_t amount) } void fillSimData(PageData& pageData) -// Fill the TWD history buffer with simulated data +// Fill part of the TWD history buffer with simulated data { int value = 20; int16_t value2 = 0; - for (int i = 0; i < 600; i++) { + for (int i = 0; i < 900; i++) { value += random(-20, 20); if (value < 0) value += 360; if (value >= 360) value -= 360; value2 = static_cast(DegToRad(value) * 1000.0); - pageData.boatHstry.twdHstry->add(value2); // Fill the buffer with some test data + pageData.boatHstry.twdHstry->add(value2); } } @@ -67,18 +70,18 @@ void fillTstBuffer(PageData& pageData) float value = 0; int value2 = 0; for (int i = 0; i < 60; i++) { - pageData.boatHstry.twdHstry->add(-10); // Fill the buffer with some test data + pageData.boatHstry.twdHstry->add(-10); // -> irregular data } for (int i = 0; i < 20; i++) { for (int j = 0; j < 20; j++) { value += 10; value2 = static_cast(DegToRad(value) * 1000.0); - pageData.boatHstry.twdHstry->add(value2); // Fill the buffer with some test data + pageData.boatHstry.twdHstry->add(value2); } for (int j = 0; j < 20; j++) { value -= 10; value2 = static_cast(DegToRad(value) * 1000.0); - pageData.boatHstry.twdHstry->add(value2); // Fill the buffer with some test data + pageData.boatHstry.twdHstry->add(value2); } } } @@ -172,10 +175,9 @@ public: static const int yOffset = 48; // Offset for y coordinates of chart area static int cHeight; // height of chart area static int bufSize; // History buffer size: 960 values for appox. 16 min. history chart - int intvBufSize; // Buffer size used for currently selected time interval + static int intvBufSize; // Buffer size used for currently selected time interval int count; // current size of buffer - int numWndVals; // number of wind values available for current interval selection - static int linesToShow; // current number of lines to display on chart + static int numWndVals; // number of wind values available for current interval selection static int bufStart; // 1st data value in buffer to show int numAddedBufVals; // Number of values added to buffer since last display size_t currIdx; // Current index in TWD history buffer @@ -223,7 +225,6 @@ public: simTws = 0; twsValue = 0; bufStart = 0; - linesToShow = 0; oldDataIntv = 0; numAddedBufVals, currIdx, lastIdx = 0; lastAddedIdx = pageData.boatHstry.twdHstry->getLastIdx(); @@ -247,43 +248,38 @@ public: simTwd -= 360.0; int16_t z = static_cast(DegToRad(simTwd) * 1000.0); + LOG_DEBUG(GwLog::ERROR, "PageWindPlot Simulation: getLast TWD: %.0f, lastIdx: %d", pageData.boatHstry.twdHstry->getLast() / 1000.0 * RAD_TO_DEG, pageData.boatHstry.twdHstry->getLastIdx()); pageData.boatHstry.twdHstry->add(z); // Fill the buffer with some test data + LOG_DEBUG(GwLog::ERROR, "PageWindPlot Simulation: getAdded TWD: %.0f, lastIdx: %d", pageData.boatHstry.twdHstry->getLast() / 1000.0 * RAD_TO_DEG, pageData.boatHstry.twdHstry->getLastIdx()); - simTws += random(-20, 20); // TWS value in knots + 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; - LOG_DEBUG(GwLog::ERROR, "PageWindPlot Simulation: simTwd: %f, twsValue: %f", simTwd, twsValue); } else { twsValue = pageData.boatHstry.twsHstry->getLast() / 10.0 * 1.94384; // TWS value in knots } // Identify buffer size and buffer start position for chart - intvBufSize = cHeight * dataIntv; count = pageData.boatHstry.twdHstry->getCurrentSize(); - numWndVals = min(count, intvBufSize); currIdx = pageData.boatHstry.twdHstry->getLastIdx(); numAddedBufVals = (currIdx - lastAddedIdx + bufSize) % bufSize; // Number of values added to buffer since last display if (dataIntv != oldDataIntv) { // new data interval selected by user - linesToShow = min(numWndVals / dataIntv, cHeight - 60); - bufStart = max(0, count - (linesToShow * dataIntv)); - currIdx++; // eliminate current added value for bufStart calculation + intvBufSize = cHeight * dataIntv; + numWndVals = min(count, (cHeight - 60) * dataIntv); + bufStart = max(0, count - numWndVals); + lastAddedIdx = currIdx; oldDataIntv = dataIntv; } else { - if (numAddedBufVals >= dataIntv) { - linesToShow += (numAddedBufVals / dataIntv); // Number of lines to show on chart - LOG_DEBUG(GwLog::ERROR, "PageWindPlot bufStart: bufStart: %d, numAddedBufVals: %d, linesToShow: %d, count %d, bufSize %d, Cnt=Size: %d", bufStart, numAddedBufVals, linesToShow, count, bufSize, count == bufSize); - lastAddedIdx = currIdx; - } - if (count == bufSize && currIdx != lastIdx) { - int numVals = (currIdx - lastIdx + bufSize) % bufSize; - bufStart = ((bufStart - numVals) % bufSize + bufSize) % bufSize; // keep 1st chart value constant in a rolling buffer when new data is added - lastIdx = currIdx; + numWndVals = numWndVals + numAddedBufVals; + lastAddedIdx = currIdx; + if (count == bufSize) { + bufStart = max(0, bufStart - numAddedBufVals); } } - LOG_DEBUG(GwLog::ERROR, "PageWindPlot Dataset: TWD: %.1f, TWS: %.1f, DBT: %.1f, count: %d, intvBufSize: %d, numWndVals: %d, bufStart: %d, linesToShow: %d, numAddedBufVals: %d, lastIdx: %d", + LOG_DEBUG(GwLog::ERROR, "PageWindPlot Dataset: TWD: %.0f, TWS: %.1f, DBT: %.1f, count: %d, intvBufSize: %d, numWndVals: %d, bufStart: %d, numAddedBufVals: %d, lastIdx: %d", pageData.boatHstry.twdHstry->getLast() / 1000.0 * radToDeg, pageData.boatHstry.twsHstry->getLast() / 10.0 * 1.94384, pageData.boatHstry.dbtHstry->getLast() / 10.0, - count, intvBufSize, numWndVals, bufStart, linesToShow, numAddedBufVals, lastIdx); + count, intvBufSize, numWndVals, bufStart, numAddedBufVals, pageData.boatHstry.twdHstry->getLastIdx()); // initialize chart range values if (wndCenter == INT_MIN) { @@ -309,7 +305,7 @@ public: if (wndRight >= 360) wndRight -= 360; LOG_DEBUG(GwLog::DEBUG, "PageWindPlot FirstVal: %f, LastVal: %d, count: %d, diffRng: %d, chartRng: %d, Center: %d, scale: %f", pageData.boatHstry.twdHstry->getFirst() / 1000.0 * radToDeg, - pageData.boatHstry.twdHstry->get(linesToShow) / 1000.0 * radToDeg, count, diffRng, chrtRng, wndCenter, chrtScl); + pageData.boatHstry.twdHstry->getLast() / 1000.0 * radToDeg, count, diffRng, chrtRng, wndCenter, chrtScl); // Draw page //*********************************************************************** @@ -324,7 +320,7 @@ public: // chart labels char sWndLbl[4]; // char buffer for Wind angle label - getdisplay().setFont(&Ubuntu_Bold12pt7b); + getdisplay().setFont(&Ubuntu_Bold12pt8b); getdisplay().setCursor(xCenter - 88, yOffset - 3); getdisplay().print("TWD"); // Wind name // getdisplay().setCursor(xCenter - 20, yOffset - 3); @@ -344,7 +340,7 @@ public: getdisplay().drawCircle(width - 5, yOffset - 17, 2, commonData->fgcolor); // symbol getdisplay().drawCircle(width - 5, yOffset - 17, 3, commonData->fgcolor); // symbol - if (pageData.boatHstry.twdHstry->getMax() == twdLowest) { + if (pageData.boatHstry.twdHstry->getMax() == pageData.boatHstry.twdHstry->getMinVal()) { // only values in buffer -> no valid wind data available wndDataValid = false; } else { @@ -353,8 +349,8 @@ public: // Draw wind values in chart //*********************************************************************** if (wndDataValid) { - for (int i = 0; i < linesToShow; i++) { - chrtVal = static_cast(pageData.boatHstry.twdHstry->get((bufStart + (i * dataIntv)) % bufSize)); // show the latest wind values in buffer; keep 1st value constant in a rolling buffer + 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 if (chrtVal == INT16_MIN) { chrtPrevVal = INT16_MIN; /* if (i == linesToShow - 1) { @@ -373,8 +369,9 @@ public: chrtVal = (chrtVal / 1000.0 * radToDeg) + 0.5; // Convert to degrees and round x = ((chrtVal - wndLeft + 360) % 360) * chrtScl; y = yOffset + cHeight - i; // Position in chart area - if (i > linesToShow - 30) - LOG_DEBUG(GwLog::ERROR, "PageWindPlot Chart: i: %d, chrtVal: %d, bufStart: %d count: %d, linesToShow: %d", i, chrtVal, bufStart, count, linesToShow); + + if (i >= (numWndVals / dataIntv) - 10) + LOG_DEBUG(GwLog::ERROR, "PageWindPlot Chart: i: %d, chrtVal: %d, bufStart: %d count: %d, linesToShow: %d", i, chrtVal, bufStart, count, (numWndVals / dataIntv)); if ((i == 0) || (chrtPrevVal == INT16_MIN)) { // just a dot for 1st chart point or after some invalid values @@ -403,10 +400,8 @@ public: } // Reaching chart area top end if (i >= (cHeight - 1)) { - linesToShow -= min(60, cHeight); // free top 40 lines of chart for new values - if (count >= numWndVals) { - bufStart = (bufStart + (60 * dataIntv)) % bufSize; // next start value in buffer to show - } + 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; LOG_DEBUG(GwLog::ERROR, "PageWindPlot FreeTop: Minimum: %d, Maximum: %d, OldwndCenter: %d", minWndDir, maxWndDir, wndCenter); @@ -417,7 +412,7 @@ public: wndCenter = int((midWndDir + (midWndDir >= 0 ? 5 : -5)) / 10) * 10; // Set new center value; round to nearest 10 degree value } } - LOG_DEBUG(GwLog::ERROR, "PageWindPlot FreeTop: cHeight: %d, LinesToShow: %d, bufStart: %d, numWndVals: %d, wndCenter: %d", cHeight, linesToShow, bufStart, numWndVals, wndCenter); + LOG_DEBUG(GwLog::ERROR, "PageWindPlot FreeTop: cHeight: %d, bufStart: %d, numWndVals: %d, wndCenter: %d", cHeight, bufStart, numWndVals, wndCenter); break; } } @@ -425,7 +420,7 @@ public: } else { // No valid data available LOG_DEBUG(GwLog::LOG, "PageWindPlot: No valid data available"); - getdisplay().setFont(&Ubuntu_Bold10pt7b); + getdisplay().setFont(&Ubuntu_Bold10pt8b); getdisplay().fillRect(xCenter - 66, height / 2 - 20, 146, 24, commonData->bgcolor); // Clear area for TWS value drawTextCenter(xCenter, height / 2 - 10, "No sensor data"); } @@ -438,11 +433,8 @@ public: int xPosTws; static const int yPosTws = yOffset + 40; - // xPosTws = flipTws ? 30 : width - 145; xPosTws = flipTws ? 20 : width - 138; currentZone = (y >= yPosTws - 38) && (y <= yPosTws + 6) && (x >= xPosTws - 4) && (x <= xPosTws + 146) ? 1 : 0; // Define current zone for TWS value - // currentZone = (x >= xPosTws - 4) && (x <= xPosTws + 142) ? 1 : 0; // Define current zone for TWS value - // LOG_DEBUG(GwLog::DEBUG, "PageWindPlot TWS: xPos: %d, yPos: %d, x: %d y: %d, currZone: %d, lastZone: %d", xPosTws, yPosTws, x, y, currentZone, lastZone); if (currentZone != lastZone) { // Only flip when x moves to a different zone if ((y >= yPosTws - 38) && (y <= yPosTws + 6) && (x >= xPosTws - 4) && (x <= xPosTws + 146)) { @@ -455,16 +447,19 @@ public: getdisplay().fillRect(xPosTws - 4, yPosTws - 38, 142, 44, commonData->bgcolor); // Clear area for TWS value getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b); getdisplay().setCursor(xPosTws, yPosTws); - // twsValue = pageData.boatHstry.twsHstry->getLast() / 10.0 * 1.94384; // TWS value in knots if (twsValue < 0 || twsValue >= 100) { getdisplay().print("--.-"); } else { - getdisplay().printf("%2.1f", twsValue); // Value + if (twsValue < 10.0) { + getdisplay().printf("!%3.1f", twsValue); // Value + } else { + getdisplay().printf("%4.1f", twsValue); // Value} + } } - getdisplay().setFont(&Ubuntu_Bold12pt7b); + getdisplay().setFont(&Ubuntu_Bold12pt8b); getdisplay().setCursor(xPosTws + 82, yPosTws - 14); getdisplay().print("TWS"); // Name - getdisplay().setFont(&Ubuntu_Bold8pt7b); + getdisplay().setFont(&Ubuntu_Bold8pt8b); getdisplay().setCursor(xPosTws + 78, yPosTws + 1); getdisplay().printf(" kn"); // Unit } @@ -472,7 +467,7 @@ public: // chart Y axis labels; print at last to overwrite potential chart lines in label area int yPos; int chrtLbl; - getdisplay().setFont(&Ubuntu_Bold8pt7b); + getdisplay().setFont(&Ubuntu_Bold8pt8b); for (int i = 1; i <= 3; i++) { yPos = yOffset + (i * 60); getdisplay().fillRect(0, yPos, width, 1, commonData->fgcolor); @@ -483,16 +478,13 @@ public: chrtLbl = ((i - 1 + (prevY < yOffset + 30)) * dataIntv) * -1; // change label if last data point is more than 30 lines (= seconds) from chart line } else { int j = 3 - i; - // chrtLbl = (int((((count / dataIntv) - 50) * dataIntv / 60) + 1) - ((j - 1) * dataIntv)) * -1; // 50 lines left below last chart line - chrtLbl = (int(((linesToShow - 50) * dataIntv / 60) + 1) - (j * dataIntv)) * -1; // 50 lines left below last chart line + chrtLbl = (int((((numWndVals / dataIntv) - 50) * dataIntv / 60) + 1) - (j * dataIntv)) * -1; // 50 lines left below last chart line } - // if (chrtLbl <= 0) { getdisplay().printf("%3d", chrtLbl); // Wind value label - // } } unsigned long finish = millis() - start; - LOG_DEBUG(GwLog::ERROR, "PageWindPlot Time: %lu", finish); +// LOG_DEBUG(GwLog::ERROR, "PageWindPlot Time: %lu", finish); // Update display getdisplay().nextPage(); // Partial update (fast) }; @@ -513,7 +505,8 @@ PageDescription registerPageWindPlot( "WindPlot", // Page name createPage, // Action 0, // Number of bus values depends on selection in Web configuration - { "TWD", "TWA", "TWS", "HDM", "AWA", "AWS", "STW", "COG", "SOG" }, // Bus values we need in the page +// { "TWD", "TWA", "TWS", "HDM", "AWA", "AWS", "STW", "COG", "SOG" }, // Bus values we need in the page + { }, // Bus values we need in the page true // Show display header on/off ); From c48c6a2e48c2af172946928e0a3fa96a57142fa7 Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Sat, 19 Jul 2025 00:26:37 +0200 Subject: [PATCH 22/26] Move buffer handling to obp60task; reset OBPSensorTask; add true wind calculation --- lib/obp60task/OBPDataOperations.cpp | 136 ++++++++++++++++++++++++++++ lib/obp60task/OBPDataOperations.h | 48 ++++++++++ lib/obp60task/OBPSensorTask.cpp | 59 ------------ lib/obp60task/OBPSensorTask.h | 11 --- lib/obp60task/obp60task.cpp | 104 ++++++++++++++++++++- 5 files changed, 287 insertions(+), 71 deletions(-) create mode 100644 lib/obp60task/OBPDataOperations.cpp create mode 100644 lib/obp60task/OBPDataOperations.h diff --git a/lib/obp60task/OBPDataOperations.cpp b/lib/obp60task/OBPDataOperations.cpp new file mode 100644 index 0000000..daca553 --- /dev/null +++ b/lib/obp60task/OBPDataOperations.cpp @@ -0,0 +1,136 @@ +#include "OBPDataOperations.h" + +void WindUtils::to2PI(double* a) +{ + while (*a < 0) { + *a += 2 * M_PI; + } + *a = fmod(*a, 2 * M_PI); +} + +void WindUtils::toPI(double* a) +{ + *a += M_PI; + to2PI(a); + *a -= M_PI; +} + +void WindUtils::to360(double* a) +{ + while (*a < 0) { + *a += 360; + } + *a = fmod(*a, 360); +} + +void WindUtils::to180(double* a) +{ + *a += 180; + to360(a); + *a -= 180; +} + +void WindUtils::toCart(const double* phi, const double* r, double* x, double* y) +{ + *x = *r * sin(radians(*phi)); + *y = *r * cos(radians(*phi)); +} + +void WindUtils::toPol(const double* x, const double* y, double* phi, double* r) +{ + *phi = 90 - degrees(atan2(*y, *x)); + to360(phi); + *r = sqrt(*x * *x + *y * *y); +} + +void WindUtils::addPolar(const double* phi1, const double* r1, + const double* phi2, const double* r2, + double* phi, double* r) +{ + double x1, y1, x2, y2; + toCart(phi1, r1, &x1, &y1); + toCart(phi2, r2, &x2, &y2); + x1 += x2; + y1 += y2; + toPol(&x1, &y1, phi, r); +} + +void WindUtils::calcTwdSA(const double* AWA, const double* AWS, + const double* CTW, const double* STW, const double* HDT, + double* TWD, double* TWS) +{ + double AWD = *AWA + *HDT; + double stw = -*STW; + Serial.println("calcTwdSA: AWA: " + String(*AWA) + ", AWS: " + String(*AWS) + ", CTW: " + String(*CTW) + ", STW: " + String(*STW) + ", HDT: " + String(*HDT)); + addPolar(&AWD, AWS, CTW, &stw, TWD, TWS); + + // Normalize TWD to 0-360° + while (*TWD < 0) + *TWD += 360; + while (*TWD >= 360) + *TWD -= 360; + + Serial.println("calcTwdSA: TWD: " + String(*TWD) + ", TWS: " + String(*TWS)); +} + +bool WindUtils::calcTrueWind(const double* awaVal, const double* awsVal, + const double* cogVal, const double* stwVal, const double* hdtVal, + const double* hdmVal, double* twdVal, double* twsVal) +{ + double hdt, ctw; + double hdmVar = 3.0; // Magnetic declination, can be set from config if needed + double twd, tws; + + if (*hdtVal == __DBL_MIN__) { + if (*hdmVal != __DBL_MIN__) { + hdt = *hdmVal + hdmVar; // Use corrected HDM if HDT is not available + } else { + return false; // Cannot calculate without valid HDT or HDM + } + } + ctw = *hdtVal + ((*cogVal - *hdtVal) / 2); // Estimate CTW from COG + + if ((*awaVal == __DBL_MIN__) || (*awsVal == __DBL_MIN__) || (*cogVal == __DBL_MIN__) || (*stwVal == __DBL_MIN__)) { + // Cannot calculate true wind without valid AWA, AWS, COG, or STW + return false; + } else { + calcTwdSA(awaVal, awsVal, cogVal, stwVal, hdtVal, &twd, &tws); + *twdVal = twd; + *twsVal = tws; + + return true; + } +} + +/* +// make function available in Python for testing +static PyObject* true_wind(PyObject* self, PyObject* args) { + double AWA,AWS,CTW,STW,HDT,TWS,TWD; + if (!PyArg_ParseTuple(args, "ddddd", &AWA, &AWS, &CTW, &STW, &HDT)) { + return NULL; + } + + calc_true_wind(&AWA, &AWS, &CTW, &STW, &HDT, &TWD, &TWS); + + PyObject* twd = PyFloat_FromDouble(TWD); + PyObject* tws = PyFloat_FromDouble(TWS); + PyObject* tw = PyTuple_Pack(2,twd,tws); + return tw; +} + +static PyMethodDef methods[] = { + {"true_wind", true_wind, METH_VARARGS, NULL}, + {NULL, NULL, 0, NULL} +}; + +static struct PyModuleDef module = { + PyModuleDef_HEAD_INIT, + "truewind", // Module name + NULL, // Optional docstring + -1, + methods +}; + +PyMODINIT_FUNC PyInit_truewind(void) { + return PyModule_Create(&module); +}*/ \ No newline at end of file diff --git a/lib/obp60task/OBPDataOperations.h b/lib/obp60task/OBPDataOperations.h new file mode 100644 index 0000000..2ea8fdc --- /dev/null +++ b/lib/obp60task/OBPDataOperations.h @@ -0,0 +1,48 @@ +#pragma once +// #include +#include "GwApi.h" +#include +#include + +// #define radians(a) (a*0.017453292519943295) +// #define degrees(a) (a*57.29577951308232) + +class WindUtils { + +public: + static void to360(double* a); + static void to180(double* a); + static void to2PI(double* a); + static void toPI(double* a); + static void toCart(const double* phi, const double* r, double* x, double* y); + static void toPol(const double* x, const double* y, double* phi, double* r); + static void addPolar(const double* phi1, const double* r1, + const double* phi2, const double* r2, + double* phi, double* r); + static bool calcTrueWind(const double* awaVal, const double* awsVal, + const double* cogVal, const double* stwVal, const double* hdtVal, + const double* hdmVal, double* twdVal, double* twsVal); + static void calcTwdSA(const double* AWA, const double* AWS, + const double* CTW, const double* STW, const double* HDT, + double* TWD, double* TWS); +}; + +/* +// make function available in Python for testing +static PyObject* true_wind(PyObject* self, PyObject* args); +static PyMethodDef methods[] = { + {"true_wind", true_wind, METH_VARARGS, NULL}, + {NULL, NULL, 0, NULL} +}; + +static struct PyModuleDef module = { + PyModuleDef_HEAD_INIT, + "truewind", // Module name + NULL, // Optional docstring + -1, + methods +}; + +PyMODINIT_FUNC PyInit_truewind(void) { + return PyModule_Create(&module); +} */ \ No newline at end of file diff --git a/lib/obp60task/OBPSensorTask.cpp b/lib/obp60task/OBPSensorTask.cpp index 9a46788..311da1c 100644 --- a/lib/obp60task/OBPSensorTask.cpp +++ b/lib/obp60task/OBPSensorTask.cpp @@ -17,7 +17,6 @@ #include "ObpNmea0183.h" // Check NMEA0183 sentence for uncorrect content #include "OBP60Extensions.h" // Lib for hardware extensions #include "movingAvg.h" // Lib for moving average building -#include "OBPRingBuffer.h" // Lib with ring buffer for history storage of some boat data #include "time.h" // For getting NTP time #include // Internal ESP32 RTC clock @@ -70,14 +69,6 @@ void sensorTask(void *param){ batV.begin(); batC.begin(); - // Create ring buffers for history storage of some boat data - // later read additonal data types from config and specify buffers accordingly - 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) - RingBuffer dbtHstry(960); // Circular buffer to store water depth values (DBT) - // Link ring buffer pointers to shared memory for transfer to pages - shared->setHstryBuf(twdHstry, twsHstry, dbtHstry); - // Start timer Timer1.start(); // Start Timer1 for blinking LED @@ -356,24 +347,6 @@ void sensorTask(void *param){ } } - // Boat data history buffer initialization - // later, read data types from config and specify hstryValList accordingly - GwApi::BoatValue *twdBVal=new GwApi::BoatValue(GwBoatData::_TWD); - GwApi::BoatValue *twsBVal=new GwApi::BoatValue(GwBoatData::_TWS); - GwApi::BoatValue *dbtBVal=new GwApi::BoatValue(GwBoatData::_DBT); - GwApi::BoatValue *hstryValList[]={twdBVal, twsBVal, dbtBVal}; // List of boat values for history storage - api->getBoatDataValues(3, hstryValList); - 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 dbtHstryMax = 3276; // Max value for depth in m (=327), shifted by 10 for 1 decimal - // Initialize history buffers with meta data - twdHstry.setMetaData(twdBVal->getName(), twdBVal->getFormat(), hstryUpdFreq, hstryMinVal, twdHstryMax); - twsHstry.setMetaData(twsBVal->getName(), twsBVal->getFormat(), hstryUpdFreq, hstryMinVal, twsHstryMax); - dbtHstry.setMetaData(dbtBVal->getName(), dbtBVal->getFormat(), hstryUpdFreq, hstryMinVal, dbtHstryMax); - bool simulation = api->getConfig()->getBool(api->getConfig()->useSimuData, false); - int rotoffset = api->getConfig()->getConfigItem(api->getConfig()->rotOffset,true)->asInt(); static long loopCounter = 0; // Loop counter for 1Wire data transmission @@ -387,7 +360,6 @@ void sensorTask(void *param){ long starttime11 = millis(); // Copy GPS data to RTC all 5min long starttime12 = millis(); // Get RTC data all 500ms long starttime13 = millis(); // Get 1Wire sensor data all 2s - unsigned long starttime20 = millis(); // Get history TWD, TWS, DBT data each 1s tN2kMsg N2kMsg; shared->setSensorData(sensors); //set initially read values @@ -804,37 +776,6 @@ void sensorTask(void *param){ } } - // Read TWD, TWS, DBT data from boatData each 1000ms for history and windplot display - if((millis() > starttime20 + 1000) && !simulation){ - int16_t val; - starttime20 = millis(); - api->getBoatDataValues(3, hstryValList); - if (twdBVal->valid) { - val = static_cast(std::round(twdBVal->value * 1000)); // Shift value to store decimals in int16_t); - if (val < hstryMinVal || val > twdHstryMax) { - val = INT16_MIN; // Add invalid value - } - twdHstry.add(val); - } - if (twsBVal->valid) { - val = static_cast(twsBVal->value * 10); // Shift value to store decimals in int16_t - if (val < hstryMinVal || val > twsHstryMax) { - val = INT16_MIN; // Add invalid value - } - twsHstry.add(val); - } - if (dbtBVal->valid) { - val = static_cast(dbtBVal->value * 10); // Shift value to store decimals in int16_t - if (val < hstryMinVal || val > dbtHstryMax) { - val = INT16_MIN; // Add invalid value - } - dbtHstry.add(val); - } - - int counttime = millis() - starttime20; - api->getLogger()->logDebug(GwLog::ERROR,"SensorTask write time: %d", counttime); - } - shared->setSensorData(sensors); } vTaskDelete(NULL); diff --git a/lib/obp60task/OBPSensorTask.h b/lib/obp60task/OBPSensorTask.h index 60a94e0..7dd9f24 100644 --- a/lib/obp60task/OBPSensorTask.h +++ b/lib/obp60task/OBPSensorTask.h @@ -8,7 +8,6 @@ class SharedData{ private: SemaphoreHandle_t locker; SensorData sensors; - tBoatHstryData boatHstry; public: GwApi *api=NULL; SharedData(GwApi *api){ @@ -23,16 +22,6 @@ class SharedData{ GWSYNCHRONIZED(&locker); return sensors; } - void setHstryBuf(RingBuffer& twdHstry, RingBuffer& twsHstry, RingBuffer& dbtHstry) { - GWSYNCHRONIZED(&locker); - boatHstry={&twdHstry, &twsHstry, &dbtHstry}; -// api->getLogger()->logDebug(GwLog::ERROR, "SharedData setHstryBuf - passed object ptrs: TWD: %p, TWS: %p, STW: %p", &twdHstry, &twsHstry, &dbtHstry); -// api->getLogger()->logDebug(GwLog::ERROR, "SharedData setHstryBuf: TWD: %p, TWS: %p, STW: %p", boatHstry.twdHstry, boatHstry.twsHstry, boatHstry.dbtHstry); - } - tBoatHstryData getHstryBuf() { - GWSYNCHRONIZED(&locker); - return boatHstry; - } }; void createSensorTask(SharedData *shared); diff --git a/lib/obp60task/obp60task.cpp b/lib/obp60task/obp60task.cpp index 8d0012e..2d4e72b 100644 --- a/lib/obp60task/obp60task.cpp +++ b/lib/obp60task/obp60task.cpp @@ -13,6 +13,8 @@ #include "OBP60Extensions.h" // Functions lib for extension board #include "OBP60Keypad.h" // Functions for keypad #include "BoatDataCalibration.h" // Functions lib for data instance calibration +#include "OBPRingBuffer.h" // Functions lib with ring buffer for history storage of some boat data +#include "OBPDataOperations.h" // Functions lib for data operations such as true wind calculation #ifdef BOARD_OBP40S3 #include "driver/rtc_io.h" // Needs for weakup from deep sleep @@ -390,6 +392,11 @@ void OBP60Task(GwApi *api){ commonData.logger=logger; commonData.config=config; + // 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) + RingBuffer dbtHstry(960); // Circular buffer to store water depth values (DBT) + #ifdef HARDWARE_V21 // Keyboard coordinates for page footer initKeys(commonData); @@ -525,6 +532,8 @@ 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 = {&twdHstry, &twsHstry, &dbtHstry}; } // add out of band system page (always available) Page *syspage = allPages.pages[0]->creator(commonData); @@ -532,6 +541,33 @@ void OBP60Task(GwApi *api){ // Read all calibration data settings from config calibrationData.readConfig(config, logger); + // List of boat values for history storage + GwApi::BoatValue *twdBVal=new GwApi::BoatValue(GwBoatData::_TWD); + GwApi::BoatValue *twsBVal=new GwApi::BoatValue(GwBoatData::_TWS); + GwApi::BoatValue *dbtBVal=new GwApi::BoatValue(GwBoatData::_DBT); + GwApi::BoatValue *hstryValList[]={twdBVal, twsBVal, dbtBVal}; + api->getBoatDataValues(3, hstryValList); + // Boat data history buffer initialization + 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 dbtHstryMax = 3276; // Max value for depth in m (=327), shifted by 10 for 1 decimal + // Initialize history buffers with meta data + twdHstry.setMetaData(twdBVal->getName(), twdBVal->getFormat(), hstryUpdFreq, hstryMinVal, twdHstryMax); + twsHstry.setMetaData(twsBVal->getName(), twsBVal->getFormat(), hstryUpdFreq, hstryMinVal, twsHstryMax); + dbtHstry.setMetaData(dbtBVal->getName(), dbtBVal->getFormat(), hstryUpdFreq, hstryMinVal, dbtHstryMax); + bool simulation = api->getConfig()->getBool(api->getConfig()->useSimuData, false); + + // List of boat values for true winds calculation + GwApi::BoatValue *awaBVal=new GwApi::BoatValue(GwBoatData::_AWA); + GwApi::BoatValue *awsBVal=new GwApi::BoatValue(GwBoatData::_AWS); + GwApi::BoatValue *cogBVal=new GwApi::BoatValue(GwBoatData::_COG); + GwApi::BoatValue *stwBVal=new GwApi::BoatValue(GwBoatData::_STW); + GwApi::BoatValue *hdtBVal=new GwApi::BoatValue(GwBoatData::_HDT); + GwApi::BoatValue *hdmBVal=new GwApi::BoatValue(GwBoatData::_HDM); + GwApi::BoatValue *WndCalcValList[]={awaBVal, awsBVal, cogBVal, stwBVal, hdtBVal, hdmBVal}; + // Display screenshot handler for HTTP request // http://192.168.15.1/api/user/OBP60Task/screenshot api->registerRequestHandler("screenshot", [api, &pageNumber, pages](AsyncWebServerRequest *request) { @@ -587,6 +623,18 @@ void OBP60Task(GwApi *api){ GwApi::BoatValue *lon = boatValues.findValueOrCreate("LON"); // Load GpsLongitude GwApi::BoatValue *hdop = boatValues.findValueOrCreate("HDOP"); // Load GpsHDOP +/* // Boat values for wind conversion for main loop; will be calculated in case values are not available by sensors + GwApi::BoatValue *twd = boatValues.findValueOrCreate("TWD"); // True Wind Direction + GwApi::BoatValue *tws = boatValues.findValueOrCreate("TWS"); // True Wind Speed + GwApi::BoatValue *twa = boatValues.findValueOrCreate("TWA"); // True Wind Angle + GwApi::BoatValue *awaBVal = boatValues.findValueOrCreate("AWA"); // Apparent Wind Angle + GwApi::BoatValue *awsBVal = boatValues.findValueOrCreate("AWS"); // Apparent Wind Speed + GwApi::BoatValue *cogBVal = boatValues.findValueOrCreate("COG"); // Course Over Ground + GwApi::BoatValue *stwBVal = boatValues.findValueOrCreate("STW"); // Speed Through Water + GwApi::BoatValue *hdtBVal = boatValues.findValueOrCreate("HDT"); // Heading True + GwApi::BoatValue *ctwBVal = boatValues.findValueOrCreate("CTW"); // Course Through Water + GwApi::BoatValue *hdmBVal = boatValues.findValueOrCreate("HDM"); // Heading Magnetic +*/ LOG_DEBUG(GwLog::LOG,"obp60task: start mainloop"); commonData.time = boatValues.findValueOrCreate("GPST"); // Load GpsTime @@ -600,6 +648,7 @@ void OBP60Task(GwApi *api){ long starttime3 = millis(); // Display update all 1s long starttime4 = millis(); // Delayed display update after 4s when select a new page long starttime5 = millis(); // Calculate sunrise and sunset all 1s + unsigned long starttime10 = millis(); // Get history TWD, TWS, DBT data and calculate true winds each 1s pages[pageNumber].page->setupKeys(); // Initialize keys for first page @@ -789,6 +838,60 @@ void OBP60Task(GwApi *api){ } } + // Read TWD, TWS, DBT data from boatData each 1000ms for history and windplot display + if((millis() > starttime10 + 1000) && !simulation) { + double twdVal, twsVal, dbtVal; + double awaVal, awsVal, cogVal, stwVal, hdtVal, hdmVal; + double DBL_MIN = std::numeric_limits::lowest(); + + starttime10 = millis(); + LOG_DEBUG(GwLog::DEBUG,"History buffer write cycle"); + api->getBoatDataValues(3, hstryValList); + + if (!twdBVal->valid || !twsBVal->valid) { + api->getBoatDataValues(6, WndCalcValList); // Get all values for true wind calculation + awaVal = awaBVal->valid ? awaBVal->value * RAD_TO_DEG : __DBL_MIN__; + awsVal = awsBVal->valid ? awsBVal->value : __DBL_MIN__; + cogVal = cogBVal->valid ? cogBVal->value * RAD_TO_DEG : __DBL_MIN__; + stwVal = stwBVal->valid ? stwBVal->value : __DBL_MIN__; + hdtVal = hdtBVal->valid ? hdtBVal->value * RAD_TO_DEG : __DBL_MIN__; + hdmVal = hdmBVal->valid ? hdmBVal->value * RAD_TO_DEG : __DBL_MIN__; + LOG_DEBUG(GwLog::ERROR,"obp60task - Read data: AWA: %f, AWS: %f, COG: %f, STW: %f, HDT: %f, TWD=%f, TWS=%f", awaBVal->value, awsBVal->value, + cogBVal->value, stwBVal->value, hdtBVal->value, twdBVal->value, twsBVal->value); + + bool isCalculated = WindUtils::calcTrueWind(&awaVal, &awsVal, &cogVal, &stwVal, &cogVal, &hdmVal, &twdVal, &twsVal); // Calculate true wind if TWD not available +// bool isCalculated = WindUtils::calcTrueWind(&awaVal, &awsVal, &cogVal, &stwVal, &hdtVal, &hdmVal, &twdVal, &twsVal); // Calculate true wind if TWD not available + LOG_DEBUG(GwLog::ERROR,"obp60task - calc Wind: AWA: %f, AWS: %f, COG: %f, STW: %f, HDT: %f, TWD=%f, TWS=%f, converted? %d", awaBVal->value * RAD_TO_DEG, awsBVal->value * 3.6 / 1.852, + cogBVal->value * RAD_TO_DEG, stwBVal->value * 3.6 / 1.852, hdtBVal->value * RAD_TO_DEG, twdVal, twsVal, isCalculated); + } + if (twdBVal->valid) { + twdVal = std::round(twdBVal->value * 1000); // Shift value to store decimals in int16_t); + if (twdVal < hstryMinVal || twdVal > twdHstryMax) { + twdVal = INT16_MIN; // Add invalid value + } + } + twdHstry.add(static_cast(twdVal)); + + if (twsBVal->valid) { + twsVal = static_cast(twsBVal->value * 10); // Shift value to store decimals in int16_t + if (twsVal < hstryMinVal || twsVal > twsHstryMax) { + twsVal = INT16_MIN; // Add invalid value + } + } + twsHstry.add(twsVal); + + if (dbtBVal->valid) { + dbtVal = dbtBVal->value * 10; // Shift value to store decimals in int16_t + if (dbtVal < hstryMinVal || dbtVal > dbtHstryMax) { + dbtVal = INT16_MIN; // Add invalid value + } + dbtHstry.add(dbtVal); + } + + int counttime = millis() - starttime10; + LOG_DEBUG(GwLog::ERROR,"obp60task: History buffer write time: %d", counttime); + } + // Refresh display data, default all 1s currentPage = pages[pageNumber].page; int pagetime = 1000; @@ -839,7 +942,6 @@ void OBP60Task(GwApi *api){ currentPage->displayNew(pages[pageNumber].parameters); lastPage=pageNumber; } - pages[pageNumber].parameters.boatHstry = shared->getHstryBuf(); // Add boat history data to page parameters //call the page code LOG_DEBUG(GwLog::DEBUG,"calling page %d",pageNumber); // Show footer if enabled (together with header) From fe2223839fcf822a56f0b930705b2bd9fa700d4d Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Fri, 25 Jul 2025 08:42:43 +0200 Subject: [PATCH 23/26] added calibration to buffer; separated buffer and wind code in opb60task; prepared simulation; getMin/Max fix for ringbuffer for invalid data; fix for chart center; cleanup code --- lib/obp60task/OBPDataOperations.cpp | 160 ++++++++++--------- lib/obp60task/OBPDataOperations.h | 52 +++---- lib/obp60task/OBPRingBuffer.h | 1 + lib/obp60task/OBPRingBuffer.tpp | 21 ++- lib/obp60task/PageWindPlot.cpp | 199 +++++++++++------------- lib/obp60task/Pagedata.h | 7 +- lib/obp60task/config.json | 11 ++ lib/obp60task/config_obp40.json | 11 ++ lib/obp60task/obp60task.cpp | 229 ++++++++++++++++------------ 9 files changed, 367 insertions(+), 324 deletions(-) diff --git a/lib/obp60task/OBPDataOperations.cpp b/lib/obp60task/OBPDataOperations.cpp index daca553..cc6ac2a 100644 --- a/lib/obp60task/OBPDataOperations.cpp +++ b/lib/obp60task/OBPDataOperations.cpp @@ -1,45 +1,51 @@ #include "OBPDataOperations.h" -void WindUtils::to2PI(double* a) +double WindUtils::to2PI(double a) { - while (*a < 0) { - *a += 2 * M_PI; + a = fmod(a, 2 * M_PI); + if (a < 0.0) { + a += 2 * M_PI; } - *a = fmod(*a, 2 * M_PI); + return a; } -void WindUtils::toPI(double* a) +double WindUtils::toPI(double a) { - *a += M_PI; - to2PI(a); - *a -= M_PI; + a += M_PI; + a = to2PI(a); + a -= M_PI; + + return a; } -void WindUtils::to360(double* a) +double WindUtils::to360(double a) { - while (*a < 0) { - *a += 360; + a = fmod(a, 360); + if (a < 0.0) { + a += 360; } - *a = fmod(*a, 360); + return a; } -void WindUtils::to180(double* a) +double WindUtils::to180(double a) { - *a += 180; - to360(a); - *a -= 180; + a += 180; + a = to360(a); + a -= 180; + + return a; } void WindUtils::toCart(const double* phi, const double* r, double* x, double* y) { - *x = *r * sin(radians(*phi)); - *y = *r * cos(radians(*phi)); + *x = *r * sin(*phi); + *y = *r * cos(*phi); } void WindUtils::toPol(const double* x, const double* y, double* phi, double* r) { - *phi = 90 - degrees(atan2(*y, *x)); - to360(phi); + *phi = (M_PI / 2) - atan2(*y, *x); + *phi = to2PI(*phi); *r = sqrt(*x * *x + *y * *y); } @@ -57,80 +63,96 @@ 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* TWD, double* TWS, double* TWA) { - double AWD = *AWA + *HDT; + double awd = *AWA + *HDT; + awd = to2PI(awd); double stw = -*STW; - Serial.println("calcTwdSA: AWA: " + String(*AWA) + ", AWS: " + String(*AWS) + ", CTW: " + String(*CTW) + ", STW: " + String(*STW) + ", HDT: " + String(*HDT)); - addPolar(&AWD, AWS, CTW, &stw, TWD, TWS); + // Serial.println("\ncalcTwdSA: AWA: " + String(*AWA) + ", AWS: " + String(*AWS) + ", CTW: " + String(*CTW) + ", STW: " + String(*STW) + ", HDT: " + String(*HDT)); + addPolar(&awd, AWS, CTW, &stw, TWD, TWS); - // Normalize TWD to 0-360° - while (*TWD < 0) - *TWD += 360; - while (*TWD >= 360) - *TWD -= 360; - - Serial.println("calcTwdSA: TWD: " + String(*TWD) + ", TWS: " + String(*TWS)); + // Normalize TWD and TWA to 0-360° + *TWD = to2PI(*TWD); + *TWA = toPI(*TWD - *HDT); + // Serial.println("calcTwdSA: TWD: " + String(*TWD) + ", TWS: " + String(*TWS)); } bool WindUtils::calcTrueWind(const double* awaVal, const double* awsVal, - const double* cogVal, const double* stwVal, const double* hdtVal, - const double* hdmVal, double* twdVal, double* twsVal) + 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 hdt, ctw; - double hdmVar = 3.0; // Magnetic declination, can be set from config if needed - double twd, tws; + double stw, hdt, ctw; + double twd, tws, twa; + static const double DBL_MIN = std::numeric_limits::lowest(); - if (*hdtVal == __DBL_MIN__) { - if (*hdmVal != __DBL_MIN__) { - hdt = *hdmVal + hdmVar; // Use corrected HDM if HDT is not available + 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 + hdt = to2PI(hdt); + } else if (*cogVal != DBL_MIN) { + hdt = *cogVal; // Use COG as fallback if HDT and HDM are not available } else { return false; // Cannot calculate without valid HDT or HDM } } - ctw = *hdtVal + ((*cogVal - *hdtVal) / 2); // Estimate CTW from COG - if ((*awaVal == __DBL_MIN__) || (*awsVal == __DBL_MIN__) || (*cogVal == __DBL_MIN__) || (*stwVal == __DBL_MIN__)) { + if (*cogVal != DBL_MIN) { + ctw = *cogVal; // Use COG as CTW if available + // ctw = *cogVal + ((*cogVal - hdt) / 2); // Estimate CTW from COG + } else { + ctw = hdt; // 2nd approximation for CTW; + return false; + } + + if (*stwVal != DBL_MIN) { + stw = *stwVal; // Use STW if available + } else if (*sogVal != DBL_MIN) { + stw = *sogVal; + } else { + // If STW and SOG are not available, we cannot calculate true wind + return false; + } + + if ((*awaVal == DBL_MIN) || (*awsVal == DBL_MIN) || (*cogVal == DBL_MIN) || (*stwVal == DBL_MIN)) { // Cannot calculate true wind without valid AWA, AWS, COG, or STW return false; } else { - calcTwdSA(awaVal, awsVal, cogVal, stwVal, hdtVal, &twd, &tws); + calcTwdSA(awaVal, awsVal, &ctw, stwVal, &hdt, &twd, &tws, &twa); *twdVal = twd; *twsVal = tws; + *twaVal = twa; return true; } } -/* -// make function available in Python for testing -static PyObject* true_wind(PyObject* self, PyObject* args) { - double AWA,AWS,CTW,STW,HDT,TWS,TWD; - if (!PyArg_ParseTuple(args, "ddddd", &AWA, &AWS, &CTW, &STW, &HDT)) { - return NULL; +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); } - - calc_true_wind(&AWA, &AWS, &CTW, &STW, &HDT, &TWD, &TWS); - - PyObject* twd = PyFloat_FromDouble(TWD); - PyObject* tws = PyFloat_FromDouble(TWS); - PyObject* tw = PyTuple_Pack(2,twd,tws); - return tw; } -static PyMethodDef methods[] = { - {"true_wind", true_wind, METH_VARARGS, NULL}, - {NULL, NULL, 0, NULL} -}; +/* double genTwdSimDat() +{ + simTwd += random(-20, 20); + if (simTwd < 0.0) + simTwd += 360.0; + if (simTwd >= 360.0) + simTwd -= 360.0; -static struct PyModuleDef module = { - PyModuleDef_HEAD_INIT, - "truewind", // Module name - NULL, // Optional docstring - -1, - methods -}; + int16_t z = static_cast(DegToRad(simTwd) * 1000.0); + pageData.boatHstry.twdHstry->add(z); // Fill the buffer with some test data -PyMODINIT_FUNC PyInit_truewind(void) { - return PyModule_Create(&module); -}*/ \ No newline at end of file + 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 2ea8fdc..c9e4386 100644 --- a/lib/obp60task/OBPDataOperations.h +++ b/lib/obp60task/OBPDataOperations.h @@ -1,48 +1,36 @@ #pragma once -// #include #include "GwApi.h" +#include "OBPRingBuffer.h" #include #include -// #define radians(a) (a*0.017453292519943295) -// #define degrees(a) (a*57.29577951308232) +typedef struct { + RingBuffer* twdHstry; + RingBuffer* twsHstry; +} 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 { public: - static void to360(double* a); - static void to180(double* a); - static void to2PI(double* a); - static void toPI(double* a); + static double to2PI(double a); + static double toPI(double a); + static double to360(double a); + static double to180(double a); static void toCart(const double* phi, const double* r, double* x, double* y); static void toPol(const double* x, const double* y, double* phi, double* r); static void addPolar(const double* phi1, const double* r1, const double* phi2, const double* r2, double* phi, double* r); - static bool calcTrueWind(const double* awaVal, const double* awsVal, - const double* cogVal, const double* stwVal, const double* hdtVal, - const double* hdmVal, double* twdVal, double* twsVal); static void calcTwdSA(const double* AWA, const double* AWS, const double* CTW, const double* STW, const double* HDT, - double* TWD, double* TWS); -}; - -/* -// make function available in Python for testing -static PyObject* true_wind(PyObject* self, PyObject* args); -static PyMethodDef methods[] = { - {"true_wind", true_wind, METH_VARARGS, NULL}, - {NULL, NULL, 0, NULL} -}; - -static struct PyModuleDef module = { - PyModuleDef_HEAD_INIT, - "truewind", // Module name - NULL, // Optional docstring - -1, - methods -}; - -PyMODINIT_FUNC PyInit_truewind(void) { - return PyModule_Create(&module); -} */ \ No newline at end of file + double* TWD, double* TWS, double* TWA); + 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 3aba92b..4b5e5bd 100644 --- a/lib/obp60task/OBPRingBuffer.h +++ b/lib/obp60task/OBPRingBuffer.h @@ -31,6 +31,7 @@ public: RingBuffer(size_t size); void setMetaData(String name, String format, int updateFrequency, T minValue, T maxValue); // Set meta data for buffer bool getMetaData(String& name, String& format, int& updateFrequency, T& minValue, T& maxValue); // Get meta data of buffer + String getName() const; // Get buffer name 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 diff --git a/lib/obp60task/OBPRingBuffer.tpp b/lib/obp60task/OBPRingBuffer.tpp index 4c9580f..a0da425 100644 --- a/lib/obp60task/OBPRingBuffer.tpp +++ b/lib/obp60task/OBPRingBuffer.tpp @@ -57,6 +57,13 @@ bool RingBuffer::getMetaData(String& name, String& format, int& updateFrequen return true; } +// Get buffer name +template +String RingBuffer::getName() const +{ + return dataName; +} + // Add a new value to buffer template void RingBuffer::add(const T& value) @@ -129,9 +136,9 @@ T RingBuffer::getMin() const return MIN_VAL; } - T minVal = getFirst(); + T minVal = MAX_VAL; T value; - for (size_t i = 1; i < count; i++) { + for (size_t i = 0; i < count; i++) { value = get(i); if (value < minVal && value != MIN_VAL) { minVal = value; @@ -150,7 +157,7 @@ T RingBuffer::getMin(size_t amount) const if (amount > count) amount = count; - T minVal = getLast(); + T minVal = MAX_VAL; T value; for (size_t i = 0; i < amount; i++) { value = get(count - 1 - i); @@ -169,9 +176,9 @@ T RingBuffer::getMax() const return MIN_VAL; } - T maxVal = getFirst(); + T maxVal = MIN_VAL; T value; - for (size_t i = 1; i < count; i++) { + for (size_t i = 0; i < count; i++) { value = get(i); if (value > maxVal && value != MIN_VAL) { maxVal = value; @@ -190,7 +197,7 @@ T RingBuffer::getMax(size_t amount) const if (amount > count) amount = count; - T maxVal = getLast(); + T maxVal = MIN_VAL; T value; for (size_t i = 0; i < amount; i++) { value = get(count - 1 - i); @@ -328,7 +335,7 @@ bool RingBuffer::isFull() const return is_Full; } -// Get lowest possible value for buffer; used for initialized buffer data +// Get lowest possible value for buffer; used for non-set buffer data template T RingBuffer::getMinVal() const { diff --git a/lib/obp60task/PageWindPlot.cpp b/lib/obp60task/PageWindPlot.cpp index a07c03c..1a209d1 100644 --- a/lib/obp60task/PageWindPlot.cpp +++ b/lib/obp60task/PageWindPlot.cpp @@ -4,20 +4,17 @@ #include "OBP60Extensions.h" #include "OBPRingBuffer.h" #include "Pagedata.h" -#include // just for RadToDeg function #include static const double radToDeg = 180.0 / M_PI; // Conversion factor from radians to degrees -// #define radians(a) (a * 0.017453292519943295) -// #define degrees(a) (a * 57.29577951308232) // 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(); + // size_t capacity = windDirHstry.getCapacity(); + // size_t last = windDirHstry.getLastIdx(); if (windDirHstry.isEmpty() || amount <= 0) { return minVal; @@ -30,7 +27,7 @@ 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(((last - i) % capacity + capacity) % capacity); value = windDirHstry.get(count - 1 - i); if (value == minVal) { @@ -49,43 +46,6 @@ int getRng(const RingBuffer& windDirHstry, int center, size_t amount) return maxRng; } -void fillSimData(PageData& pageData) -// Fill part of the TWD history buffer with simulated data -{ - int value = 20; - int16_t value2 = 0; - for (int i = 0; i < 900; i++) { - value += random(-20, 20); - if (value < 0) - value += 360; - if (value >= 360) - value -= 360; - value2 = static_cast(DegToRad(value) * 1000.0); - pageData.boatHstry.twdHstry->add(value2); - } -} - -void fillTstBuffer(PageData& pageData) -{ - float value = 0; - int value2 = 0; - for (int i = 0; i < 60; i++) { - pageData.boatHstry.twdHstry->add(-10); // -> irregular data - } - for (int i = 0; i < 20; i++) { - for (int j = 0; j < 20; j++) { - value += 10; - value2 = static_cast(DegToRad(value) * 1000.0); - pageData.boatHstry.twdHstry->add(value2); - } - for (int j = 0; j < 20; j++) { - value -= 10; - value2 = static_cast(DegToRad(value) * 1000.0); - pageData.boatHstry.twdHstry->add(value2); - } - } -} - // **************************************************************** class PageWindPlot : public Page { @@ -105,7 +65,7 @@ public: virtual void setupKeys() { Page::setupKeys(); - commonData->keydata[0].label = "MODE"; + // commonData->keydata[0].label = "MODE"; commonData->keydata[1].label = "INTV"; commonData->keydata[4].label = "TWS"; } @@ -113,7 +73,7 @@ public: // Key functions virtual int handleKey(int key) { - // Set chart mode TWD | TWS + // Set chart mode TWD | TWS -> to be implemented if (key == 1) { if (chrtMode == 'D') { chrtMode = 'S'; @@ -159,9 +119,10 @@ public: GwLog* logger = commonData->logger; float twsValue; // TWS value in chart area - String twdName, twdUnit; // TWD name and unit - int updFreq; // Update frequency for TWD - int16_t twdLowest, twdHighest; // TWD range + 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 bool isInitialized = false; // Flag to indicate that page is initialized static bool wndDataValid = false; // Flag to indicate if wind data is valid @@ -191,6 +152,7 @@ public: 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 + int midWndDir; // New value for wndCenter after chart start / shift static int simTwd; // Simulation value for TWD static float simTws; // Simulation value for TWS @@ -201,7 +163,7 @@ public: static int chrtPrevVal; // Last wind value in chart area for check if value crosses 180 degree line LOG_DEBUG(GwLog::LOG, "Display page WindPlot"); - unsigned long start = millis(); + unsigned long WndPlotStart = millis(); // Get config data simulation = config->getBool(config->useSimuData); @@ -210,11 +172,6 @@ public: String backlightMode = config->getString(config->backlight); if (!isInitialized) { - - if (simulation) { - fillSimData(pageData); // Fill the buffer with some test data - } - width = getdisplay().width(); height = getdisplay().height(); xCenter = width / 2; @@ -230,8 +187,33 @@ public: lastAddedIdx = pageData.boatHstry.twdHstry->getLastIdx(); pageData.boatHstry.twdHstry->getMetaData(twdName, twdUnit, updFreq, twdLowest, twdHighest); wndCenter = INT_MIN; + midWndDir = 0; + diffRng = dfltRng; + chrtRng = dfltRng; + isInitialized = true; // Set flag to indicate that page is now initialized - LOG_DEBUG(GwLog::ERROR, "PageWindPlot Start1: lastAddedIdx: %d, simTwd: %.1f, isInitialized: %d, SimData: %d", lastAddedIdx, simTwd / 1000 + radToDeg, isInitialized, simulation); + } + + const int numBoatData = 2; + GwApi::BoatValue* bvalue; + String DataName[numBoatData]; + double DataValue[numBoatData]; + bool DataValid[numBoatData]; + String DataText[numBoatData]; + String DataUnit[numBoatData]; + String DataFormat[numBoatData]; + + // read boat data values; TWD only for validation test, TWS for display of current value + for (int i = 0; i < numBoatData; i++) { + bvalue = pageData.values[i]; + DataName[i] = xdrDelete(bvalue->getName()); + DataName[i] = DataName[i].substring(0, 6); // String length limit for value name + calibrationData.calibrateInstance(bvalue, logger); // Check if boat data value is to be calibrated + DataValue[i] = bvalue->value; // Value as double in SI unit + DataValid[i] = bvalue->valid; + DataText[i] = formatValue(bvalue, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places + DataUnit[i] = formatValue(bvalue, *commonData).unit; + DataFormat[i] = bvalue->getFormat(); // Unit of value } // Optical warning by limit violation (unused) @@ -240,30 +222,11 @@ public: setFlashLED(false); } - if (simulation) { - 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); - LOG_DEBUG(GwLog::ERROR, "PageWindPlot Simulation: getLast TWD: %.0f, lastIdx: %d", pageData.boatHstry.twdHstry->getLast() / 1000.0 * RAD_TO_DEG, pageData.boatHstry.twdHstry->getLastIdx()); - pageData.boatHstry.twdHstry->add(z); // Fill the buffer with some test data - LOG_DEBUG(GwLog::ERROR, "PageWindPlot Simulation: getAdded TWD: %.0f, lastIdx: %d", pageData.boatHstry.twdHstry->getLast() / 1000.0 * RAD_TO_DEG, pageData.boatHstry.twdHstry->getLastIdx()); - - 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; - } else { - twsValue = pageData.boatHstry.twsHstry->getLast() / 10.0 * 1.94384; // TWS value in knots - } - // Identify buffer size and buffer start position for chart count = pageData.boatHstry.twdHstry->getCurrentSize(); currIdx = pageData.boatHstry.twdHstry->getLastIdx(); numAddedBufVals = (currIdx - lastAddedIdx + bufSize) % bufSize; // Number of values added to buffer since last display - if (dataIntv != oldDataIntv) { + if (dataIntv != oldDataIntv || count == 1) { // new data interval selected by user intvBufSize = cHeight * dataIntv; numWndVals = min(count, (cHeight - 60) * dataIntv); @@ -277,16 +240,21 @@ public: bufStart = max(0, bufStart - numAddedBufVals); } } - LOG_DEBUG(GwLog::ERROR, "PageWindPlot Dataset: TWD: %.0f, TWS: %.1f, DBT: %.1f, count: %d, intvBufSize: %d, numWndVals: %d, bufStart: %d, numAddedBufVals: %d, lastIdx: %d", - pageData.boatHstry.twdHstry->getLast() / 1000.0 * radToDeg, pageData.boatHstry.twsHstry->getLast() / 10.0 * 1.94384, pageData.boatHstry.dbtHstry->getLast() / 10.0, - count, intvBufSize, numWndVals, bufStart, numAddedBufVals, pageData.boatHstry.twdHstry->getLastIdx()); + LOG_DEBUG(GwLog::ERROR, "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, DataValid[0], + intvBufSize, numWndVals, bufStart, numAddedBufVals, pageData.boatHstry.twdHstry->getLastIdx(), oldDataIntv, dataIntv); - // initialize chart range values - if (wndCenter == INT_MIN) { - wndCenter = max(0, int(pageData.boatHstry.twdHstry->get(numWndVals - intvBufSize) / 1000.0 * radToDeg)); // get 1st value of current data interval - wndCenter = (int((wndCenter + (wndCenter >= 0 ? 5 : -5)) / 10) * 10) % 360; // Set new center value; round to nearest 10 degree value; 360° -> 0° - diffRng = dfltRng; - chrtRng = dfltRng; + // 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; + } + LOG_DEBUG(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); } else { // check and adjust range between left, center, and right chart limit diffRng = getRng(*pageData.boatHstry.twdHstry, wndCenter, numWndVals); @@ -304,8 +272,6 @@ public: wndRight = (chrtRng < 180 ? wndCenter + chrtRng : wndCenter + chrtRng - 1); if (wndRight >= 360) wndRight -= 360; - LOG_DEBUG(GwLog::DEBUG, "PageWindPlot FirstVal: %f, LastVal: %d, count: %d, diffRng: %d, chartRng: %d, Center: %d, scale: %f", pageData.boatHstry.twdHstry->getFirst() / 1000.0 * radToDeg, - pageData.boatHstry.twdHstry->getLast() / 1000.0 * radToDeg, count, diffRng, chrtRng, wndCenter, chrtScl); // Draw page //*********************************************************************** @@ -322,11 +288,9 @@ public: char sWndLbl[4]; // char buffer for Wind angle label getdisplay().setFont(&Ubuntu_Bold12pt8b); getdisplay().setCursor(xCenter - 88, yOffset - 3); - getdisplay().print("TWD"); // Wind name - // getdisplay().setCursor(xCenter - 20, yOffset - 3); + getdisplay().print("TWD"); // Wind data name snprintf(sWndLbl, 4, "%03d", (wndCenter < 0) ? (wndCenter + 360) : wndCenter); drawTextCenter(xCenter, yOffset - 11, sWndLbl); - // getdisplay().print(sWndLbl); // Wind center value getdisplay().drawCircle(xCenter + 25, yOffset - 17, 2, commonData->fgcolor); // symbol getdisplay().drawCircle(xCenter + 25, yOffset - 17, 3, commonData->fgcolor); // symbol getdisplay().setCursor(1, yOffset - 3); @@ -343,7 +307,19 @@ public: if (pageData.boatHstry.twdHstry->getMax() == pageData.boatHstry.twdHstry->getMinVal()) { // only values in buffer -> no valid wind data available wndDataValid = false; + } else if (!DataValid[0]) { + // currently no valid TWD data available + numNoData++; + wndDataValid = true; + if (numNoData > 3) { + // If more than 4 invalid values in a row, send message + wndDataValid = false; + getdisplay().setFont(&Ubuntu_Bold10pt8b); + getdisplay().fillRect(xCenter - 66, height / 2 - 20, 146, 24, commonData->bgcolor); // Clear area for TWS value + drawTextCenter(xCenter, height / 2 - 10, "No sensor data"); + } } else { + numNoData = 0; // reset data error counter wndDataValid = true; // At least some wind data available } // Draw wind values in chart @@ -361,17 +337,17 @@ public: } if (numNoData > 4) { // If more than 4 invalid values in a row, send message - getdisplay().setFont(&Ubuntu_Bold10pt7b); + getdisplay().setFont(&Ubuntu_Bold10pt8b); getdisplay().fillRect(xCenter - 66, height / 2 - 20, 146, 24, commonData->bgcolor); // Clear area for TWS value drawTextCenter(xCenter, height / 2 - 10, "No sensor data"); } */ } else { - chrtVal = (chrtVal / 1000.0 * radToDeg) + 0.5; // Convert to degrees and round + chrtVal = static_cast((chrtVal / 1000.0 * radToDeg) + 0.5); // Convert to degrees and round x = ((chrtVal - wndLeft + 360) % 360) * chrtScl; y = yOffset + cHeight - i; // Position in chart area if (i >= (numWndVals / dataIntv) - 10) - LOG_DEBUG(GwLog::ERROR, "PageWindPlot Chart: i: %d, chrtVal: %d, bufStart: %d count: %d, linesToShow: %d", i, chrtVal, bufStart, count, (numWndVals / dataIntv)); + LOG_DEBUG(GwLog::DEBUG, "PageWindPlot Chart: i: %d, chrtVal: %d, bufStart: %d, count: %d, linesToShow: %d", i, chrtVal, bufStart, count, (numWndVals / dataIntv)); if ((i == 0) || (chrtPrevVal == INT16_MIN)) { // just a dot for 1st chart point or after some invalid values @@ -404,15 +380,15 @@ public: int minWndDir = pageData.boatHstry.twdHstry->getMin(numWndVals) / 1000.0 * radToDeg; int maxWndDir = pageData.boatHstry.twdHstry->getMax(numWndVals) / 1000.0 * radToDeg; - LOG_DEBUG(GwLog::ERROR, "PageWindPlot FreeTop: Minimum: %d, Maximum: %d, OldwndCenter: %d", minWndDir, maxWndDir, wndCenter); - if ((minWndDir > wndCenter) || (maxWndDir < wndCenter)) { + LOG_DEBUG(GwLog::DEBUG, "PageWindPlot FreeTop: Minimum: %d, Maximum: %d, OldwndCenter: %d", minWndDir, maxWndDir, wndCenter); + if ((minWndDir + 540 >= wndCenter + 540) || (maxWndDir + 540 <= wndCenter + 540)) { // Check if all wind value are left or right of center value -> optimize chart range - int midWndDir = pageData.boatHstry.twdHstry->getMid(numWndVals) / 1000.0 * radToDeg; + 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 } } - LOG_DEBUG(GwLog::ERROR, "PageWindPlot FreeTop: cHeight: %d, bufStart: %d, numWndVals: %d, wndCenter: %d", cHeight, bufStart, numWndVals, wndCenter); + LOG_DEBUG(GwLog::DEBUG, "PageWindPlot FreeTop: cHeight: %d, bufStart: %d, numWndVals: %d, wndCenter: %d", cHeight, bufStart, numWndVals, wndCenter); break; } } @@ -433,6 +409,8 @@ public: 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; currentZone = (y >= yPosTws - 38) && (y <= yPosTws + 6) && (x >= xPosTws - 4) && (x <= xPosTws + 146) ? 1 : 0; // Define current zone for TWS value if (currentZone != lastZone) { @@ -447,21 +425,24 @@ public: getdisplay().fillRect(xPosTws - 4, yPosTws - 38, 142, 44, commonData->bgcolor); // Clear area for TWS value getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b); getdisplay().setCursor(xPosTws, yPosTws); - if (twsValue < 0 || twsValue >= 100) { + if (!DataValid[1]) { getdisplay().print("--.-"); } else { - if (twsValue < 10.0) { - getdisplay().printf("!%3.1f", twsValue); // Value + if (DataValue[1] < 9.95) { + getdisplay().printf("!%3.1f", DataValue[1] + 0.05); // Value, round to 1 decimal } else { - getdisplay().printf("%4.1f", twsValue); // Value} + getdisplay().printf("%4.1f", DataValue[1] + 0.05); // Value, round to 1 decimal } } getdisplay().setFont(&Ubuntu_Bold12pt8b); getdisplay().setCursor(xPosTws + 82, yPosTws - 14); - getdisplay().print("TWS"); // Name +// getdisplay().print("TWS"); // Name + getdisplay().print(DataName[1]); // Name getdisplay().setFont(&Ubuntu_Bold8pt8b); - getdisplay().setCursor(xPosTws + 78, yPosTws + 1); - getdisplay().printf(" kn"); // Unit +// getdisplay().setCursor(xPosTws + 78, yPosTws + 1); + getdisplay().setCursor(xPosTws + 82, yPosTws + 1); +// getdisplay().printf(" kn"); // Unit + getdisplay().print(DataUnit[1]); // Unit } // chart Y axis labels; print at last to overwrite potential chart lines in label area @@ -471,7 +452,7 @@ public: for (int i = 1; i <= 3; i++) { yPos = yOffset + (i * 60); getdisplay().fillRect(0, yPos, width, 1, commonData->fgcolor); - getdisplay().fillRect(0, yPos - 8, 26, 16, commonData->bgcolor); // Clear small area to remove potential chart lines + getdisplay().fillRect(0, yPos - 8, 24, 16, commonData->bgcolor); // Clear small area to remove potential chart lines getdisplay().setCursor(1, yPos + 4); if (count >= intvBufSize) { // Calculate minute value for label @@ -483,8 +464,8 @@ public: getdisplay().printf("%3d", chrtLbl); // Wind value label } - unsigned long finish = millis() - start; -// LOG_DEBUG(GwLog::ERROR, "PageWindPlot Time: %lu", finish); + unsigned long finish = millis() - WndPlotStart; + LOG_DEBUG(GwLog::ERROR, "PageWindPlot Time: %lu", finish); // Update display getdisplay().nextPage(); // Partial update (fast) }; @@ -505,9 +486,9 @@ PageDescription registerPageWindPlot( "WindPlot", // Page name createPage, // Action 0, // Number of bus values depends on selection in Web configuration -// { "TWD", "TWA", "TWS", "HDM", "AWA", "AWS", "STW", "COG", "SOG" }, // Bus values we need in the page - { }, // Bus values we need in the page + { "TWD", "TWS" }, // Bus values we need in the page +// {}, // Bus values we need in the page true // Show display header on/off ); -#endif +#endif \ No newline at end of file diff --git a/lib/obp60task/Pagedata.h b/lib/obp60task/Pagedata.h index e518221..ad252ea 100644 --- a/lib/obp60task/Pagedata.h +++ b/lib/obp60task/Pagedata.h @@ -5,15 +5,10 @@ #include #include "LedSpiTask.h" #include "OBPRingBuffer.h" +#include "OBPDataOperations.h" #define MAX_PAGE_NUMBER 10 // Max number of pages for show data -typedef struct{ - RingBuffer* twdHstry; - RingBuffer* twsHstry; - RingBuffer* dbtHstry; -} tBoatHstryData; - typedef std::vector ValueList; typedef struct{ diff --git a/lib/obp60task/config.json b/lib/obp60task/config.json index da8be86..c1f9ab8 100644 --- a/lib/obp60task/config.json +++ b/lib/obp60task/config.json @@ -219,6 +219,17 @@ "obp60":"true" } }, + { + "name": "calcTrueWnds", + "label": "Calculate True Wind", + "type": "boolean", + "default": "false", + "description": "If not available, calculate true wind data from appearant wind and other boat data", + "category": "OBP60 Settings", + "capabilities": { + "obp60": "true" + } + }, { "name": "lengthFormat", "label": "Length Format", diff --git a/lib/obp60task/config_obp40.json b/lib/obp60task/config_obp40.json index 782e7f0..8747e4e 100644 --- a/lib/obp60task/config_obp40.json +++ b/lib/obp60task/config_obp40.json @@ -219,6 +219,17 @@ "obp40": "true" } }, + { + "name": "calcTrueWnds", + "label": "Calculate True Wind", + "type": "boolean", + "default": "false", + "description": "If not available, calculate true wind data from appearant wind and other boat data", + "category": "OBP40 Settings", + "capabilities": { + "obp40": "true" + } + }, { "name": "lengthFormat", "label": "Length Format", diff --git a/lib/obp60task/obp60task.cpp b/lib/obp60task/obp60task.cpp index 2d4e72b..505d89c 100644 --- a/lib/obp60task/obp60task.cpp +++ b/lib/obp60task/obp60task.cpp @@ -376,6 +376,112 @@ void underVoltageDetection(GwApi *api, CommonData &common){ } } +//bool addTrueWind(GwApi* api, BoatValueList* boatValues, double *twd, double *tws, double *twa) { +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; + bool isCalculated = false; + const double DBL_MIN = std::numeric_limits::lowest(); + + GwApi::BoatValue *twdBVal = boatValues->findValueOrCreate("TWD"); + GwApi::BoatValue *twsBVal = boatValues->findValueOrCreate("TWS"); + GwApi::BoatValue *twaBVal = boatValues->findValueOrCreate("TWA"); + GwApi::BoatValue *awaBVal = boatValues->findValueOrCreate("AWA"); + GwApi::BoatValue *awsBVal = boatValues->findValueOrCreate("AWS"); + GwApi::BoatValue *cogBVal = boatValues->findValueOrCreate("COG"); + GwApi::BoatValue *stwBVal = boatValues->findValueOrCreate("STW"); + GwApi::BoatValue *sogBVal = boatValues->findValueOrCreate("SOG"); + GwApi::BoatValue *hdtBVal = boatValues->findValueOrCreate("HDT"); + GwApi::BoatValue *hdmBVal = boatValues->findValueOrCreate("HDM"); + GwApi::BoatValue *varBVal = boatValues->findValueOrCreate("VAR"); + awaVal = awaBVal->valid ? awaBVal->value : DBL_MIN; + awsVal = awsBVal->valid ? awsBVal->value : DBL_MIN; + cogVal = cogBVal->valid ? cogBVal->value : DBL_MIN; + stwVal = stwBVal->valid ? stwBVal->value : DBL_MIN; + sogVal = sogBVal->valid ? sogBVal->value : DBL_MIN; + 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::ERROR,"obp60task addTrueWind: AWA: %.1f, AWS: %.1f, COG: %.1f, STW: %.1f, 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, 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, &twdBVal->value, &twsBVal->value, &twaBVal->value); + twdBVal->valid = isCalculated; + twsBVal->valid = isCalculated; + twaBVal->valid = isCalculated; + + api->getLogger()->logDebug(GwLog::ERROR,"obp60task calcTrueWind: TWD_Valid? %d, TWD=%.1f, TWS=%.1f, TWA=%.1f, isCalculated? %d", twdBVal->valid, twdBVal->value * RAD_TO_DEG, twsBVal->value * 3.6 / 1.852, + twaBVal->value * RAD_TO_DEG, isCalculated); + + return isCalculated; +} + +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 + + 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 + // Initialize history buffers with meta data + hstryBufList.twdHstry->setMetaData("TWD", "formatCourse", hstryUpdFreq, hstryMinVal, twdHstryMax); + hstryBufList.twsHstry->setMetaData("TWS", "formatKnots", hstryUpdFreq, hstryMinVal, twsHstryMax); + + GwApi::BoatValue *twdBVal = boatValues->findValueOrCreate(hstryBufList.twdHstry->getName()); + GwApi::BoatValue *twsBVal = boatValues->findValueOrCreate(hstryBufList.twsHstry->getName()); + GwApi::BoatValue *twaBVal = boatValues->findValueOrCreate("TWA"); +} + +void handleHstryBuf(GwApi* api, BoatValueList* boatValues, tBoatHstryData hstryBufList) { + // Handle history buffers for TWD, TWS + + GwLog *logger = api->getLogger(); + + int16_t twdHstryMin = hstryBufList.twdHstry->getMinVal(); + int16_t twdHstryMax = hstryBufList.twdHstry->getMaxVal(); + int16_t twsHstryMin = hstryBufList.twsHstry->getMinVal(); + int16_t twsHstryMax = hstryBufList.twsHstry->getMaxVal(); + int16_t twdBuf, twsBuf; + 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"); + + api->getLogger()->logDebug(GwLog::ERROR,"obp60task handleHstryBuf: twdBVal: %f, twsBVal: %f, twaBVal: %f, TWD_isValid? %d", twdBVal->value * RAD_TO_DEG, + twsBVal->value * 3.6 / 1.852, twaBVal->value * RAD_TO_DEG, twdBVal->valid); + calBVal = new GwApi::BoatValue("TWD"); // temporary solution for calibration of history buffer values + calBVal->setFormat(twdBVal->getFormat()); + if (twdBVal->valid) { + 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); + } + } + 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->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); + } + } + delete calBVal; + calBVal = nullptr; +} + // OBP60 Task //#################################################################################### void OBP60Task(GwApi *api){ @@ -392,11 +498,6 @@ void OBP60Task(GwApi *api){ commonData.logger=logger; commonData.config=config; - // 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) - RingBuffer dbtHstry(960); // Circular buffer to store water depth values (DBT) - #ifdef HARDWARE_V21 // Keyboard coordinates for page footer initKeys(commonData); @@ -494,6 +595,11 @@ void OBP60Task(GwApi *api){ //commonData.distanceformat=config->getString(xxx); //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}; + //fill the page data from config numPages=config->getInt(config->visiblePages,1); if (numPages < 1) numPages=1; @@ -532,8 +638,10 @@ 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 = {&twdHstry, &twsHstry, &dbtHstry}; + if (pages[i].description->pageName == "WindPlot") { + // Add boat history data to page parameters + pages[i].parameters.boatHstry = hstryBufList; + } } // add out of band system page (always available) Page *syspage = allPages.pages[0]->creator(commonData); @@ -541,32 +649,12 @@ void OBP60Task(GwApi *api){ // Read all calibration data settings from config calibrationData.readConfig(config, logger); - // List of boat values for history storage - GwApi::BoatValue *twdBVal=new GwApi::BoatValue(GwBoatData::_TWD); - GwApi::BoatValue *twsBVal=new GwApi::BoatValue(GwBoatData::_TWS); - GwApi::BoatValue *dbtBVal=new GwApi::BoatValue(GwBoatData::_DBT); - GwApi::BoatValue *hstryValList[]={twdBVal, twsBVal, dbtBVal}; - api->getBoatDataValues(3, hstryValList); - // Boat data history buffer initialization - 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 dbtHstryMax = 3276; // Max value for depth in m (=327), shifted by 10 for 1 decimal - // Initialize history buffers with meta data - twdHstry.setMetaData(twdBVal->getName(), twdBVal->getFormat(), hstryUpdFreq, hstryMinVal, twdHstryMax); - twsHstry.setMetaData(twsBVal->getName(), twsBVal->getFormat(), hstryUpdFreq, hstryMinVal, twsHstryMax); - dbtHstry.setMetaData(dbtBVal->getName(), dbtBVal->getFormat(), hstryUpdFreq, hstryMinVal, dbtHstryMax); - bool simulation = api->getConfig()->getBool(api->getConfig()->useSimuData, false); + // 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); - // List of boat values for true winds calculation - GwApi::BoatValue *awaBVal=new GwApi::BoatValue(GwBoatData::_AWA); - GwApi::BoatValue *awsBVal=new GwApi::BoatValue(GwBoatData::_AWS); - GwApi::BoatValue *cogBVal=new GwApi::BoatValue(GwBoatData::_COG); - GwApi::BoatValue *stwBVal=new GwApi::BoatValue(GwBoatData::_STW); - GwApi::BoatValue *hdtBVal=new GwApi::BoatValue(GwBoatData::_HDT); - GwApi::BoatValue *hdmBVal=new GwApi::BoatValue(GwBoatData::_HDM); - GwApi::BoatValue *WndCalcValList[]={awaBVal, awsBVal, cogBVal, stwBVal, hdtBVal, hdmBVal}; + // Initialize history buffer for certain boat data + initHstryBuf(api, &boatValues, hstryBufList); // Display screenshot handler for HTTP request // http://192.168.15.1/api/user/OBP60Task/screenshot @@ -623,18 +711,6 @@ void OBP60Task(GwApi *api){ GwApi::BoatValue *lon = boatValues.findValueOrCreate("LON"); // Load GpsLongitude GwApi::BoatValue *hdop = boatValues.findValueOrCreate("HDOP"); // Load GpsHDOP -/* // Boat values for wind conversion for main loop; will be calculated in case values are not available by sensors - GwApi::BoatValue *twd = boatValues.findValueOrCreate("TWD"); // True Wind Direction - GwApi::BoatValue *tws = boatValues.findValueOrCreate("TWS"); // True Wind Speed - GwApi::BoatValue *twa = boatValues.findValueOrCreate("TWA"); // True Wind Angle - GwApi::BoatValue *awaBVal = boatValues.findValueOrCreate("AWA"); // Apparent Wind Angle - GwApi::BoatValue *awsBVal = boatValues.findValueOrCreate("AWS"); // Apparent Wind Speed - GwApi::BoatValue *cogBVal = boatValues.findValueOrCreate("COG"); // Course Over Ground - GwApi::BoatValue *stwBVal = boatValues.findValueOrCreate("STW"); // Speed Through Water - GwApi::BoatValue *hdtBVal = boatValues.findValueOrCreate("HDT"); // Heading True - GwApi::BoatValue *ctwBVal = boatValues.findValueOrCreate("CTW"); // Course Through Water - GwApi::BoatValue *hdmBVal = boatValues.findValueOrCreate("HDM"); // Heading Magnetic -*/ LOG_DEBUG(GwLog::LOG,"obp60task: start mainloop"); commonData.time = boatValues.findValueOrCreate("GPST"); // Load GpsTime @@ -648,7 +724,6 @@ void OBP60Task(GwApi *api){ long starttime3 = millis(); // Display update all 1s long starttime4 = millis(); // Delayed display update after 4s when select a new page long starttime5 = millis(); // Calculate sunrise and sunset all 1s - unsigned long starttime10 = millis(); // Get history TWD, TWS, DBT data and calculate true winds each 1s pages[pageNumber].page->setupKeys(); // Initialize keys for first page @@ -836,62 +911,8 @@ void OBP60Task(GwApi *api){ getdisplay().fillScreen(commonData.bgcolor); // Clear display getdisplay().nextPage(); // Full update } - } - - // Read TWD, TWS, DBT data from boatData each 1000ms for history and windplot display - if((millis() > starttime10 + 1000) && !simulation) { - double twdVal, twsVal, dbtVal; - double awaVal, awsVal, cogVal, stwVal, hdtVal, hdmVal; - double DBL_MIN = std::numeric_limits::lowest(); - - starttime10 = millis(); - LOG_DEBUG(GwLog::DEBUG,"History buffer write cycle"); - api->getBoatDataValues(3, hstryValList); - - if (!twdBVal->valid || !twsBVal->valid) { - api->getBoatDataValues(6, WndCalcValList); // Get all values for true wind calculation - awaVal = awaBVal->valid ? awaBVal->value * RAD_TO_DEG : __DBL_MIN__; - awsVal = awsBVal->valid ? awsBVal->value : __DBL_MIN__; - cogVal = cogBVal->valid ? cogBVal->value * RAD_TO_DEG : __DBL_MIN__; - stwVal = stwBVal->valid ? stwBVal->value : __DBL_MIN__; - hdtVal = hdtBVal->valid ? hdtBVal->value * RAD_TO_DEG : __DBL_MIN__; - hdmVal = hdmBVal->valid ? hdmBVal->value * RAD_TO_DEG : __DBL_MIN__; - LOG_DEBUG(GwLog::ERROR,"obp60task - Read data: AWA: %f, AWS: %f, COG: %f, STW: %f, HDT: %f, TWD=%f, TWS=%f", awaBVal->value, awsBVal->value, - cogBVal->value, stwBVal->value, hdtBVal->value, twdBVal->value, twsBVal->value); - - bool isCalculated = WindUtils::calcTrueWind(&awaVal, &awsVal, &cogVal, &stwVal, &cogVal, &hdmVal, &twdVal, &twsVal); // Calculate true wind if TWD not available -// bool isCalculated = WindUtils::calcTrueWind(&awaVal, &awsVal, &cogVal, &stwVal, &hdtVal, &hdmVal, &twdVal, &twsVal); // Calculate true wind if TWD not available - LOG_DEBUG(GwLog::ERROR,"obp60task - calc Wind: AWA: %f, AWS: %f, COG: %f, STW: %f, HDT: %f, TWD=%f, TWS=%f, converted? %d", awaBVal->value * RAD_TO_DEG, awsBVal->value * 3.6 / 1.852, - cogBVal->value * RAD_TO_DEG, stwBVal->value * 3.6 / 1.852, hdtBVal->value * RAD_TO_DEG, twdVal, twsVal, isCalculated); - } - if (twdBVal->valid) { - twdVal = std::round(twdBVal->value * 1000); // Shift value to store decimals in int16_t); - if (twdVal < hstryMinVal || twdVal > twdHstryMax) { - twdVal = INT16_MIN; // Add invalid value - } - } - twdHstry.add(static_cast(twdVal)); - - if (twsBVal->valid) { - twsVal = static_cast(twsBVal->value * 10); // Shift value to store decimals in int16_t - if (twsVal < hstryMinVal || twsVal > twsHstryMax) { - twsVal = INT16_MIN; // Add invalid value - } - } - twsHstry.add(twsVal); - - if (dbtBVal->valid) { - dbtVal = dbtBVal->value * 10; // Shift value to store decimals in int16_t - if (dbtVal < hstryMinVal || dbtVal > dbtHstryMax) { - dbtVal = INT16_MIN; // Add invalid value - } - dbtHstry.add(dbtVal); - } - - int counttime = millis() - starttime10; - LOG_DEBUG(GwLog::ERROR,"obp60task: History buffer write time: %d", counttime); - } - + } + // Refresh display data, default all 1s currentPage = pages[pageNumber].page; int pagetime = 1000; @@ -907,6 +928,12 @@ void OBP60Task(GwApi *api){ api->getBoatDataValues(boatValues.numValues,boatValues.allBoatValues); api->getStatus(commonData.status); + if (calcTrueWnds) { + addTrueWind(api, &boatValues); + } + // Handle history buffers for TWD, TWS for wind plot page and other usage + handleHstryBuf(api, &boatValues, hstryBufList); + // Clear display // getdisplay().fillRect(0, 0, getdisplay().width(), getdisplay().height(), commonData.bgcolor); getdisplay().fillScreen(commonData.bgcolor); // Clear display From 2954a9a58b3503cd17c27f7ce4bcde148d3cbeb5 Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Fri, 25 Jul 2025 19:50:42 +0200 Subject: [PATCH 24/26] adjust page call to new standard; clean debug code; fix TWS print alignment --- lib/obp60task/PageWindPlot.cpp | 67 ++++++++++++++++------------------ lib/obp60task/obp60task.cpp | 29 +++++++++++---- lib/obp60task/platformio.ini | 2 +- 3 files changed, 53 insertions(+), 45 deletions(-) diff --git a/lib/obp60task/PageWindPlot.cpp b/lib/obp60task/PageWindPlot.cpp index 1a209d1..cf6430c 100644 --- a/lib/obp60task/PageWindPlot.cpp +++ b/lib/obp60task/PageWindPlot.cpp @@ -113,7 +113,7 @@ public: return key; } - virtual void displayPage(PageData& pageData) + int displayPage(PageData& pageData) { GwConfigHandler* config = commonData->config; GwLog* logger = commonData->logger; @@ -124,6 +124,16 @@ public: static int16_t twdLowest, twdHighest; // TWD range // static int16_t twdBufMinVal; // lowest possible twd buffer value; used for non-set data + // current boat data values; TWD only for validation test, TWS for display of current value + const int numBoatData = 2; + GwApi::BoatValue* bvalue; + String BDataName[numBoatData]; + double BDataValue[numBoatData]; + bool BDataValid[numBoatData]; + String BDataText[numBoatData]; + String BDataUnit[numBoatData]; + String BDataFormat[numBoatData]; + static bool isInitialized = false; // Flag to indicate that page is initialized static bool wndDataValid = false; // Flag to indicate if wind data is valid static int numNoData; // Counter for multiple invalid data values in a row @@ -163,7 +173,6 @@ public: static int chrtPrevVal; // Last wind value in chart area for check if value crosses 180 degree line LOG_DEBUG(GwLog::LOG, "Display page WindPlot"); - unsigned long WndPlotStart = millis(); // Get config data simulation = config->getBool(config->useSimuData); @@ -194,26 +203,17 @@ public: isInitialized = true; // Set flag to indicate that page is now initialized } - const int numBoatData = 2; - GwApi::BoatValue* bvalue; - String DataName[numBoatData]; - double DataValue[numBoatData]; - bool DataValid[numBoatData]; - String DataText[numBoatData]; - String DataUnit[numBoatData]; - String DataFormat[numBoatData]; - // read boat data values; TWD only for validation test, TWS for display of current value for (int i = 0; i < numBoatData; i++) { bvalue = pageData.values[i]; - DataName[i] = xdrDelete(bvalue->getName()); - DataName[i] = DataName[i].substring(0, 6); // String length limit for value name + // BDataName[i] = xdrDelete(bvalue->getName()); + BDataName[i] = BDataName[i].substring(0, 6); // String length limit for value name calibrationData.calibrateInstance(bvalue, logger); // Check if boat data value is to be calibrated - DataValue[i] = bvalue->value; // Value as double in SI unit - DataValid[i] = bvalue->valid; - DataText[i] = formatValue(bvalue, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places - DataUnit[i] = formatValue(bvalue, *commonData).unit; - DataFormat[i] = bvalue->getFormat(); // Unit of value + BDataValue[i] = bvalue->value; // Value as double in SI unit + BDataValid[i] = bvalue->valid; + BDataText[i] = formatValue(bvalue, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places + BDataUnit[i] = formatValue(bvalue, *commonData).unit; + BDataFormat[i] = bvalue->getFormat(); // Unit of value } // Optical warning by limit violation (unused) @@ -240,8 +240,8 @@ public: bufStart = max(0, bufStart - numAddedBufVals); } } - LOG_DEBUG(GwLog::ERROR, "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, DataValid[0], + LOG_DEBUG(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); // Set wndCenter from 1st real buffer value @@ -307,16 +307,13 @@ public: if (pageData.boatHstry.twdHstry->getMax() == pageData.boatHstry.twdHstry->getMinVal()) { // only values in buffer -> no valid wind data available wndDataValid = false; - } else if (!DataValid[0]) { + } else if (!BDataValid[0]) { // currently no valid TWD data available numNoData++; wndDataValid = true; if (numNoData > 3) { // If more than 4 invalid values in a row, send message wndDataValid = false; - getdisplay().setFont(&Ubuntu_Bold10pt8b); - getdisplay().fillRect(xCenter - 66, height / 2 - 20, 146, 24, commonData->bgcolor); // Clear area for TWS value - drawTextCenter(xCenter, height / 2 - 10, "No sensor data"); } } else { numNoData = 0; // reset data error counter @@ -397,8 +394,8 @@ public: // No valid data available LOG_DEBUG(GwLog::LOG, "PageWindPlot: No valid data available"); getdisplay().setFont(&Ubuntu_Bold10pt8b); - getdisplay().fillRect(xCenter - 66, height / 2 - 20, 146, 24, commonData->bgcolor); // Clear area for TWS value - drawTextCenter(xCenter, height / 2 - 10, "No sensor data"); + getdisplay().fillRect(xCenter - 33, height / 2 - 20, 66, 24, commonData->bgcolor); // Clear area for message + drawTextCenter(xCenter, height / 2 - 10, "No data"); } // Print TWS value @@ -425,24 +422,25 @@ public: getdisplay().fillRect(xPosTws - 4, yPosTws - 38, 142, 44, commonData->bgcolor); // Clear area for TWS value getdisplay().setFont(&DSEG7Classic_BoldItalic16pt7b); getdisplay().setCursor(xPosTws, yPosTws); - if (!DataValid[1]) { + if (!BDataValid[1]) { getdisplay().print("--.-"); } else { - if (DataValue[1] < 9.95) { - getdisplay().printf("!%3.1f", DataValue[1] + 0.05); // Value, round to 1 decimal + double dbl = BDataValue[1] * 3.6 / 1.852; + if (dbl < 10.0) { + getdisplay().printf("!%3.1f", dbl); // Value, round to 1 decimal } else { - getdisplay().printf("%4.1f", DataValue[1] + 0.05); // Value, round to 1 decimal + getdisplay().printf("%4.1f", dbl); // Value, round to 1 decimal } } getdisplay().setFont(&Ubuntu_Bold12pt8b); getdisplay().setCursor(xPosTws + 82, yPosTws - 14); // getdisplay().print("TWS"); // Name - getdisplay().print(DataName[1]); // Name + getdisplay().print(BDataName[1]); // Name getdisplay().setFont(&Ubuntu_Bold8pt8b); // getdisplay().setCursor(xPosTws + 78, yPosTws + 1); getdisplay().setCursor(xPosTws + 82, yPosTws + 1); // getdisplay().printf(" kn"); // Unit - getdisplay().print(DataUnit[1]); // Unit + getdisplay().print(BDataUnit[1]); // Unit } // chart Y axis labels; print at last to overwrite potential chart lines in label area @@ -464,10 +462,7 @@ public: getdisplay().printf("%3d", chrtLbl); // Wind value label } - unsigned long finish = millis() - WndPlotStart; - LOG_DEBUG(GwLog::ERROR, "PageWindPlot Time: %lu", finish); - // Update display - getdisplay().nextPage(); // Partial update (fast) + return PAGE_UPDATE; }; }; diff --git a/lib/obp60task/obp60task.cpp b/lib/obp60task/obp60task.cpp index 48ce978..a84116a 100644 --- a/lib/obp60task/obp60task.cpp +++ b/lib/obp60task/obp60task.cpp @@ -381,6 +381,7 @@ 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; bool isCalculated = false; const double DBL_MIN = std::numeric_limits::lowest(); @@ -403,15 +404,27 @@ 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::ERROR,"obp60task addTrueWind: AWA: %.1f, AWS: %.1f, COG: %.1f, STW: %.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, 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, 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, &twdBVal->value, &twsBVal->value, &twaBVal->value); - twdBVal->valid = isCalculated; - twsBVal->valid = isCalculated; - twaBVal->valid = isCalculated; +// isCalculated = WindUtils::calcTrueWind(&awaVal, &awsVal, &cogVal, &stwVal, &sogVal, &hdtVal, &hdmVal, &varVal, &twdBVal->value, &twsBVal->value, &twaBVal->value); + isCalculated = WindUtils::calcTrueWind(&awaVal, &awsVal, &cogVal, &stwVal, &sogVal, &hdtVal, &hdmVal, &varVal, &twd, &tws, &twa); - api->getLogger()->logDebug(GwLog::ERROR,"obp60task calcTrueWind: TWD_Valid? %d, TWD=%.1f, TWS=%.1f, TWA=%.1f, isCalculated? %d", twdBVal->valid, twdBVal->value * RAD_TO_DEG, twsBVal->value * 3.6 / 1.852, + if (isCalculated) { // 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; + } + } + api->getLogger()->logDebug(GwLog::DEBUG,"obp60task calcTrueWind: TWD_Valid? %d, TWD=%.1f, TWS=%.1f, TWA=%.1f, isCalculated? %d", twdBVal->valid, twdBVal->value * RAD_TO_DEG, twsBVal->value * 3.6 / 1.852, twaBVal->value * RAD_TO_DEG, isCalculated); return isCalculated; @@ -451,7 +464,7 @@ void handleHstryBuf(GwApi* api, BoatValueList* boatValues, tBoatHstryData hstryB GwApi::BoatValue *twsBVal = boatValues->findValueOrCreate(hstryBufList.twsHstry->getName()); GwApi::BoatValue *twaBVal = boatValues->findValueOrCreate("TWA"); - api->getLogger()->logDebug(GwLog::ERROR,"obp60task handleHstryBuf: twdBVal: %f, twsBVal: %f, twaBVal: %f, TWD_isValid? %d", twdBVal->value * RAD_TO_DEG, + api->getLogger()->logDebug(GwLog::DEBUG,"obp60task handleHstryBuf: twdBVal: %f, twsBVal: %f, twaBVal: %f, TWD_isValid? %d", twdBVal->value * RAD_TO_DEG, twsBVal->value * 3.6 / 1.852, twaBVal->value * RAD_TO_DEG, twdBVal->valid); calBVal = new GwApi::BoatValue("TWD"); // temporary solution for calibration of history buffer values calBVal->setFormat(twdBVal->getFormat()); @@ -959,7 +972,7 @@ void OBP60Task(GwApi *api){ api->getStatus(commonData.status); if (calcTrueWnds) { - addTrueWind(api, &boatValues); + // addTrueWind(api, &boatValues); } // Handle history buffers for TWD, TWS for wind plot page and other usage handleHstryBuf(api, &boatValues, hstryBufList); diff --git a/lib/obp60task/platformio.ini b/lib/obp60task/platformio.ini index 074c140..43b94ea 100644 --- a/lib/obp60task/platformio.ini +++ b/lib/obp60task/platformio.ini @@ -102,5 +102,5 @@ build_flags= ${env.build_flags} upload_port = /dev/ttyUSB0 #OBP40 download via external USB/Serail converter upload_protocol = esptool #firmware upload via USB OTG seriell, by first upload need to set the ESP32-S3 in the upload mode with shortcut GND to Pin27 -upload_speed = 230400 +upload_speed = 921600 monitor_speed = 115200 From f79124eed3d4b4a1bfb903e7bacdde860a99f5bd Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Fri, 25 Jul 2025 19:54:01 +0200 Subject: [PATCH 25/26] Revert speed change in platformio.ini --- lib/obp60task/platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/obp60task/platformio.ini b/lib/obp60task/platformio.ini index 7e84fec..16cd23c 100644 --- a/lib/obp60task/platformio.ini +++ b/lib/obp60task/platformio.ini @@ -104,5 +104,5 @@ build_flags= ${env.build_flags} upload_port = /dev/ttyUSB0 #OBP40 download via external USB/Serail converter upload_protocol = esptool #firmware upload via USB OTG seriell, by first upload need to set the ESP32-S3 in the upload mode with shortcut GND to Pin27 -upload_speed = 921600 +upload_speed = 230400 monitor_speed = 115200 From da451bee703bea5a096e9e549d165cdabf86fb35 Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Sat, 26 Jul 2025 09:27:35 +0200 Subject: [PATCH 26/26] removed test comment from wind function call --- lib/obp60task/obp60task.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/obp60task/obp60task.cpp b/lib/obp60task/obp60task.cpp index a84116a..11b986d 100644 --- a/lib/obp60task/obp60task.cpp +++ b/lib/obp60task/obp60task.cpp @@ -972,7 +972,7 @@ void OBP60Task(GwApi *api){ api->getStatus(commonData.status); if (calcTrueWnds) { - // addTrueWind(api, &boatValues); + addTrueWind(api, &boatValues); } // Handle history buffers for TWD, TWS for wind plot page and other usage handleHstryBuf(api, &boatValues, hstryBufList);