From 4706152022defdb2c83fc212bda7b348e7753e41 Mon Sep 17 00:00:00 2001 From: Jan Philipp Ecker Date: Fri, 8 Aug 2025 18:00:25 +0200 Subject: [PATCH] Introduces periodic Spoolman Healthcheck Introduces a spoolman healthcheck that is executed every 60 seconds. Also fixes a bug with the periodic wifi update. --- src/api.cpp | 395 +++++++++++++++++++++++++++------------------------ src/api.h | 2 +- src/config.h | 52 +++---- src/main.cpp | 13 +- src/nfc.cpp | 2 - 5 files changed, 249 insertions(+), 215 deletions(-) diff --git a/src/api.cpp b/src/api.cpp index 4b65f4b..8770fd2 100644 --- a/src/api.cpp +++ b/src/api.cpp @@ -5,7 +5,7 @@ #include #include "debug.h" -volatile spoolmanApiStateType spoolmanApiState = API_INIT; +volatile spoolmanApiStateType spoolmanApiState = API_IDLE; //bool spoolman_connected = false; String spoolmanUrl = ""; bool octoEnabled = false; @@ -14,6 +14,8 @@ String octoUrl = ""; String octoToken = ""; uint16_t remainingWeight = 0; bool spoolmanConnected = false; +bool spoolmanExtraFieldsChecked = false; +TaskHandle_t* apiTask; struct SendToApiParams { SpoolmanApiRequestType requestType; @@ -94,6 +96,11 @@ JsonDocument fetchSingleSpoolInfo(int spoolId) { void sendToApi(void *parameter) { HEAP_DEBUG_MESSAGE("sendToApi begin"); + // Wait until API is IDLE + while(spoolmanApiState != API_IDLE){ + Serial.println("Waiting!"); + yield(); + } spoolmanApiState = API_TRANSMITTING; SendToApiParams* params = (SendToApiParams*)parameter; @@ -236,7 +243,7 @@ bool updateSpoolTagId(String uidString, const char* payload) { 6144, // Stackgröße in Bytes (void*)params, // Parameter 0, // Priorität - NULL // Task-Handle (nicht benötigt) + apiTask // Task-Handle (nicht benötigt) ); updateDoc.clear(); @@ -282,7 +289,7 @@ uint8_t updateSpoolWeight(String spoolId, uint16_t weight) { 6144, // Stackgröße in Bytes (void*)params, // Parameter 0, // Priorität - NULL // Task-Handle (nicht benötigt) + apiTask // Task-Handle (nicht benötigt) ); updateDoc.clear(); @@ -319,17 +326,17 @@ uint8_t updateSpoolLocation(String spoolId, String location){ params->spoolsUrl = spoolsUrl; params->updatePayload = updatePayload; - if(spoolmanApiState == API_IDLE){ - // Erstelle die Task - BaseType_t result = xTaskCreate( - sendToApi, // Task-Funktion - "SendToApiTask", // Task-Name - 6144, // Stackgröße in Bytes - (void*)params, // Parameter - 0, // Priorität - NULL // Task-Handle (nicht benötigt) - ); + if(apiTask == nullptr){ + // Erstelle die Task + BaseType_t result = xTaskCreate( + sendToApi, // Task-Funktion + "SendToApiTask", // Task-Name + 6144, // Stackgröße in Bytes + (void*)params, // Parameter + 0, // Priorität + apiTask // Task-Handle + ); }else{ Serial.println("Not spawning new task, API still active!"); } @@ -374,7 +381,7 @@ bool updateSpoolOcto(int spoolId) { 6144, // Stackgröße in Bytes (void*)params, // Parameter 0, // Priorität - NULL // Task-Handle (nicht benötigt) + apiTask // Task-Handle (nicht benötigt) ); updateDoc.clear(); @@ -427,7 +434,7 @@ bool updateSpoolBambuData(String payload) { 6144, // Stackgröße in Bytes (void*)params, // Parameter 0, // Priorität - NULL // Task-Handle (nicht benötigt) + apiTask // Task-Handle (nicht benötigt) ); return true; @@ -435,198 +442,222 @@ bool updateSpoolBambuData(String payload) { // #### Spoolman init bool checkSpoolmanExtraFields() { - HTTPClient http; - String checkUrls[] = { - spoolmanUrl + apiUrl + "/field/spool", - spoolmanUrl + apiUrl + "/field/filament" - }; + // Only check extra fields if they have not been checked before + if(!spoolmanExtraFieldsChecked){ + HTTPClient http; + String checkUrls[] = { + spoolmanUrl + apiUrl + "/field/spool", + spoolmanUrl + apiUrl + "/field/filament" + }; - String spoolExtra[] = { - "nfc_id" - }; + String spoolExtra[] = { + "nfc_id" + }; - String filamentExtra[] = { - "nozzle_temperature", - "price_meter", - "price_gramm", - "bambu_setting_id", - "bambu_cali_id", - "bambu_idx", - "bambu_k", - "bambu_flow_ratio", - "bambu_max_volspeed" - }; + String filamentExtra[] = { + "nozzle_temperature", + "price_meter", + "price_gramm", + "bambu_setting_id", + "bambu_cali_id", + "bambu_idx", + "bambu_k", + "bambu_flow_ratio", + "bambu_max_volspeed" + }; - String spoolExtraFields[] = { - "{\"name\": \"NFC ID\"," - "\"key\": \"nfc_id\"," - "\"field_type\": \"text\"}" - }; + String spoolExtraFields[] = { + "{\"name\": \"NFC ID\"," + "\"key\": \"nfc_id\"," + "\"field_type\": \"text\"}" + }; - String filamentExtraFields[] = { - "{\"name\": \"Nozzle Temp\"," - "\"unit\": \"°C\"," - "\"field_type\": \"integer_range\"," - "\"default_value\": \"[190,230]\"," - "\"key\": \"nozzle_temperature\"}", + String filamentExtraFields[] = { + "{\"name\": \"Nozzle Temp\"," + "\"unit\": \"°C\"," + "\"field_type\": \"integer_range\"," + "\"default_value\": \"[190,230]\"," + "\"key\": \"nozzle_temperature\"}", - "{\"name\": \"Price/m\"," - "\"unit\": \"€\"," - "\"field_type\": \"float\"," - "\"key\": \"price_meter\"}", + "{\"name\": \"Price/m\"," + "\"unit\": \"€\"," + "\"field_type\": \"float\"," + "\"key\": \"price_meter\"}", + + "{\"name\": \"Price/g\"," + "\"unit\": \"€\"," + "\"field_type\": \"float\"," + "\"key\": \"price_gramm\"}", + + "{\"name\": \"Bambu Setting ID\"," + "\"field_type\": \"text\"," + "\"key\": \"bambu_setting_id\"}", + + "{\"name\": \"Bambu Cali ID\"," + "\"field_type\": \"text\"," + "\"key\": \"bambu_cali_id\"}", + + "{\"name\": \"Bambu Filament IDX\"," + "\"field_type\": \"text\"," + "\"key\": \"bambu_idx\"}", + + "{\"name\": \"Bambu k\"," + "\"field_type\": \"float\"," + "\"key\": \"bambu_k\"}", + + "{\"name\": \"Bambu Flow Ratio\"," + "\"field_type\": \"float\"," + "\"key\": \"bambu_flow_ratio\"}", + + "{\"name\": \"Bambu Max Vol. Speed\"," + "\"unit\": \"mm3/s\"," + "\"field_type\": \"integer\"," + "\"default_value\": \"12\"," + "\"key\": \"bambu_max_volspeed\"}" + }; + + Serial.println("Überprüfe Extrafelder..."); + + int urlLength = sizeof(checkUrls) / sizeof(checkUrls[0]); + + for (uint8_t i = 0; i < urlLength; i++) { + Serial.println(); + Serial.println("-------- Prüfe Felder für "+checkUrls[i]+" --------"); + http.begin(checkUrls[i]); + int httpCode = http.GET(); - "{\"name\": \"Price/g\"," - "\"unit\": \"€\"," - "\"field_type\": \"float\"," - "\"key\": \"price_gramm\"}", + if (httpCode == HTTP_CODE_OK) { + String payload = http.getString(); + JsonDocument doc; + DeserializationError error = deserializeJson(doc, payload); + if (!error) { + String* extraFields; + String* extraFieldData; + u16_t extraLength; - "{\"name\": \"Bambu Setting ID\"," - "\"field_type\": \"text\"," - "\"key\": \"bambu_setting_id\"}", - - "{\"name\": \"Bambu Cali ID\"," - "\"field_type\": \"text\"," - "\"key\": \"bambu_cali_id\"}", - - "{\"name\": \"Bambu Filament IDX\"," - "\"field_type\": \"text\"," - "\"key\": \"bambu_idx\"}", - - "{\"name\": \"Bambu k\"," - "\"field_type\": \"float\"," - "\"key\": \"bambu_k\"}", - - "{\"name\": \"Bambu Flow Ratio\"," - "\"field_type\": \"float\"," - "\"key\": \"bambu_flow_ratio\"}", - - "{\"name\": \"Bambu Max Vol. Speed\"," - "\"unit\": \"mm3/s\"," - "\"field_type\": \"integer\"," - "\"default_value\": \"12\"," - "\"key\": \"bambu_max_volspeed\"}" - }; - - Serial.println("Überprüfe Extrafelder..."); - - int urlLength = sizeof(checkUrls) / sizeof(checkUrls[0]); - - for (uint8_t i = 0; i < urlLength; i++) { - Serial.println(); - Serial.println("-------- Prüfe Felder für "+checkUrls[i]+" --------"); - http.begin(checkUrls[i]); - int httpCode = http.GET(); - - if (httpCode == HTTP_CODE_OK) { - String payload = http.getString(); - JsonDocument doc; - DeserializationError error = deserializeJson(doc, payload); - if (!error) { - String* extraFields; - String* extraFieldData; - u16_t extraLength; - - if (i == 0) { - extraFields = spoolExtra; - extraFieldData = spoolExtraFields; - extraLength = sizeof(spoolExtra) / sizeof(spoolExtra[0]); - } else { - extraFields = filamentExtra; - extraFieldData = filamentExtraFields; - extraLength = sizeof(filamentExtra) / sizeof(filamentExtra[0]); - } - - for (uint8_t s = 0; s < extraLength; s++) { - bool found = false; - for (JsonObject field : doc.as()) { - if (field["key"].is() && field["key"] == extraFields[s]) { - Serial.println("Feld gefunden: " + extraFields[s]); - found = true; - break; - } + if (i == 0) { + extraFields = spoolExtra; + extraFieldData = spoolExtraFields; + extraLength = sizeof(spoolExtra) / sizeof(spoolExtra[0]); + } else { + extraFields = filamentExtra; + extraFieldData = filamentExtraFields; + extraLength = sizeof(filamentExtra) / sizeof(filamentExtra[0]); } - if (!found) { - Serial.println("Feld nicht gefunden: " + extraFields[s]); - // Extrafeld hinzufügen - http.begin(checkUrls[i] + "/" + extraFields[s]); - http.addHeader("Content-Type", "application/json"); - int httpCode = http.POST(extraFieldData[s]); + for (uint8_t s = 0; s < extraLength; s++) { + bool found = false; + for (JsonObject field : doc.as()) { + if (field["key"].is() && field["key"] == extraFields[s]) { + Serial.println("Feld gefunden: " + extraFields[s]); + found = true; + break; + } + } + if (!found) { + Serial.println("Feld nicht gefunden: " + extraFields[s]); - if (httpCode > 0) { - // Antwortscode und -nachricht abrufen - String response = http.getString(); - //Serial.println("HTTP-Code: " + String(httpCode)); - //Serial.println("Antwort: " + response); - if (httpCode != HTTP_CODE_OK) { + // Extrafeld hinzufügen + http.begin(checkUrls[i] + "/" + extraFields[s]); + http.addHeader("Content-Type", "application/json"); + int httpCode = http.POST(extraFieldData[s]); + if (httpCode > 0) { + // Antwortscode und -nachricht abrufen + String response = http.getString(); + //Serial.println("HTTP-Code: " + String(httpCode)); + //Serial.println("Antwort: " + response); + if (httpCode != HTTP_CODE_OK) { + + return false; + } + } else { + // Fehler beim Senden der Anfrage + Serial.println("Fehler beim Senden der Anfrage: " + String(http.errorToString(httpCode))); return false; } - } else { - // Fehler beim Senden der Anfrage - Serial.println("Fehler beim Senden der Anfrage: " + String(http.errorToString(httpCode))); - return false; + //http.end(); } - //http.end(); + yield(); + vTaskDelay(100 / portTICK_PERIOD_MS); } - yield(); - vTaskDelay(100 / portTICK_PERIOD_MS); } + doc.clear(); } - doc.clear(); } + + Serial.println("-------- ENDE Prüfe Felder --------"); + Serial.println(); + + http.end(); + + spoolmanExtraFieldsChecked = true; + return true; + }else{ + return true; } - - Serial.println("-------- ENDE Prüfe Felder --------"); - Serial.println(); - - http.end(); - - return true; } -bool checkSpoolmanInstance(const String& url) { +bool checkSpoolmanInstance() { HTTPClient http; - String healthUrl = url + apiUrl + "/health"; + bool returnValue = false; - Serial.print("Überprüfe Spoolman-Instanz unter: "); - Serial.println(healthUrl); + // Only do the spoolman instance check if there is no active API request going on + if(spoolmanApiState == API_IDLE){ + spoolmanApiState = API_TRANSMITTING; + String healthUrl = spoolmanUrl + apiUrl + "/health"; - http.begin(healthUrl); - int httpCode = http.GET(); + Serial.print("Checking spoolman instance: "); + Serial.println(healthUrl); - if (httpCode > 0) { - if (httpCode == HTTP_CODE_OK) { - String payload = http.getString(); - JsonDocument doc; - DeserializationError error = deserializeJson(doc, payload); - if (!error && doc["status"].is()) { - const char* status = doc["status"]; - http.end(); + http.begin(healthUrl); + int httpCode = http.GET(); - if (!checkSpoolmanExtraFields()) { - Serial.println("Fehler beim Überprüfen der Extrafelder."); + if (httpCode > 0) { + if (httpCode == HTTP_CODE_OK) { + String payload = http.getString(); + JsonDocument doc; + DeserializationError error = deserializeJson(doc, payload); + if (!error && doc["status"].is()) { + const char* status = doc["status"]; + http.end(); - // TBD - oledShowMessage("Spoolman Error creating Extrafields"); - vTaskDelay(2000 / portTICK_PERIOD_MS); - - return false; + if (!checkSpoolmanExtraFields()) { + Serial.println("Fehler beim Überprüfen der Extrafelder."); + + // TBD + oledShowMessage("Spoolman Error creating Extrafields"); + vTaskDelay(2000 / portTICK_PERIOD_MS); + + return false; + } + + spoolmanApiState = API_IDLE; + oledShowTopRow(); + spoolmanConnected = true; + returnValue = strcmp(status, "healthy") == 0; + }else{ + spoolmanConnected = false; } - spoolmanApiState = API_IDLE; - oledShowTopRow(); - spoolmanConnected = true; - return strcmp(status, "healthy") == 0; + doc.clear(); + }else{ + spoolmanConnected = false; } - - doc.clear(); + } else { + spoolmanConnected = false; + Serial.println("Error contacting spoolman instance! HTTP Code: " + String(httpCode)); } - } else { - Serial.println("Error contacting spoolman instance! HTTP Code: " + String(httpCode)); + http.end(); + returnValue = false; + spoolmanApiState = API_IDLE; + }else{ + // If the check is skipped, return the previous status + Serial.println("Skipping spoolman healthcheck, API is active."); + returnValue = spoolmanConnected; } - http.end(); - return false; + Serial.println("Healthcheck completed!"); + return returnValue; } bool saveSpoolmanUrl(const String& url, bool octoOn, const String& octo_url, const String& octoTk) { @@ -639,12 +670,13 @@ bool saveSpoolmanUrl(const String& url, bool octoOn, const String& octo_url, con preferences.end(); //TBD: This could be handled nicer in the future + spoolmanExtraFieldsChecked = false; spoolmanUrl = url; octoEnabled = octoOn; octoUrl = octo_url; octoToken = octoTk; - return true; + return checkSpoolmanInstance(); } String loadSpoolmanUrl() { @@ -664,15 +696,10 @@ String loadSpoolmanUrl() { bool initSpoolman() { oledShowProgressBar(3, 7, DISPLAY_BOOT_TEXT, "Spoolman init"); spoolmanUrl = loadSpoolmanUrl(); - spoolmanUrl.trim(); - if (spoolmanUrl == "") { - Serial.println("Keine Spoolman-URL gefunden."); - return false; - } - - bool success = checkSpoolmanInstance(spoolmanUrl); + + bool success = checkSpoolmanInstance(); if (!success) { - Serial.println("Spoolman nicht erreichbar."); + Serial.println("Spoolman not available"); return false; } diff --git a/src/api.h b/src/api.h index 23fb6d8..4ad427f 100644 --- a/src/api.h +++ b/src/api.h @@ -29,7 +29,7 @@ extern String octoUrl; extern String octoToken; extern bool spoolmanConnected; -bool checkSpoolmanInstance(const String& url); +bool checkSpoolmanInstance(); bool saveSpoolmanUrl(const String& url, bool octoOn, const String& octoWh, const String& octoTk); String loadSpoolmanUrl(); // Neue Funktion zum Laden der URL bool checkSpoolmanExtraFields(); // Neue Funktion zum Überprüfen der Extrafelder diff --git a/src/config.h b/src/config.h index a6468a7..cd79e38 100644 --- a/src/config.h +++ b/src/config.h @@ -3,37 +3,39 @@ #include -#define BAMBU_DEFAULT_AUTOSEND_TIME 60 +#define BAMBU_DEFAULT_AUTOSEND_TIME 60 +#define NVS_NAMESPACE_API "api" +#define NVS_KEY_SPOOLMAN_URL "spoolmanUrl" +#define NVS_KEY_OCTOPRINT_ENABLED "octoEnabled" +#define NVS_KEY_OCTOPRINT_URL "octoUrl" +#define NVS_KEY_OCTOPRINT_TOKEN "octoToken" -#define NVS_NAMESPACE_API "api" -#define NVS_KEY_SPOOLMAN_URL "spoolmanUrl" -#define NVS_KEY_OCTOPRINT_ENABLED "octoEnabled" -#define NVS_KEY_OCTOPRINT_URL "octoUrl" -#define NVS_KEY_OCTOPRINT_TOKEN "octoToken" +#define NVS_NAMESPACE_BAMBU "bambu" +#define NVS_KEY_BAMBU_IP "bambuIp" +#define NVS_KEY_BAMBU_ACCESSCODE "bambuCode" +#define NVS_KEY_BAMBU_SERIAL "bambuSerial" +#define NVS_KEY_BAMBU_AUTOSEND_ENABLE "autosendEnable" +#define NVS_KEY_BAMBU_AUTOSEND_TIME "autosendTime" -#define NVS_NAMESPACE_BAMBU "bambu" -#define NVS_KEY_BAMBU_IP "bambuIp" -#define NVS_KEY_BAMBU_ACCESSCODE "bambuCode" -#define NVS_KEY_BAMBU_SERIAL "bambuSerial" -#define NVS_KEY_BAMBU_AUTOSEND_ENABLE "autosendEnable" -#define NVS_KEY_BAMBU_AUTOSEND_TIME "autosendTime" +#define NVS_NAMESPACE_SCALE "scale" +#define NVS_KEY_CALIBRATION "cal_value" +#define NVS_KEY_AUTOTARE "auto_tare" +#define SCALE_DEFAULT_CALIBRATION_VALUE 430.0f; -#define NVS_NAMESPACE_SCALE "scale" -#define NVS_KEY_CALIBRATION "cal_value" -#define NVS_KEY_AUTOTARE "auto_tare" -#define SCALE_DEFAULT_CALIBRATION_VALUE 430.0f; +#define BAMBU_USERNAME "bblp" -#define BAMBU_USERNAME "bblp" - -#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin) -#define SCREEN_ADDRESS 0x3CU // See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32 -#define SCREEN_WIDTH 128U -#define SCREEN_HEIGHT 64U -#define SCREEN_TOP_BAR_HEIGHT 16U -#define SCREEN_PROGRESS_BAR_HEIGHT 12U -#define DISPLAY_BOOT_TEXT "FilaMan" +#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin) +#define SCREEN_ADDRESS 0x3CU // See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32 +#define SCREEN_WIDTH 128U +#define SCREEN_HEIGHT 64U +#define SCREEN_TOP_BAR_HEIGHT 16U +#define SCREEN_PROGRESS_BAR_HEIGHT 12U +#define DISPLAY_BOOT_TEXT "FilaMan" +#define WIFI_CHECK_INTERVAL 60000U +#define DISPLAY_UPDATE_INTERVAL 1000U +#define SPOOLMAN_HEALTHCHECK_INTERVAL 60000U extern const uint8_t PN532_IRQ; extern const uint8_t PN532_RESET; diff --git a/src/main.cpp b/src/main.cpp index d301583..2b75b6c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -97,7 +97,8 @@ int16_t lastWeight = 0; // WIFI check variables unsigned long lastWifiCheckTime = 0; -const unsigned long wifiCheckInterval = 60000; // Überprüfe alle 60 Sekunden (60000 ms) +unsigned long lastTopRowUpdateTime = 0; +unsigned long lastSpoolmanHealcheckTime = 0; // Button debounce variables unsigned long lastButtonPress = 0; @@ -115,17 +116,23 @@ void loop() { } // Überprüfe regelmäßig die WLAN-Verbindung - if (intervalElapsed(currentMillis, lastWifiCheckTime, wifiCheckInterval)) + if (intervalElapsed(currentMillis, lastWifiCheckTime, WIFI_CHECK_INTERVAL)) { checkWiFiConnection(); } // Periodic display update - if (intervalElapsed(currentMillis, lastWifiCheckTime, 1000)) + if (intervalElapsed(currentMillis, lastTopRowUpdateTime, DISPLAY_UPDATE_INTERVAL)) { oledShowTopRow(); } + // Periodic spoolman health check + if (intervalElapsed(currentMillis, lastSpoolmanHealcheckTime, SPOOLMAN_HEALTHCHECK_INTERVAL)) + { + checkSpoolmanInstance(); + } + // Wenn Bambu auto set Spool aktiv if (bambuCredentials.autosend_enable && autoSetToBambuSpoolId > 0) { diff --git a/src/nfc.cpp b/src/nfc.cpp index 83dfb5e..5a8bdc1 100644 --- a/src/nfc.cpp +++ b/src/nfc.cpp @@ -241,8 +241,6 @@ bool decodeNdefAndReturnJson(const byte* encodedMessage) { Serial.println("SPOOL-ID gefunden: " + doc["sm_id"].as()); activeSpoolId = doc["sm_id"].as(); lastSpoolId = activeSpoolId; - - Serial.println("Api state: " + String(spoolmanApiState)); } else if(doc["location"].is() && doc["location"] != "") {