From f84ef25a30c40c3ed77245daf5dbe905db093eca Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Fri, 17 Apr 2026 23:45:11 +0200 Subject: [PATCH 1/5] Initial version of Page Current --- lib/obp60task/OBP60Formatter.cpp | 2 +- lib/obp60task/PageCurrent.cpp | 489 +++++++++++++++++++++++++++++++ lib/obp60task/config_obp40.json | 10 + lib/obp60task/config_obp60.json | 10 + lib/obp60task/config_obp70.json | 10 + lib/obp60task/obp60task.cpp | 2 + 6 files changed, 522 insertions(+), 1 deletion(-) create mode 100644 lib/obp60task/PageCurrent.cpp diff --git a/lib/obp60task/OBP60Formatter.cpp b/lib/obp60task/OBP60Formatter.cpp index ad40372..9eb3916 100644 --- a/lib/obp60task/OBP60Formatter.cpp +++ b/lib/obp60task/OBP60Formatter.cpp @@ -227,7 +227,7 @@ FormattedData formatValue(GwApi::BoatValue *value, CommonData &commondata, bool result.cvalue = course; } //######################################################## - else if (value->getFormat() == "formatKnots" && (value->getName() == "SOG" || value->getName() == "STW")){ + else if (value->getFormat() == "formatKnots" && (value->getName() == "SOG" || value->getName() == "STW") || value->getName() == "DFT"){ double speed = 0; if (usesimudata == false) { speed = value->value; diff --git a/lib/obp60task/PageCurrent.cpp b/lib/obp60task/PageCurrent.cpp new file mode 100644 index 0000000..fda2503 --- /dev/null +++ b/lib/obp60task/PageCurrent.cpp @@ -0,0 +1,489 @@ +#if defined BOARD_OBP60S3 || defined BOARD_OBP40S3 + +#include "Pagedata.h" +#include "OBP60Extensions.h" +#include "OBPDataOperations.h" +// #include + +// Screen coordinates +struct Points { + int16_t x1, y1; + int16_t x2, y2; + int16_t x3, y3; +}; + +// Screen coordinates for boat data values (top-left, bottom-left, top-right, bottom-right corners) +static constexpr Points POS[] = { + { 10, 65, 10, 95, 10, 115 }, // Position left top for value, name, unit + { 10, 270, 10, 220, 10, 190 }, // Position left bottom + { 295, 65, 340, 95, 340, 115 }, // Position right top + { 295, 270, 340, 220, 340, 190 } // Position right bottom +}; + +// Define wave visual (XBM Format) +static constexpr int WAVE_W = 132; +static constexpr int WAVE_H = 20; +static const uint8_t wave_bitmap[] PROGMEM = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x80, 0x00, 0xff, 0xf8, 0x00, 0x00, 0xff, 0x80, 0x00, + 0x7f, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xf0, 0x03, 0xff, 0xfe, 0x00, 0x01, 0xff, 0xf0, + 0x01, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xbf, 0xe0, 0x3f, 0xc0, 0xe2, 0x07, + 0xff, 0x1f, 0xf8, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0x80, 0x07, 0xff, 0x80, + 0x00, 0xff, 0xff, 0xe0, 0x07, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf0, 0x07, 0xfe, 0x00, 0x00, 0x78, + 0x0f, 0x80, 0x3f, 0xff, 0x80, 0x00, 0x00, 0x07, 0x00, 0x00, 0x0f, 0xfe, 0x00, 0x01, 0xff, 0x00, + 0x00, 0x3f, 0xf0, 0x07, 0xfe, 0x00, 0x00, 0x00, 0x3f, 0xf0, 0x00, 0x3f, 0xff, 0x80, 0x07, 0xff, + 0xe0, 0x00, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x7e, 0x00, 0xff, 0xff, 0xf0, 0x3f, + 0xe3, 0xf8, 0x01, 0xc0, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xe7, 0xfc, 0x0f, 0xff, + 0xff, 0x00, 0x3f, 0x00, 0x00, 0x1f, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xf0, 0x03, + 0xff, 0xfc, 0x00, 0x03, 0xf0, 0x00, 0x07, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc0, + 0x00, 0x7f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, + 0x00, 0x00, 0x07, 0x80, 0xfc, 0x00, 0x00, 0x3f, 0xc0, 0x00, 0x07, 0xe0, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xe0, 0x00, 0x03, 0xff, 0x80, 0x01, 0xff, 0xf8, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x1e, 0x00, 0x0f, 0xff, 0xf0, 0x07, 0xff, 0xff, 0x00, 0xfc, 0x0f, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x03, 0xc0, 0x3e, 0x03, 0xff, 0xff, 0xe0, 0x3f, 0xff, 0xe0, 0x01, 0xe0, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xf8, 0x00, 0x7f, 0xff, 0x00, 0x07, 0xff, 0x80, 0x00, + 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xe0, 0x00, 0x1f, 0xfc, 0x00, 0x00, 0xfe, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 +}; + +class PageCurrent : public Page { +private: + GwLog* logger; + + int width; // Screen width + int height; // Screen height + + bool keylock = false; // Keylock + + bool useSimuData; + bool holdValues; + String flashLED; + String backlightMode; + uint8_t leeK; + + static constexpr int NUMVALUES = 10; // no. of data values in this page + static constexpr double DBL_MAX = std::numeric_limits::max(); + + // Old values for hold function + String sValueOld[NUMVALUES] = { "", "", "", "", "", "", "", "", "", "" }; + String unitOld[NUMVALUES] = { "", "", "", "", "", "", "", "", "", "" }; + + struct Current { + double set; // direction TO which current flows (0..2*PI, true dir) + double dft; // current drift speed (m/s) + }; + + double calcLeeway( + double leeK, // leeway coefficient + double roll, // heel of boat (rad) + double stw // speed through water (m/s) + ) + { + if (leeK == DBL_MAX || roll == DBL_MAX || stw == DBL_MAX || stw == 0) { + return 0; + } + + double lay = leeK * roll / (stw * stw); + return lay; + } + + double calcCTW( + double awa, // apparent wind angle (0..2PI -> representation within OBP60) + double hdt, // heading true (rad) + double lay // leeway angle (rad) + ) + { + double ctw; // course through water (rad true) + + if (awa >= M_PI) { // apparent wind > 180° -> comes from port + ctw = hdt + lay; + } else { // wind comes from starboard + ctw = hdt - lay; + } + + return ctw; + } + + Current calcSetAndDrift( + double lay, // leeway angle (rad) + double sog, // speed over ground (m/s) + double cog, // course over ground (rad true) + double stw, // speed through water (m/s) + double hdt, // heading true (rad) + double awa // apparent wind angle (rad) + ) + { + Current crnt; + double ctw; // course through water (rad true) + + ctw = calcCTW(awa, hdt, lay); + + // Ground velocity vector (East, North) + double vg_x = sog * std::sin(cog); + double vg_y = sog * std::cos(cog); + + // Water-relative velocity vector (East, North) + double vw_x = stw * std::sin(ctw); + double vw_y = stw * std::cos(ctw); + + // Current vector = ground - water + double vc_x = vg_x - vw_x; + double vc_y = vg_y - vw_y; + + crnt.dft = std::sqrt(vc_x * vc_x + vc_y * vc_y); + + crnt.set = std::atan2(vc_x, vc_y); // atan2(x, y) because 0° = North, clockwise positive + crnt.set = WindUtils::to2PI(crnt.set); // internal respresentation of wind is [0..2PI] + + LOG_DEBUG(GwLog::DEBUG, "PageCurrent-setDrift: sog: %.3f, ctw: %.3f, stw: %.3f, vc_x: %.3f, vc_y: %.3f, set: %.3f, drift: %.3f", + sog, ctw, stw, vc_x, vc_y, crnt.set, crnt.dft); + + return crnt; + }; + + // Draw compass rose with boat direction up; top heading of compass rose in degree [0..2PI] + void drawCompassRose(double topHeading) + { + int radius = 110; + String sDegree; + int centerX = 200; + int centerY = 150; + + getdisplay().fillCircle(centerX, centerY, radius + 10, commonData->fgcolor); + getdisplay().fillCircle(centerX, centerY, radius + 7, commonData->bgcolor); + + getdisplay().setFont(&Ubuntu_Bold8pt8b); + + for (int i = 0; i < 360; i += 10) { + + float topAngle = (float)(i * DEG_TO_RAD - topHeading); // Calculate the relative angle for drawing; subtracting topHeading shifts the entire rose + float sinVal = sin(topAngle); + float cosVal = cos(topAngle); + + // Coordinates for the labels (positioned slightly inside the rose) + float xLabel = centerX + (radius - 27) * sinVal; + float yLabel = centerY - (radius - 24) * cosVal; + + if (i % 30 == 0) { + // Draw scale markers (triangles) + float dx = 1; + float xx1 = -dx; + float xx2 = +dx; + float yy1 = -(radius - 10); + float yy2 = -(radius + 10); + + getdisplay().fillTriangle(centerX + (int)(cosVal * xx1 - sinVal * yy1), centerY + (int)(sinVal * xx1 + cosVal * yy1), + centerX + (int)(cosVal * xx2 - sinVal * yy1), centerY + (int)(sinVal * xx2 + cosVal * yy1), + centerX + (int)(cosVal * xx1 - sinVal * yy2), centerY + (int)(sinVal * xx1 + cosVal * yy2), commonData->fgcolor); + getdisplay().fillTriangle(centerX + (int)(cosVal * xx2 - sinVal * yy1), centerY + (int)(sinVal * xx2 + cosVal * yy1), + centerX + (int)(cosVal * xx1 - sinVal * yy2), centerY + (int)(sinVal * xx1 + cosVal * yy2), + centerX + (int)(cosVal * xx2 - sinVal * yy2), centerY + (int)(sinVal * xx2 + cosVal * yy2), commonData->fgcolor); + + // Print degree labels + sDegree = String(i); + int16_t x1, y1; + uint16_t w, h; + displayGetTextBounds(sDegree, (int)xLabel, (int)yLabel, &x1, &y1, &w, &h); + getdisplay().setCursor(xLabel - w / 2, yLabel + h / 2); + getdisplay().print(sDegree); + + } else { + // Draw dots for intermediate 10 degree steps + float xDot = centerX + radius * sinVal; + float yDot = centerY - radius * cosVal; + getdisplay().fillCircle((int)xDot, (int)yDot, 2, commonData->fgcolor); + } + } + } + + void drawRotatedArrow( + float size, // size of the arrow = speed in m/s [0.5..5.2] + float direction // angle the arrow is pointing to [0..2PI] + ) + { + if (size < 0.05) { // no arrow for current set below 0.05 m/s + return; + } + + // size [0.5 .. 5.2] -> length [60 .. 130] + float maxsize = constrain(size, 0.5, 5.2); + float length = 60.0 + ((maxsize - 0.5) / (5.2 - 0.5)) * (130.0 - 60.0); + int16_t cx = width / 2; + int16_t cy = height / 2; + + // geometry (arrow upwards) + float h = length / 2.0; + float w = length / 4.0; + float headW = length / 1.5; + float splitY = -h / 3; + + struct Pt { + float x, y; + }; + + // 7 edges of arrow + Pt p[7] = { + { -w / 2, h }, { w / 2, h }, { w / 2, splitY }, { headW / 2, splitY }, + { 0, -h }, { -headW / 2, splitY }, { -w / 2, splitY } + }; + + // edges of arrow with reduced size of 1 px for thicker frame + float offset = 1.0; + Pt p_in[7] = { + { -w / 2 + offset, h - offset }, { w / 2 - offset, h - offset }, + { w / 2 - offset, splitY + offset }, { headW / 2 - offset, splitY + offset }, + { 0, -h + offset }, { -headW / 2 + offset, splitY + offset }, + { -w / 2 + offset, splitY + offset } + }; + + // calculate rotation + float s = sin(direction); + float c = cos(direction); + int16_t rx[7], ry[7]; + + for (int i = 0; i < 7; i++) { + // rotate frame + rx[i] = cx + (int16_t)(p[i].x * c - p[i].y * s); + ry[i] = cy + (int16_t)(p[i].x * s + p[i].y * c); + } + + // fill arrow with white area to delete any background data + getdisplay().fillTriangle(rx[0], ry[0], rx[1], ry[1], rx[2], ry[2], commonData->bgcolor); + getdisplay().fillTriangle(rx[0], ry[0], rx[2], ry[2], rx[6], ry[6], commonData->bgcolor); + getdisplay().fillTriangle(rx[5], ry[5], rx[4], ry[4], rx[3], ry[3], commonData->bgcolor); + +/* // draw arrow frame (double for 2 px thickness) + for (int i = 0; i < 7; i++) { + int next = (i + 1) % 7; + // outer frame + getdisplay().drawLine(rx[i], ry[i], rx[next], ry[next], commonData->fgcolor); + // inner frame + getdisplay().drawLine(rxi[i], ryi[i], rxi[next], ryi[next], commonData->fgcolor); + } +*/ + // 5. Rahmen mit "Overdraw" für konstante Dicke + // draw arrow frame with "overdraw" for constant thickness + for (int i = 0; i < 7; i++) { + int next = (i + 1) % 7; + int16_t x0 = rx[i], y0 = ry[i]; + int16_t x1 = rx[next], y1 = ry[next]; + + // original line + getdisplay().drawLine(x0, y0, x1, y1, GxEPD_BLACK); + + // offset by 1 pixel in x or y depending on delta + if (abs(x1 - x0) > abs(y1 - y0)) { + getdisplay().drawLine(x0, y0 + 1, x1, y1 + 1, GxEPD_BLACK); + } else { + getdisplay().drawLine(x0 + 1, y0, x1 + 1, y1, GxEPD_BLACK); + } + } + } + +public: + PageCurrent(CommonData& common) + { + commonData = &common; + logger = commonData->logger; + LOG_DEBUG(GwLog::LOG, "Instantiate PageCurrent"); + + width = getdisplay().width(); // Screen width + height = getdisplay().height(); // Screen height + + // Get config data + useSimuData = commonData->config->getBool(commonData->config->useSimuData); + holdValues = commonData->config->getBool(commonData->config->holdvalues); + flashLED = commonData->config->getString(commonData->config->flashLED); + backlightMode = commonData->config->getString(commonData->config->backlight); + // leeK = commonData->config->getInt(commonData->config->leeK); + leeK = 9; + } + + virtual int handleKey(int key) + { + // Keylock function + if (key == 11) { // Code for keylock + commonData->keylock = !commonData->keylock; + return 0; // Commit the key + } + return key; + } + + virtual void displayNew(PageData& pageData) + { +#ifdef BOARD_OBP60S3 + // Clear optical warning + if (flashLED == "Limit Violation") { + setBlinkingLED(false); + setFlashLED(false); + } +#endif + } + + int displayPage(PageData& pageData) + { + enum DataIdx { + AWA = 0, + HDT, + SET, + DFT, + SOG, + COG, + STW, + HDM, + VAR, + ROLL, + NUM_VALS + }; + + double lay; + Current current; + + LOG_DEBUG(GwLog::LOG, "Display PageCurrent"); + + // Get boat values for page + // 0=AWA, 1=HDT, 2=SET, 3=DFT, 4=SOG, 5=COG, 6=STW, 7=HDM, 8=VAR, 9=ROLL + std::vector bValue(pageData.values.begin(), pageData.values.end()); + + LOG_DEBUG(GwLog::DEBUG, "PageCurrent: printing #1: %s, %.3f, #2: %s, %.3f, #3: %s, %.3f, #4: %s, %.3f", + bValue[AWA]->getName().c_str(), bValue[AWA]->value, bValue[HDT]->getName().c_str(), bValue[HDT]->value, + bValue[SET]->getName().c_str(), bValue[SET]->value, bValue[DFT]->getName().c_str(), bValue[DFT]->value); + + double awa = bValue[AWA]->valid ? bValue[AWA]->value : DBL_MAX; + double hdt = bValue[HDT]->valid ? bValue[HDT]->value : DBL_MAX; + current.set = bValue[SET]->valid ? bValue[SET]->value : DBL_MAX; + current.dft = bValue[DFT]->valid ? bValue[DFT]->value : DBL_MAX; + double sog = bValue[SOG]->valid ? bValue[SOG]->value : DBL_MAX; + double cog = bValue[COG]->valid ? bValue[COG]->value : DBL_MAX; + double stw = bValue[STW]->valid ? bValue[STW]->value : DBL_MAX; + double hdm = bValue[HDM]->valid ? bValue[HDM]->value : DBL_MAX; + double var = bValue[VAR]->valid ? bValue[VAR]->value : DBL_MAX; + double roll = bValue[ROLL]->valid ? bValue[ROLL]->value : DBL_MAX; + + // Calculate current data + //*********************************************************** + + LOG_DEBUG(GwLog::DEBUG, "PageCurrent: hdt 2nd: %.3f", hdt); + if (current.set == DBL_MAX || current.dft == DBL_MAX) { // If SET or DRIFT not available, try to calculate them + + if (hdt == DBL_MAX) { // HDT not available + if (hdm != DBL_MAX) { + hdt = hdm + (var != DBL_MAX ? var : 0.0); // Use corrected HDM if HDT is not available; just use HDM if VAR is not available + hdt = WindUtils::to2PI(hdt); + } + } + LOG_DEBUG(GwLog::DEBUG, "PageCurrent: hdt 3rd: %.3f", hdt); + + leeK = 4; + roll = 10 * DEG_TO_RAD; + lay = calcLeeway(leeK, roll, stw); + current = calcSetAndDrift(lay, sog, cog, stw, hdt, awa); + } + + LOG_DEBUG(GwLog::DEBUG, "PageCurrent: leeK: %d, lay: %.3f, roll: %.3f, stw: %.3f, awa: %.3f, hdt: %.3f, cog: %.3f, sog: %.3f, set: %.3f, dft: %.3f", + leeK, lay, roll, stw, awa, hdt, cog, sog, current.set, current.dft); + + // Draw page + //*********************************************************** + + displaySetPartialWindow(0, 0, width, height); // Set partial update + getdisplay().setTextColor(commonData->fgcolor); + + for (int i = 0; i < 4; i++) { // Display first 4 values + String name = xdrDelete(bValue[i]->getName()); // Value name + name = name.substring(0, 6); // String length limit for value name + if (!bValue[i]->valid) { + switch (i) { + case AWA: + break; // we don't change AWA any time, so there is no adjustment option + case HDT: + bValue[i]->value = hdt; + bValue[i]->setFormat("formatCourse"); + bValue[i]->valid = true; // Valid information + break; + case SET: + bValue[i]->value = current.set; + bValue[i]->setFormat("formatCourse"); + bValue[i]->valid = true; // Valid information + break; + case DFT: + bValue[i]->value = current.dft; + bValue[i]->setFormat("formatKnots"); + bValue[i]->valid = true; // Valid information + break; + default: + break; + } + } + String sValue = formatValue(bValue[i], *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places + String unit = formatValue(bValue[i], *commonData).unit; // Unit of value + + LOG_DEBUG(GwLog::DEBUG, "PageCurrent: name: %s, value: %s, format: %s, unit: %s", + name, sValue, bValue[i]->getFormat().c_str(), unit); + + // Show bus data + getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b); + getdisplay().setCursor(POS[i].x1, POS[i].y1); + if (!holdValues || useSimuData) { + getdisplay().print(sValue); + } else { + getdisplay().print(sValueOld[i]); + } + + // Show name + getdisplay().setFont(&Ubuntu_Bold12pt8b); + getdisplay().setCursor(POS[i].x2, POS[i].y2); + getdisplay().print(name); + + // Show unit + getdisplay().setFont(&Ubuntu_Bold8pt8b); + getdisplay().setCursor(POS[i].x3, POS[i].y3); + + if (holdValues) { + getdisplay().print(unitOld[i]); + } else { + getdisplay().print(unit); + } + + if (bValue[i]->valid) { + sValueOld[i] = sValue; // Save the old value + unitOld[i] = unit; // Save the old unit + } + } + + // Horizontal separator left + getdisplay().fillRect(0, 149, 60, 2, commonData->fgcolor); + // Horizontal separator right + getdisplay().fillRect(339, 149, 60, 2, commonData->fgcolor); + + drawCompassRose(hdt); + getdisplay().drawBitmap((width - WAVE_W) / 2, (height - WAVE_H) / 2, wave_bitmap, WAVE_W, WAVE_H, commonData->fgcolor); + drawRotatedArrow(current.dft, WindUtils::to2PI(current.set - hdt)); + + return PAGE_UPDATE; + }; +}; + +static Page* createPage(CommonData& common) +{ + return new PageCurrent(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 + * this will be number of BoatValue pointers in pageData.values + */ +PageDescription registerPageCurrent( + "Current", // Page name + createPage, // Action + 0, // Number of bus values depends on selection in Web configuration + { "AWA", "HDT", "SET", "DFT", "SOG", "COG", "STW", "HDM", "VAR", "ROLL" }, // Bus values we need in the page + true // Show display header on/off +); + +#endif diff --git a/lib/obp60task/config_obp40.json b/lib/obp60task/config_obp40.json index 0926692..34faa71 100644 --- a/lib/obp60task/config_obp40.json +++ b/lib/obp60task/config_obp40.json @@ -1534,6 +1534,7 @@ "Battery2", "Clock", "Compass", + "Current", "DigitalOut", "DST810", "Fluid", @@ -1865,6 +1866,7 @@ "Battery2", "Clock", "Compass", + "Current", "DigitalOut", "DST810", "Fluid", @@ -2187,6 +2189,7 @@ "Battery2", "Clock", "Compass", + "Current", "DigitalOut", "DST810", "Fluid", @@ -2500,6 +2503,7 @@ "Battery2", "Clock", "Compass", + "Current", "DigitalOut", "DST810", "Fluid", @@ -2804,6 +2808,7 @@ "Battery2", "Clock", "Compass", + "Current", "DigitalOut", "DST810", "Fluid", @@ -3099,6 +3104,7 @@ "Battery2", "Clock", "Compass", + "Current", "DigitalOut", "DST810", "Fluid", @@ -3385,6 +3391,7 @@ "Battery2", "Clock", "Compass", + "Current", "DigitalOut", "DST810", "Fluid", @@ -3662,6 +3669,7 @@ "Battery2", "Clock", "Compass", + "Current", "DigitalOut", "DST810", "Fluid", @@ -3930,6 +3938,7 @@ "Battery2", "Clock", "Compass", + "Current", "DigitalOut", "DST810", "Fluid", @@ -4189,6 +4198,7 @@ "Battery2", "Clock", "Compass", + "Current", "DigitalOut", "DST810", "Fluid", diff --git a/lib/obp60task/config_obp60.json b/lib/obp60task/config_obp60.json index a51f1f5..e138acf 100644 --- a/lib/obp60task/config_obp60.json +++ b/lib/obp60task/config_obp60.json @@ -1512,6 +1512,7 @@ "Battery2", "Clock", "Compass", + "Current", "DigitalOut", "DST810", "Fluid", @@ -1813,6 +1814,7 @@ "Battery2", "Clock", "Compass", + "Current", "DigitalOut", "DST810", "Fluid", @@ -2106,6 +2108,7 @@ "Battery2", "Clock", "Compass", + "Current", "DigitalOut", "DST810", "Fluid", @@ -2391,6 +2394,7 @@ "Battery2", "Clock", "Compass", + "Current", "DigitalOut", "DST810", "Fluid", @@ -2668,6 +2672,7 @@ "Battery2", "Clock", "Compass", + "Current", "DigitalOut", "DST810", "Fluid", @@ -2937,6 +2942,7 @@ "Battery2", "Clock", "Compass", + "Current", "DigitalOut", "DST810", "Fluid", @@ -3198,6 +3204,7 @@ "Battery2", "Clock", "Compass", + "Current", "DigitalOut", "DST810", "Fluid", @@ -3451,6 +3458,7 @@ "Battery2", "Clock", "Compass", + "Current", "DigitalOut", "DST810", "Fluid", @@ -3696,6 +3704,7 @@ "Battery2", "Clock", "Compass", + "Current", "DigitalOut", "DST810", "Fluid", @@ -3933,6 +3942,7 @@ "Battery2", "Clock", "Compass", + "Current", "DigitalOut", "DST810", "Fluid", diff --git a/lib/obp60task/config_obp70.json b/lib/obp60task/config_obp70.json index b9b7a99..3727c04 100644 --- a/lib/obp60task/config_obp70.json +++ b/lib/obp60task/config_obp70.json @@ -1504,6 +1504,7 @@ "Battery2", "Clock", "Compass", + "Current", "DigitalOut", "DST810", "Fluid", @@ -1805,6 +1806,7 @@ "Battery2", "Clock", "Compass", + "Current", "DigitalOut", "DST810", "Fluid", @@ -2098,6 +2100,7 @@ "Battery2", "Clock", "Compass", + "Current", "DigitalOut", "DST810", "Fluid", @@ -2383,6 +2386,7 @@ "Battery2", "Clock", "Compass", + "Current", "DigitalOut", "DST810", "Fluid", @@ -2660,6 +2664,7 @@ "Battery2", "Clock", "Compass", + "Current", "DigitalOut", "DST810", "Fluid", @@ -2929,6 +2934,7 @@ "Battery2", "Clock", "Compass", + "Current", "DigitalOut", "DST810", "Fluid", @@ -3190,6 +3196,7 @@ "Battery2", "Clock", "Compass", + "Current", "DigitalOut", "DST810", "Fluid", @@ -3443,6 +3450,7 @@ "Battery2", "Clock", "Compass", + "Current", "DigitalOut", "DST810", "Fluid", @@ -3688,6 +3696,7 @@ "Battery2", "Clock", "Compass", + "Current", "DigitalOut", "DST810", "Fluid", @@ -3925,6 +3934,7 @@ "Battery2", "Clock", "Compass", + "Current", "DigitalOut", "DST810", "Fluid", diff --git a/lib/obp60task/obp60task.cpp b/lib/obp60task/obp60task.cpp index feba029..1079277 100644 --- a/lib/obp60task/obp60task.cpp +++ b/lib/obp60task/obp60task.cpp @@ -233,6 +233,8 @@ void registerAllPages(PageList &list){ list.add(®isterPageClock); extern PageDescription registerPageCompass; list.add(®isterPageCompass); + extern PageDescription registerPageCurrent; + list.add(®isterPageCurrent); extern PageDescription registerPageWhite; list.add(®isterPageWhite); extern PageDescription registerPageBME280; From 1098afa8b2ad5547d37ba8516d357508ac4beb70 Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Sat, 25 Apr 2026 01:23:42 +0200 Subject: [PATCH 2/5] True Wind: calculate HDT only if true wind calc is requested --- lib/obp60task/OBPDataOperations.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/obp60task/OBPDataOperations.cpp b/lib/obp60task/OBPDataOperations.cpp index 278ef0e..171e507 100644 --- a/lib/obp60task/OBPDataOperations.cpp +++ b/lib/obp60task/OBPDataOperations.cpp @@ -467,6 +467,10 @@ bool WindUtils::handleWinds(bool calcWinds) double twd, tws, twa, awd; bool twCalculated = false; + if (!calcWinds) { // don't calculate anything if true wind calculation has not been set in configuration + return twCalculated; + } + double awaVal = awaBVal->valid ? awaBVal->value : DBL_MAX; double awsVal = awsBVal->valid ? awsBVal->value : DBL_MAX; double cogVal = cogBVal->valid ? cogBVal->value : DBL_MAX; @@ -511,8 +515,8 @@ bool WindUtils::handleWinds(bool calcWinds) } } - if (calcWinds && (!twaBVal->valid || !twsBVal->valid || !twdBVal->valid)) { - // calculate true winds if user setting and at least one of three true wind values does not exist + if (!twaBVal->valid || !twsBVal->valid || !twdBVal->valid) { + // calculate true winds at least one of three true wind values does not exist twCalculated = calcTrueWinds(&awaVal, &awsVal, &awd, &cogVal, &stwVal, &sogVal, &hdtVal, &twa, &tws, &twd); if (twCalculated) { // Replace values only, if successfully calculated and not already available if (!twaBVal->valid) { From 2f30e858c8e803399365430980ec5cdcdf3f1609 Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Sat, 25 Apr 2026 02:37:39 +0200 Subject: [PATCH 3/5] PageCurrent: added leeway coeff, changed boat value handling, fixed ROLL --- lib/obp60task/PageCurrent.cpp | 147 +++++++++++++++----------------- lib/obp60task/config_obp40.json | 38 +++++++++ lib/obp60task/config_obp60.json | 38 +++++++++ 3 files changed, 146 insertions(+), 77 deletions(-) diff --git a/lib/obp60task/PageCurrent.cpp b/lib/obp60task/PageCurrent.cpp index fda2503..df280f6 100644 --- a/lib/obp60task/PageCurrent.cpp +++ b/lib/obp60task/PageCurrent.cpp @@ -3,15 +3,26 @@ #include "Pagedata.h" #include "OBP60Extensions.h" #include "OBPDataOperations.h" -// #include +#include -// Screen coordinates +// Screen coordinates for three boat values struct Points { int16_t x1, y1; int16_t x2, y2; int16_t x3, y3; }; +// leeway K coefficient selection options from OBP configuration page +static const std::unordered_map leeKMap = { + { "---", 0 }, + { "Racer [2]", 2 }, + { "Multihull Draggerboard [4]", 4 }, + { "Performance Cruiser [6]", 6 }, + { "Family Cruiser [9]", 9 }, + { "Heavy Displacement [13]", 13 }, + { "Multihull Fixed Keel [16]", 16 } +}; + // Screen coordinates for boat data values (top-left, bottom-left, top-right, bottom-right corners) static constexpr Points POS[] = { { 10, 65, 10, 95, 10, 115 }, // Position left top for value, name, unit @@ -61,7 +72,8 @@ private: bool holdValues; String flashLED; String backlightMode; - uint8_t leeK; + String leeKStd; + double leeK; static constexpr int NUMVALUES = 10; // no. of data values in this page static constexpr double DBL_MAX = std::numeric_limits::max(); @@ -120,11 +132,11 @@ private: ctw = calcCTW(awa, hdt, lay); - // Ground velocity vector (East, North) + // Ground velocity vector (east, north) double vg_x = sog * std::sin(cog); double vg_y = sog * std::cos(cog); - // Water-relative velocity vector (East, North) + // Water-relative velocity vector (east, north) double vw_x = stw * std::sin(ctw); double vw_y = stw * std::cos(ctw); @@ -137,8 +149,8 @@ private: crnt.set = std::atan2(vc_x, vc_y); // atan2(x, y) because 0° = North, clockwise positive crnt.set = WindUtils::to2PI(crnt.set); // internal respresentation of wind is [0..2PI] - LOG_DEBUG(GwLog::DEBUG, "PageCurrent-setDrift: sog: %.3f, ctw: %.3f, stw: %.3f, vc_x: %.3f, vc_y: %.3f, set: %.3f, drift: %.3f", - sog, ctw, stw, vc_x, vc_y, crnt.set, crnt.dft); + //LOG_DEBUG(GwLog::DEBUG, "PageCurrent-setDrift: sog: %.3f, ctw: %.3f, stw: %.3f, vc_x: %.3f, vc_y: %.3f, set: %.3f, drift: %.3f", + // sog, ctw, stw, vc_x, vc_y, crnt.set, crnt.dft); return crnt; }; @@ -254,16 +266,6 @@ private: getdisplay().fillTriangle(rx[0], ry[0], rx[2], ry[2], rx[6], ry[6], commonData->bgcolor); getdisplay().fillTriangle(rx[5], ry[5], rx[4], ry[4], rx[3], ry[3], commonData->bgcolor); -/* // draw arrow frame (double for 2 px thickness) - for (int i = 0; i < 7; i++) { - int next = (i + 1) % 7; - // outer frame - getdisplay().drawLine(rx[i], ry[i], rx[next], ry[next], commonData->fgcolor); - // inner frame - getdisplay().drawLine(rxi[i], ryi[i], rxi[next], ryi[next], commonData->fgcolor); - } -*/ - // 5. Rahmen mit "Overdraw" für konstante Dicke // draw arrow frame with "overdraw" for constant thickness for (int i = 0; i < 7; i++) { int next = (i + 1) % 7; @@ -297,8 +299,15 @@ public: holdValues = commonData->config->getBool(commonData->config->holdvalues); flashLED = commonData->config->getString(commonData->config->flashLED); backlightMode = commonData->config->getString(commonData->config->backlight); - // leeK = commonData->config->getInt(commonData->config->leeK); - leeK = 9; + + leeKStd = commonData->config->getString(commonData->config->leeKStd); + auto it = leeKMap.find(leeKStd.c_str()); + if (it != leeKMap.end()) { + leeK = it->second; + } else if (leeKStd == "Individual value") { + leeK = (commonData->config->getString(commonData->config->leeK)).toDouble(); + } + LOG_DEBUG(GwLog::DEBUG, "PageCurrent: leeKStd: %s, leeK: %.3f", leeKStd.c_str(), leeK); } virtual int handleKey(int key) @@ -347,43 +356,49 @@ public: // 0=AWA, 1=HDT, 2=SET, 3=DFT, 4=SOG, 5=COG, 6=STW, 7=HDM, 8=VAR, 9=ROLL std::vector bValue(pageData.values.begin(), pageData.values.end()); - LOG_DEBUG(GwLog::DEBUG, "PageCurrent: printing #1: %s, %.3f, #2: %s, %.3f, #3: %s, %.3f, #4: %s, %.3f", - bValue[AWA]->getName().c_str(), bValue[AWA]->value, bValue[HDT]->getName().c_str(), bValue[HDT]->value, - bValue[SET]->getName().c_str(), bValue[SET]->value, bValue[DFT]->getName().c_str(), bValue[DFT]->value); - - double awa = bValue[AWA]->valid ? bValue[AWA]->value : DBL_MAX; - double hdt = bValue[HDT]->valid ? bValue[HDT]->value : DBL_MAX; - current.set = bValue[SET]->valid ? bValue[SET]->value : DBL_MAX; - current.dft = bValue[DFT]->valid ? bValue[DFT]->value : DBL_MAX; - double sog = bValue[SOG]->valid ? bValue[SOG]->value : DBL_MAX; - double cog = bValue[COG]->valid ? bValue[COG]->value : DBL_MAX; - double stw = bValue[STW]->valid ? bValue[STW]->value : DBL_MAX; - double hdm = bValue[HDM]->valid ? bValue[HDM]->value : DBL_MAX; - double var = bValue[VAR]->valid ? bValue[VAR]->value : DBL_MAX; - double roll = bValue[ROLL]->valid ? bValue[ROLL]->value : DBL_MAX; + LOG_DEBUG(GwLog::DEBUG, "PageCurrent: printing #1: %s, %.3f, valid: %d, #2: %s, %.3f, valid: %d, #3: %s, %.3f, valid: %d, #4: %s, %.3f, valid: %d", + bValue[AWA]->getName().c_str(), bValue[AWA]->value, bValue[AWA]->valid, bValue[HDT]->getName().c_str(), bValue[HDT]->value, bValue[HDT]->valid, + bValue[SET]->getName().c_str(), bValue[SET]->value, bValue[SET]->valid, bValue[DFT]->getName().c_str(), bValue[DFT]->value, bValue[DFT]->valid); // Calculate current data //*********************************************************** - LOG_DEBUG(GwLog::DEBUG, "PageCurrent: hdt 2nd: %.3f", hdt); - if (current.set == DBL_MAX || current.dft == DBL_MAX) { // If SET or DRIFT not available, try to calculate them + if (!bValue[SET]->valid || !bValue[DFT]->valid) { // If SET or DRIFT not available, try to calculate them + if (bValue[SOG]->valid && bValue[COG]->valid && bValue[STW]->valid && bValue[AWA]->valid) { // calculate current only if all required values are available - if (hdt == DBL_MAX) { // HDT not available - if (hdm != DBL_MAX) { - hdt = hdm + (var != DBL_MAX ? var : 0.0); // Use corrected HDM if HDT is not available; just use HDM if VAR is not available - hdt = WindUtils::to2PI(hdt); + if (!bValue[HDT]->valid) { // HDT not available + if (bValue[HDM]->valid) { + if (bValue[VAR]->valid) { + bValue[HDT]->value = bValue[HDM]->value + bValue[VAR]->value; // Use corrected HDM if HDT is not available + bValue[HDT]->value = WindUtils::to2PI(bValue[HDT]->value); + bValue[HDT]->valid = true; + } else { + // if HDT cannot be fully substituted by HDM+VAR, continue with HDM only + bValue[HDT] = bValue[HDM]; + } + } + } + + if (!bValue[ROLL]->valid) { + bValue[ROLL]->value = 0; // delete last value if roll value is not valid anymore; gateway keeps last value + } + lay = calcLeeway(leeK, bValue[ROLL]->value, bValue[STW]->value); + + if (bValue[HDT]->valid) { // That's either HDT or HDM + current = calcSetAndDrift(lay, bValue[SOG]->value, bValue[COG]->value, bValue[STW]->value, bValue[HDT]->value, bValue[AWA]->value); + bValue[SET]->value = current.set; + bValue[SET]->setFormat("formatCourse"); + bValue[SET]->valid = true; + bValue[DFT]->value = current.dft; + bValue[DFT]->setFormat("formatKnots"); + bValue[DFT]->valid = true; } } - LOG_DEBUG(GwLog::DEBUG, "PageCurrent: hdt 3rd: %.3f", hdt); - - leeK = 4; - roll = 10 * DEG_TO_RAD; - lay = calcLeeway(leeK, roll, stw); - current = calcSetAndDrift(lay, sog, cog, stw, hdt, awa); } - LOG_DEBUG(GwLog::DEBUG, "PageCurrent: leeK: %d, lay: %.3f, roll: %.3f, stw: %.3f, awa: %.3f, hdt: %.3f, cog: %.3f, sog: %.3f, set: %.3f, dft: %.3f", - leeK, lay, roll, stw, awa, hdt, cog, sog, current.set, current.dft); + LOG_DEBUG(GwLog::DEBUG, "PageCurrent: leeKStd: %s, leeK: %.3f, lay: %.3f, roll: %.3f, stw: %.3f, awa: %.3f, hdt: %.3f, cog: %.3f, sog: %.3f, set: %.3f, dft: %.3f", + leeKStd.c_str(), leeK, lay, bValue[ROLL]->value, bValue[STW]->value, bValue[AWA]->value, bValue[HDT]->value, bValue[COG]->value, bValue[SOG]->value, + bValue[SET]->value, bValue[DFT]->value); // Draw page //*********************************************************** @@ -394,36 +409,10 @@ public: for (int i = 0; i < 4; i++) { // Display first 4 values String name = xdrDelete(bValue[i]->getName()); // Value name name = name.substring(0, 6); // String length limit for value name - if (!bValue[i]->valid) { - switch (i) { - case AWA: - break; // we don't change AWA any time, so there is no adjustment option - case HDT: - bValue[i]->value = hdt; - bValue[i]->setFormat("formatCourse"); - bValue[i]->valid = true; // Valid information - break; - case SET: - bValue[i]->value = current.set; - bValue[i]->setFormat("formatCourse"); - bValue[i]->valid = true; // Valid information - break; - case DFT: - bValue[i]->value = current.dft; - bValue[i]->setFormat("formatKnots"); - bValue[i]->valid = true; // Valid information - break; - default: - break; - } - } String sValue = formatValue(bValue[i], *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places String unit = formatValue(bValue[i], *commonData).unit; // Unit of value - LOG_DEBUG(GwLog::DEBUG, "PageCurrent: name: %s, value: %s, format: %s, unit: %s", - name, sValue, bValue[i]->getFormat().c_str(), unit); - - // Show bus data + // Show boat data value getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b); getdisplay().setCursor(POS[i].x1, POS[i].y1); if (!holdValues || useSimuData) { @@ -458,9 +447,13 @@ public: // Horizontal separator right getdisplay().fillRect(339, 149, 60, 2, commonData->fgcolor); - drawCompassRose(hdt); + if (bValue[HDT]->valid) { + drawCompassRose(bValue[HDT]->value); + } else { + drawCompassRose(0.0); + }; getdisplay().drawBitmap((width - WAVE_W) / 2, (height - WAVE_H) / 2, wave_bitmap, WAVE_W, WAVE_H, commonData->fgcolor); - drawRotatedArrow(current.dft, WindUtils::to2PI(current.set - hdt)); + drawRotatedArrow(bValue[DFT]->value, WindUtils::to2PI(bValue[SET]->value - bValue[HDT]->value)); return PAGE_UPDATE; }; @@ -482,7 +475,7 @@ PageDescription registerPageCurrent( "Current", // Page name createPage, // Action 0, // Number of bus values depends on selection in Web configuration - { "AWA", "HDT", "SET", "DFT", "SOG", "COG", "STW", "HDM", "VAR", "ROLL" }, // Bus values we need in the page + { "AWA", "HDT", "SET", "DFT", "SOG", "COG", "STW", "HDM", "VAR", "xdrROLL" }, // Bus values we need in the page true // Show display header on/off ); diff --git a/lib/obp60task/config_obp40.json b/lib/obp60task/config_obp40.json index 34faa71..656eeec 100644 --- a/lib/obp60task/config_obp40.json +++ b/lib/obp60task/config_obp40.json @@ -75,6 +75,44 @@ "obp40": "true" } }, + { + "name": "leeKStd", + "label": "Leeway Coefficient", + "type": "list", + "default": "---", + "description": "Boat coefficient K for leeway", + "list": [ + "---", + "Racer [2]", + "Multihull Draggerboard [4]", + "Performance Cruiser [6]", + "Family Cruiser [9]", + "Heavy Displacement [13]", + "Multihull Fixed Keel [16]", + "Individual value" + ], + "category": "OBP40 Settings", + "capabilities": { + "obp40": "true" + } + }, + { + "name": "leeK", + "label": "Leeway Coefficient", + "type": "number", + "default": "0.00", + "check": "checkMinMax", + "min": 0, + "max": 20, + "description": "Boat coefficient K for leeway - individual setting [0..20]", + "category": "OBP40 Settings", + "capabilities": { + "obp40":"true" + }, + "condition": [ + { "leeKStd": ["Individual value"] } + ] + }, { "name": "fuelTank", "label": "Fuel Tank [l]", diff --git a/lib/obp60task/config_obp60.json b/lib/obp60task/config_obp60.json index e138acf..b5a2c27 100644 --- a/lib/obp60task/config_obp60.json +++ b/lib/obp60task/config_obp60.json @@ -75,6 +75,44 @@ "obp60":"true" } }, + { + "name": "leeKStd", + "label": "Leeway Coefficient", + "type": "list", + "default": "---", + "description": "Boat coefficient K for leeway", + "list": [ + "---", + "Racer [2]", + "Multihull Draggerboard [4]", + "Performance Cruiser [6]", + "Family Cruiser [9]", + "Heavy Displacement [13]", + "Multihull Fixed Keel [16]", + "Individual value" + ], + "category": "OBP60 Settings", + "capabilities": { + "obp60": "true" + } + }, + { + "name": "leeK", + "label": "Leeway Coefficient", + "type": "number", + "default": "0.00", + "check": "checkMinMax", + "min": 0, + "max": 20, + "description": "Boat coefficient K for leeway - individual setting [0..20]", + "category": "OBP60 Settings", + "capabilities": { + "obp60":"true" + }, + "condition": [ + { "leeKStd": ["Individual value"] } + ] + }, { "name": "fuelTank", "label": "Fuel Tank [l]", From 2d5838ebc87a7230f65e92c99b4ccaec8ed46349 Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Sun, 26 Apr 2026 01:19:19 +0200 Subject: [PATCH 4/5] PageCurrent: make ROLL value selectable in config; display name+unit right-aligned; code clean-up --- lib/obp60task/PageCurrent.cpp | 142 ++++++++++++++++++-------------- lib/obp60task/config_obp40.json | 10 +++ lib/obp60task/config_obp60.json | 10 +++ 3 files changed, 99 insertions(+), 63 deletions(-) diff --git a/lib/obp60task/PageCurrent.cpp b/lib/obp60task/PageCurrent.cpp index df280f6..e9db95b 100644 --- a/lib/obp60task/PageCurrent.cpp +++ b/lib/obp60task/PageCurrent.cpp @@ -5,13 +5,6 @@ #include "OBPDataOperations.h" #include -// Screen coordinates for three boat values -struct Points { - int16_t x1, y1; - int16_t x2, y2; - int16_t x3, y3; -}; - // leeway K coefficient selection options from OBP configuration page static const std::unordered_map leeKMap = { { "---", 0 }, @@ -23,12 +16,35 @@ static const std::unordered_map leeKMap = { { "Multihull Fixed Keel [16]", 16 } }; -// Screen coordinates for boat data values (top-left, bottom-left, top-right, bottom-right corners) +enum bValIdx { + ROLL = 0, + SET, + DFT, + HDT, + STW, + COG, + SOG, + HDM, + VAR, + AWA, + NUM_VALS +}; + +// Screen coordinates for boat values +struct Points { + int16_t x1, y1; + int16_t x2, y2; + int16_t x3, y3; +}; + +// Screen coordinates for four boat data values (top-left, bottom-left, top-right, bottom-right) static constexpr Points POS[] = { - { 10, 65, 10, 95, 10, 115 }, // Position left top for value, name, unit - { 10, 270, 10, 220, 10, 190 }, // Position left bottom - { 295, 65, 340, 95, 340, 115 }, // Position right top - { 295, 270, 340, 220, 340, 190 } // Position right bottom + { 10, 65, 10, 95, 10, 115 }, // Position top left for value, name, unit + { 10, 270, 10, 220, 10, 190 }, // Position bottom left + { 295, 65, 390, 95, 390, 115 }, // Position top right + { 295, 270, 390, 220, 390, 190 } // Position bottom right + // { 295, 65, 340, 95, 340, 115 }, // Position top right + // { 295, 270, 340, 220, 340, 190 } // Position bottom right }; // Define wave visual (XBM Format) @@ -75,12 +91,9 @@ private: String leeKStd; double leeK; - static constexpr int NUMVALUES = 10; // no. of data values in this page - static constexpr double DBL_MAX = std::numeric_limits::max(); - // Old values for hold function - String sValueOld[NUMVALUES] = { "", "", "", "", "", "", "", "", "", "" }; - String unitOld[NUMVALUES] = { "", "", "", "", "", "", "", "", "", "" }; + String sValueOld[NUM_VALS] = { "", "", "", "", "", "", "", "", "", "" }; + String unitOld[NUM_VALS] = { "", "", "", "", "", "", "", "", "", "" }; struct Current { double set; // direction TO which current flows (0..2*PI, true dir) @@ -93,7 +106,7 @@ private: double stw // speed through water (m/s) ) { - if (leeK == DBL_MAX || roll == DBL_MAX || stw == DBL_MAX || stw == 0) { + if (stw == 0) { return 0; } @@ -149,8 +162,8 @@ private: crnt.set = std::atan2(vc_x, vc_y); // atan2(x, y) because 0° = North, clockwise positive crnt.set = WindUtils::to2PI(crnt.set); // internal respresentation of wind is [0..2PI] - //LOG_DEBUG(GwLog::DEBUG, "PageCurrent-setDrift: sog: %.3f, ctw: %.3f, stw: %.3f, vc_x: %.3f, vc_y: %.3f, set: %.3f, drift: %.3f", - // sog, ctw, stw, vc_x, vc_y, crnt.set, crnt.dft); + // LOG_DEBUG(GwLog::DEBUG, "PageCurrent-setDrift: sog: %.3f, ctw: %.3f, stw: %.3f, vc_x: %.3f, vc_y: %.3f, set: %.3f, drift: %.3f", + // sog, ctw, stw, vc_x, vc_y, crnt.set, crnt.dft); return crnt; }; @@ -273,13 +286,13 @@ private: int16_t x1 = rx[next], y1 = ry[next]; // original line - getdisplay().drawLine(x0, y0, x1, y1, GxEPD_BLACK); + getdisplay().drawLine(x0, y0, x1, y1, commonData->fgcolor); // offset by 1 pixel in x or y depending on delta if (abs(x1 - x0) > abs(y1 - y0)) { - getdisplay().drawLine(x0, y0 + 1, x1, y1 + 1, GxEPD_BLACK); + getdisplay().drawLine(x0, y0 + 1, x1, y1 + 1, commonData->fgcolor); } else { - getdisplay().drawLine(x0 + 1, y0, x1 + 1, y1, GxEPD_BLACK); + getdisplay().drawLine(x0 + 1, y0, x1 + 1, y1, commonData->fgcolor); } } } @@ -333,32 +346,19 @@ public: int displayPage(PageData& pageData) { - enum DataIdx { - AWA = 0, - HDT, - SET, - DFT, - SOG, - COG, - STW, - HDM, - VAR, - ROLL, - NUM_VALS - }; - double lay; Current current; LOG_DEBUG(GwLog::LOG, "Display PageCurrent"); // Get boat values for page - // 0=AWA, 1=HDT, 2=SET, 3=DFT, 4=SOG, 5=COG, 6=STW, 7=HDM, 8=VAR, 9=ROLL + // 0=ROLL, 1=SET, 2=DFT, 3=HDT, 4=STW, 5=COG, 6=SOG, 7=HDM, 8=VAR, 9=AWA std::vector bValue(pageData.values.begin(), pageData.values.end()); - LOG_DEBUG(GwLog::DEBUG, "PageCurrent: printing #1: %s, %.3f, valid: %d, #2: %s, %.3f, valid: %d, #3: %s, %.3f, valid: %d, #4: %s, %.3f, valid: %d", - bValue[AWA]->getName().c_str(), bValue[AWA]->value, bValue[AWA]->valid, bValue[HDT]->getName().c_str(), bValue[HDT]->value, bValue[HDT]->valid, - bValue[SET]->getName().c_str(), bValue[SET]->value, bValue[SET]->valid, bValue[DFT]->getName().c_str(), bValue[DFT]->value, bValue[DFT]->valid); + LOG_DEBUG(GwLog::DEBUG, "PageCurrent: printing #1: %s, %.3f, valid: %d, #2: %s, %.3f, valid: %d, #3: %s, %.3f, valid: %d, #4: %s, %.3f, valid: %d, #5: %s, %.3f, valid: %d", + bValue[ROLL]->getName().c_str(), bValue[ROLL]->value, bValue[ROLL]->valid, bValue[SET]->getName().c_str(), bValue[SET]->value, bValue[SET]->valid, + bValue[DFT]->getName().c_str(), bValue[DFT]->value, bValue[DFT]->valid, bValue[HDT]->getName().c_str(), bValue[HDT]->value, bValue[HDT]->valid, + bValue[STW]->getName().c_str(), bValue[STW]->value, bValue[STW]->valid); // Calculate current data //*********************************************************** @@ -379,9 +379,12 @@ public: } } - if (!bValue[ROLL]->valid) { - bValue[ROLL]->value = 0; // delete last value if roll value is not valid anymore; gateway keeps last value + if (!bValue[ROLL]->valid || (bValue[ROLL]->getFormat() != "formatXdr:A:D" && bValue[ROLL]->getFormat() != "formatXdr:A:rd" && bValue[ROLL]->getFormat() != "formatCourse")) { + bValue[ROLL]->value = 0; // delete last value if roll value is not valid anymore (gateway keeps last value) or wrong type selected by user } + LOG_DEBUG(GwLog::DEBUG, "PageCurrent: Roll: %s, %.3f, valid: %d, format: %s", + bValue[ROLL]->getName().c_str(), bValue[ROLL]->value, bValue[ROLL]->valid, bValue[ROLL]->getFormat().c_str()); + lay = calcLeeway(leeK, bValue[ROLL]->value, bValue[STW]->value); if (bValue[HDT]->valid) { // That's either HDT or HDM @@ -406,11 +409,13 @@ public: displaySetPartialWindow(0, 0, width, height); // Set partial update getdisplay().setTextColor(commonData->fgcolor); - for (int i = 0; i < 4; i++) { // Display first 4 values - String name = xdrDelete(bValue[i]->getName()); // Value name - name = name.substring(0, 6); // String length limit for value name - String sValue = formatValue(bValue[i], *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places - String unit = formatValue(bValue[i], *commonData).unit; // Unit of value + static const int bValType[] = { HDT, STW, SET, DFT }; // Sequence of boat data types to show on page + for (int i = 0; i < 4; i++) { + String name = xdrDelete(bValue[bValType[i]]->getName()); // Value name + name = name.substring(0, 4); // String length limit for value name + String sValue = formatValue(bValue[bValType[i]], *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places + String unit = formatValue(bValue[bValType[i]], *commonData).unit; // Unit of value + unit = unit.substring(0, 6); // String length limit for value unit // Show boat data value getdisplay().setFont(&DSEG7Classic_BoldItalic20pt7b); @@ -423,20 +428,29 @@ public: // Show name getdisplay().setFont(&Ubuntu_Bold12pt8b); - getdisplay().setCursor(POS[i].x2, POS[i].y2); - getdisplay().print(name); - - // Show unit - getdisplay().setFont(&Ubuntu_Bold8pt8b); - getdisplay().setCursor(POS[i].x3, POS[i].y3); - - if (holdValues) { - getdisplay().print(unitOld[i]); - } else { - getdisplay().print(unit); + if (i < 2) { // left side values + getdisplay().setCursor(POS[i].x2, POS[i].y2); + getdisplay().print(name); + } else { // right side values + drawTextRalign(POS[i].x2, POS[i].y2, name); } - if (bValue[i]->valid) { + String dUnit; // Unit to show + if (holdValues) { + dUnit = unitOld[i]; + } else { + dUnit = unit; + } + // Show unit + getdisplay().setFont(&Ubuntu_Bold8pt8b); + if (i < 2) { // left side values + getdisplay().setCursor(POS[i].x3, POS[i].y3); + getdisplay().print(dUnit); + } else { // right side values + drawTextRalign(POS[i].x3, POS[i].y3, dUnit); + } + + if (bValue[bValType[i]]->valid) { sValueOld[i] = sValue; // Save the old value unitOld[i] = unit; // Save the old unit } @@ -453,7 +467,9 @@ public: drawCompassRose(0.0); }; getdisplay().drawBitmap((width - WAVE_W) / 2, (height - WAVE_H) / 2, wave_bitmap, WAVE_W, WAVE_H, commonData->fgcolor); - drawRotatedArrow(bValue[DFT]->value, WindUtils::to2PI(bValue[SET]->value - bValue[HDT]->value)); + if (bValue[SET]->valid && bValue[DFT]->valid) { + drawRotatedArrow(bValue[DFT]->value, WindUtils::to2PI(bValue[SET]->value - bValue[HDT]->value)); + } return PAGE_UPDATE; }; @@ -474,8 +490,8 @@ static Page* createPage(CommonData& common) PageDescription registerPageCurrent( "Current", // Page name createPage, // Action - 0, // Number of bus values depends on selection in Web configuration - { "AWA", "HDT", "SET", "DFT", "SOG", "COG", "STW", "HDM", "VAR", "xdrROLL" }, // Bus values we need in the page + 1, // Number of bus values depends on selection in Web configuration -> boat value for ROLL + { "SET", "DFT", "HDT", "STW", "COG", "SOG", "HDM", "VAR", "AWA" }, // Bus values we need in the page true // Show display header on/off ); diff --git a/lib/obp60task/config_obp40.json b/lib/obp60task/config_obp40.json index 656eeec..169a8fc 100644 --- a/lib/obp60task/config_obp40.json +++ b/lib/obp60task/config_obp40.json @@ -1628,6 +1628,7 @@ }, "condition": { "page1type": [ + "Current", "Fluid", "FourValues", "FourValues2", @@ -1959,6 +1960,7 @@ }, "condition": { "page2type": [ + "Current", "Fluid", "FourValues", "FourValues2", @@ -2281,6 +2283,7 @@ }, "condition": { "page3type": [ + "Current", "Fluid", "FourValues", "FourValues2", @@ -2594,6 +2597,7 @@ }, "condition": { "page4type": [ + "Current", "Fluid", "FourValues", "FourValues2", @@ -2898,6 +2902,7 @@ }, "condition": { "page5type": [ + "Current", "Fluid", "FourValues", "FourValues2", @@ -3193,6 +3198,7 @@ }, "condition": { "page6type": [ + "Current", "Fluid", "FourValues", "FourValues2", @@ -3479,6 +3485,7 @@ }, "condition": { "page7type": [ + "Current", "Fluid", "FourValues", "FourValues2", @@ -3756,6 +3763,7 @@ }, "condition": { "page8type": [ + "Current", "Fluid", "FourValues", "FourValues2", @@ -4024,6 +4032,7 @@ }, "condition": { "page9type": [ + "Current", "Fluid", "FourValues", "FourValues2", @@ -4283,6 +4292,7 @@ }, "condition": { "page10type": [ + "Current", "Fluid", "FourValues", "FourValues2", diff --git a/lib/obp60task/config_obp60.json b/lib/obp60task/config_obp60.json index b5a2c27..31c2e9b 100644 --- a/lib/obp60task/config_obp60.json +++ b/lib/obp60task/config_obp60.json @@ -1606,6 +1606,7 @@ }, "condition": { "page1type": [ + "Current", "Fluid", "FourValues", "FourValues2", @@ -1907,6 +1908,7 @@ }, "condition": { "page2type": [ + "Current", "Fluid", "FourValues", "FourValues2", @@ -2200,6 +2202,7 @@ }, "condition": { "page3type": [ + "Current", "Fluid", "FourValues", "FourValues2", @@ -2485,6 +2488,7 @@ }, "condition": { "page4type": [ + "Current", "Fluid", "FourValues", "FourValues2", @@ -2762,6 +2766,7 @@ }, "condition": { "page5type": [ + "Current", "Fluid", "FourValues", "FourValues2", @@ -3031,6 +3036,7 @@ }, "condition": { "page6type": [ + "Current", "Fluid", "FourValues", "FourValues2", @@ -3292,6 +3298,7 @@ }, "condition": { "page7type": [ + "Current", "Fluid", "FourValues", "FourValues2", @@ -3545,6 +3552,7 @@ }, "condition": { "page8type": [ + "Current", "Fluid", "FourValues", "FourValues2", @@ -3790,6 +3798,7 @@ }, "condition": { "page9type": [ + "Current", "Fluid", "FourValues", "FourValues2", @@ -4027,6 +4036,7 @@ }, "condition": { "page10type": [ + "Current", "Fluid", "FourValues", "FourValues2", From 9c9d43539025864e5bac5cd6dbe331edf70f5b6a Mon Sep 17 00:00:00 2001 From: Ulrich Meine Date: Mon, 27 Apr 2026 22:42:27 +0200 Subject: [PATCH 5/5] PageCurrent: Delete old comments --- lib/obp60task/PageCurrent.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/obp60task/PageCurrent.cpp b/lib/obp60task/PageCurrent.cpp index e9db95b..2c34e8a 100644 --- a/lib/obp60task/PageCurrent.cpp +++ b/lib/obp60task/PageCurrent.cpp @@ -43,8 +43,6 @@ static constexpr Points POS[] = { { 10, 270, 10, 220, 10, 190 }, // Position bottom left { 295, 65, 390, 95, 390, 115 }, // Position top right { 295, 270, 390, 220, 390, 190 } // Position bottom right - // { 295, 65, 340, 95, 340, 115 }, // Position top right - // { 295, 270, 340, 220, 340, 190 } // Position bottom right }; // Define wave visual (XBM Format)