From ee5077e0a517e025a88be301cd63695759574368 Mon Sep 17 00:00:00 2001 From: Norbert Walter Date: Sat, 7 Feb 2026 16:59:17 +0000 Subject: [PATCH 1/9] Add PageAutopilot --- lib/obp60task/config_obp40.json | 10 ++++++++++ lib/obp60task/config_obp60.json | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/lib/obp60task/config_obp40.json b/lib/obp60task/config_obp40.json index 54a5409..bb58948 100644 --- a/lib/obp60task/config_obp40.json +++ b/lib/obp60task/config_obp40.json @@ -1517,6 +1517,7 @@ "default": "Voltage", "description": "Type of page for page 1", "list": [ + "Autopilot", "BME280", "Battery", "Battery2", @@ -1847,6 +1848,7 @@ "default": "WindRose", "description": "Type of page for page 2", "list": [ + "Autopilot", "BME280", "Battery", "Battery2", @@ -2168,6 +2170,7 @@ "default": "OneValue", "description": "Type of page for page 3", "list": [ + "Autopilot", "BME280", "Battery", "Battery2", @@ -2480,6 +2483,7 @@ "default": "TwoValues", "description": "Type of page for page 4", "list": [ + "Autopilot", "BME280", "Battery", "Battery2", @@ -2783,6 +2787,7 @@ "default": "ThreeValues", "description": "Type of page for page 5", "list": [ + "Autopilot", "BME280", "Battery", "Battery2", @@ -3077,6 +3082,7 @@ "default": "FourValues", "description": "Type of page for page 6", "list": [ + "Autopilot", "BME280", "Battery", "Battery2", @@ -3362,6 +3368,7 @@ "default": "FourValues2", "description": "Type of page for page 7", "list": [ + "Autopilot", "BME280", "Battery", "Battery2", @@ -3638,6 +3645,7 @@ "default": "Clock", "description": "Type of page for page 8", "list": [ + "Autopilot", "BME280", "Battery", "Battery2", @@ -3905,6 +3913,7 @@ "default": "RollPitch", "description": "Type of page for page 9", "list": [ + "Autopilot", "BME280", "Battery", "Battery2", @@ -4163,6 +4172,7 @@ "default": "Battery2", "description": "Type of page for page 10", "list": [ + "Autopilot", "BME280", "Battery", "Battery2", diff --git a/lib/obp60task/config_obp60.json b/lib/obp60task/config_obp60.json index 8318af7..12d4681 100644 --- a/lib/obp60task/config_obp60.json +++ b/lib/obp60task/config_obp60.json @@ -1494,6 +1494,7 @@ "default": "Voltage", "description": "Type of page for page 1", "list": [ + "Autopilot", "BME280", "Battery", "Battery2", @@ -1794,6 +1795,7 @@ "default": "WindRose", "description": "Type of page for page 2", "list": [ + "Autopilot", "BME280", "Battery", "Battery2", @@ -2086,6 +2088,7 @@ "default": "OneValue", "description": "Type of page for page 3", "list": [ + "Autopilot", "BME280", "Battery", "Battery2", @@ -2370,6 +2373,7 @@ "default": "TwoValues", "description": "Type of page for page 4", "list": [ + "Autopilot", "BME280", "Battery", "Battery2", @@ -2646,6 +2650,7 @@ "default": "ThreeValues", "description": "Type of page for page 5", "list": [ + "Autopilot", "BME280", "Battery", "Battery2", @@ -2914,6 +2919,7 @@ "default": "FourValues", "description": "Type of page for page 6", "list": [ + "Autopilot", "BME280", "Battery", "Battery2", @@ -3174,6 +3180,7 @@ "default": "FourValues2", "description": "Type of page for page 7", "list": [ + "Autopilot", "BME280", "Battery", "Battery2", @@ -3426,6 +3433,7 @@ "default": "Clock", "description": "Type of page for page 8", "list": [ + "Autopilot", "BME280", "Battery", "Battery2", @@ -3670,6 +3678,7 @@ "default": "RollPitch", "description": "Type of page for page 9", "list": [ + "Autopilot", "BME280", "Battery", "Battery2", @@ -3906,6 +3915,7 @@ "default": "Battery2", "description": "Type of page for page 10", "list": [ + "Autopilot", "BME280", "Battery", "Battery2", From 99404991a3a60d9bbc3a2ff841a47a15ad8fbfac Mon Sep 17 00:00:00 2001 From: Norbert Walter Date: Sun, 8 Feb 2026 12:40:20 +0000 Subject: [PATCH 2/9] Add rudder bargraf --- lib/obp60task/OBP60Extensions.cpp | 63 ++++++++++++++++++++++++++++++- lib/obp60task/OBP60Extensions.h | 3 ++ 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/lib/obp60task/OBP60Extensions.cpp b/lib/obp60task/OBP60Extensions.cpp index 76c6334..57471d4 100644 --- a/lib/obp60task/OBP60Extensions.cpp +++ b/lib/obp60task/OBP60Extensions.cpp @@ -923,7 +923,7 @@ void solarGraphic(uint x, uint y, int pcolor, int bcolor){ } -// Generator graphic with fill level +// Generator graphic void generatorGraphic(uint x, uint y, int pcolor, int bcolor){ // Show battery int xb = x; // X position @@ -940,6 +940,67 @@ void generatorGraphic(uint x, uint y, int pcolor, int bcolor){ getdisplay().print("G"); } +// Display rudder position as horizontal bargraph +/-30 degrees +void displayRudderPosition(int rudderPosition, uint16_t cx, uint16_t cy, uint16_t fg, uint16_t bg){ + const int w = 300; + const int h = 20; + const int t = 3; // Line thickness + const int halfw = w/2; + const int halfh = h/2; + // Calculate top-left of bar (cx,cy are center of 0°) + int left = int(cx) - halfw; + int top = int(cy) - halfh; + + // Pixels per degree for +/-30° -> 60° span + const float pxPerDeg = float(w) / 60.0f; // =5.0 + + // Draw outer border (thickness t) + for (int i = 0; i < t; i++) { + getdisplay().drawRect(left + i, top + i, w - 2 * i, h - 2 * i, fg); + } + + // Fill inner area with background + getdisplay().fillRect(left + t, top + t, w - 2 * t, h - 2 * t, bg); + + // Clamp rudder position to -30..30 + if (rudderPosition > 30) rudderPosition = 30; + if (rudderPosition < -30) rudderPosition = -30; + + // Compute fill width in pixels + int fillPx = int(round(rudderPosition * pxPerDeg)); // positive -> right + + // Fill area from center to position (if non-zero) + int centerx = cx; + int innerTop = top + t; + int innerH = h - 2 * t; + if (fillPx > 0) { + // Right side + getdisplay().fillRect(centerx, innerTop, fillPx, innerH, fg); + } else if (fillPx < 0) { + // Left side + getdisplay().fillRect(centerx + fillPx, innerTop, -fillPx, innerH, fg); + } + + // Draw tick marks every 5° and labels outside the bar + getdisplay().setTextColor(fg); + getdisplay().setFont(&Ubuntu_Bold8pt8b); + for (int angle = -30; angle <= 30; angle += 5) { + int xpos = int(round(centerx + angle * pxPerDeg)); + // Vertical tick inside bar + getdisplay().drawLine(xpos, top, xpos, top + h, fg); + // Label outside: below the bar + String lbl = String(angle); + lbl += "\u00B0"; // Degree symbol + int16_t bx, by; + uint16_t bw, bh; + getdisplay().getTextBounds(lbl, 0, 0, &bx, &by, &bw, &bh); + int16_t tx = xpos - bw/2; + int16_t ty = top + h + bh + 2; // A little spacing + getdisplay().setCursor(tx, ty); + getdisplay().print(lbl); + } +} + // Function to handle HTTP image request // http://192.168.15.1/api/user/OBP60Task/screenshot void doImageRequest(GwApi *api, int *pageno, const PageStruct pages[MAX_PAGE_NUMBER], AsyncWebServerRequest *request) { diff --git a/lib/obp60task/OBP60Extensions.h b/lib/obp60task/OBP60Extensions.h index 604c356..de6db20 100644 --- a/lib/obp60task/OBP60Extensions.h +++ b/lib/obp60task/OBP60Extensions.h @@ -128,6 +128,9 @@ void solarGraphic(uint x, uint y, int pcolor, int bcolor); // S void generatorGraphic(uint x, uint y, int pcolor, int bcolor); // Generator graphic void startLedTask(GwApi *api); +// Display rudder position as horizontal bargraph +/-30 degrees +void displayRudderPosition(int rudderPosition, uint16_t x, uint16_t y, uint16_t fg, uint16_t bg); + void doImageRequest(GwApi *api, int *pageno, const PageStruct pages[MAX_PAGE_NUMBER], AsyncWebServerRequest *request); // Icons From 4468c0555b26cf50a33cef2ec01ff1f3d3f5822e Mon Sep 17 00:00:00 2001 From: norbert-walter Date: Sun, 8 Feb 2026 14:09:10 +0100 Subject: [PATCH 3/9] Implement rudder bargraf in PageAutopilot --- lib/obp60task/OBP60Extensions.cpp | 7 +++---- lib/obp60task/PageAutopilot.cpp | 26 +++++++++++++++----------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/lib/obp60task/OBP60Extensions.cpp b/lib/obp60task/OBP60Extensions.cpp index 57471d4..5b3d278 100644 --- a/lib/obp60task/OBP60Extensions.cpp +++ b/lib/obp60task/OBP60Extensions.cpp @@ -942,7 +942,7 @@ void generatorGraphic(uint x, uint y, int pcolor, int bcolor){ // Display rudder position as horizontal bargraph +/-30 degrees void displayRudderPosition(int rudderPosition, uint16_t cx, uint16_t cy, uint16_t fg, uint16_t bg){ - const int w = 300; + const int w = 360; const int h = 20; const int t = 3; // Line thickness const int halfw = w/2; @@ -987,15 +987,14 @@ void displayRudderPosition(int rudderPosition, uint16_t cx, uint16_t cy, uint16_ for (int angle = -30; angle <= 30; angle += 5) { int xpos = int(round(centerx + angle * pxPerDeg)); // Vertical tick inside bar - getdisplay().drawLine(xpos, top, xpos, top + h, fg); + getdisplay().drawLine(xpos, top, xpos, top + h + 2, fg); // Label outside: below the bar String lbl = String(angle); - lbl += "\u00B0"; // Degree symbol int16_t bx, by; uint16_t bw, bh; getdisplay().getTextBounds(lbl, 0, 0, &bx, &by, &bw, &bh); int16_t tx = xpos - bw/2; - int16_t ty = top + h + bh + 2; // A little spacing + int16_t ty = top + h + bh + 5; // A little spacing getdisplay().setCursor(tx, ty); getdisplay().print(lbl); } diff --git a/lib/obp60task/PageAutopilot.cpp b/lib/obp60task/PageAutopilot.cpp index 6cbf299..f5161f5 100644 --- a/lib/obp60task/PageAutopilot.cpp +++ b/lib/obp60task/PageAutopilot.cpp @@ -21,8 +21,9 @@ const int ShowDTW = 7; const int ShowBTW = 8; const int Compass_X0 = 200; // X center point of compass band -const int Compass_Y0 = 220; // Y position of compass lines -const int Compass_LineLength = 22; // Length of compass lines +const int Compass_Y0 = 90; // Y position of compass lines +//const int Compass_LineLength = 22; // Length of compass lines +const int Compass_LineLength = 15; // Length of compass lines const float Compass_LineDelta = 8.0;// Compass band: 1deg = 5 Pixels, 10deg = 50 Pixels class PageAutopilot : public Page @@ -38,8 +39,11 @@ class PageAutopilot : public Page virtual void setupKeys(){ Page::setupKeys(); - commonData->keydata[0].label = "CMP"; - commonData->keydata[1].label = "SRC"; + commonData->keydata[0].label = "-10"; + commonData->keydata[1].label = "-1"; + commonData->keydata[2].label = "Auto"; + commonData->keydata[3].label = "+1"; + commonData->keydata[4].label = "+10"; } virtual int handleKey(int key){ @@ -106,15 +110,13 @@ class PageAutopilot : public Page setBlinkingLED(false); setFlashLED(false); } - - if (bvalue == NULL) return PAGE_OK; // WTF why this statement? //*********************************************************** // Set display in partial refresh mode getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update getdisplay().setTextColor(commonData->fgcolor); - +/* // Horizontal line 2 pix top & bottom // Print data on top half getdisplay().fillRect(0, 130, 400, 2, commonData->fgcolor); @@ -138,7 +140,7 @@ class PageAutopilot : public Page OldDataText[WhichDataDisplay] = DataText[WhichDataDisplay]; // Save the old value OldDataUnits[WhichDataDisplay] = DataUnits[WhichDataDisplay]; // Save the old unit } - +*/ // Now draw compass band // Get the data double TheAngle = DataValue[WhichDataCompass]; @@ -152,13 +154,13 @@ class PageAutopilot : public Page buffer[0]=0; getdisplay().setFont(&Ubuntu_Bold16pt8b); - getdisplay().setCursor(10, Compass_Y0-60); + getdisplay().setCursor(10, Compass_Y0-40); getdisplay().print(DataName[WhichDataCompass]); // Page name - // Draw compass base line and pointer getdisplay().fillRect(0, Compass_Y0, 400, 3, commonData->fgcolor); - getdisplay().fillTriangle(Compass_X0,Compass_Y0-40,Compass_X0-10,Compass_Y0-80,Compass_X0+10,Compass_Y0-80,commonData->fgcolor); + //getdisplay().fillTriangle(Compass_X0,Compass_Y0-40,Compass_X0-10,Compass_Y0-80,Compass_X0+10,Compass_Y0-80,commonData->fgcolor); + getdisplay().fillTriangle(Compass_X0,Compass_Y0-30,Compass_X0-10,Compass_Y0-60,Compass_X0+10,Compass_Y0-60,commonData->fgcolor); // Draw trendlines for ( int i = 1; i < abs(TheTrend) / 2; i++){ int x1; @@ -238,6 +240,8 @@ class PageAutopilot : public Page // if ( x_test > 390) // x_test = 320; + displayRudderPosition(12, 200, 160, commonData->fgcolor, commonData->bgcolor); + return PAGE_UPDATE; }; From 71512e726285bd49d076511b7244c24b25284226 Mon Sep 17 00:00:00 2001 From: Norbert Walter Date: Sun, 8 Feb 2026 13:18:39 +0000 Subject: [PATCH 4/9] Actualize PageAutopilot --- lib/obp60task/OBP60Extensions.cpp | 20 ++++++++++++-------- lib/obp60task/OBP60Extensions.h | 5 +++-- lib/obp60task/PageAutopilot.cpp | 2 +- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/lib/obp60task/OBP60Extensions.cpp b/lib/obp60task/OBP60Extensions.cpp index 5b3d278..3c4d9a2 100644 --- a/lib/obp60task/OBP60Extensions.cpp +++ b/lib/obp60task/OBP60Extensions.cpp @@ -940,8 +940,8 @@ void generatorGraphic(uint x, uint y, int pcolor, int bcolor){ getdisplay().print("G"); } -// Display rudder position as horizontal bargraph +/-30 degrees -void displayRudderPosition(int rudderPosition, uint16_t cx, uint16_t cy, uint16_t fg, uint16_t bg){ +// Display rudder position as horizontal bargraph with configurable +/- range (degrees) +void displayRudderPosition(int rudderPosition, uint8_t rangeDeg, uint16_t cx, uint16_t cy, uint16_t fg, uint16_t bg){ const int w = 360; const int h = 20; const int t = 3; // Line thickness @@ -951,8 +951,12 @@ void displayRudderPosition(int rudderPosition, uint16_t cx, uint16_t cy, uint16_ int left = int(cx) - halfw; int top = int(cy) - halfh; - // Pixels per degree for +/-30° -> 60° span - const float pxPerDeg = float(w) / 60.0f; // =5.0 + // clamp provided range to allowed bounds [10,45] + if (rangeDeg < 10) rangeDeg = 10; + if (rangeDeg > 45) rangeDeg = 45; + + // Pixels per degree for +/-rangeDeg -> total span = 2*rangeDeg + const float pxPerDeg = float(w) / (2.0f * float(rangeDeg)); // Draw outer border (thickness t) for (int i = 0; i < t; i++) { @@ -962,9 +966,9 @@ void displayRudderPosition(int rudderPosition, uint16_t cx, uint16_t cy, uint16_ // Fill inner area with background getdisplay().fillRect(left + t, top + t, w - 2 * t, h - 2 * t, bg); - // Clamp rudder position to -30..30 - if (rudderPosition > 30) rudderPosition = 30; - if (rudderPosition < -30) rudderPosition = -30; + // Clamp rudder position to -rangeDeg..rangeDeg + if (rudderPosition > (int)rangeDeg) rudderPosition = (int)rangeDeg; + if (rudderPosition < -((int)rangeDeg)) rudderPosition = -((int)rangeDeg); // Compute fill width in pixels int fillPx = int(round(rudderPosition * pxPerDeg)); // positive -> right @@ -984,7 +988,7 @@ void displayRudderPosition(int rudderPosition, uint16_t cx, uint16_t cy, uint16_ // Draw tick marks every 5° and labels outside the bar getdisplay().setTextColor(fg); getdisplay().setFont(&Ubuntu_Bold8pt8b); - for (int angle = -30; angle <= 30; angle += 5) { + for (int angle = -((int)rangeDeg); angle <= (int)rangeDeg; angle += 5) { int xpos = int(round(centerx + angle * pxPerDeg)); // Vertical tick inside bar getdisplay().drawLine(xpos, top, xpos, top + h + 2, fg); diff --git a/lib/obp60task/OBP60Extensions.h b/lib/obp60task/OBP60Extensions.h index de6db20..6d9e4a9 100644 --- a/lib/obp60task/OBP60Extensions.h +++ b/lib/obp60task/OBP60Extensions.h @@ -128,8 +128,9 @@ void solarGraphic(uint x, uint y, int pcolor, int bcolor); // S void generatorGraphic(uint x, uint y, int pcolor, int bcolor); // Generator graphic void startLedTask(GwApi *api); -// Display rudder position as horizontal bargraph +/-30 degrees -void displayRudderPosition(int rudderPosition, uint16_t x, uint16_t y, uint16_t fg, uint16_t bg); +// Display rudder position as horizontal bargraph with configurable +/- range (degrees) +// 'rangeDeg' is unsigned and will be clamped to [10,45] +void displayRudderPosition(int rudderPosition, uint8_t rangeDeg, uint16_t x, uint16_t y, uint16_t fg, uint16_t bg); void doImageRequest(GwApi *api, int *pageno, const PageStruct pages[MAX_PAGE_NUMBER], AsyncWebServerRequest *request); diff --git a/lib/obp60task/PageAutopilot.cpp b/lib/obp60task/PageAutopilot.cpp index f5161f5..8a698ce 100644 --- a/lib/obp60task/PageAutopilot.cpp +++ b/lib/obp60task/PageAutopilot.cpp @@ -240,7 +240,7 @@ class PageAutopilot : public Page // if ( x_test > 390) // x_test = 320; - displayRudderPosition(12, 200, 160, commonData->fgcolor, commonData->bgcolor); + displayRudderPosition(12, 20, 200, 160, commonData->fgcolor, commonData->bgcolor); return PAGE_UPDATE; }; From 7f747e9b35756f6dbde1b808fb6e4aa1299060cb Mon Sep 17 00:00:00 2001 From: norbert-walter Date: Sun, 8 Feb 2026 14:49:40 +0100 Subject: [PATCH 5/9] Add data connections for PageAutopilot --- lib/obp60task/OBP60Extensions.cpp | 4 ++++ lib/obp60task/OBP60Extensions.h | 2 +- lib/obp60task/PageAutopilot.cpp | 15 +++++++++------ 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/lib/obp60task/OBP60Extensions.cpp b/lib/obp60task/OBP60Extensions.cpp index 3c4d9a2..3b3ee88 100644 --- a/lib/obp60task/OBP60Extensions.cpp +++ b/lib/obp60task/OBP60Extensions.cpp @@ -966,6 +966,9 @@ void displayRudderPosition(int rudderPosition, uint8_t rangeDeg, uint16_t cx, ui // Fill inner area with background getdisplay().fillRect(left + t, top + t, w - 2 * t, h - 2 * t, bg); + // Draw center line + getdisplay().drawRect(cx - 1, top + 1, 3 , h - 2, fg); + // Clamp rudder position to -rangeDeg..rangeDeg if (rudderPosition > (int)rangeDeg) rudderPosition = (int)rangeDeg; if (rudderPosition < -((int)rangeDeg)) rudderPosition = -((int)rangeDeg); @@ -985,6 +988,7 @@ void displayRudderPosition(int rudderPosition, uint8_t rangeDeg, uint16_t cx, ui getdisplay().fillRect(centerx + fillPx, innerTop, -fillPx, innerH, fg); } + // Draw tick marks every 5° and labels outside the bar getdisplay().setTextColor(fg); getdisplay().setFont(&Ubuntu_Bold8pt8b); diff --git a/lib/obp60task/OBP60Extensions.h b/lib/obp60task/OBP60Extensions.h index 6d9e4a9..41e3717 100644 --- a/lib/obp60task/OBP60Extensions.h +++ b/lib/obp60task/OBP60Extensions.h @@ -130,7 +130,7 @@ void startLedTask(GwApi *api); // Display rudder position as horizontal bargraph with configurable +/- range (degrees) // 'rangeDeg' is unsigned and will be clamped to [10,45] -void displayRudderPosition(int rudderPosition, uint8_t rangeDeg, uint16_t x, uint16_t y, uint16_t fg, uint16_t bg); +void displayRudderPosition(int rudderPosition, uint8_t rangeDeg, uint16_t cx, uint16_t cy, uint16_t fg, uint16_t bg); void doImageRequest(GwApi *api, int *pageno, const PageStruct pages[MAX_PAGE_NUMBER], AsyncWebServerRequest *request); diff --git a/lib/obp60task/PageAutopilot.cpp b/lib/obp60task/PageAutopilot.cpp index 8a698ce..09d14e2 100644 --- a/lib/obp60task/PageAutopilot.cpp +++ b/lib/obp60task/PageAutopilot.cpp @@ -5,8 +5,9 @@ // These constants have to match the declaration below in : // PageDescription registerPageAutopilot( -// {"HDM","HDT", "COG", "STW", "SOG", "DBT","XTE", "DTW", "BTW"}, // Bus values we need in the page -const int HowManyValues = 9; +// {"HDM","HDT", "COG", "STW", "SOG", "DBT","XTE", "DTW", "BTW", "RPOS", "ROT"}, // Bus values we need in the page + +const int HowManyValues = 11; const int AverageValues = 4; @@ -19,6 +20,8 @@ const int ShowDBT = 5; const int ShowXTE = 6; const int ShowDTW = 7; const int ShowBTW = 8; +const int ShowRPOS = 9; +const int ShowROT = 10; const int Compass_X0 = 200; // X center point of compass band const int Compass_Y0 = 90; // Y position of compass lines @@ -73,8 +76,8 @@ class PageAutopilot : public Page GwLog *logger = commonData->logger; // Old values for hold function - static String OldDataText[HowManyValues] = {"", "", "","", "", "","", "", ""}; - static String OldDataUnits[HowManyValues] = {"", "", "","", "", "","", "", ""}; + static String OldDataText[HowManyValues] = {"", "", "", "", "", "","", "", "", "", ""}; + static String OldDataUnits[HowManyValues] = {"", "", "", "", "", "","", "", "", "", ""}; // Get config data String lengthformat = config->getString(config->lengthFormat); @@ -240,7 +243,7 @@ class PageAutopilot : public Page // if ( x_test > 390) // x_test = 320; - displayRudderPosition(12, 20, 200, 160, commonData->fgcolor, commonData->bgcolor); + displayRudderPosition(DataValue[ShowRPOS], 20, 200, 160, commonData->fgcolor, commonData->bgcolor); return PAGE_UPDATE; }; @@ -260,7 +263,7 @@ PageDescription registerPageAutopilot( "Autopilot", // Page name createPage, // Action 0, // Number of bus values depends on selection in Web configuration - {"HDM","HDT", "COG", "STW", "SOG", "DBT","XTE", "DTW", "BTW"}, // Bus values we need in the page + {"HDM","HDT", "COG", "STW", "SOG", "DBT","XTE", "DTW", "BTW", "RPOS", "ROT"}, // Bus values we need in the page true // Show display header on/off ); From 6c7997e3694711beb6207972509d4d0c15d7b818 Mon Sep 17 00:00:00 2001 From: norbert-walter Date: Sun, 8 Feb 2026 22:00:22 +0100 Subject: [PATCH 6/9] Fix iRTC time for N2K bus --- lib/gwwifi/GwWifi.cpp | 15 +++++++++- lib/obp60task/OBPSensorTask.cpp | 53 +++++++++++++++++---------------- lib/obp60task/PageAutopilot.cpp | 2 +- lib/obp60task/config_obp40.json | 4 +-- lib/obp60task/config_obp60.json | 3 +- 5 files changed, 46 insertions(+), 31 deletions(-) diff --git a/lib/gwwifi/GwWifi.cpp b/lib/gwwifi/GwWifi.cpp index c81acec..00ae59c 100644 --- a/lib/gwwifi/GwWifi.cpp +++ b/lib/gwwifi/GwWifi.cpp @@ -104,8 +104,21 @@ void GwWifi::loop(){ if (lastConnectStart > now || (lastConnectStart + RETRY_MILLIS) < now) { LOG_DEBUG(GwLog::LOG,"wifiClient: retry connect to %s", wifiSSID->asCString()); - WiFi.disconnect(); + +//+++++++++ Old part +++++++++++++++++++++ +// WiFi.disconnect(); +// connectInternal(); +//++++++++++++++++++++++++++++++++++++++++ + +//+++++++++ New part +++++++++++++++++++++ + WiFi.disconnect(true); + delay(300); + esp_wifi_stop(); + delay(100); + esp_wifi_start(); connectInternal(); +//++++++++++++++++++++++++++++++++++++++++ + } } else{ diff --git a/lib/obp60task/OBPSensorTask.cpp b/lib/obp60task/OBPSensorTask.cpp index 25d861b..1424907 100644 --- a/lib/obp60task/OBPSensorTask.cpp +++ b/lib/obp60task/OBPSensorTask.cpp @@ -371,7 +371,7 @@ void sensorTask(void *param){ GwApi::BoatValue *hdop=new GwApi::BoatValue(GwBoatData::_HDOP); GwApi::BoatValue *valueList[]={gpsdays, gpsseconds, hdop}; - // Internal RTC with NTP init + // Internal iRTC with NTP init ESP32Time rtc(0); if (api->getConfig()->getString(api->getConfig()->timeSource) == "iRTC") { GwApi::Status status; @@ -432,17 +432,17 @@ void sensorTask(void *param){ iRTC RTC GPS N2K 0 0 0 (1) - 0 0 (1) (X) - 0 (1) 0 (X) - 0 1 <-(1) (X) - (1) 0 0 (X) - 1 0 (1) (X) - 1 ->(1) 0 (X) - 1 1 <-(1) (X) + 0 0 (1) X + 0 (1) 0 X + 0 1 <-(1) X + (1) 0 0 X + 1 0 (1) X + 1 ->(1) 0 X + 1 1 <-(1) X */ - // If RTC DS1388 ready, then copy iRTC and GPS data to RTC all 1min + // If RTC DS1388 ready, then copy iRTC and GPS data to RTC all 1 min if(millis() > starttime11 + 1*60*1000){ starttime11 = millis(); // Set RTC chip via iRTC (NTP) @@ -475,7 +475,7 @@ void sensorTask(void *param){ // Adjust RTC time as unix time value ds1388.adjust(adjusttime); } - } + } } // Set RTC chip via N2K or 183 in case the internal GPS is off (only one time) @@ -524,7 +524,7 @@ void sensorTask(void *param){ // N2K sysTime is double in n2klib double sysTime = (dt.hour() * 3600) + (dt.minute() * 60) + dt.second(); if(!isnan(daysAt1970) && !isnan(sysTime)){ - //api->getLogger()->logDebug(GwLog::LOG,"RTC time: %04d/%02d/%02d %02d:%02d:%02d",sensors.rtcTime.tm_year+1900,sensors.rtcTime.tm_mon, sensors.rtcTime.tm_mday, sensors.rtcTime.tm_hour, sensors.rtcTime.tm_min, sensors.rtcTime.tm_sec); + //api->getLogger()->logDebug(GwLog::LOG,"RTC time: %04d/%02d/%02d %02d:%02d:%02d",sensors.rtcTime.tm_year+1900,sensors.rtcTime.tm_mon+1, sensors.rtcTime.tm_mday, sensors.rtcTime.tm_hour, sensors.rtcTime.tm_min, sensors.rtcTime.tm_sec); //api->getLogger()->logDebug(GwLog::LOG,"Send PGN126992: %10d %10d",daysAt1970, (uint16_t)sysTime); SetN2kPGN126992(N2kMsg,0,daysAt1970,sysTime,N2ktimes_LocalCrystalClock); api->sendN2kMessage(N2kMsg); @@ -533,25 +533,26 @@ void sensorTask(void *param){ } // Send date and time from software RTC (iRTC) if (iRTC_ready == true && RTC_ready == false && GPS_ready == false) { - // Use internal RTC feature - sensors.rtcTime = rtc.getTimeStruct(); // Save software RTC values in SensorData - // TODO implement daysAt1970 and sysTime as methods of DateTime + sensors.rtcTime = rtc.getTimeStruct(); + const short daysOfYear[12] = {0,31,59,90,120,151,181,212,243,273,304,334}; - uint16_t switchYear = ((sensors.rtcTime.tm_year-1)-1968)/4 - ((sensors.rtcTime.tm_year-1)-1900)/100 + ((sensors.rtcTime.tm_year-1)-1600)/400; - long daysAt1970 = (sensors.rtcTime.tm_year-1970)*365 + switchYear + daysOfYear[sensors.rtcTime.tm_mon-1] + sensors.rtcTime.tm_mday-1; - // If switch year then add one day - if ((sensors.rtcTime.tm_mon > 2) && (sensors.rtcTime.tm_year % 4 == 0 && (sensors.rtcTime.tm_year % 100 != 0 || sensors.rtcTime.tm_year % 400 == 0))) { + int year = sensors.rtcTime.tm_year + 1900; + int month = sensors.rtcTime.tm_mon; + int day = sensors.rtcTime.tm_mday; + uint16_t switchYear = ((year - 1) - 1968) / 4 - ((year - 1) - 1900) / 100 + ((year - 1) - 1600) / 400; + long daysAt1970 = (year - 1970) * 365L + switchYear + daysOfYear[month] + day - 1; + + // Leap day add if date is after Feb (i.e. month >= March) + if (month >= 2 && (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0))) { daysAt1970 += 1; } - // N2K sysTime is double in n2klib - double sysTime = (sensors.rtcTime.tm_hour * 3600) + (sensors.rtcTime.tm_min * 60) + sensors.rtcTime.tm_sec; - if(!isnan(daysAt1970) && !isnan(sysTime)){ - //api->getLogger()->logDebug(GwLog::LOG,"RTC time: %04d/%02d/%02d %02d:%02d:%02d",sensors.rtcTime.tm_year+1900,sensors.rtcTime.tm_mon, sensors.rtcTime.tm_mday, sensors.rtcTime.tm_hour, sensors.rtcTime.tm_min, sensors.rtcTime.tm_sec); - //api->getLogger()->logDebug(GwLog::LOG,"Send PGN126992: %10d %10d",daysAt1970, (uint16_t)sysTime); - SetN2kPGN126992(N2kMsg,0,daysAt1970,sysTime,N2ktimes_LocalCrystalClock); - api->sendN2kMessage(N2kMsg); - } + double sysTime = sensors.rtcTime.tm_hour * 3600.0 + sensors.rtcTime.tm_min * 60.0 + sensors.rtcTime.tm_sec; + //api->getLogger()->logDebug(GwLog::LOG, "iRTC time: %04d/%02d/%02d %02d:%02d:%02d", year, month + 1, day, sensors.rtcTime.tm_hour, sensors.rtcTime.tm_min, sensors.rtcTime.tm_sec); + //api->getLogger()->logDebug(GwLog::LOG,"Send PGN126992: %10d %10d",daysAt1970, (uint16_t)sysTime); + SetN2kPGN126992(N2kMsg, 0, daysAt1970, sysTime, N2ktimes_LocalCrystalClock); + api->sendN2kMessage(N2kMsg); } + } // Send 1Wire data for all temperature sensors to N2K all 2s diff --git a/lib/obp60task/PageAutopilot.cpp b/lib/obp60task/PageAutopilot.cpp index 09d14e2..ecfc62e 100644 --- a/lib/obp60task/PageAutopilot.cpp +++ b/lib/obp60task/PageAutopilot.cpp @@ -243,7 +243,7 @@ class PageAutopilot : public Page // if ( x_test > 390) // x_test = 320; - displayRudderPosition(DataValue[ShowRPOS], 20, 200, 160, commonData->fgcolor, commonData->bgcolor); + displayRudderPosition(DataValue[ShowSOG], 20, 200, 160, commonData->fgcolor, commonData->bgcolor); return PAGE_UPDATE; }; diff --git a/lib/obp60task/config_obp40.json b/lib/obp60task/config_obp40.json index bb58948..0617c41 100644 --- a/lib/obp60task/config_obp40.json +++ b/lib/obp60task/config_obp40.json @@ -1245,8 +1245,8 @@ "name": "timeSource", "label": "Status Time Source", "type": "list", - "default": "GPS", - "description": "Data source for date and time display in status line [RTC|iRTC|GPS]", + "default": "iRTC", + "description": "Data source for date and time display in status line [iRTC|RTC|GPS]", "list": [ {"l":"Internal real time clock (iRTC)","v":"iRTC"}, {"l":"External real time clock (RTC)","v":"RTC"}, diff --git a/lib/obp60task/config_obp60.json b/lib/obp60task/config_obp60.json index 12d4681..305688f 100644 --- a/lib/obp60task/config_obp60.json +++ b/lib/obp60task/config_obp60.json @@ -1235,8 +1235,9 @@ "label": "Status Time Source", "type": "list", "default": "GPS", - "description": "Data source for date and time display in status line [RTC|GPS]", + "description": "Data source for date and time display in status line [iRTC|RTC|GPS]", "list": [ + {"l":"Internal real time clock (iRTC)","v":"iRTC"}, {"l":"Real time clock (RTC)","v":"RTC"}, {"l":"Time via bus (GPS)","v":"GPS"} ], From 02b2c888ee6cc787af8570517573befd867efe78 Mon Sep 17 00:00:00 2001 From: norbert-walter Date: Sun, 15 Feb 2026 19:16:24 +0100 Subject: [PATCH 7/9] Fix for GwWifi --- lib/gwwifi/GWWifi.h | 2 +- lib/obp60task/NetworkClient.cpp | 16 +- lib/obp60task/NetworkClient.h | 2 +- lib/obp60task/PageNavigation.cpp | 39 +- lib/obp60task/PageNavigation_old.cpp | 543 +++++++++++++++++++++++++++ 5 files changed, 559 insertions(+), 43 deletions(-) create mode 100644 lib/obp60task/PageNavigation_old.cpp diff --git a/lib/gwwifi/GWWifi.h b/lib/gwwifi/GWWifi.h index cb5c980..c9fe262 100644 --- a/lib/gwwifi/GWWifi.h +++ b/lib/gwwifi/GWWifi.h @@ -34,6 +34,6 @@ class GwWifi{ bool connectClientAsync(); // Non-blocking version for other tasks String apIP(); bool isApActive(){return apActive;} - bool isClientActive(){return wifiClient->asBoolean();}} + bool isClientActive(){return wifiClient->asBoolean();} }; #endif \ No newline at end of file diff --git a/lib/obp60task/NetworkClient.cpp b/lib/obp60task/NetworkClient.cpp index 66b973d..c6a7962 100644 --- a/lib/obp60task/NetworkClient.cpp +++ b/lib/obp60task/NetworkClient.cpp @@ -1,4 +1,7 @@ #include "NetworkClient.h" +#include "GWWifi.h" // WiFi management (thread-safe) + +extern GwWifi gwWifi; // Extern declaration of global WiFi instance extern "C" { #include "puff.h" @@ -51,8 +54,13 @@ bool NetworkClient::httpGetGzip(const String& url, uint8_t*& outData, size_t& ou const size_t capacity = READLIMIT; // Read limit for data (can be adjusted in NetworkClient.h) uint8_t* buffer = (uint8_t*)malloc(capacity); + if (!gwWifi.clientConnected()) { + if (DEBUGING) {Serial.println("No WiFi connection");} + return false; + } + if (!buffer) { - if (DEBUG) {Serial.println("Malloc failed (buffer");} + if (DEBUGING) {Serial.println("Malloc failed buffer");} return false; } @@ -106,7 +114,7 @@ bool NetworkClient::httpGetGzip(const String& url, uint8_t*& outData, size_t& ou len += read; lastData = millis(); - if (DEBUG) {Serial.printf("Read chunk: %d (total: %d)\n", read, (int)len);} + if (DEBUGING) {Serial.printf("Read chunk: %d (total: %d)\n", read, (int)len);} if (len < 20) continue; // Not enough data for header @@ -122,7 +130,7 @@ bool NetworkClient::httpGetGzip(const String& url, uint8_t*& outData, size_t& ou int res = puff(test, &testLen, buffer + headerOffset, &srcLen); if (res == 0) { - if (DEBUG) {Serial.printf("Decompress OK! Size: %lu bytes\n", testLen);} + if (DEBUGING) {Serial.printf("Decompress OK! Size: %lu bytes\n", testLen);} outData = test; outLen = testLen; complete = true; @@ -167,7 +175,7 @@ bool NetworkClient::fetchAndDecompressJson(const String& url) { return false; } - if (DEBUG) {Serial.println("JSON OK!");} + if (DEBUGING) {Serial.println("JSON OK!");} _valid = true; return true; } diff --git a/lib/obp60task/NetworkClient.h b/lib/obp60task/NetworkClient.h index 84d7a87..03e7f83 100644 --- a/lib/obp60task/NetworkClient.h +++ b/lib/obp60task/NetworkClient.h @@ -3,7 +3,7 @@ #include #include -#define DEBUG false // Debug flag for NetworkClient for more live information +#define DEBUGING false // Debug flag for NetworkClient for more live information #define READLIMIT 200000 // HTTP read limit in byte for gzip content (can be adjusted) #define CONNECTIONTIMEOUT 3000 // Timeout in ms for HTTP connection #define TCPREADTIMEOUT 2000 // Timeout in ms for read HTTP client stack diff --git a/lib/obp60task/PageNavigation.cpp b/lib/obp60task/PageNavigation.cpp index 550d70c..bd4adf1 100644 --- a/lib/obp60task/PageNavigation.cpp +++ b/lib/obp60task/PageNavigation.cpp @@ -4,13 +4,9 @@ #include "OBP60Extensions.h" #include "NetworkClient.h" // Network connection #include "ImageDecoder.h" // Image decoder for navigation map -#include "GWWifi.h" // WiFi management (thread-safe) #include "Logo_OBP_400x300_sw.h" -// Extern declaration of global WiFi instance -extern GwWifi gwWifi; - // Defines for reading of navigation map #define JSON_BUFFER 30000 // Max buffer size for JSON content (30 kB picture + values) NetworkClient net(JSON_BUFFER); // Define network client @@ -29,7 +25,6 @@ bool showValues = false; // Show values HDT, SOG, DBT in navigation map int imageBackupHeight = 0; size_t imageBackupSize = 0; bool hasImageBackup = false; - static bool wifiConnectRequested; // Track if WiFi connection was requested public: PageNavigation(CommonData &common){ @@ -60,15 +55,12 @@ bool showValues = false; // Show values HDT, SOG, DBT in navigation map } return 0; // Commit the key } - // Code for zoom + + // Code for zoom - if(key == 2){ zoom ++; // Zoom + if(zoom >17){ zoom = 17; } - // Optional: Versuche WiFi-Verbindung nach Zoom-Änderung - // Dies ermöglicht eine neue Kartendarstellung - gwWifi.connectClientAsync(); return 0; // Commit the key } if(key == 5){ @@ -103,13 +95,6 @@ bool showValues = false; // Show values HDT, SOG, DBT in navigation map zoom = zoomLevel; // Over write zoom level with setup value showValues = showValuesMap; // Over write showValues with setup value firstRun = false; // Restet variable - - // Versuche beim ersten Laden eine WiFi-Verbindung herzustellen (non-blocking) - // Dies ist thread-safe und blockiert das UI nicht - if (!gwWifi.clientConnected()) { - LOG_DEBUG(GwLog::LOG, "PageNavigation: Initiating WiFi connection for map download"); - gwWifi.connectClientAsync(); - } } // Local variables @@ -359,16 +344,6 @@ bool showValues = false; // Show values HDT, SOG, DBT in navigation map // Load navigation map //*********************************************************** - // Prüfe WiFi-Verbindung (Thread-Safe) - // Diese Methode ist synchronisiert und blockiert maximal 1 Sekunde - bool wifiConnected = gwWifi.clientConnected(); - - if (!wifiConnected && !wifiConnectRequested) { - LOG_DEBUG(GwLog::LOG, "PageNavigation: WiFi not connected, attempting async connect"); - gwWifi.connectClientAsync(); - wifiConnectRequested = true; - } - // URL to OBP Maps Converter // For more details see: https://github.com/norbert-walter/maps-converter String url = String("http://") + server + ":" + port + // OBP Server @@ -400,8 +375,7 @@ bool showValues = false; // Show values HDT, SOG, DBT in navigation map getdisplay().setTextColor(commonData->fgcolor); // If a network connection to URL then load the navigation map - if (wifiConnected && net.fetchAndDecompressJson(url)) { - wifiConnectRequested = false; // Reset flag after successful connection + if (net.fetchAndDecompressJson(url)) { auto& json = net.json(); // Extract JSON content int numPix = json["number_pixels"] | 0; // Read number of pixels @@ -468,12 +442,6 @@ bool showValues = false; // Show values HDT, SOG, DBT in navigation map } lostCounter++; // Increment lost counter - - // Nur einmal pro Sekunde einen neuen Verbindungsversuch machen - if (!wifiConnectRequested) { - gwWifi.connectClientAsync(); - wifiConnectRequested = true; - } } @@ -520,9 +488,6 @@ bool showValues = false; // Show values HDT, SOG, DBT in navigation map }; }; -// Initialize static member variable -bool PageNavigation::wifiConnectRequested = false; - static Page *createPage(CommonData &common){ return new PageNavigation(common); }/** diff --git a/lib/obp60task/PageNavigation_old.cpp b/lib/obp60task/PageNavigation_old.cpp new file mode 100644 index 0000000..550d70c --- /dev/null +++ b/lib/obp60task/PageNavigation_old.cpp @@ -0,0 +1,543 @@ +#if defined BOARD_OBP60S3 || defined BOARD_OBP40S3 + +#include "Pagedata.h" +#include "OBP60Extensions.h" +#include "NetworkClient.h" // Network connection +#include "ImageDecoder.h" // Image decoder for navigation map +#include "GWWifi.h" // WiFi management (thread-safe) + +#include "Logo_OBP_400x300_sw.h" + +// Extern declaration of global WiFi instance +extern GwWifi gwWifi; + +// Defines for reading of navigation map +#define JSON_BUFFER 30000 // Max buffer size for JSON content (30 kB picture + values) +NetworkClient net(JSON_BUFFER); // Define network client +ImageDecoder decoder; // Define image decoder + +class PageNavigation : public Page +{ +// Values for buttons +bool firstRun = true; // Detect the first page run +int zoom = 15; // Default zoom level +bool showValues = false; // Show values HDT, SOG, DBT in navigation map + + private: + uint8_t* imageBackupData = nullptr; + int imageBackupWidth = 0; + int imageBackupHeight = 0; + size_t imageBackupSize = 0; + bool hasImageBackup = false; + static bool wifiConnectRequested; // Track if WiFi connection was requested + + public: + PageNavigation(CommonData &common){ + commonData = &common; + common.logger->logDebug(GwLog::LOG,"Instantiate PageNavigation"); + imageBackupData = (uint8_t*)heap_caps_malloc((GxEPD_WIDTH * GxEPD_HEIGHT), MALLOC_CAP_SPIRAM); + } + + // Set botton labels + virtual void setupKeys(){ + Page::setupKeys(); + commonData->keydata[0].label = "ZOOM -"; + commonData->keydata[1].label = "ZOOM +"; + commonData->keydata[4].label = "VALUES"; + } + + virtual int handleKey(int key){ + // Code for keylock + if(key == 11){ + commonData->keylock = !commonData->keylock; + return 0; // Commit the key + } + // Code for zoom - + if(key == 1){ + zoom --; // Zoom - + if(zoom <7){ + zoom = 7; + } + return 0; // Commit the key + } + // Code for zoom + + if(key == 2){ + zoom ++; // Zoom + + if(zoom >17){ + zoom = 17; + } + // Optional: Versuche WiFi-Verbindung nach Zoom-Änderung + // Dies ermöglicht eine neue Kartendarstellung + gwWifi.connectClientAsync(); + return 0; // Commit the key + } + if(key == 5){ + showValues = !showValues; // Toggle show values + return 0; // Commit the key + } + return key; + } + + int displayPage(PageData &pageData){ + GwConfigHandler *config = commonData->config; + GwLog *logger = commonData->logger; + + // 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); + String mapsource = config->getString(config->mapsource); + String ipAddress = config->getString(config->ipAddress); + int localPort = config->getInt(config->localPort); + String mapType = config->getString(config->maptype); + int zoomLevel = config->getInt(config->zoomlevel); + bool grid = config->getBool(config->grid); + String orientation = config->getString(config->orientation); + int refreshDistance = config->getInt(config->refreshDistance); + bool showValuesMap = config->getBool(config->showvalues); + bool ownHeading = config->getBool(config->ownheading); + + if(firstRun == true){ + zoom = zoomLevel; // Over write zoom level with setup value + showValues = showValuesMap; // Over write showValues with setup value + firstRun = false; // Restet variable + + // Versuche beim ersten Laden eine WiFi-Verbindung herzustellen (non-blocking) + // Dies ist thread-safe und blockiert das UI nicht + if (!gwWifi.clientConnected()) { + LOG_DEBUG(GwLog::LOG, "PageNavigation: Initiating WiFi connection for map download"); + gwWifi.connectClientAsync(); + } + } + + // Local variables + String server = "norbert-walter.dnshome.de"; + int port = 80; + int mType = 1; + int dType = 1; + int mapRot = 0; + int symbolRot = 0; + int mapGrid = 0; + + + // Old values for hold function + static double value1old = 0; + static String svalue1old = ""; + static String unit1old = ""; + static double value2old = 0; + static String svalue2old = ""; + static String unit2old = ""; + static double value3old = 0; // Deg + static String svalue3old = ""; + static String unit3old = ""; + static double value4old = 0; + static String svalue4old = ""; + static String unit4old = ""; + static double value5old = 0; + static String svalue5old = ""; + static String unit5old = ""; + static double value6old = 0; + static String svalue6old = ""; + static String unit6old = ""; + + static double latitude = 0; + static double latitudeold = 0; + static double longitude = 0; + static double longitudeold = 0; + static double trueHeading = 0; + static double magneticHeading = 0; + static double speedOverGround = 0; + static double depthBelowTransducer = 0; + static int lostCounter = 0; // Counter for connection lost to the map server (increment by each page refresh) + int imgWidth = 0; + int imgHeight = 0; + + // Get boat values #1 Latitude + 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 + double value1 = bvalue1->value; // Value as double in SI unit + bool valid1 = bvalue1->valid; // Valid information + String svalue1 = formatValue(bvalue1, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places + String unit1 = formatValue(bvalue1, *commonData).unit; // Unit of value + + // Get boat values #2 Longitude + GwApi::BoatValue *bvalue2 = pageData.values[1]; // Second 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 + 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 + + // Get boat values #3 HDT + 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 + 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 + + // Get boat values #4 HDM + 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 + 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 + + // Get boat values #5 SOG + 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 + 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 + + // Get boat values #6 DBT + GwApi::BoatValue *bvalue6 = pageData.values[5]; // Second element in list (only one value by PageOneValue) + String name6 = xdrDelete(bvalue6->getName()); // Value name + name6 = name6.substring(0, 6); // String length limit for value name + double value6 = bvalue6->value; // Value as double in SI unit + bool valid6 = bvalue6->valid; // Valid information + String svalue6 = formatValue(bvalue6, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places + String unit6 = formatValue(bvalue6, *commonData).unit; // Unit of value + + // Optical warning by limit violation (unused) + if(String(flashLED) == "Limit Violation"){ + setBlinkingLED(false); + setFlashLED(false); + } + + // Logging boat values + if (bvalue1 == NULL) return PAGE_OK; // WTF why this statement? + LOG_DEBUG(GwLog::LOG,"Drawing at PageNavigation, %s: %f, %s: %f, %s: %f, %s: %f, %s: %f, %s: %f", name1.c_str(), value1, name2.c_str(), value2, name3.c_str(), value3, name4.c_str(), value4, name5.c_str(), value5, name6.c_str(), value6); + + // Set variables + //*********************************************************** + + // Latitude + if(valid1){ + latitude = value1; + latitudeold = value1; + value3old = value1; + } + else{ + latitude = value1old; + } + // Longitude + if(valid2){ + longitude = value2; + longitudeold = value2; + value2old = value2; + } + else{ + longitude = value2old; + } + // HDT value (True Heading, GPS) + if(valid3){ + trueHeading = (value3 * 360) / (2 * PI); + value3old = trueHeading; + } + else{ + trueHeading = value3old; + } + // HDM value (Magnetic Heading) + if(valid4){ + magneticHeading = (value4 * 360) / (2 * PI); + value4old = magneticHeading; + } + else{ + speedOverGround = value4old; + } + // SOG value (Speed Over Ground) + if(valid5){ + speedOverGround = value5; + value5old = value5; + } + else{ + speedOverGround = value5old; + } + // DBT value (Depth Below Transducer) + if(valid6){ + depthBelowTransducer = value6; + value6old = value6; + } + else{ + depthBelowTransducer = value6old; + } + + // Prepare config values for URL + //*********************************************************** + + // Server settings + if(mapsource == "OBP Service"){ + server = "norbert-walter.dnshome.de"; + port = 80; + } + else if(mapsource == "Local Service"){ + server = String(ipAddress); + port = localPort; + } + else{ + server = "norbert-walter.dnshome.de"; + port = 80; + } + + // Type of navigation map + if(mapType == "Open Street Map"){ + mType = 1; // Map type + dType = 1; // Dithering type + } + else if(mapType == "Google Street"){ + mType = 3; + dType = 2; + } + else if(mapType == "Open Topo Map"){ + mType = 5; + dType = 2; + } + else if(mapType == "Stadimaps Toner"){ + mType = 7; + dType = 1; + } + else if(mapType == "Free Nautical Chart"){ + mType = 9; + dType = 1; + } + else{ + mType = 1; + dType = 1; + } + + // Map grid on/off + if(grid == true){ + mapGrid = 1; + } + else{ + mapGrid = 0; + } + + // Map orientation + if(orientation == "North Direction"){ + mapRot = 0; + // If true heading available then use HDT oterwise HDM + if(valid3 == true){ + symbolRot = trueHeading; + } + else{ + symbolRot = magneticHeading; + } + } + else if(orientation == "Travel Direction"){ + // If true heading available then use HDT oterwise HDM + if(valid3 == true){ + mapRot = trueHeading; + symbolRot = trueHeading; + } + else{ + mapRot = magneticHeading; + symbolRot = magneticHeading; + } + } + else{ + mapRot = 0; + // If true heading available then use HDT oterwise HDM + if(valid3 == true){ + symbolRot = trueHeading; + } + else{ + symbolRot = magneticHeading; + } + } + + // Load navigation map + //*********************************************************** + + // Prüfe WiFi-Verbindung (Thread-Safe) + // Diese Methode ist synchronisiert und blockiert maximal 1 Sekunde + bool wifiConnected = gwWifi.clientConnected(); + + if (!wifiConnected && !wifiConnectRequested) { + LOG_DEBUG(GwLog::LOG, "PageNavigation: WiFi not connected, attempting async connect"); + gwWifi.connectClientAsync(); + wifiConnectRequested = true; + } + + // URL to OBP Maps Converter + // For more details see: https://github.com/norbert-walter/maps-converter + String url = String("http://") + server + ":" + port + // OBP Server + String("/get_image_json?") + // Service: Output B&W picture as JSON (Base64 + gzip) + "zoom=" + zoom + // Default zoom level: 15 + "&lat=" + String(latitude, 6) + // Latitude + "&lon=" + String(longitude, 6) + // Longitude + "&mrot=" + mapRot + // Rotation angle navigation map in degree + "&mtype=" + mType + // Default Map: Open Street Map + "&dtype=" + dType + // Dithering type: Atkinson dithering + "&width=400" + // With navigation map + "&height=250" + // Height navigation map + "&cutout=0" + // No picture cutouts + "&tab=0" + // No tab size + "&border=2" + // Border line size: 2 pixel + "&symbol=2" + // Symbol: Triangle + "&srot=" + symbolRot + // Symbol rotation angle + "&ssize=15" + // Symbole size: 15 pixel + "&grid=" + mapGrid // Show grid: On + ; + + // Draw page + //*********************************************************** + + // ############### Draw Navigation Map ################ + + // Set display in partial refresh mode + getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update + getdisplay().setTextColor(commonData->fgcolor); + + // If a network connection to URL then load the navigation map + if (wifiConnected && net.fetchAndDecompressJson(url)) { + wifiConnectRequested = false; // Reset flag after successful connection + + auto& json = net.json(); // Extract JSON content + int numPix = json["number_pixels"] | 0; // Read number of pixels + imgWidth = json["width"] | 0; // Read width of image + imgHeight = json["height"] | 0; // Read height og image + + const char* b64src = json["picture_base64"].as(); // Read picture as Base64 content + size_t b64len = strlen(b64src); // Calculate length of Base64 content + // Copy Base64 content in PSRAM + char* b64 = (char*) heap_caps_malloc(b64len + 1, MALLOC_CAP_SPIRAM); // Allcate PSRAM for Base64 content + if (!b64) { + LOG_DEBUG(GwLog::ERROR,"Error PageNavigation: PSRAM alloc base64 failed"); + return PAGE_UPDATE; + } + memcpy(b64, b64src, b64len + 1); // Copy Base64 content in PSRAM + + // Set image buffer in PSRAM + //size_t imgSize = getdisplay().width() * getdisplay().height(); + size_t imgSize = numPix; // Calculate image size + uint8_t* imageData = (uint8_t*) heap_caps_malloc(imgSize, MALLOC_CAP_SPIRAM); // Allocate PSRAM for image + if (!imageData) { + LOG_DEBUG(GwLog::ERROR,"Error PageNavigation: PPSRAM alloc image buffer failed"); + free(b64); + return PAGE_UPDATE; + } + + // Decode Base64 content to image + size_t decodedSize = 0; + decoder.decodeBase64(b64, imageData, imgSize, decodedSize); + + // Copy actual navigation man to ackup map + imageBackupWidth = imgWidth; + imageBackupHeight = imgHeight; + imageBackupSize = imgSize; + if (decodedSize > 0) { + memcpy(imageBackupData, imageData, decodedSize); + imageBackupSize = decodedSize; + } + hasImageBackup = true; + lostCounter = 0; + + // Show image (navigation map) + getdisplay().drawBitmap(0, 25, imageData, imgWidth, imgHeight, commonData->fgcolor); + + // Clean PSRAM + free(b64); + free(imageData); + } + // If no network connection then use backup navigation map + else{ + // Show backup image (backup navigation map) + if (hasImageBackup) { + getdisplay().drawBitmap(0, 25, imageBackupData, imageBackupWidth, imageBackupHeight, commonData->fgcolor); + } + + // Show info: Connection lost when 5 page refreshes has a connection lost to the map server + // Short connection losts are uncritical + if(lostCounter >= 5){ + getdisplay().setFont(&Ubuntu_Bold12pt8b); + getdisplay().fillRect(200, 250 , 200, 25, commonData->fgcolor); // Black rect + getdisplay().fillRect(202, 252 , 196, 21, commonData->bgcolor); // White rect + getdisplay().setCursor(210, 270); + getdisplay().print("Map server lost"); + } + + lostCounter++; // Increment lost counter + + // Nur einmal pro Sekunde einen neuen Verbindungsversuch machen + if (!wifiConnectRequested) { + gwWifi.connectClientAsync(); + wifiConnectRequested = true; + } + } + + + // ############### Draw Values ################ + getdisplay().setFont(&Ubuntu_Bold12pt8b); + + // Show zoom level + getdisplay().fillRect(355, 25 , 45, 25, commonData->fgcolor); // Black rect + getdisplay().fillRect(357, 27 , 41, 21, commonData->bgcolor); // White rect + getdisplay().setCursor(364, 45); + getdisplay().print(zoom); + // If true heading available then use HDT oterwise HDM + if(showValues == true){ + // Frame + getdisplay().fillRect(0, 25 , 130, 65, commonData->fgcolor); // Black rect + getdisplay().fillRect(2, 27 , 126, 61, commonData->bgcolor); // White rect + if(valid3 == true){ + // HDT + getdisplay().setCursor(10, 45); + getdisplay().print(name3); + getdisplay().setCursor(70, 45); + getdisplay().print(svalue3); + } + else{ + // HDM + getdisplay().setCursor(10, 45); + getdisplay().print(name4); + getdisplay().setCursor(70, 45); + getdisplay().print(svalue4); + } + // SOG + getdisplay().setCursor(10, 65); + getdisplay().print(name5); + getdisplay().setCursor(70, 65); + getdisplay().print(svalue5); + // DBT + getdisplay().setCursor(10, 85); + getdisplay().print(name6); + getdisplay().setCursor(70, 85); + getdisplay().print(svalue6); + } + + return PAGE_UPDATE; + }; +}; + +// Initialize static member variable +bool PageNavigation::wifiConnectRequested = false; + +static Page *createPage(CommonData &common){ + return new PageNavigation(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 registerPageNavigation( + "Navigation", // Page name + createPage, // Action + 0, // Number of bus values depends on selection in Web configuration + {"LAT","LON","HDT","HDM","SOG","DBT"}, // Bus values we need in the page + true // Show display header on/off +); + +#endif From 3043be8e1d5f27e7be92678fd0b0ca793e9ef6c0 Mon Sep 17 00:00:00 2001 From: norbert-walter Date: Sun, 15 Feb 2026 19:17:52 +0100 Subject: [PATCH 8/9] Code cleaning --- lib/obp60task/PageNavigation_old.cpp | 543 --------------------------- 1 file changed, 543 deletions(-) delete mode 100644 lib/obp60task/PageNavigation_old.cpp diff --git a/lib/obp60task/PageNavigation_old.cpp b/lib/obp60task/PageNavigation_old.cpp deleted file mode 100644 index 550d70c..0000000 --- a/lib/obp60task/PageNavigation_old.cpp +++ /dev/null @@ -1,543 +0,0 @@ -#if defined BOARD_OBP60S3 || defined BOARD_OBP40S3 - -#include "Pagedata.h" -#include "OBP60Extensions.h" -#include "NetworkClient.h" // Network connection -#include "ImageDecoder.h" // Image decoder for navigation map -#include "GWWifi.h" // WiFi management (thread-safe) - -#include "Logo_OBP_400x300_sw.h" - -// Extern declaration of global WiFi instance -extern GwWifi gwWifi; - -// Defines for reading of navigation map -#define JSON_BUFFER 30000 // Max buffer size for JSON content (30 kB picture + values) -NetworkClient net(JSON_BUFFER); // Define network client -ImageDecoder decoder; // Define image decoder - -class PageNavigation : public Page -{ -// Values for buttons -bool firstRun = true; // Detect the first page run -int zoom = 15; // Default zoom level -bool showValues = false; // Show values HDT, SOG, DBT in navigation map - - private: - uint8_t* imageBackupData = nullptr; - int imageBackupWidth = 0; - int imageBackupHeight = 0; - size_t imageBackupSize = 0; - bool hasImageBackup = false; - static bool wifiConnectRequested; // Track if WiFi connection was requested - - public: - PageNavigation(CommonData &common){ - commonData = &common; - common.logger->logDebug(GwLog::LOG,"Instantiate PageNavigation"); - imageBackupData = (uint8_t*)heap_caps_malloc((GxEPD_WIDTH * GxEPD_HEIGHT), MALLOC_CAP_SPIRAM); - } - - // Set botton labels - virtual void setupKeys(){ - Page::setupKeys(); - commonData->keydata[0].label = "ZOOM -"; - commonData->keydata[1].label = "ZOOM +"; - commonData->keydata[4].label = "VALUES"; - } - - virtual int handleKey(int key){ - // Code for keylock - if(key == 11){ - commonData->keylock = !commonData->keylock; - return 0; // Commit the key - } - // Code for zoom - - if(key == 1){ - zoom --; // Zoom - - if(zoom <7){ - zoom = 7; - } - return 0; // Commit the key - } - // Code for zoom + - if(key == 2){ - zoom ++; // Zoom + - if(zoom >17){ - zoom = 17; - } - // Optional: Versuche WiFi-Verbindung nach Zoom-Änderung - // Dies ermöglicht eine neue Kartendarstellung - gwWifi.connectClientAsync(); - return 0; // Commit the key - } - if(key == 5){ - showValues = !showValues; // Toggle show values - return 0; // Commit the key - } - return key; - } - - int displayPage(PageData &pageData){ - GwConfigHandler *config = commonData->config; - GwLog *logger = commonData->logger; - - // 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); - String mapsource = config->getString(config->mapsource); - String ipAddress = config->getString(config->ipAddress); - int localPort = config->getInt(config->localPort); - String mapType = config->getString(config->maptype); - int zoomLevel = config->getInt(config->zoomlevel); - bool grid = config->getBool(config->grid); - String orientation = config->getString(config->orientation); - int refreshDistance = config->getInt(config->refreshDistance); - bool showValuesMap = config->getBool(config->showvalues); - bool ownHeading = config->getBool(config->ownheading); - - if(firstRun == true){ - zoom = zoomLevel; // Over write zoom level with setup value - showValues = showValuesMap; // Over write showValues with setup value - firstRun = false; // Restet variable - - // Versuche beim ersten Laden eine WiFi-Verbindung herzustellen (non-blocking) - // Dies ist thread-safe und blockiert das UI nicht - if (!gwWifi.clientConnected()) { - LOG_DEBUG(GwLog::LOG, "PageNavigation: Initiating WiFi connection for map download"); - gwWifi.connectClientAsync(); - } - } - - // Local variables - String server = "norbert-walter.dnshome.de"; - int port = 80; - int mType = 1; - int dType = 1; - int mapRot = 0; - int symbolRot = 0; - int mapGrid = 0; - - - // Old values for hold function - static double value1old = 0; - static String svalue1old = ""; - static String unit1old = ""; - static double value2old = 0; - static String svalue2old = ""; - static String unit2old = ""; - static double value3old = 0; // Deg - static String svalue3old = ""; - static String unit3old = ""; - static double value4old = 0; - static String svalue4old = ""; - static String unit4old = ""; - static double value5old = 0; - static String svalue5old = ""; - static String unit5old = ""; - static double value6old = 0; - static String svalue6old = ""; - static String unit6old = ""; - - static double latitude = 0; - static double latitudeold = 0; - static double longitude = 0; - static double longitudeold = 0; - static double trueHeading = 0; - static double magneticHeading = 0; - static double speedOverGround = 0; - static double depthBelowTransducer = 0; - static int lostCounter = 0; // Counter for connection lost to the map server (increment by each page refresh) - int imgWidth = 0; - int imgHeight = 0; - - // Get boat values #1 Latitude - 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 - double value1 = bvalue1->value; // Value as double in SI unit - bool valid1 = bvalue1->valid; // Valid information - String svalue1 = formatValue(bvalue1, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places - String unit1 = formatValue(bvalue1, *commonData).unit; // Unit of value - - // Get boat values #2 Longitude - GwApi::BoatValue *bvalue2 = pageData.values[1]; // Second 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 - 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 - - // Get boat values #3 HDT - 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 - 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 - - // Get boat values #4 HDM - 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 - 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 - - // Get boat values #5 SOG - 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 - 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 - - // Get boat values #6 DBT - GwApi::BoatValue *bvalue6 = pageData.values[5]; // Second element in list (only one value by PageOneValue) - String name6 = xdrDelete(bvalue6->getName()); // Value name - name6 = name6.substring(0, 6); // String length limit for value name - double value6 = bvalue6->value; // Value as double in SI unit - bool valid6 = bvalue6->valid; // Valid information - String svalue6 = formatValue(bvalue6, *commonData).svalue; // Formatted value as string including unit conversion and switching decimal places - String unit6 = formatValue(bvalue6, *commonData).unit; // Unit of value - - // Optical warning by limit violation (unused) - if(String(flashLED) == "Limit Violation"){ - setBlinkingLED(false); - setFlashLED(false); - } - - // Logging boat values - if (bvalue1 == NULL) return PAGE_OK; // WTF why this statement? - LOG_DEBUG(GwLog::LOG,"Drawing at PageNavigation, %s: %f, %s: %f, %s: %f, %s: %f, %s: %f, %s: %f", name1.c_str(), value1, name2.c_str(), value2, name3.c_str(), value3, name4.c_str(), value4, name5.c_str(), value5, name6.c_str(), value6); - - // Set variables - //*********************************************************** - - // Latitude - if(valid1){ - latitude = value1; - latitudeold = value1; - value3old = value1; - } - else{ - latitude = value1old; - } - // Longitude - if(valid2){ - longitude = value2; - longitudeold = value2; - value2old = value2; - } - else{ - longitude = value2old; - } - // HDT value (True Heading, GPS) - if(valid3){ - trueHeading = (value3 * 360) / (2 * PI); - value3old = trueHeading; - } - else{ - trueHeading = value3old; - } - // HDM value (Magnetic Heading) - if(valid4){ - magneticHeading = (value4 * 360) / (2 * PI); - value4old = magneticHeading; - } - else{ - speedOverGround = value4old; - } - // SOG value (Speed Over Ground) - if(valid5){ - speedOverGround = value5; - value5old = value5; - } - else{ - speedOverGround = value5old; - } - // DBT value (Depth Below Transducer) - if(valid6){ - depthBelowTransducer = value6; - value6old = value6; - } - else{ - depthBelowTransducer = value6old; - } - - // Prepare config values for URL - //*********************************************************** - - // Server settings - if(mapsource == "OBP Service"){ - server = "norbert-walter.dnshome.de"; - port = 80; - } - else if(mapsource == "Local Service"){ - server = String(ipAddress); - port = localPort; - } - else{ - server = "norbert-walter.dnshome.de"; - port = 80; - } - - // Type of navigation map - if(mapType == "Open Street Map"){ - mType = 1; // Map type - dType = 1; // Dithering type - } - else if(mapType == "Google Street"){ - mType = 3; - dType = 2; - } - else if(mapType == "Open Topo Map"){ - mType = 5; - dType = 2; - } - else if(mapType == "Stadimaps Toner"){ - mType = 7; - dType = 1; - } - else if(mapType == "Free Nautical Chart"){ - mType = 9; - dType = 1; - } - else{ - mType = 1; - dType = 1; - } - - // Map grid on/off - if(grid == true){ - mapGrid = 1; - } - else{ - mapGrid = 0; - } - - // Map orientation - if(orientation == "North Direction"){ - mapRot = 0; - // If true heading available then use HDT oterwise HDM - if(valid3 == true){ - symbolRot = trueHeading; - } - else{ - symbolRot = magneticHeading; - } - } - else if(orientation == "Travel Direction"){ - // If true heading available then use HDT oterwise HDM - if(valid3 == true){ - mapRot = trueHeading; - symbolRot = trueHeading; - } - else{ - mapRot = magneticHeading; - symbolRot = magneticHeading; - } - } - else{ - mapRot = 0; - // If true heading available then use HDT oterwise HDM - if(valid3 == true){ - symbolRot = trueHeading; - } - else{ - symbolRot = magneticHeading; - } - } - - // Load navigation map - //*********************************************************** - - // Prüfe WiFi-Verbindung (Thread-Safe) - // Diese Methode ist synchronisiert und blockiert maximal 1 Sekunde - bool wifiConnected = gwWifi.clientConnected(); - - if (!wifiConnected && !wifiConnectRequested) { - LOG_DEBUG(GwLog::LOG, "PageNavigation: WiFi not connected, attempting async connect"); - gwWifi.connectClientAsync(); - wifiConnectRequested = true; - } - - // URL to OBP Maps Converter - // For more details see: https://github.com/norbert-walter/maps-converter - String url = String("http://") + server + ":" + port + // OBP Server - String("/get_image_json?") + // Service: Output B&W picture as JSON (Base64 + gzip) - "zoom=" + zoom + // Default zoom level: 15 - "&lat=" + String(latitude, 6) + // Latitude - "&lon=" + String(longitude, 6) + // Longitude - "&mrot=" + mapRot + // Rotation angle navigation map in degree - "&mtype=" + mType + // Default Map: Open Street Map - "&dtype=" + dType + // Dithering type: Atkinson dithering - "&width=400" + // With navigation map - "&height=250" + // Height navigation map - "&cutout=0" + // No picture cutouts - "&tab=0" + // No tab size - "&border=2" + // Border line size: 2 pixel - "&symbol=2" + // Symbol: Triangle - "&srot=" + symbolRot + // Symbol rotation angle - "&ssize=15" + // Symbole size: 15 pixel - "&grid=" + mapGrid // Show grid: On - ; - - // Draw page - //*********************************************************** - - // ############### Draw Navigation Map ################ - - // Set display in partial refresh mode - getdisplay().setPartialWindow(0, 0, getdisplay().width(), getdisplay().height()); // Set partial update - getdisplay().setTextColor(commonData->fgcolor); - - // If a network connection to URL then load the navigation map - if (wifiConnected && net.fetchAndDecompressJson(url)) { - wifiConnectRequested = false; // Reset flag after successful connection - - auto& json = net.json(); // Extract JSON content - int numPix = json["number_pixels"] | 0; // Read number of pixels - imgWidth = json["width"] | 0; // Read width of image - imgHeight = json["height"] | 0; // Read height og image - - const char* b64src = json["picture_base64"].as(); // Read picture as Base64 content - size_t b64len = strlen(b64src); // Calculate length of Base64 content - // Copy Base64 content in PSRAM - char* b64 = (char*) heap_caps_malloc(b64len + 1, MALLOC_CAP_SPIRAM); // Allcate PSRAM for Base64 content - if (!b64) { - LOG_DEBUG(GwLog::ERROR,"Error PageNavigation: PSRAM alloc base64 failed"); - return PAGE_UPDATE; - } - memcpy(b64, b64src, b64len + 1); // Copy Base64 content in PSRAM - - // Set image buffer in PSRAM - //size_t imgSize = getdisplay().width() * getdisplay().height(); - size_t imgSize = numPix; // Calculate image size - uint8_t* imageData = (uint8_t*) heap_caps_malloc(imgSize, MALLOC_CAP_SPIRAM); // Allocate PSRAM for image - if (!imageData) { - LOG_DEBUG(GwLog::ERROR,"Error PageNavigation: PPSRAM alloc image buffer failed"); - free(b64); - return PAGE_UPDATE; - } - - // Decode Base64 content to image - size_t decodedSize = 0; - decoder.decodeBase64(b64, imageData, imgSize, decodedSize); - - // Copy actual navigation man to ackup map - imageBackupWidth = imgWidth; - imageBackupHeight = imgHeight; - imageBackupSize = imgSize; - if (decodedSize > 0) { - memcpy(imageBackupData, imageData, decodedSize); - imageBackupSize = decodedSize; - } - hasImageBackup = true; - lostCounter = 0; - - // Show image (navigation map) - getdisplay().drawBitmap(0, 25, imageData, imgWidth, imgHeight, commonData->fgcolor); - - // Clean PSRAM - free(b64); - free(imageData); - } - // If no network connection then use backup navigation map - else{ - // Show backup image (backup navigation map) - if (hasImageBackup) { - getdisplay().drawBitmap(0, 25, imageBackupData, imageBackupWidth, imageBackupHeight, commonData->fgcolor); - } - - // Show info: Connection lost when 5 page refreshes has a connection lost to the map server - // Short connection losts are uncritical - if(lostCounter >= 5){ - getdisplay().setFont(&Ubuntu_Bold12pt8b); - getdisplay().fillRect(200, 250 , 200, 25, commonData->fgcolor); // Black rect - getdisplay().fillRect(202, 252 , 196, 21, commonData->bgcolor); // White rect - getdisplay().setCursor(210, 270); - getdisplay().print("Map server lost"); - } - - lostCounter++; // Increment lost counter - - // Nur einmal pro Sekunde einen neuen Verbindungsversuch machen - if (!wifiConnectRequested) { - gwWifi.connectClientAsync(); - wifiConnectRequested = true; - } - } - - - // ############### Draw Values ################ - getdisplay().setFont(&Ubuntu_Bold12pt8b); - - // Show zoom level - getdisplay().fillRect(355, 25 , 45, 25, commonData->fgcolor); // Black rect - getdisplay().fillRect(357, 27 , 41, 21, commonData->bgcolor); // White rect - getdisplay().setCursor(364, 45); - getdisplay().print(zoom); - // If true heading available then use HDT oterwise HDM - if(showValues == true){ - // Frame - getdisplay().fillRect(0, 25 , 130, 65, commonData->fgcolor); // Black rect - getdisplay().fillRect(2, 27 , 126, 61, commonData->bgcolor); // White rect - if(valid3 == true){ - // HDT - getdisplay().setCursor(10, 45); - getdisplay().print(name3); - getdisplay().setCursor(70, 45); - getdisplay().print(svalue3); - } - else{ - // HDM - getdisplay().setCursor(10, 45); - getdisplay().print(name4); - getdisplay().setCursor(70, 45); - getdisplay().print(svalue4); - } - // SOG - getdisplay().setCursor(10, 65); - getdisplay().print(name5); - getdisplay().setCursor(70, 65); - getdisplay().print(svalue5); - // DBT - getdisplay().setCursor(10, 85); - getdisplay().print(name6); - getdisplay().setCursor(70, 85); - getdisplay().print(svalue6); - } - - return PAGE_UPDATE; - }; -}; - -// Initialize static member variable -bool PageNavigation::wifiConnectRequested = false; - -static Page *createPage(CommonData &common){ - return new PageNavigation(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 registerPageNavigation( - "Navigation", // Page name - createPage, // Action - 0, // Number of bus values depends on selection in Web configuration - {"LAT","LON","HDT","HDM","SOG","DBT"}, // Bus values we need in the page - true // Show display header on/off -); - -#endif From 66e71acac372da58d8b70f921bc0a09865b866d5 Mon Sep 17 00:00:00 2001 From: norbert-walter Date: Fri, 20 Feb 2026 09:52:41 +0100 Subject: [PATCH 9/9] Fix for GwWifi --- lib/gwwifi/GwWifi.cpp | 27 ++++++++------------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/lib/gwwifi/GwWifi.cpp b/lib/gwwifi/GwWifi.cpp index 3a8991f..cd475c2 100644 --- a/lib/gwwifi/GwWifi.cpp +++ b/lib/gwwifi/GwWifi.cpp @@ -131,33 +131,20 @@ void GwWifi::loop(){ if (lastConnectStart > now || (lastConnectStart + RETRY_MILLIS) < now) { LOG_DEBUG(GwLog::LOG,"wifiClient: retry connect to %s", wifiSSID->asCString()); -<<<<<<< HEAD + // CRITICAL SECTION: WiFi-Operationen müssen serialisiert werden if (acquireMutex()){ - WiFi.disconnect(); + WiFi.disconnect(true); + delay(300); + esp_wifi_stop(); + delay(100); + esp_wifi_start(); releaseMutex(); connectInternal(); } else{ LOG_DEBUG(GwLog::ERROR,"GwWifi: mutex timeout in loop"); } -======= - -//+++++++++ Old part +++++++++++++++++++++ -// WiFi.disconnect(); -// connectInternal(); -//++++++++++++++++++++++++++++++++++++++++ - -//+++++++++ New part +++++++++++++++++++++ - WiFi.disconnect(true); - delay(300); - esp_wifi_stop(); - delay(100); - esp_wifi_start(); - connectInternal(); -//++++++++++++++++++++++++++++++++++++++++ - ->>>>>>> autopilot2 } } else{ @@ -178,6 +165,7 @@ void GwWifi::loop(){ } } } + bool GwWifi::clientConnected(){ // CRITICAL SECTION: WiFi.status() muss geschützt werden if (!acquireMutex()){ @@ -188,6 +176,7 @@ bool GwWifi::clientConnected(){ releaseMutex(); return result; }; + bool GwWifi::connectClient(){ // CRITICAL SECTION: Disconnect und Connect müssen atomar sein if (!acquireMutex()){