From a6fd3ef599a2859594864823e158474b0815a346 Mon Sep 17 00:00:00 2001 From: Thomas Hooge Date: Mon, 23 Feb 2026 15:34:10 +0100 Subject: [PATCH] System page with N2K device list by improved patching feature and patch --- lib/obp60task/PageSystem.cpp | 102 ++++++++++++++++++++--- lib/obp60task/extra_task.py | 22 +++-- lib/obp60task/patches/01-nmea2000.patch | 103 ++++++++++++++++++++++++ 3 files changed, 210 insertions(+), 17 deletions(-) create mode 100644 lib/obp60task/patches/01-nmea2000.patch diff --git a/lib/obp60task/PageSystem.cpp b/lib/obp60task/PageSystem.cpp index 92af7b2..a8b3ee4 100644 --- a/lib/obp60task/PageSystem.cpp +++ b/lib/obp60task/PageSystem.cpp @@ -15,6 +15,7 @@ #include "images/logo64.xbm" #include #include "qrcode.h" +#include #ifdef BOARD_OBP40S3 #include "dirent.h" @@ -40,6 +41,7 @@ private: String rtc_module; String gps_module; String env_module; + String flashLED; String batt_sensor; String solar_sensor; @@ -50,6 +52,17 @@ private: char mode = 'N'; // (N)ormal, (S)ettings, (D)evice list, (C)ard +#ifdef PATCH_N2K + struct device { + uint64_t NAME; + uint8_t id; + char hex_name[17]; + uint16_t manuf_code; + const char *model; + }; + std::vector devicelist; +#endif + public: PageSystem(CommonData &common){ commonData = &common; @@ -76,6 +89,7 @@ public: rot_sensor = common.config->getString(common.config->useRotSensor); homelat = common.config->getString(common.config->homeLAT).toDouble(); homelon = common.config->getString(common.config->homeLON).toDouble(); + flashLED = common.config->getString(common.config->flashLED); } void setupKeys() { @@ -168,19 +182,43 @@ public: } } + void displayNew(PageData &pageData) { +#ifdef BOARD_OBP60S3 + // Clear optical warning + if (flashLED == "Limit Violation") { + setBlinkingLED(false); + setFlashLED(false); + } +#endif + +#ifdef PATCH_N2K + // load current device list + tN2kDeviceList *pDevList = pageData.api->getN2kDeviceList(); + // TODO check if changed + if (pDevList->ReadResetIsListUpdated()) { + // only reload if changed + devicelist.clear(); + for (uint8_t i = 0; i <= 252; i++) { + const tNMEA2000::tDevice *d = pDevList->FindDeviceBySource(i); + if (d == nullptr) { + continue; + } + device dev; + dev.id = i; + dev.NAME = d->GetName(); + snprintf(dev.hex_name, sizeof(dev.hex_name), "%08X%08X", (uint32_t)(dev.NAME >> 32), (uint32_t)(dev.NAME & 0xFFFFFFFF)); + dev.manuf_code = d->GetManufacturerCode(); + dev.model = d->GetModelID(); + devicelist.push_back(dev); + } + } +#endif + }; + int displayPage(PageData &pageData){ GwConfigHandler *config = commonData->config; GwLog *logger = commonData->logger; - // Get config data - String flashLED = config->getString(config->flashLED); - - // Optical warning by limit violation (unused) - if(String(flashLED) == "Limit Violation"){ - setBlinkingLED(false); - setFlashLED(false); - } - // Logging boat values logger->logDebug(GwLog::LOG, "Drawing at PageSystem, Mode=%c", mode); @@ -461,12 +499,54 @@ public: getdisplay().print("NMEA2000 device list"); getdisplay().setFont(&Ubuntu_Bold8pt8b); - getdisplay().setCursor(20, 80); + getdisplay().setCursor(20, 70); getdisplay().print("RxD: "); getdisplay().print(String(commonData->status.n2kRx)); - getdisplay().setCursor(20, 100); + getdisplay().setCursor(120, 70); getdisplay().print("TxD: "); getdisplay().print(String(commonData->status.n2kTx)); + +#ifdef PATCH_N2K + x0 = 20; + y0 = 100; + + getdisplay().setFont(&Ubuntu_Bold10pt8b); + getdisplay().setCursor(x0, y0); + getdisplay().print("ID"); + getdisplay().setCursor(x0 + 50, y0); + getdisplay().print("Model"); + getdisplay().setCursor(x0 + 250, y0); + getdisplay().print("Manuf."); + getdisplay().drawLine(18, y0 + 4, 360 , y0 + 4 , commonData->fgcolor); + + getdisplay().setFont(&Ubuntu_Bold8pt8b); + y0 = 120; + uint8_t n_dev = 0; + for (const device& item : devicelist) { + if (n_dev > 8) { + break; + } + getdisplay().setCursor(x0, y0 + n_dev * 20); + getdisplay().print(item.id); + getdisplay().setCursor(x0 + 50, y0 + n_dev * 20); + getdisplay().print(item.model); + getdisplay().setCursor(x0 + 250, y0 + n_dev * 20); + getdisplay().print(item.manuf_code); + n_dev++; + } + getdisplay().setCursor(x0, y0 + (n_dev + 1) * 20); + if (n_dev == 0) { + getdisplay().printf("no devices found on bus"); + + } else { + getdisplay().drawLine(18, y0 + n_dev * 20, 360 , y0 + n_dev * 20, commonData->fgcolor); + getdisplay().printf("%d devices of %d in total", n_dev, devicelist.size()); + } +#else + getdisplay().setCursor(20, 100); + getdisplay().print("NMEA2000 not exposed to obp60 task"); +#endif + } // Update display diff --git a/lib/obp60task/extra_task.py b/lib/obp60task/extra_task.py index 66ee3b1..dfc8257 100644 --- a/lib/obp60task/extra_task.py +++ b/lib/obp60task/extra_task.py @@ -2,6 +2,14 @@ import subprocess +def cleanup_patches(source, target, env): + for p in patchfiles: + patch = os.path.join(patchdir, p) + print(f"removing {patch}") + res = subprocess.run(["git", "apply", "-R", patch], capture_output=True, text=True) + if res.returncode != 0: + print(res.stderr) + patching = False epdtype = "unknown" @@ -46,11 +54,13 @@ if patching: print("patchdir not found, no patches applied") else: patchfiles = [f for f in os.listdir(patchdir)] - for p in patchfiles: - patch = os.path.join(patchdir, p) - print(f"applying {patch}") - res = subprocess.run(["git", "apply", patch], capture_output=True, text=True) - if res.returncode != 0: - print(res.stderr) + if len(patchfiles) > 0: + for p in patchfiles: + patch = os.path.join(patchdir, p) + print(f"applying {patch}") + res = subprocess.run(["git", "apply", patch], capture_output=True, text=True) + if res.returncode != 0: + print(res.stderr) + env.AddPostAction("$PROGPATH", cleanup_patches) else: print("no patches found") diff --git a/lib/obp60task/patches/01-nmea2000.patch b/lib/obp60task/patches/01-nmea2000.patch new file mode 100644 index 0000000..3d50bea --- /dev/null +++ b/lib/obp60task/patches/01-nmea2000.patch @@ -0,0 +1,103 @@ +diff --git a/lib/api/GwApi.h b/lib/api/GwApi.h +index 88f9690..9663a65 100644 +--- a/lib/api/GwApi.h ++++ b/lib/api/GwApi.h +@@ -2,6 +2,8 @@ + #define _GWAPI_H + #include "GwMessage.h" + #include "N2kMsg.h" ++#include "Nmea2kTwai.h" ++#include "N2kDeviceList.h" + #include "NMEA0183Msg.h" + #include "GWConfig.h" + #include "GwBoatData.h" +@@ -222,6 +224,8 @@ class GwApi{ + * accessing boat data must only be executed from within the main thread + * you need to use the request pattern as shown in GwExampleTask.cpp + */ ++ virtual Nmea2kTwai *getNMEA2000()=0; ++ virtual tN2kDeviceList *getN2kDeviceList()=0; + virtual GwBoatData *getBoatData()=0; + virtual ~GwApi(){} + }; +diff --git a/lib/obp60task/OBP60Extensions.h b/lib/obp60task/OBP60Extensions.h +index 604c356..2fe4496 100644 +--- a/lib/obp60task/OBP60Extensions.h ++++ b/lib/obp60task/OBP60Extensions.h +@@ -15,6 +15,9 @@ + #define MOUNT_POINT "/sdcard" + #endif + ++// Patches to apply to gateway code ++#define PATCH_N2K ++ + // FRAM address reservations 32kB: 0x0000 - 0x7FFF + // 0x0000 - 0x03ff: single variables + #define FRAM_PAGE_NO 0x0002 +diff --git a/lib/usercode/GwUserCode.cpp b/lib/usercode/GwUserCode.cpp +index 1b007f8..90087d4 100644 +--- a/lib/usercode/GwUserCode.cpp ++++ b/lib/usercode/GwUserCode.cpp +@@ -216,6 +216,14 @@ public: + { + return api->getLogger(); + } ++ virtual Nmea2kTwai *getNMEA2000() ++ { ++ return api->getNMEA2000(); ++ } ++ virtual tN2kDeviceList *getN2kDeviceList() ++ { ++ return api->getN2kDeviceList(); ++ } + virtual GwBoatData *getBoatData() + { + return api->getBoatData(); +@@ -428,4 +436,4 @@ void GwUserCode::handleWebRequest(const String &url,AsyncWebServerRequest *req){ + } + LOG_DEBUG(GwLog::DEBUG,"no task found for web request %s[%s]",url.c_str(),tname.c_str()); + req->send(404, "text/plain", "not found"); +-} +\ No newline at end of file ++} +diff --git a/src/main.cpp b/src/main.cpp +index 44c715f..fdb0366 100644 +--- a/src/main.cpp ++++ b/src/main.cpp +@@ -100,6 +100,7 @@ GwLog logger(LOGLEVEL,NULL); + GwConfigHandler config(&logger); + + #include "Nmea2kTwai.h" ++#include + static const unsigned long CAN_RECOVERY_PERIOD=3000; //ms + static const unsigned long NMEA2000_HEARTBEAT_INTERVAL=5000; + class Nmea2kTwaiLog : public Nmea2kTwai{ +@@ -126,6 +127,7 @@ class Nmea2kTwaiLog : public Nmea2kTwai{ + #endif + + Nmea2kTwai &NMEA2000=*(new Nmea2kTwaiLog((gpio_num_t)ESP32_CAN_TX_PIN,(gpio_num_t)ESP32_CAN_RX_PIN,CAN_RECOVERY_PERIOD,&logger)); ++tN2kDeviceList *pN2kDeviceList; + + #ifdef GWBUTTON_PIN + bool fixedApPass=false; +@@ -333,6 +335,12 @@ public: + status.n2kTx=countNMEA2KOut.getGlobal(); + channels.fillStatus(status); + } ++ virtual Nmea2kTwai *getNMEA2000(){ ++ return &NMEA2000; ++ } ++ virtual tN2kDeviceList *getN2kDeviceList(){ ++ return pN2kDeviceList; ++ } + virtual GwBoatData *getBoatData(){ + return &boatData; + } +@@ -935,6 +943,7 @@ void setup() { + NMEA2000.SetMsgHandler([](const tN2kMsg &n2kMsg){ + handleN2kMessage(n2kMsg,N2K_CHANNEL_ID); + }); ++ pN2kDeviceList = new tN2kDeviceList(&NMEA2000); + NMEA2000.Open(); + logger.logDebug(GwLog::LOG,"starting addon tasks"); + logger.flush();