Compare commits
	
		
			27 Commits
		
	
	
		
			v1.5.12-be
			...
			v1.5.12-be
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 1f21954703 | |||
| 3e59ce1366 | |||
| 1f880fc8f1 | |||
| 69bf5f90fa | |||
| 382caeaced | |||
| 47bdf022ec | |||
| 02febfa943 | |||
| 257f4df800 | |||
| bff6e72219 | |||
| 26e905050d | |||
| 046f770a52 | |||
| 2587227e78 | |||
| 0f19dc4f46 | |||
| 721dac1ead | |||
| 08abd1a37f | |||
| da78861613 | |||
| 9231a303f3 | |||
| d12e766cd7 | |||
| af7bc23703 | |||
| de39892f64 | |||
| 40cb835e51 | |||
| eb9d9e74f4 | |||
| d8af3f45e5 | |||
| 96bb8f9c7c | |||
| b8b6893cd0 | |||
| 0a246c1fe4 | |||
| 965ea5da1e | 
							
								
								
									
										63
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										63
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -1,5 +1,68 @@ | ||||
| # Changelog | ||||
|  | ||||
| ## [1.5.12-beta14] - 2025-08-29 | ||||
| ### Changed | ||||
| - update platformio.ini for beta version v1.5.12-beta14 | ||||
| - optimize JSON payload structure and enhance NFC tag validation process | ||||
|  | ||||
|  | ||||
| ## [1.5.12-beta13] - 2025-08-29 | ||||
| ### Changed | ||||
| - update platformio.ini for beta version v1.5.12-beta13 | ||||
| - reorganize clearUserDataArea function for improved clarity and safety | ||||
|  | ||||
|  | ||||
| ## [1.5.12-beta12] - 2025-08-29 | ||||
| ### Changed | ||||
| - update platformio.ini for beta version v1.5.12-beta12 | ||||
|  | ||||
| ### Fixed | ||||
| - reset NFC state on API send failure to allow retry | ||||
| - update createdFilamentId reset value to 65535 for better task handling | ||||
| - update createdVendorId reset value to 65535 for improved API handling | ||||
|  | ||||
|  | ||||
| ## [1.5.12-beta11] - 2025-08-29 | ||||
| ### Changed | ||||
| - update platformio.ini for beta version v1.5.12-beta11 | ||||
|  | ||||
| ### Fixed | ||||
| - update spoolman ID reset values to 65535 for better API response detection | ||||
|  | ||||
|  | ||||
| ## [1.5.12-beta10] - 2025-08-29 | ||||
| ### Changed | ||||
| - update platformio.ini for beta version v1.5.12-beta10 | ||||
| - streamline task creation in checkVendor and checkFilament functions | ||||
|  | ||||
|  | ||||
| ## [1.5.12-beta9] - 2025-08-29 | ||||
| ### Added | ||||
| - update vendor and filament ID handling to use NULL and add delays for stability | ||||
|  | ||||
| ### Changed | ||||
| - update platformio.ini for beta version v1.5.12-beta9 | ||||
|  | ||||
|  | ||||
| ## [1.5.12-beta8] - 2025-08-29 | ||||
| ### Added | ||||
| - add delay to ensure proper setting of vendor and filament IDs after API state changes | ||||
|  | ||||
| ### Changed | ||||
| - update platformio.ini for beta version v1.5.12-beta8 | ||||
|  | ||||
| ### Fixed | ||||
| - correct color_hex key usage and comment out unused date fields in spool creation | ||||
|  | ||||
|  | ||||
| ## [1.5.12-beta7] - 2025-08-29 | ||||
| ### Changed | ||||
| - update platformio.ini for beta version v1.5.12-beta7 | ||||
|  | ||||
| ### Fixed | ||||
| - improve API state handling and vendor name formatting | ||||
|  | ||||
|  | ||||
| ## [1.5.12-beta6] - 2025-08-29 | ||||
| ### Changed | ||||
| - update platformio.ini for beta version v1.5.12-beta6 | ||||
|   | ||||
| @@ -9,7 +9,7 @@ | ||||
| ; https://docs.platformio.org/page/projectconf.html | ||||
|  | ||||
| [common] | ||||
| version = "1.5.12-beta6" | ||||
| version = "1.5.12-beta14" | ||||
| to_old_version = "1.5.0" | ||||
|  | ||||
| ## | ||||
|   | ||||
							
								
								
									
										141
									
								
								src/api.cpp
									
									
									
									
									
								
							
							
						
						
									
										141
									
								
								src/api.cpp
									
									
									
									
									
								
							| @@ -5,6 +5,7 @@ | ||||
| #include <Preferences.h> | ||||
| #include "debug.h" | ||||
| #include "scale.h" | ||||
| #include "nfc.h" | ||||
| #include <time.h> | ||||
| volatile spoolmanApiStateType spoolmanApiState = API_IDLE; | ||||
|  | ||||
| @@ -121,7 +122,7 @@ void sendToApi(void *parameter) { | ||||
|  | ||||
|     // Wait until API is IDLE | ||||
|     while(spoolmanApiState != API_IDLE){ | ||||
|         Serial.println("Waiting!"); | ||||
|         vTaskDelay(100 / portTICK_PERIOD_MS); | ||||
|         yield(); | ||||
|     } | ||||
|     spoolmanApiState = API_TRANSMITTING; | ||||
| @@ -151,7 +152,7 @@ void sendToApi(void *parameter) { | ||||
|     else httpCode = http.PUT(updatePayload); | ||||
|  | ||||
|     if (httpCode == HTTP_CODE_OK) { | ||||
|         Serial.println("Spoolman erfolgreich aktualisiert"); | ||||
|         Serial.println("Spoolman Abfrage erfolgreich"); | ||||
|  | ||||
|         // Restgewicht der Spule auslesen | ||||
|         String payload = http.getString(); | ||||
| @@ -345,9 +346,8 @@ void sendToApi(void *parameter) { | ||||
|             break; | ||||
|         } | ||||
|         Serial.println("Fehler beim Senden an Spoolman! HTTP Code: " + String(httpCode)); | ||||
|  | ||||
|         // TBD: really required? | ||||
|         vTaskDelay(2000 / portTICK_PERIOD_MS); | ||||
|         nfcReaderState = NFC_IDLE; // Reset NFC state to allow retry | ||||
|     } | ||||
|  | ||||
|     http.end(); | ||||
| @@ -617,7 +617,7 @@ uint16_t createVendor(String vendor) { | ||||
|     // Create new vendor in Spoolman database using task system | ||||
|     // Note: Due to async nature, the ID will be stored in createdVendorId global variable | ||||
|     // Note: This function assumes that the caller has already ensured API is IDLE | ||||
|     createdVendorId = 0; // Reset previous value | ||||
|     createdVendorId = 65535; // Reset previous value | ||||
|      | ||||
|     String spoolsUrl = spoolmanUrl + apiUrl + "/vendor"; | ||||
|     Serial.print("Create vendor with URL: "); | ||||
| @@ -667,8 +667,8 @@ uint16_t createVendor(String vendor) { | ||||
|      | ||||
|     // Wait for task completion and return the created vendor ID | ||||
|     // Note: createdVendorId will be set by sendToApi when response is received | ||||
|     while(spoolmanApiState != API_IDLE) { | ||||
|         vTaskDelay(100 / portTICK_PERIOD_MS); | ||||
|     while(createdVendorId == 65535) { | ||||
|         vTaskDelay(50 / portTICK_PERIOD_MS); | ||||
|     } | ||||
|      | ||||
|     return createdVendorId; | ||||
| @@ -676,9 +676,12 @@ uint16_t createVendor(String vendor) { | ||||
|  | ||||
| uint16_t checkVendor(String vendor) { | ||||
|     // Check if vendor exists using task system | ||||
|     foundVendorId = 0; // Reset previous value | ||||
|     foundVendorId = 65535; // Reset to invalid value to detect when API response is received | ||||
|      | ||||
|     String spoolsUrl = spoolmanUrl + apiUrl + "/vendor?name=" + vendor; | ||||
|     String vendorName = vendor; | ||||
|     vendorName.trim(); | ||||
|     vendorName.replace(" ", "+"); | ||||
|     String spoolsUrl = spoolmanUrl + apiUrl + "/vendor?name=" + vendorName; | ||||
|     Serial.print("Check vendor with URL: "); | ||||
|     Serial.println(spoolsUrl); | ||||
|  | ||||
| @@ -693,25 +696,25 @@ uint16_t checkVendor(String vendor) { | ||||
|     params->updatePayload = ""; // Empty for GET request | ||||
|  | ||||
|     // Check if API is idle before creating task | ||||
|     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) | ||||
|         ); | ||||
|     } else { | ||||
|         Serial.println("Not spawning new task, API still active!"); | ||||
|         delete params; | ||||
|         return 0; | ||||
|     while (spoolmanApiState != API_IDLE) | ||||
|     { | ||||
|         vTaskDelay(100 / portTICK_PERIOD_MS); | ||||
|     } | ||||
|      | ||||
|     // Wait for task completion | ||||
|     while(spoolmanApiState != API_IDLE) { | ||||
|         vTaskDelay(100 / portTICK_PERIOD_MS); | ||||
|     // 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) | ||||
|     ); | ||||
|      | ||||
|     // Wait until foundVendorId is updated by the API response (not 65535 anymore) | ||||
|     while (foundVendorId == 65535) | ||||
|     { | ||||
|         vTaskDelay(50 / portTICK_PERIOD_MS); | ||||
|     } | ||||
|  | ||||
|     // Check if vendor was found | ||||
| @@ -737,7 +740,7 @@ uint16_t createFilament(uint16_t vendorId, const JsonDocument& payload) { | ||||
|     // Create new filament in Spoolman database using task system | ||||
|     // Note: Due to async nature, the ID will be stored in createdFilamentId global variable | ||||
|     // Note: This function assumes that the caller has already ensured API is IDLE | ||||
|     createdFilamentId = 0; // Reset previous value | ||||
|     createdFilamentId = 65535; // Reset previous value | ||||
|      | ||||
|     String spoolsUrl = spoolmanUrl + apiUrl + "/filament"; | ||||
|     Serial.print("Create filament with URL: "); | ||||
| @@ -745,16 +748,16 @@ uint16_t createFilament(uint16_t vendorId, const JsonDocument& payload) { | ||||
|  | ||||
|     // Create JSON payload for filament creation | ||||
|     JsonDocument filamentDoc; | ||||
|     filamentDoc["name"] = payload["name"].as<String>(); | ||||
|     filamentDoc["name"] = payload["color_name"].as<String>(); | ||||
|     filamentDoc["vendor_id"] = String(vendorId); | ||||
|     filamentDoc["material"] = payload["type"].as<String>(); | ||||
|     filamentDoc["density"] = (payload["density"].is<String>() && payload["density"].as<String>().length() > 0) ? payload["density"].as<String>() : "1.24"; | ||||
|     filamentDoc["diameter"] = (payload["diameter"].is<String>() && payload["diameter"].as<String>().length() > 0) ? payload["diameter"].as<String>() : "1.75"; | ||||
|     filamentDoc["weight"] = payload["weight"].as<String>(); | ||||
|     filamentDoc["weight"] = String(weight); | ||||
|     filamentDoc["spool_weight"] = payload["spool_weight"].as<String>(); | ||||
|     filamentDoc["article_number"] = payload["artnr"].as<String>(); | ||||
|     filamentDoc["extruder_temp"] = payload["extruder_temp"].is<String>() ? payload["extruder_temp"].as<String>() : ""; | ||||
|     filamentDoc["bed_temp"] = payload["bed_temp"].is<String>() ? payload["bed_temp"].as<String>() : ""; | ||||
|     filamentDoc["settings_extruder_temp"] = payload["extruder_temp"].is<String>() ? payload["extruder_temp"].as<String>() : ""; | ||||
|     filamentDoc["settings_bed_temp"] = payload["bed_temp"].is<String>() ? payload["bed_temp"].as<String>() : ""; | ||||
|      | ||||
|     if (payload["artnr"].is<String>()) | ||||
|     { | ||||
| @@ -772,7 +775,7 @@ uint16_t createFilament(uint16_t vendorId, const JsonDocument& payload) { | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         filamentDoc["color_hex"] = (payload["color"].is<String>() && payload["color"].as<String>().length() >= 6) ? payload["color"].as<String>() : "FFFFFF"; | ||||
|         filamentDoc["color_hex"] = (payload["color_hex"].is<String>() && payload["color_hex"].as<String>().length() >= 6) ? payload["color_hex"].as<String>() : "FFFFFF"; | ||||
|     } | ||||
|  | ||||
|     String filamentPayload; | ||||
| @@ -812,8 +815,8 @@ uint16_t createFilament(uint16_t vendorId, const JsonDocument& payload) { | ||||
|      | ||||
|     // Wait for task completion and return the created filament ID | ||||
|     // Note: createdFilamentId will be set by sendToApi when response is received | ||||
|     while(spoolmanApiState != API_IDLE) { | ||||
|         vTaskDelay(100 / portTICK_PERIOD_MS); | ||||
|     while(createdFilamentId == 65535) { | ||||
|         vTaskDelay(50 / portTICK_PERIOD_MS); | ||||
|     } | ||||
|      | ||||
|     return createdFilamentId; | ||||
| @@ -821,7 +824,7 @@ uint16_t createFilament(uint16_t vendorId, const JsonDocument& payload) { | ||||
|  | ||||
| uint16_t checkFilament(uint16_t vendorId, const JsonDocument& payload) { | ||||
|     // Check if filament exists using task system | ||||
|     foundFilamentId = 0; // Reset previous value | ||||
|     foundFilamentId = 65535; // Reset to invalid value to detect when API response is received | ||||
|  | ||||
|     String spoolsUrl = spoolmanUrl + apiUrl + "/filament?vendor.id=" + String(vendorId) + "&external_id=" + String(payload["artnr"].as<String>()); | ||||
|     Serial.print("Check filament with URL: "); | ||||
| @@ -837,26 +840,19 @@ uint16_t checkFilament(uint16_t vendorId, const JsonDocument& payload) { | ||||
|     params->spoolsUrl = spoolsUrl; | ||||
|     params->updatePayload = ""; // Empty for GET request | ||||
|  | ||||
|     // Check if API is idle before creating task | ||||
|     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) | ||||
|         ); | ||||
|     } else { | ||||
|         Serial.println("Not spawning new task, API still active!"); | ||||
|         delete params; | ||||
|         return 0; | ||||
|     } | ||||
|      // 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) | ||||
|     ); | ||||
|      | ||||
|     // Wait for task completion | ||||
|     while(spoolmanApiState != API_IDLE) { | ||||
|         vTaskDelay(100 / portTICK_PERIOD_MS); | ||||
|     // Wait until foundFilamentId is updated by the API response (not 65535 anymore) | ||||
|     while (foundFilamentId == 65535) { | ||||
|         vTaskDelay(50 / portTICK_PERIOD_MS); | ||||
|     } | ||||
|  | ||||
|     // Check if filament was found | ||||
| @@ -882,22 +878,21 @@ uint16_t createSpool(uint16_t vendorId, uint16_t filamentId, JsonDocument& paylo | ||||
|     // Create new spool in Spoolman database using task system | ||||
|     // Note: Due to async nature, the ID will be stored in createdSpoolId global variable | ||||
|     // Note: This function assumes that the caller has already ensured API is IDLE | ||||
|     createdSpoolId = 0; // Reset previous value | ||||
|     createdSpoolId = 65535; // Reset to invalid value to detect when API response is received | ||||
|      | ||||
|     String spoolsUrl = spoolmanUrl + apiUrl + "/spool"; | ||||
|     Serial.print("Create spool with URL: "); | ||||
|     Serial.println(spoolsUrl); | ||||
|     String currentDate = getCurrentDateISO8601(); | ||||
|     //String currentDate = getCurrentDateISO8601(); | ||||
|  | ||||
|     // Create JSON payload for spool creation | ||||
|     JsonDocument spoolDoc; | ||||
|     spoolDoc["first_used"] = String(currentDate); | ||||
|     spoolDoc["last_used"] = String(currentDate); | ||||
|     //spoolDoc["first_used"] = String(currentDate); | ||||
|     //spoolDoc["last_used"] = String(currentDate); | ||||
|     spoolDoc["filament_id"] = String(filamentId); | ||||
|     spoolDoc["initial_weight"] = weight > 10 ? String(weight) : "1000"; | ||||
|     spoolDoc["initial_weight"] = weight > 10 ? String(weight-payload["spool_weight"].as<int>()) : "1000"; | ||||
|     spoolDoc["spool_weight"] = (payload["spool_weight"].is<String>() && payload["spool_weight"].as<String>().length() > 0) ? payload["spool_weight"].as<String>() : "180"; | ||||
|     spoolDoc["remaining_weight"] = (payload["weight"].is<String>() && payload["weight"].as<String>().length() > 0) ? payload["weight"].as<String>() : "1000"; | ||||
|     spoolDoc["used_weight"] = "0"; | ||||
|     spoolDoc["lot_nr"] = (payload["lotnr"].is<String>() && payload["lotnr"].as<String>().length() > 0) ? payload["lotnr"].as<String>() : ""; | ||||
|     spoolDoc["comment"] = "automatically generated"; | ||||
|     spoolDoc["extra"]["nfc_id"] = "\"" + uidString + "\""; | ||||
| @@ -937,16 +932,34 @@ uint16_t createSpool(uint16_t vendorId, uint16_t filamentId, JsonDocument& paylo | ||||
|      | ||||
|     // Wait for task completion and return the created spool ID | ||||
|     // Note: createdSpoolId will be set by sendToApi when response is received | ||||
|     while(spoolmanApiState != API_IDLE) { | ||||
|         vTaskDelay(100 / portTICK_PERIOD_MS); | ||||
|     while(createdSpoolId == 65535) { | ||||
|         vTaskDelay(50 / portTICK_PERIOD_MS); | ||||
|     } | ||||
|  | ||||
|     // Write data to tag with startWriteJsonToTag | ||||
|     // void startWriteJsonToTag(const bool isSpoolTag, const char* payload); | ||||
|     payload["sm_id"].set(String(createdSpoolId)); | ||||
|      | ||||
|     // Create optimized JSON structure with sm_id at the beginning for fast-path detection | ||||
|     JsonDocument optimizedPayload; | ||||
|     optimizedPayload["sm_id"] = String(createdSpoolId);  // Place sm_id first for fast scanning | ||||
|      | ||||
|     // Copy all other fields from original payload (excluding sm_id if it exists) | ||||
|     for (JsonPair kv : payload.as<JsonObject>()) { | ||||
|         if (strcmp(kv.key().c_str(), "sm_id") != 0) {  // Skip sm_id to avoid duplication | ||||
|             optimizedPayload[kv.key()] = kv.value(); | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     String payloadString; | ||||
|     serializeJson(payload, payloadString); | ||||
|     serializeJson(optimizedPayload, payloadString); | ||||
|      | ||||
|     Serial.println("Optimized JSON with sm_id first:"); | ||||
|     Serial.println(payloadString); | ||||
|      | ||||
|     optimizedPayload.clear(); | ||||
|      | ||||
|     nfcReaderState = NFC_IDLE; | ||||
|     vTaskDelay(50 / portTICK_PERIOD_MS); | ||||
|     startWriteJsonToTag(true, payloadString.c_str()); | ||||
|  | ||||
|     return createdSpoolId; | ||||
|   | ||||
							
								
								
									
										865
									
								
								src/nfc.cpp
									
									
									
									
									
								
							
							
						
						
									
										865
									
								
								src/nfc.cpp
									
									
									
									
									
								
							| @@ -99,9 +99,7 @@ bool formatNdefTag() { | ||||
|     } | ||||
|    | ||||
|     return success; | ||||
|   } | ||||
|  | ||||
| uint16_t readTagSize() | ||||
| }uint16_t readTagSize() | ||||
| { | ||||
|   uint8_t buffer[4]; | ||||
|   memset(buffer, 0, 4); | ||||
| @@ -109,97 +107,717 @@ uint16_t readTagSize() | ||||
|   return buffer[2]*8; | ||||
| } | ||||
|  | ||||
| String detectNtagType() | ||||
| { | ||||
|   // Read capability container from page 3 to determine exact NTAG type | ||||
|   uint8_t ccBuffer[4]; | ||||
|   memset(ccBuffer, 0, 4); | ||||
|    | ||||
|   if (!nfc.ntag2xx_ReadPage(3, ccBuffer)) { | ||||
|     Serial.println("Failed to read capability container"); | ||||
|     return "UNKNOWN"; | ||||
|   } | ||||
|  | ||||
|   // Also read configuration pages to get more info | ||||
|   uint8_t configBuffer[4]; | ||||
|   memset(configBuffer, 0, 4); | ||||
|    | ||||
|   Serial.print("Capability Container: "); | ||||
|   for (int i = 0; i < 4; i++) { | ||||
|     if (ccBuffer[i] < 0x10) Serial.print("0"); | ||||
|     Serial.print(ccBuffer[i], HEX); | ||||
|     Serial.print(" "); | ||||
|   } | ||||
|   Serial.println(); | ||||
|  | ||||
|   // NTAG type detection based on capability container | ||||
|   // CC[2] contains the data area size in bytes / 8 | ||||
|   uint16_t dataAreaSize = ccBuffer[2] * 8; | ||||
|    | ||||
|   Serial.print("Data area size from CC: "); | ||||
|   Serial.println(dataAreaSize); | ||||
|  | ||||
|   // Try to read different configuration pages to determine exact type | ||||
|   String tagType = "UNKNOWN"; | ||||
|    | ||||
|   // Try to read page 41 (NTAG213 ends at page 39, so this should fail) | ||||
|   uint8_t testBuffer[4]; | ||||
|   bool canReadPage41 = nfc.ntag2xx_ReadPage(41, testBuffer); | ||||
|    | ||||
|   // Try to read page 130 (NTAG215 ends at page 129, so this should fail for NTAG213/215) | ||||
|   bool canReadPage130 = nfc.ntag2xx_ReadPage(130, testBuffer); | ||||
|  | ||||
|   if (dataAreaSize <= 180 && !canReadPage41) { | ||||
|     tagType = "NTAG213"; | ||||
|     Serial.println("Detected: NTAG213 (cannot read beyond page 39)"); | ||||
|   } else if (dataAreaSize <= 540 && canReadPage41 && !canReadPage130) { | ||||
|     tagType = "NTAG215"; | ||||
|     Serial.println("Detected: NTAG215 (can read page 41, cannot read page 130)"); | ||||
|   } else if (dataAreaSize <= 928 && canReadPage130) { | ||||
|     tagType = "NTAG216"; | ||||
|     Serial.println("Detected: NTAG216 (can read page 130)"); | ||||
|   } else { | ||||
|     // Fallback: use data area size from capability container | ||||
|     if (dataAreaSize <= 180) { | ||||
|       tagType = "NTAG213"; | ||||
|       Serial.println("Fallback detection: NTAG213 based on data area size"); | ||||
|     } else if (dataAreaSize <= 540) { | ||||
|       tagType = "NTAG215"; | ||||
|       Serial.println("Fallback detection: NTAG215 based on data area size"); | ||||
|     } else { | ||||
|       tagType = "NTAG216"; | ||||
|       Serial.println("Fallback detection: NTAG216 based on data area size"); | ||||
|     } | ||||
|   } | ||||
|    | ||||
|   return tagType; | ||||
| } | ||||
|  | ||||
| uint16_t getAvailableUserDataSize() | ||||
| { | ||||
|   String tagType = detectNtagType(); | ||||
|   uint16_t userDataSize = 0; | ||||
|    | ||||
|   if (tagType == "NTAG213") { | ||||
|     // NTAG213: User data from page 4-39 (36 pages * 4 bytes = 144 bytes) | ||||
|     userDataSize = 144; | ||||
|     Serial.println("NTAG213 confirmed - 144 bytes user data available"); | ||||
|   } else if (tagType == "NTAG215") { | ||||
|     // NTAG215: User data from page 4-129 (126 pages * 4 bytes = 504 bytes) | ||||
|     userDataSize = 504; | ||||
|     Serial.println("NTAG215 confirmed - 504 bytes user data available"); | ||||
|   } else if (tagType == "NTAG216") { | ||||
|     // NTAG216: User data from page 4-225 (222 pages * 4 bytes = 888 bytes) | ||||
|     userDataSize = 888; | ||||
|     Serial.println("NTAG216 confirmed - 888 bytes user data available"); | ||||
|   } else { | ||||
|     // Unknown tag type, use conservative estimate | ||||
|     uint16_t tagSize = readTagSize(); | ||||
|     userDataSize = tagSize - 60; // Reserve 60 bytes for headers/config | ||||
|     Serial.print("Unknown NTAG type, using conservative estimate: "); | ||||
|     Serial.println(userDataSize); | ||||
|   } | ||||
|    | ||||
|   return userDataSize; | ||||
| } | ||||
|  | ||||
| uint16_t getMaxUserDataPages() | ||||
| { | ||||
|   String tagType = detectNtagType(); | ||||
|   uint16_t maxPages = 0; | ||||
|    | ||||
|   if (tagType == "NTAG213") { | ||||
|     maxPages = 39; // Pages 4-39 are user data | ||||
|   } else if (tagType == "NTAG215") { | ||||
|     maxPages = 129; // Pages 4-129 are user data | ||||
|   } else if (tagType == "NTAG216") { | ||||
|     maxPages = 225; // Pages 4-225 are user data | ||||
|   } else { | ||||
|     // Conservative fallback | ||||
|     maxPages = 39; | ||||
|     Serial.println("Unknown tag type, using NTAG213 page limit as fallback"); | ||||
|   } | ||||
|    | ||||
|   Serial.print("Maximum writable page: "); | ||||
|   Serial.println(maxPages); | ||||
|   return maxPages; | ||||
| } | ||||
|  | ||||
| bool initializeNdefStructure() { | ||||
|     // Write minimal NDEF structure without destroying the tag | ||||
|     // This creates a clean slate while preserving tag functionality | ||||
|      | ||||
|     Serial.println("Initialisiere sichere NDEF-Struktur..."); | ||||
|      | ||||
|     // Minimal NDEF structure: TLV with empty message | ||||
|     uint8_t minimalNdef[8] = { | ||||
|         0x03,           // NDEF Message TLV Tag | ||||
|         0x03,           // Length (3 bytes for minimal empty record) | ||||
|         0xD0,           // NDEF Record Header (TNF=0x0:Empty + SR + ME + MB) | ||||
|         0x00,           // Type Length (0 = empty record) | ||||
|         0x00,           // Payload Length (0 = empty record) | ||||
|         0xFE,           // Terminator TLV | ||||
|         0x00, 0x00      // Padding | ||||
|     }; | ||||
|      | ||||
|     // Write the minimal structure starting at page 4 | ||||
|     uint8_t pageBuffer[4]; | ||||
|      | ||||
|     for (int i = 0; i < 8; i += 4) { | ||||
|         memcpy(pageBuffer, &minimalNdef[i], 4); | ||||
|          | ||||
|         if (!nfc.ntag2xx_WritePage(4 + (i / 4), pageBuffer)) { | ||||
|             Serial.print("Fehler beim Initialisieren von Seite "); | ||||
|             Serial.println(4 + (i / 4)); | ||||
|             return false; | ||||
|         } | ||||
|          | ||||
|         Serial.print("Seite "); | ||||
|         Serial.print(4 + (i / 4)); | ||||
|         Serial.print(" initialisiert: "); | ||||
|         for (int j = 0; j < 4; j++) { | ||||
|             if (pageBuffer[j] < 0x10) Serial.print("0"); | ||||
|             Serial.print(pageBuffer[j], HEX); | ||||
|             Serial.print(" "); | ||||
|         } | ||||
|         Serial.println(); | ||||
|     } | ||||
|      | ||||
|     Serial.println("✓ Sichere NDEF-Struktur initialisiert"); | ||||
|     Serial.println("✓ Tag bleibt funktionsfähig und überschreibbar"); | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| bool clearUserDataArea() { | ||||
|     // IMPORTANT: Only clear user data pages, NOT configuration pages | ||||
|     // NTAG layout: Pages 0-3 (header), 4-N (user data), N+1-N+3 (config) - NEVER touch config! | ||||
|     String tagType = detectNtagType(); | ||||
|      | ||||
|     // Calculate safe user data page ranges (NEVER touch config pages!) | ||||
|     uint16_t firstUserPage = 4; | ||||
|     uint16_t lastUserPage = 0; | ||||
|      | ||||
|     if (tagType == "NTAG213") { | ||||
|         lastUserPage = 39;  // Pages 40-42 are config - DO NOT TOUCH! | ||||
|         Serial.println("NTAG213: Sichere Löschung Seiten 4-39"); | ||||
|     } else if (tagType == "NTAG215") { | ||||
|         lastUserPage = 129; // Pages 130-132 are config - DO NOT TOUCH! | ||||
|         Serial.println("NTAG215: Sichere Löschung Seiten 4-129"); | ||||
|     } else if (tagType == "NTAG216") { | ||||
|         lastUserPage = 225; // Pages 226-228 are config - DO NOT TOUCH! | ||||
|         Serial.println("NTAG216: Sichere Löschung Seiten 4-225"); | ||||
|     } else { | ||||
|         // Conservative fallback - only clear a small safe area | ||||
|         lastUserPage = 39; | ||||
|         Serial.println("UNKNOWN TAG: Konservative Löschung Seiten 4-39"); | ||||
|     } | ||||
|      | ||||
|     Serial.println("WARNUNG: Vollständiges Löschen kann Tag beschädigen!"); | ||||
|     Serial.println("Verwende stattdessen selective NDEF-Überschreibung..."); | ||||
|      | ||||
|     // Instead of clearing everything, just write a minimal NDEF structure | ||||
|     // This is much safer and preserves tag integrity | ||||
|     return initializeNdefStructure(); | ||||
| } | ||||
|  | ||||
| uint8_t ntag2xx_WriteNDEF(const char *payload) { | ||||
|   // Determine exact tag type and capabilities first | ||||
|   String tagType = detectNtagType(); | ||||
|   uint16_t tagSize = readTagSize(); | ||||
|   Serial.print("Tag Size: ");Serial.println(tagSize); | ||||
|   uint16_t availableUserData = getAvailableUserDataSize(); | ||||
|   uint16_t maxWritablePage = getMaxUserDataPages(); | ||||
|    | ||||
|   Serial.println("=== NFC TAG ANALYSIS ==="); | ||||
|   Serial.print("Tag Type: ");Serial.println(tagType); | ||||
|   Serial.print("Total Tag Size: ");Serial.println(tagSize); | ||||
|   Serial.print("Available User Data: ");Serial.println(availableUserData); | ||||
|   Serial.print("Max Writable Page: ");Serial.println(maxWritablePage); | ||||
|   Serial.println("========================"); | ||||
|  | ||||
|   // Perform additional tag validation by testing write boundaries | ||||
|   Serial.println("=== TAG VALIDATION ==="); | ||||
|   uint8_t testBuffer[4] = {0x00, 0x00, 0x00, 0x00}; | ||||
|    | ||||
|   // Test if we can actually read the max page | ||||
|   if (!nfc.ntag2xx_ReadPage(maxWritablePage, testBuffer)) { | ||||
|     Serial.print("WARNING: Cannot read declared max page "); | ||||
|     Serial.println(maxWritablePage); | ||||
|      | ||||
|     // Find actual maximum writable page by testing backwards | ||||
|     uint16_t actualMaxPage = maxWritablePage; | ||||
|     for (uint16_t testPage = maxWritablePage; testPage >= 4; testPage--) { | ||||
|       if (nfc.ntag2xx_ReadPage(testPage, testBuffer)) { | ||||
|         actualMaxPage = testPage; | ||||
|         Serial.print("Found actual max readable page: "); | ||||
|         Serial.println(actualMaxPage); | ||||
|         break; | ||||
|       } | ||||
|     } | ||||
|     maxWritablePage = actualMaxPage; | ||||
|   } else { | ||||
|     Serial.print("✓ Max page ");Serial.print(maxWritablePage);Serial.println(" is readable"); | ||||
|   } | ||||
|    | ||||
|   // Calculate maximum available user data based on actual writable pages | ||||
|   uint16_t actualUserDataSize = (maxWritablePage - 3) * 4; // -3 because pages 0-3 are header | ||||
|   availableUserData = actualUserDataSize; | ||||
|    | ||||
|   Serial.print("Actual available user data: "); | ||||
|   Serial.print(actualUserDataSize); | ||||
|   Serial.println(" bytes"); | ||||
|   Serial.println("========================"); | ||||
|  | ||||
|   uint8_t pageBuffer[4] = {0, 0, 0, 0}; | ||||
|   Serial.println("Beginne mit dem Schreiben der NDEF-Nachricht..."); | ||||
|    | ||||
|   // Figure out how long the string is | ||||
|   uint8_t len = strlen(payload); | ||||
|   uint16_t payloadLen = strlen(payload); | ||||
|   Serial.print("Länge der Payload: "); | ||||
|   Serial.println(len); | ||||
|   Serial.println(payloadLen); | ||||
|    | ||||
|   Serial.print("Payload: ");Serial.println(payload); | ||||
|  | ||||
|   // Setup the record header | ||||
|   // See NFCForum-TS-Type-2-Tag_1.1.pdf for details | ||||
|   uint8_t pageHeader[21] = { | ||||
|     /* NDEF Message TLV - JSON Record */ | ||||
|     0x03, /* Tag Field (0x03 = NDEF Message) */ | ||||
|     (uint8_t)(len+3+16), /* Payload Length (including NDEF header) */ | ||||
|     0xD2, /* NDEF Record Header (TNF=0x2:MIME Media + SR + ME + MB) */ | ||||
|     0x10, /* Type Length for the record type indicator */ | ||||
|     (uint8_t)(len), /* Payload len */ | ||||
|     'a', 'p', 'p', 'l', 'i', 'c', 'a', 't', 'i', 'o', 'n', '/', 'j', 's', 'o', 'n' | ||||
|   }; | ||||
|   // MIME type for JSON | ||||
|   const char mimeType[] = "application/json"; | ||||
|   uint8_t mimeTypeLen = strlen(mimeType); | ||||
|    | ||||
|   // Calculate NDEF record size | ||||
|   uint8_t ndefRecordHeaderSize = 3; // Header byte + Type Length + Payload Length (short record) | ||||
|   uint16_t ndefRecordSize = ndefRecordHeaderSize + mimeTypeLen + payloadLen; | ||||
|    | ||||
|   // Calculate TLV size - need to check if we need extended length format | ||||
|   uint8_t tlvHeaderSize; | ||||
|   uint16_t totalTlvSize; | ||||
|    | ||||
|   if (ndefRecordSize <= 254) { | ||||
|     // Standard TLV format: Tag (1) + Length (1) + Value (ndefRecordSize) | ||||
|     tlvHeaderSize = 2; | ||||
|     totalTlvSize = tlvHeaderSize + ndefRecordSize + 1; // +1 for terminator TLV | ||||
|   } else { | ||||
|     // Extended TLV format: Tag (1) + 0xFF + Length (2) + Value (ndefRecordSize)   | ||||
|     tlvHeaderSize = 4; | ||||
|     totalTlvSize = tlvHeaderSize + ndefRecordSize + 1; // +1 for terminator TLV | ||||
|   } | ||||
|  | ||||
|   // Make sure the URI payload will fit in dataLen (include 0xFE trailer) | ||||
|   if ((len < 1) || (len + 1 > (tagSize - sizeof(pageHeader))))  | ||||
|   { | ||||
|   Serial.print("NDEF Record Size: "); | ||||
|   Serial.println(ndefRecordSize); | ||||
|   Serial.print("Total TLV Size: "); | ||||
|   Serial.println(totalTlvSize); | ||||
|  | ||||
|   // Check if the message fits in the available user data space | ||||
|   if (totalTlvSize > availableUserData) { | ||||
|     Serial.println(); | ||||
|     Serial.println("!!!!!!!!!!!!!!!!!!!!!!!!"); | ||||
|     Serial.println("Fehler: Die Nutzlast passt nicht in die Datenlänge."); | ||||
|     Serial.println("FEHLER: Payload zu groß für diesen Tag-Typ!"); | ||||
|     Serial.print("Tag-Typ: ");Serial.println(tagType); | ||||
|     Serial.print("Benötigt: ");Serial.print(totalTlvSize);Serial.println(" Bytes"); | ||||
|     Serial.print("Verfügbar: ");Serial.print(availableUserData);Serial.println(" Bytes"); | ||||
|     Serial.print("Überschuss: ");Serial.print(totalTlvSize - availableUserData);Serial.println(" Bytes"); | ||||
|      | ||||
|     if (tagType == "NTAG213") { | ||||
|       Serial.println("EMPFEHLUNG: Verwenden Sie einen NTAG215 (504 Bytes) oder NTAG216 (888 Bytes) Tag!"); | ||||
|       Serial.println("Oder kürzen Sie die Payload um mindestens " + String(totalTlvSize - availableUserData) + " Bytes."); | ||||
|     } | ||||
|     Serial.println("!!!!!!!!!!!!!!!!!!!!!!!!"); | ||||
|     Serial.println(); | ||||
|      | ||||
|     oledShowMessage("Tag zu klein für Payload"); | ||||
|     vTaskDelay(3000 / portTICK_PERIOD_MS); | ||||
|     return 0; | ||||
|   } | ||||
|  | ||||
|   // Kombiniere Header und Payload | ||||
|   int totalSize = sizeof(pageHeader) + len; | ||||
|   uint8_t* combinedData = (uint8_t*) malloc(totalSize); | ||||
|   if (combinedData == NULL)  | ||||
|   { | ||||
|     Serial.println("Fehler: Nicht genug Speicher vorhanden."); | ||||
|     oledShowMessage("Tag too small"); | ||||
|   Serial.println("✓ Payload passt in den Tag - Schreibvorgang wird fortgesetzt"); | ||||
|  | ||||
|   // STEP 1: Read current tag content for debugging | ||||
|   Serial.println(); | ||||
|   Serial.println("=== SCHRITT 1: AKTUELLER TAG-INHALT ==="); | ||||
|   uint8_t currentContent[64]; // Read first 16 pages | ||||
|   memset(currentContent, 0, 64); | ||||
|    | ||||
|   for (uint8_t page = 4; page < 20; page++) { | ||||
|     uint8_t pageData[4]; | ||||
|     if (nfc.ntag2xx_ReadPage(page, pageData)) { | ||||
|       memcpy(¤tContent[(page-4)*4], pageData, 4); | ||||
|       Serial.print("Seite "); | ||||
|       Serial.print(page); | ||||
|       Serial.print(": "); | ||||
|       for (int i = 0; i < 4; i++) { | ||||
|         if (pageData[i] < 0x10) Serial.print("0"); | ||||
|         Serial.print(pageData[i], HEX); | ||||
|         Serial.print(" "); | ||||
|       } | ||||
|       Serial.println(); | ||||
|     } else { | ||||
|       Serial.print("Fehler beim Lesen von Seite "); | ||||
|       Serial.println(page); | ||||
|     } | ||||
|   } | ||||
|   Serial.println("========================================="); | ||||
|  | ||||
|   // STEP 2: Simple write test - write one test page | ||||
|   Serial.println(); | ||||
|   Serial.println("=== SCHRITT 2: SCHREIBTEST ==="); | ||||
|   uint8_t testPage[4] = {0xAA, 0xBB, 0xCC, 0xDD}; // Test pattern | ||||
|    | ||||
|   if (!nfc.ntag2xx_WritePage(10, testPage)) { // Use page 10 for test | ||||
|     Serial.println("FEHLER: Einfacher Schreibtest fehlgeschlagen!"); | ||||
|     Serial.println("Tag ist möglicherweise schreibgeschützt oder defekt"); | ||||
|     oledShowMessage("Tag write protected?"); | ||||
|     vTaskDelay(3000 / portTICK_PERIOD_MS); | ||||
|     return 0; | ||||
|   } | ||||
|    | ||||
|   // Verify test write | ||||
|   uint8_t readBack[4]; | ||||
|   if (!nfc.ntag2xx_ReadPage(10, readBack)) { | ||||
|     Serial.println("FEHLER: Kann Testdaten nicht zurücklesen!"); | ||||
|     return 0; | ||||
|   } | ||||
|    | ||||
|   bool testSuccess = true; | ||||
|   for (int i = 0; i < 4; i++) { | ||||
|     if (readBack[i] != testPage[i]) { | ||||
|       testSuccess = false; | ||||
|       break; | ||||
|     } | ||||
|   } | ||||
|    | ||||
|   if (!testSuccess) { | ||||
|     Serial.println("FEHLER: Schreibtest fehlgeschlagen - Daten stimmen nicht überein!"); | ||||
|     Serial.print("Geschrieben: "); | ||||
|     for (int i = 0; i < 4; i++) { | ||||
|       Serial.print(testPage[i], HEX); Serial.print(" "); | ||||
|     } | ||||
|     Serial.println(); | ||||
|     Serial.print("Gelesen: "); | ||||
|     for (int i = 0; i < 4; i++) { | ||||
|       Serial.print(readBack[i], HEX); Serial.print(" "); | ||||
|     } | ||||
|     Serial.println(); | ||||
|     return 0; | ||||
|   } | ||||
|    | ||||
|   Serial.println("✓ Schreibtest erfolgreich - Tag ist beschreibbar"); | ||||
|   Serial.println("================================"); | ||||
|  | ||||
|   // STEP 3: NDEF initialization with verification | ||||
|   Serial.println(); | ||||
|   Serial.println("=== SCHRITT 3: NDEF-INITIALISIERUNG ==="); | ||||
|   if (!initializeNdefStructure()) { | ||||
|     Serial.println("FEHLER: Konnte NDEF-Struktur nicht initialisieren!"); | ||||
|     oledShowMessage("NDEF init failed"); | ||||
|     vTaskDelay(2000 / portTICK_PERIOD_MS); | ||||
|     return 0; | ||||
|   } | ||||
|    | ||||
|   // Verify NDEF initialization | ||||
|   uint8_t ndefCheck[8]; | ||||
|   bool ndefVerified = true; | ||||
|   for (uint8_t page = 4; page < 6; page++) { | ||||
|     if (!nfc.ntag2xx_ReadPage(page, &ndefCheck[(page-4)*4])) { | ||||
|       ndefVerified = false; | ||||
|       break; | ||||
|     } | ||||
|   } | ||||
|    | ||||
|   if (ndefVerified) { | ||||
|     Serial.print("NDEF-Header nach Initialisierung: "); | ||||
|     for (int i = 0; i < 8; i++) { | ||||
|       if (ndefCheck[i] < 0x10) Serial.print("0"); | ||||
|       Serial.print(ndefCheck[i], HEX); | ||||
|       Serial.print(" "); | ||||
|     } | ||||
|     Serial.println(); | ||||
|   } | ||||
|    | ||||
|   Serial.println("✓ NDEF-Struktur initialisiert und verifiziert"); | ||||
|   Serial.println("=========================================="); | ||||
|  | ||||
|   // Allocate memory for the complete TLV structure | ||||
|   uint8_t* tlvData = (uint8_t*) malloc(totalTlvSize); | ||||
|   if (tlvData == NULL) { | ||||
|     Serial.println("Fehler: Nicht genug Speicher für TLV-Daten vorhanden."); | ||||
|     oledShowMessage("Memory error"); | ||||
|     vTaskDelay(2000 / portTICK_PERIOD_MS); | ||||
|     return 0; | ||||
|   } | ||||
|  | ||||
|   // Kombiniere Header und Payload | ||||
|   memcpy(combinedData, pageHeader, sizeof(pageHeader)); | ||||
|   memcpy(&combinedData[sizeof(pageHeader)], payload, len); | ||||
|   // Build TLV structure | ||||
|   uint16_t offset = 0; | ||||
|    | ||||
|   // TLV Header | ||||
|   tlvData[offset++] = 0x03; // NDEF Message TLV Tag | ||||
|    | ||||
|   if (ndefRecordSize <= 254) { | ||||
|     // Standard length format | ||||
|     tlvData[offset++] = (uint8_t)ndefRecordSize; | ||||
|   } else { | ||||
|     // Extended length format | ||||
|     tlvData[offset++] = 0xFF; | ||||
|     tlvData[offset++] = (uint8_t)(ndefRecordSize >> 8);  // High byte | ||||
|     tlvData[offset++] = (uint8_t)(ndefRecordSize & 0xFF); // Low byte | ||||
|   } | ||||
|  | ||||
|   // Schreibe die Seiten | ||||
|   uint8_t a = 0; | ||||
|   uint8_t i = 0; | ||||
|   while (totalSize > 0) { | ||||
|   // NDEF Record Header | ||||
|   tlvData[offset++] = 0xD2; // NDEF Record Header (TNF=0x2:MIME Media + SR + ME + MB) | ||||
|   tlvData[offset++] = mimeTypeLen; // Type Length | ||||
|   tlvData[offset++] = (uint8_t)payloadLen; // Payload Length (short record format) | ||||
|  | ||||
|   // MIME Type | ||||
|   memcpy(&tlvData[offset], mimeType, mimeTypeLen); | ||||
|   offset += mimeTypeLen; | ||||
|  | ||||
|   // JSON Payload | ||||
|   memcpy(&tlvData[offset], payload, payloadLen); | ||||
|   offset += payloadLen; | ||||
|  | ||||
|   // Terminator TLV | ||||
|   tlvData[offset] = 0xFE; | ||||
|  | ||||
|   Serial.print("Gesamt-TLV-Länge: "); | ||||
|   Serial.println(offset + 1); | ||||
|  | ||||
|   // Debug: Print first 64 bytes of TLV data | ||||
|   Serial.println("TLV Daten (erste 64 Bytes):"); | ||||
|   for (int i = 0; i < min((int)(offset + 1), 64); i++) { | ||||
|     if (tlvData[i] < 0x10) Serial.print("0"); | ||||
|     Serial.print(tlvData[i], HEX); | ||||
|     Serial.print(" "); | ||||
|     if ((i + 1) % 16 == 0) Serial.println(); | ||||
|   } | ||||
|   Serial.println(); | ||||
|  | ||||
|   // Write data to tag pages (starting from page 4) | ||||
|   uint16_t bytesWritten = 0; | ||||
|   uint8_t pageNumber = 4; | ||||
|   uint16_t totalBytes = offset + 1; | ||||
|  | ||||
|   Serial.println(); | ||||
|   Serial.println("=== SCHRITT 4: SCHREIBE NEUE NDEF-DATEN ==="); | ||||
|   Serial.print("Schreibe "); | ||||
|   Serial.print(totalBytes); | ||||
|   Serial.print(" Bytes in "); | ||||
|   Serial.print((totalBytes + 3) / 4); // Round up division | ||||
|   Serial.println(" Seiten..."); | ||||
|  | ||||
|   while (bytesWritten < totalBytes && pageNumber <= maxWritablePage) { | ||||
|     // Additional safety check before writing each page | ||||
|     if (pageNumber > maxWritablePage) { | ||||
|       Serial.print("STOP: Reached maximum writable page "); | ||||
|       Serial.println(maxWritablePage); | ||||
|       break; | ||||
|     } | ||||
|      | ||||
|     // Clear page buffer | ||||
|     memset(pageBuffer, 0, 4); | ||||
|     int bytesToWrite = (totalSize < 4) ? totalSize : 4; | ||||
|     memcpy(pageBuffer, combinedData + a, bytesToWrite); | ||||
|      | ||||
|     // Calculate how many bytes to write to this page | ||||
|     uint16_t bytesToWrite = min(4, (int)(totalBytes - bytesWritten)); | ||||
|      | ||||
|     // Copy data to page buffer | ||||
|     memcpy(pageBuffer, &tlvData[bytesWritten], bytesToWrite); | ||||
|  | ||||
|     //uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 };  // Buffer to store the returned UID | ||||
|     //uint8_t uidLength; | ||||
|     //nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength, 100); | ||||
|  | ||||
|     if (!(nfc.ntag2xx_WritePage(4+i, pageBuffer)))  | ||||
|     { | ||||
|       Serial.println("Fehler beim Schreiben der Seite."); | ||||
|       free(combinedData); | ||||
|     // Write page to tag | ||||
|     if (!nfc.ntag2xx_WritePage(pageNumber, pageBuffer)) { | ||||
|       Serial.print("FEHLER beim Schreiben der Seite "); | ||||
|       Serial.println(pageNumber); | ||||
|       Serial.print("Möglicherweise Page-Limit erreicht für "); | ||||
|       Serial.println(tagType); | ||||
|       Serial.print("Erwartetes Maximum: "); | ||||
|       Serial.println(maxWritablePage); | ||||
|       Serial.print("Tatsächliches Maximum scheint niedriger zu sein!"); | ||||
|        | ||||
|       // Update max page for future operations | ||||
|       if (pageNumber > 4) { | ||||
|         Serial.print("Setze neues Maximum auf Seite "); | ||||
|         Serial.println(pageNumber - 1); | ||||
|       } | ||||
|        | ||||
|       free(tlvData); | ||||
|       return 0; | ||||
|     } | ||||
|  | ||||
|     yield(); | ||||
|     //esp_task_wdt_reset(); | ||||
|     // IMMEDIATE verification after each write - this is critical! | ||||
|     Serial.print("Verifiziere Seite "); | ||||
|     Serial.print(pageNumber); | ||||
|     Serial.print("... "); | ||||
|      | ||||
|     uint8_t verifyBuffer[4]; | ||||
|     vTaskDelay(10 / portTICK_PERIOD_MS); // Small delay before verification | ||||
|      | ||||
|     if (nfc.ntag2xx_ReadPage(pageNumber, verifyBuffer)) { | ||||
|       bool writeSuccess = true; | ||||
|       for (int i = 0; i < bytesToWrite; i++) { | ||||
|         if (verifyBuffer[i] != pageBuffer[i]) { | ||||
|           writeSuccess = false; | ||||
|           Serial.println(); | ||||
|           Serial.print("VERIFIKATIONSFEHLER bei Byte "); | ||||
|           Serial.print(i); | ||||
|           Serial.print(" - Erwartet: 0x"); | ||||
|           Serial.print(pageBuffer[i], HEX); | ||||
|           Serial.print(", Gelesen: 0x"); | ||||
|           Serial.println(verifyBuffer[i], HEX); | ||||
|           break; | ||||
|         } | ||||
|       } | ||||
|        | ||||
|       if (!writeSuccess) { | ||||
|         Serial.println("❌ SCHREIBVORGANG FEHLGESCHLAGEN!"); | ||||
|         free(tlvData); | ||||
|         return 0; | ||||
|       } else { | ||||
|         Serial.println("✓"); | ||||
|       } | ||||
|     } else { | ||||
|       Serial.println("❌ Kann Seite nicht zur Verifikation lesen!"); | ||||
|       free(tlvData); | ||||
|       return 0; | ||||
|     } | ||||
|  | ||||
|     i++; | ||||
|     a += 4; | ||||
|     totalSize -= bytesToWrite; | ||||
|     Serial.print("Seite "); | ||||
|     Serial.print(pageNumber); | ||||
|     Serial.print(" ✓: "); | ||||
|     for (int i = 0; i < 4; i++) { | ||||
|       if (pageBuffer[i] < 0x10) Serial.print("0"); | ||||
|       Serial.print(pageBuffer[i], HEX); | ||||
|       Serial.print(" "); | ||||
|     } | ||||
|     Serial.println(); | ||||
|  | ||||
|     bytesWritten += bytesToWrite; | ||||
|     pageNumber++; | ||||
|      | ||||
|     yield(); | ||||
|     vTaskDelay(5 / portTICK_PERIOD_MS); // Small delay between page writes | ||||
|   } | ||||
|  | ||||
|   // Ensure the NDEF message is properly terminated | ||||
|   memset(pageBuffer, 0, 4); | ||||
|   pageBuffer[0] = 0xFE; // NDEF record footer | ||||
|   if (!(nfc.ntag2xx_WritePage(4+i, pageBuffer)))  | ||||
|   { | ||||
|     Serial.println("Fehler beim Schreiben des End-Bits."); | ||||
|     free(combinedData); | ||||
|   free(tlvData); | ||||
|    | ||||
|   if (bytesWritten < totalBytes) { | ||||
|     Serial.println("WARNUNG: Nicht alle Daten konnten geschrieben werden!"); | ||||
|     Serial.print("Geschrieben: "); | ||||
|     Serial.print(bytesWritten); | ||||
|     Serial.print(" von "); | ||||
|     Serial.print(totalBytes); | ||||
|     Serial.println(" Bytes"); | ||||
|     Serial.print("Gestoppt bei Seite: "); | ||||
|     Serial.println(pageNumber - 1); | ||||
|     return 0; | ||||
|   } | ||||
|  | ||||
|   Serial.println("NDEF-Nachricht erfolgreich geschrieben."); | ||||
|   free(combinedData); | ||||
|    | ||||
|   Serial.println(); | ||||
|   Serial.println("✓ NDEF-Nachricht erfolgreich geschrieben!"); | ||||
|   Serial.print("✓ Tag-Typ: ");Serial.println(tagType); | ||||
|   Serial.print("✓ Insgesamt ");Serial.print(bytesWritten);Serial.println(" Bytes geschrieben"); | ||||
|   Serial.print("✓ Verwendete Seiten: 4-");Serial.println(pageNumber - 1); | ||||
|   Serial.print("✓ Speicher-Auslastung: "); | ||||
|   Serial.print((bytesWritten * 100) / availableUserData); | ||||
|   Serial.println("%"); | ||||
|   Serial.println("✓ Bestehende Daten wurden überschrieben"); | ||||
|    | ||||
|   // CRITICAL: Verify the write by reading back the data | ||||
|   Serial.println(); | ||||
|   Serial.println("=== WRITE VERIFICATION ==="); | ||||
|    | ||||
|   // Wait a moment for tag to stabilize | ||||
|   vTaskDelay(100 / portTICK_PERIOD_MS); | ||||
|    | ||||
|   // Read back the written data to verify | ||||
|   uint8_t verifyBuffer[totalBytes]; | ||||
|   memset(verifyBuffer, 0, totalBytes); | ||||
|    | ||||
|   bool verificationSuccess = true; | ||||
|   uint8_t verifyPage = 4; | ||||
|   uint16_t verifyBytesRead = 0; | ||||
|    | ||||
|   while (verifyBytesRead < totalBytes && verifyPage <= maxWritablePage) { | ||||
|     uint8_t pageData[4]; | ||||
|     if (!nfc.ntag2xx_ReadPage(verifyPage, pageData)) { | ||||
|       Serial.print("VERIFICATION FAILED: Cannot read page "); | ||||
|       Serial.println(verifyPage); | ||||
|       verificationSuccess = false; | ||||
|       break; | ||||
|     } | ||||
|      | ||||
|     // Copy page data to verify buffer | ||||
|     uint16_t bytesToCopy = min(4, (int)(totalBytes - verifyBytesRead)); | ||||
|     memcpy(&verifyBuffer[verifyBytesRead], pageData, bytesToCopy); | ||||
|      | ||||
|     verifyBytesRead += bytesToCopy; | ||||
|     verifyPage++; | ||||
|   } | ||||
|    | ||||
|   if (verificationSuccess && verifyBytesRead >= totalBytes) { | ||||
|     // Compare written data with read data | ||||
|     bool dataMatches = true; | ||||
|     for (uint16_t i = 0; i < totalBytes; i++) { | ||||
|       if (verifyBuffer[i] != tlvData[i]) { | ||||
|         Serial.print("VERIFICATION FAILED: Data mismatch at byte "); | ||||
|         Serial.print(i); | ||||
|         Serial.print(" - Expected: 0x"); | ||||
|         Serial.print(tlvData[i], HEX); | ||||
|         Serial.print(", Read: 0x"); | ||||
|         Serial.println(verifyBuffer[i], HEX); | ||||
|         dataMatches = false; | ||||
|         break; | ||||
|       } | ||||
|     } | ||||
|      | ||||
|     if (dataMatches) { | ||||
|       Serial.println("✓ WRITE VERIFICATION SUCCESSFUL!"); | ||||
|       Serial.println("✓ All written data verified correctly"); | ||||
|        | ||||
|       // Additional JSON verification - try to parse the written JSON | ||||
|       Serial.println("=== JSON VERIFICATION ==="); | ||||
|        | ||||
|       // Find and extract JSON from verified data | ||||
|       // Look for the JSON payload within the NDEF structure | ||||
|       bool jsonFound = false; | ||||
|       for (uint16_t i = 0; i < totalBytes - 10; i++) { | ||||
|         if (verifyBuffer[i] == '{') { | ||||
|           // Found potential JSON start | ||||
|           String extractedJson = ""; | ||||
|           uint16_t jsonEnd = 0; | ||||
|            | ||||
|           for (uint16_t j = i; j < totalBytes; j++) { | ||||
|             if (verifyBuffer[j] >= 32 && verifyBuffer[j] <= 126) { | ||||
|               extractedJson += (char)verifyBuffer[j]; | ||||
|               if (verifyBuffer[j] == '}') { | ||||
|                 jsonEnd = j; | ||||
|                 break; | ||||
|               } | ||||
|             } | ||||
|           } | ||||
|            | ||||
|           Serial.print("Extracted JSON from tag: "); | ||||
|           Serial.println(extractedJson); | ||||
|            | ||||
|           // Try to parse the extracted JSON | ||||
|           JsonDocument testDoc; | ||||
|           DeserializationError error = deserializeJson(testDoc, extractedJson); | ||||
|           if (!error) { | ||||
|             Serial.println("✓ JSON VERIFICATION SUCCESSFUL!"); | ||||
|             Serial.print("✓ JSON is valid and parseable"); | ||||
|              | ||||
|             if (testDoc["sm_id"].is<String>()) { | ||||
|               Serial.print(" - sm_id found: "); | ||||
|               Serial.println(testDoc["sm_id"].as<String>()); | ||||
|             } else { | ||||
|               Serial.println(" - WARNING: sm_id not found in JSON!"); | ||||
|             } | ||||
|              | ||||
|             jsonFound = true; | ||||
|             testDoc.clear(); | ||||
|             break; | ||||
|           } else { | ||||
|             Serial.print("JSON parse error: "); | ||||
|             Serial.println(error.c_str()); | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|        | ||||
|       if (!jsonFound) { | ||||
|         Serial.println("WARNING: No valid JSON found in verified data!"); | ||||
|         verificationSuccess = false; | ||||
|       } | ||||
|        | ||||
|     } else { | ||||
|       verificationSuccess = false; | ||||
|     } | ||||
|   } else { | ||||
|     Serial.println("VERIFICATION FAILED: Could not read back all data"); | ||||
|     verificationSuccess = false; | ||||
|   } | ||||
|    | ||||
|   Serial.println("========================="); | ||||
|   Serial.println(); | ||||
|    | ||||
|   if (!verificationSuccess) { | ||||
|     Serial.println("❌ WRITE FAILED - Data verification unsuccessful"); | ||||
|     Serial.println("❌ Tag may not contain the expected data"); | ||||
|     return 0; | ||||
|   } | ||||
|    | ||||
|   return 1; | ||||
| } | ||||
|  | ||||
| @@ -364,8 +982,20 @@ bool decodeNdefAndReturnJson(const byte* encodedMessage, String uidString) { | ||||
|  | ||||
|   Serial.print("Actual JSON length extracted: "); | ||||
|   Serial.println(actualJsonLength); | ||||
|   Serial.println("Decoded JSON Data:"); | ||||
|   Serial.print("Total nfcJsonData length: "); | ||||
|   Serial.println(nfcJsonData.length()); | ||||
|   Serial.println("=== DECODED JSON DATA START ==="); | ||||
|   Serial.println(nfcJsonData); | ||||
|   Serial.println("=== DECODED JSON DATA END ==="); | ||||
|    | ||||
|   // Check if JSON was truncated | ||||
|   if (nfcJsonData.length() < payloadLength && !nfcJsonData.endsWith("}")) { | ||||
|     Serial.println("WARNING: JSON payload appears to be truncated!"); | ||||
|     Serial.print("Expected payload length: "); | ||||
|     Serial.println(payloadLength); | ||||
|     Serial.print("Actual extracted length: "); | ||||
|     Serial.println(nfcJsonData.length()); | ||||
|   } | ||||
|    | ||||
|   // Trim any trailing whitespace or invalid characters | ||||
|   nfcJsonData.trim(); | ||||
| @@ -434,6 +1064,73 @@ bool decodeNdefAndReturnJson(const byte* encodedMessage, String uidString) { | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| bool quickSpoolIdCheck(String uidString) { | ||||
|     // Fast-path: Read only first 2-3 pages to check for sm_id pattern | ||||
|     // This dramatically speeds up known spool recognition | ||||
|      | ||||
|     Serial.println("=== FAST-PATH: Quick sm_id Check ==="); | ||||
|      | ||||
|     // Read first 3 pages (12 bytes) after NDEF header (pages 4-6) | ||||
|     uint8_t quickData[12]; | ||||
|     memset(quickData, 0, 12); | ||||
|      | ||||
|     for (uint8_t page = 4; page < 7; page++) { | ||||
|         if (!nfc.ntag2xx_ReadPage(page, quickData + (page - 4) * 4)) { | ||||
|             Serial.print("Failed to read page "); | ||||
|             Serial.println(page); | ||||
|             return false; // Fall back to full read | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     // Convert to string for pattern matching | ||||
|     String quickCheck = ""; | ||||
|     for (int i = 0; i < 12; i++) { | ||||
|         if (quickData[i] >= 32 && quickData[i] <= 126) { | ||||
|             quickCheck += (char)quickData[i]; | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     Serial.print("Quick data (first 12 bytes): "); | ||||
|     Serial.println(quickCheck); | ||||
|      | ||||
|     // Look for sm_id pattern at the beginning | ||||
|     if (quickCheck.indexOf("\"sm_id\":\"") >= 0 && quickCheck.indexOf("\"sm_id\":\"0\"") < 0) { | ||||
|         Serial.println("✓ FAST-PATH: sm_id found in first bytes - known spool detected!"); | ||||
|          | ||||
|         // Extract sm_id from quick data if possible | ||||
|         int smIdStart = quickCheck.indexOf("\"sm_id\":\"") + 9; | ||||
|         int smIdEnd = quickCheck.indexOf("\"", smIdStart); | ||||
|          | ||||
|         if (smIdEnd > smIdStart) { | ||||
|             String quickSpoolId = quickCheck.substring(smIdStart, smIdEnd); | ||||
|             Serial.print("✓ Quick extracted sm_id: "); | ||||
|             Serial.println(quickSpoolId); | ||||
|              | ||||
|             // Set as active spool immediately | ||||
|             activeSpoolId = quickSpoolId; | ||||
|             lastSpoolId = activeSpoolId; | ||||
|              | ||||
|             oledShowProgressBar(2, octoEnabled?5:4, "Known Spool", "Quick mode"); | ||||
|             Serial.println("✓ FAST-PATH SUCCESS: Known spool processed quickly"); | ||||
|             return true; // Skip full tag reading! | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     // Check for other quick patterns | ||||
|     if (quickCheck.indexOf("\"location\":\"") >= 0) { | ||||
|         Serial.println("✓ FAST-PATH: Location tag detected"); | ||||
|         return false; // Need full read for location processing | ||||
|     } | ||||
|      | ||||
|     if (quickCheck.indexOf("\"brand\":\"") >= 0 && quickCheck.indexOf("\"sm_id\":\"0\"") >= 0) { | ||||
|         Serial.println("✓ FAST-PATH: New brand filament detected (sm_id:0)"); | ||||
|         return false; // Need full read for brand filament creation | ||||
|     } | ||||
|      | ||||
|     Serial.println("✗ FAST-PATH: No recognizable pattern - falling back to full read"); | ||||
|     return false; // Fall back to full tag reading | ||||
| } | ||||
|  | ||||
| void writeJsonToTag(void *parameter) { | ||||
|   NfcWriteParameterType* params = (NfcWriteParameterType*)parameter; | ||||
|  | ||||
| @@ -443,11 +1140,13 @@ void writeJsonToTag(void *parameter) { | ||||
|  | ||||
|   nfcReaderState = NFC_WRITING; | ||||
|  | ||||
|   // First request the reading task to be suspended and than wait until it responds | ||||
|   nfcReadingTaskSuspendRequest = true; | ||||
|   while(nfcReadingTaskSuspendState == false){ | ||||
|     vTaskDelay(100 / portTICK_PERIOD_MS); | ||||
|   } | ||||
|   // IMPORTANT: Do NOT suspend reading task during writing! | ||||
|   // We need to be able to read during write verification | ||||
|   // Just set the state to WRITING to prevent scan conflicts | ||||
|   Serial.println("NFC Write Task starting - Reader remains active for verification"); | ||||
|    | ||||
|   // Small delay to ensure any ongoing operations complete | ||||
|   vTaskDelay(100 / portTICK_PERIOD_MS); | ||||
|  | ||||
|   //pauseBambuMqttTask = true; | ||||
|   // aktualisieren der Website wenn sich der Status ändert | ||||
| @@ -535,7 +1234,9 @@ void writeJsonToTag(void *parameter) { | ||||
|   sendWriteResult(nullptr, success); | ||||
|   sendNfcData(); | ||||
|  | ||||
|   nfcReadingTaskSuspendRequest = false; | ||||
|   // Since we didn't suspend reading, we don't need to re-enable it | ||||
|   // Just reset the state back to IDLE | ||||
|   Serial.println("NFC Write Task completed - Reader was never suspended"); | ||||
|   pauseBambuMqttTask = false; | ||||
|  | ||||
|   free(params->payload); | ||||
| @@ -597,7 +1298,9 @@ void scanRfidTask(void * parameter) { | ||||
|  | ||||
|         oledShowProgressBar(0, octoEnabled?5:4, "Reading", "Detecting tag"); | ||||
|  | ||||
|         //vTaskDelay(500 / portTICK_PERIOD_MS); | ||||
|         // Wait 1 second after tag detection to stabilize connection | ||||
|         Serial.println("Tag detected, waiting 1 second for stabilization..."); | ||||
|         vTaskDelay(1000 / portTICK_PERIOD_MS); | ||||
|  | ||||
|         // create Tag UID string | ||||
|         String uidString = ""; | ||||
| @@ -611,6 +1314,18 @@ void scanRfidTask(void * parameter) { | ||||
|          | ||||
|         if (uidLength == 7) | ||||
|         { | ||||
|           // Try fast-path detection first for known spools | ||||
|           if (quickSpoolIdCheck(uidString)) { | ||||
|               Serial.println("✓ FAST-PATH: Tag processed quickly, skipping full read"); | ||||
|               pauseBambuMqttTask = false; | ||||
|               // Set reader back to idle for next scan | ||||
|               nfcReaderState = NFC_READ_SUCCESS; | ||||
|               delay(500); // Small delay before next scan | ||||
|               continue; // Skip full tag reading and continue scan loop | ||||
|           } | ||||
|  | ||||
|           Serial.println("Continuing with full tag read after fast-path check"); | ||||
|  | ||||
|           uint16_t tagSize = readTagSize(); | ||||
|           if(tagSize > 0) | ||||
|           { | ||||
| @@ -620,24 +1335,34 @@ void scanRfidTask(void * parameter) { | ||||
|  | ||||
|             // We probably have an NTAG2xx card (though it could be Ultralight as well) | ||||
|             Serial.println("Seems to be an NTAG2xx tag (7 byte UID)"); | ||||
|             Serial.print("Tag size: "); | ||||
|             Serial.print(tagSize); | ||||
|             Serial.println(" bytes"); | ||||
|              | ||||
|             uint8_t numPages = readTagSize()/4; | ||||
|              | ||||
|             for (uint8_t i = 4; i < 4+numPages; i++) { | ||||
|                | ||||
|               if (!nfc.ntag2xx_ReadPage(i, data+(i-4) * 4)) | ||||
|               { | ||||
|                 break; // Stop if reading fails | ||||
|               } | ||||
|               | ||||
|               // Check for NDEF message end | ||||
|               if (data[(i - 4) * 4] == 0xFE)  | ||||
|               { | ||||
|                 Serial.println("Found NDEF message end marker"); | ||||
|                 break; // End of NDEF message | ||||
|               } | ||||
|  | ||||
|               yield(); | ||||
|               esp_task_wdt_reset(); | ||||
|               vTaskDelay(pdMS_TO_TICKS(1)); | ||||
|               // Increased delay to ensure stable reading | ||||
|               vTaskDelay(pdMS_TO_TICKS(5)); // Increased from 1ms to 5ms | ||||
|             } | ||||
|  | ||||
|              | ||||
|             Serial.println("Tag reading completed, starting NDEF decode..."); | ||||
|              | ||||
|             if (!decodeNdefAndReturnJson(data, uidString))  | ||||
|             { | ||||
|               oledShowProgressBar(1, 1, "Failure", "Unknown tag"); | ||||
|   | ||||
| @@ -16,6 +16,7 @@ typedef enum{ | ||||
| void startNfc(); | ||||
| void scanRfidTask(void * parameter); | ||||
| void startWriteJsonToTag(const bool isSpoolTag, const char* payload); | ||||
| bool quickSpoolIdCheck(String uidString); | ||||
|  | ||||
| extern TaskHandle_t RfidReaderTask; | ||||
| extern String nfcJsonData; | ||||
|   | ||||
| @@ -49,8 +49,7 @@ void scale_loop(void * parameter) { | ||||
|   Serial.println("++++++++++++++++++++++++++++++"); | ||||
|  | ||||
|   vTaskDelay(pdMS_TO_TICKS(500)); | ||||
|   scale.tare(); | ||||
|   vTaskDelay(pdMS_TO_TICKS(500)); | ||||
|   scale_tare_counter = 10; // damit beim Starten der Waage automatisch getart wird | ||||
|    | ||||
|   for(;;) { | ||||
|     if (scale.is_ready())  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user