#include "APIClient.h" #include #include static const char *TAG_API = "API"; APIClient::APIClient() {} bool APIClient::uploadMeasurement(const String &filePath) { if (WiFi.status() != WL_CONNECTED) { ESP_LOGE(TAG_API, "No WiFi connection."); return false; } File file = SD.open(filePath, FILE_READ); if (!file) { ESP_LOGE(TAG_API, "Failed to open file %s", filePath.c_str()); return false; } // Wyciągnij samą nazwę pliku (bez ścieżki) String filename = filePath; int slashIndex = filename.lastIndexOf('/'); if (slashIndex >= 0) { filename = filename.substring(slashIndex + 1); } // multipart/form-data boundary String boundary = "----ESP32WMTBoundary"; String head = "--" + boundary + "\r\n" + "Content-Disposition: form-data; name=\"file\"; filename=\"" + filename + "\"\r\n" + "Content-Type: application/octet-stream\r\n\r\n"; String tail = "\r\n--" + boundary + "--\r\n"; size_t fileSize = file.size(); size_t totalLength = head.length() + fileSize + tail.length(); // Połączenie z hostem WiFiClient client; String host = String(config.restURL); int port = config.restPort; // Usuń prefiks protokołu z hosta host.replace("http://", ""); host.replace("https://", ""); ESP_LOGI(TAG_API, "Connecting to %s:%d for upload", host.c_str(), port); if (!client.connect(host.c_str(), port)) { ESP_LOGE(TAG_API, "Connection failed to host %s:%d", host.c_str(), port); file.close(); return false; } ESP_LOGI(TAG_API, "Uploading %s (%d bytes)", filePath.c_str(), fileSize); // --- HTTP Request --- // Endpoint wg dokumentacji API IoT client.println("POST /api/v1/measurements/measurements/upload HTTP/1.1"); client.println("Host: " + host + ":" + String(port)); // Basic Auth: base64(serial:password) String auth = String(config.restUser) + ":" + String(config.restPass); String authBase64 = base64::encode(auth); client.println("Authorization: Basic " + authBase64); client.println("Content-Length: " + String(totalLength)); client.println("Content-Type: multipart/form-data; boundary=" + boundary); client.println("Connection: close"); client.println(); // Multipart body: head + file data + tail client.print(head); uint8_t buffer[2048]; while (file.available()) { size_t len = file.read(buffer, sizeof(buffer)); client.write(buffer, len); Watchdog::feed(); } client.print(tail); file.close(); // --- Parsowanie odpowiedzi --- int httpCode = 0; String responseLine; unsigned long timeout = millis(); while (client.connected() && millis() - timeout < 15000) { if (client.available()) { responseLine = client.readStringUntil('\n'); responseLine.trim(); if (responseLine.startsWith("HTTP/1.1 ")) { httpCode = responseLine.substring(9, 12).toInt(); } if (responseLine.length() == 0) break; } Watchdog::feed(); } String responseBody = ""; while (client.available()) { responseBody += client.readString(); } client.stop(); // --- Obsługa kodów odpowiedzi wg dokumentacji API --- if (httpCode == 201) { ESP_LOGI(TAG_API, "Upload successful: 201 Created"); return true; } else if (httpCode == 401) { ESP_LOGE(TAG_API, "Unauthorized 401: Wrong serial or password!"); } else if (httpCode == 403) { ESP_LOGE(TAG_API, "Forbidden 403: Device not authorized or user tried upload."); } else if (httpCode == 400) { ESP_LOGE(TAG_API, "Bad Request 400: %s", responseBody.c_str()); } else { ESP_LOGE(TAG_API, "Upload failed with code %d. Response: %s", httpCode, responseBody.c_str()); } return false; }