diff --git a/lib/gwwifi/GWWifi.h b/lib/gwwifi/GWWifi.h index 9063a4e..cb5c980 100644 --- a/lib/gwwifi/GWWifi.h +++ b/lib/gwwifi/GWWifi.h @@ -2,6 +2,9 @@ #define _GWWIFI_H #include #include +#include +#include + class GwWifi{ private: const GwConfigHandler *config; @@ -16,15 +19,21 @@ class GwWifi{ bool apActive=false; bool fixedApPass=true; bool clientIsConnected=false; + SemaphoreHandle_t wifiMutex=nullptr; + static const TickType_t WIFI_MUTEX_TIMEOUT=pdMS_TO_TICKS(1000); + bool acquireMutex(); + void releaseMutex(); public: const char *AP_password = "esp32nmea2k"; GwWifi(const GwConfigHandler *config,GwLog *log, bool fixedApPass=true); + ~GwWifi(); void setup(); void loop(); bool clientConnected(); - bool connectClient(); + bool connectClient(); // Blocking version + 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/gwwifi/GwWifi.cpp b/lib/gwwifi/GwWifi.cpp index c81acec..19e6941 100644 --- a/lib/gwwifi/GwWifi.cpp +++ b/lib/gwwifi/GwWifi.cpp @@ -1,7 +1,6 @@ #include #include "GWWifi.h" - GwWifi::GwWifi(const GwConfigHandler *config,GwLog *log, bool fixedApPass){ this->config=config; this->logger=log; @@ -9,6 +8,28 @@ GwWifi::GwWifi(const GwConfigHandler *config,GwLog *log, bool fixedApPass){ wifiSSID=config->getConfigItem(config->wifiSSID,true); wifiPass=config->getConfigItem(config->wifiPass,true); this->fixedApPass=fixedApPass; + wifiMutex=xSemaphoreCreateMutex(); + if (wifiMutex==nullptr){ + LOG_DEBUG(GwLog::ERROR,"GwWifi: unable to create mutex"); + } +} + +GwWifi::~GwWifi(){ + if (wifiMutex!=nullptr){ + vSemaphoreDelete(wifiMutex); + wifiMutex=nullptr; + } +} + +bool GwWifi::acquireMutex(){ + if (wifiMutex==nullptr) return false; + return xSemaphoreTake(wifiMutex,WIFI_MUTEX_TIMEOUT)==pdTRUE; +} + +void GwWifi::releaseMutex(){ + if (wifiMutex!=nullptr){ + xSemaphoreGive(wifiMutex); + } } void GwWifi::setup(){ LOG_DEBUG(GwLog::LOG,"Wifi setup"); @@ -85,8 +106,14 @@ bool GwWifi::connectInternal(){ if (wifiClient->asBoolean()){ clientIsConnected=false; LOG_DEBUG(GwLog::LOG,"creating wifiClient ssid=%s",wifiSSID->asString().c_str()); + // CRITICAL SECTION: WiFi-Operationen müssen serialisiert werden + if (!acquireMutex()){ + LOG_DEBUG(GwLog::ERROR,"GwWifi: mutex timeout in connectInternal"); + return false; + } WiFi.setAutoReconnect(false); //#102 wl_status_t rt=WiFi.begin(wifiSSID->asCString(),wifiPass->asCString()); + releaseMutex(); LOG_DEBUG(GwLog::LOG,"wifiClient connect returns %d",(int)rt); lastConnectStart=millis(); return true; @@ -104,8 +131,15 @@ void GwWifi::loop(){ if (lastConnectStart > now || (lastConnectStart + RETRY_MILLIS) < now) { LOG_DEBUG(GwLog::LOG,"wifiClient: retry connect to %s", wifiSSID->asCString()); - WiFi.disconnect(); - connectInternal(); + // CRITICAL SECTION: WiFi-Operationen müssen serialisiert werden + if (acquireMutex()){ + WiFi.disconnect(); + releaseMutex(); + connectInternal(); + } + else{ + LOG_DEBUG(GwLog::ERROR,"GwWifi: mutex timeout in loop"); + } } } else{ @@ -127,10 +161,39 @@ void GwWifi::loop(){ } } bool GwWifi::clientConnected(){ - return WiFi.status() == WL_CONNECTED; + // CRITICAL SECTION: WiFi.status() muss geschützt werden + if (!acquireMutex()){ + LOG_DEBUG(GwLog::ERROR,"GwWifi: mutex timeout in clientConnected"); + return false; // Conservative: nehme an, nicht verbunden + } + bool result = WiFi.status() == WL_CONNECTED; + releaseMutex(); + return result; }; bool GwWifi::connectClient(){ + // CRITICAL SECTION: Disconnect und Connect müssen atomar sein + if (!acquireMutex()){ + LOG_DEBUG(GwLog::ERROR,"GwWifi: mutex timeout in connectClient"); + return false; + } WiFi.disconnect(); + releaseMutex(); + return connectInternal(); +} + +bool GwWifi::connectClientAsync(){ + // Non-blocking version: Versuche Mutex zu nehmen, gib aber sofort auf + // Ideal für Tasks, die nicht blockieren dürfen + if (wifiMutex==nullptr){ + LOG_DEBUG(GwLog::ERROR,"GwWifi: mutex not initialized in connectClientAsync"); + return false; + } + if (xSemaphoreTake(wifiMutex, 0)!=pdTRUE){ + LOG_DEBUG(GwLog::LOG,"GwWifi: connectClientAsync skipped - WiFi busy"); + return false; // WiFi ist aktuell busy, versuche es später nochmal + } + WiFi.disconnect(); + xSemaphoreGive(wifiMutex); return connectInternal(); } diff --git a/lib/obp60task/OBP60Hardware.h b/lib/obp60task/OBP60Hardware.h index 8768e0f..6d038d3 100644 --- a/lib/obp60task/OBP60Hardware.h +++ b/lib/obp60task/OBP60Hardware.h @@ -1,7 +1,7 @@ // General hardware definitions // CAN and RS485 bus pin definitions see obp60task.h -#ifdef HARDWARE_V21 +#if defined HARDWARE_V20 || HARDWARE_V21 // Direction pin for RS485 NMEA0183 #define OBP_DIRECTION_PIN 18 // I2C diff --git a/lib/obp60task/OBP60Keypad.h b/lib/obp60task/OBP60Keypad.h index eafe3a2..d669ccb 100644 --- a/lib/obp60task/OBP60Keypad.h +++ b/lib/obp60task/OBP60Keypad.h @@ -58,7 +58,7 @@ void initKeys(CommonData &commonData) { commonData.keydata[5].h = height; } - #ifdef HARDWARE_V21 + #if defined HARDWARE_V20 || HARDWARE_V21 // Keypad functions for original OBP60 hardware int readKeypad(GwLog* logger, uint thSensitivity, bool use_syspage) { diff --git a/lib/obp60task/PageNavigation.cpp b/lib/obp60task/PageNavigation.cpp index bd4adf1..550d70c 100644 --- a/lib/obp60task/PageNavigation.cpp +++ b/lib/obp60task/PageNavigation.cpp @@ -4,9 +4,13 @@ #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 @@ -25,6 +29,7 @@ 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){ @@ -55,12 +60,15 @@ 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){ @@ -95,6 +103,13 @@ 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 @@ -344,6 +359,16 @@ 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 @@ -375,7 +400,8 @@ 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 (net.fetchAndDecompressJson(url)) { + 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 @@ -442,6 +468,12 @@ 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; + } } @@ -488,6 +520,9 @@ 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/extra_task.py b/lib/obp60task/extra_task.py index 28e9980..66ee3b1 100644 --- a/lib/obp60task/extra_task.py +++ b/lib/obp60task/extra_task.py @@ -1,12 +1,21 @@ # PlatformIO extra script for obp60task +import subprocess + +patching = False + epdtype = "unknown" pcbvers = "unknown" for x in env["BUILD_FLAGS"]: - if x.startswith("-D HARDWARE_"): + if not x.startswith('-D'): + continue + opt = x[2:].strip() + if opt.startswith("HARDWARE_"): pcbvers = x.split('_')[1] - if x.startswith("-D DISPLAY_"): + elif opt.startswith("DISPLAY_"): epdtype = x.split('_')[1] + elif opt == 'ENABLE_PATCHES': + patching = True propfilename = os.path.join(env["PROJECT_LIBDEPS_DIR"], env["PIOENV"], "GxEPD2/library.properties") properties = {} @@ -28,3 +37,20 @@ except: env["CPPDEFINES"].extend([("BOARD", env["BOARD"]), ("EPDTYPE", epdtype), ("PCBVERS", pcbvers), ("GXEPD2VERS", gxepd2vers)]) print("added hardware info to CPPDEFINES") + +if patching: + # apply patches to gateway code + print("applying gateway patches") + patchdir = os.path.join(os.path.dirname(script), "patches") + if not os.path.isdir(patchdir): + 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) + else: + print("no patches found") diff --git a/lib/obp60task/obp60task.cpp b/lib/obp60task/obp60task.cpp index f20bfe6..5db9e58 100644 --- a/lib/obp60task/obp60task.cpp +++ b/lib/obp60task/obp60task.cpp @@ -332,7 +332,7 @@ void OBP60Task(GwApi *api){ // return; GwLog *logger=api->getLogger(); GwConfigHandler *config=api->getConfig(); -#ifdef HARDWARE_V21 +#if defined HARDWARE_V20 || HARDWARE_V21 startLedTask(api); #endif PageList allPages; @@ -341,7 +341,7 @@ void OBP60Task(GwApi *api){ commonData.logger=logger; commonData.config=config; -#ifdef HARDWARE_V21 +#if defined HARDWARE_V20 || HARDWARE_V21 // Keyboard coordinates for page footer initKeys(commonData); #endif diff --git a/lib/obp60task/platformio.ini b/lib/obp60task/platformio.ini index 63468c3..03a5463 100644 --- a/lib/obp60task/platformio.ini +++ b/lib/obp60task/platformio.ini @@ -58,6 +58,7 @@ build_flags= # -D DISPLAY_GYE042A87 #alternativ E-Ink display from Genyo Optical, R10 2.2 ohm - medium # -D DISPLAY_SE0420NQ04 #alternativ E-Ink display from SID Technology, R10 2.2 ohm - bad (burn in effects) # -D DISPLAY_ZJY400300-042CAAMFGN #alternativ E-Ink display from ZZE Technology, R10 2.2 ohm - very good +# -D ENABLE_PATCHES #enable patching of gateway code ${env.build_flags} #CONFIG_ESP_TASK_WDT_TIMEOUT_S = 10 #Task Watchdog timeout period (seconds) [1...60] 5 default upload_port = /dev/ttyACM0 #OBP60 download via USB-C direct @@ -108,6 +109,7 @@ build_flags= #-D DISPLAY_ZJY400300-042CAAMFGN #alternativ E-Ink display from ZZE Technology, R10 2.2 ohm - very good -D LIPO_ACCU_1200 #Hardware extension, LiPo accu 3,7V 1200mAh -D VOLTAGE_SENSOR #Hardware extension, LiPo voltage sensor with two resistors + #-D ENABLE_PATCHES #enable patching of gateway code ${env.build_flags} upload_port = /dev/ttyUSB0 #OBP40 download via external USB/Serail converter upload_protocol = esptool #firmware upload via USB OTG seriell, by first upload need to set the ESP32-S3 in the upload mode with shortcut GND to Pin27