diff --git a/include/ADXL345FastSPI.h b/include/ADXL345FastSPI.h index 407c5ad..271422b 100644 --- a/include/ADXL345FastSPI.h +++ b/include/ADXL345FastSPI.h @@ -6,8 +6,6 @@ class ADXL345FastSPI { public: - static constexpr uint8_t MAX_SENSORS = 4; // Maksymalna liczba ADXL345 - enum Rate { RATE_100HZ, RATE_200HZ, RATE_400HZ, RATE_800HZ, RATE_1600HZ, RATE_3200HZ }; enum Range { RANGE_2G, RANGE_4G, RANGE_8G, RANGE_16G }; @@ -58,7 +56,7 @@ public: uint8_t refreshActiveMask(); private: - static constexpr uint8_t MAX_NUM = MAX_SENSORS; + static constexpr uint8_t MAX_NUM = 4; // MAX Ilość ADXL345 SPIClass* spi_ = &SPI; ADXL345FreshSPI dev_[MAX_NUM]; diff --git a/include/Config.h b/include/Config.h index 08abafa..e23d9d4 100644 --- a/include/Config.h +++ b/include/Config.h @@ -39,7 +39,7 @@ struct Config { char restUser[30]; // login RestAPI char restPass[50]; // hasło RestAPi uint8_t apiKey[32]; // Klucz API KEY - uint32_t pause; // Pomiar co milisekund (ms) + uint16_t pause; // Pomiar co sekund uint8_t duration; // Czas pomiaru w sekundach 1-25 char S0[12]; // nazwy czujników 1-8 char S1[12]; @@ -53,7 +53,6 @@ struct Config { // Global config declaration extern Config config; -static_assert(sizeof(Config) + 1 <= EEPROM_SIZE, "Config struct exceeds EEPROM!"); class ConfigManager { public: diff --git a/include/Measure.h b/include/Measure.h index 40c5953..78490d6 100644 --- a/include/Measure.h +++ b/include/Measure.h @@ -59,7 +59,6 @@ public: void readHeaderAndPrint(const char *path); void stop(); bool isActive() const { return measurementActive_; } - String getCurrentCapturePath() const { return currentCapturePath_; } // SD utils SpaceInfo freeSpaceMB(); @@ -89,7 +88,6 @@ private: size_t bufferSize_ = 0; size_t bufferIndex_ = 0; bool measurementActive_ = false; - String currentCapturePath_ = ""; bool flushToFile(File &f); static uint8_t crc8(const uint8_t *data, size_t len); @@ -99,7 +97,7 @@ private: bool ispress = (GPIO.in & (1UL << BTN_OK)) == 0; if(ispress) { isExit = true; - measurementActive_ = false; + measurementActive_; display_.textStatus("Cancelling. Wait!"); } return ispress; diff --git a/include/Network.h b/include/Network.h index ca31afe..736b17e 100644 --- a/include/Network.h +++ b/include/Network.h @@ -9,9 +9,11 @@ #include #include #include + #include #elif defined(ESP8266) #include #include + #include #endif //#include //#include @@ -42,6 +44,12 @@ class WiFiManager { int rssiToPercent(int rssi); // rssi na procenty int8_t getRSSI(); + void handleClient(); + void startCaptivePortal(); + void handleRoot(); + void handleSave(); + void handleNotFound(); + /** * Aktualizacja systemu przez internet z adresu config.updateUrl. * @param allowInsecureTLS true => dla https wyłącz weryfikację certyfikatu. @@ -61,6 +69,10 @@ class WiFiManager { private: bool isAccessPoint = false; + bool captivePortalActive = false; + WebServer server{80}; + DNSServer dnsServer; + int expectedCaptchaAnswer = 0; }; #endif // WIFIMANAGER_H diff --git a/include/Uploader.h b/include/Uploader.h new file mode 100644 index 0000000..9ca0487 --- /dev/null +++ b/include/Uploader.h @@ -0,0 +1,24 @@ +#ifndef UPLOADER_H +#define UPLOADER_H + +#include +#include +#include +#include +#include +#include "Config.h" +#include "Display.h" +#include "Watchdog.h" + +class Uploader { +public: + Uploader(Display &display); + void processQueue(int maxFiles = 3); + +private: + Display &_display; + String loadCACert(const char* path); + bool sendFile(String filePath, String& caCert); +}; + +#endif \ No newline at end of file diff --git a/include/Version.h b/include/Version.h index 5314c9e..d06b09f 100644 --- a/include/Version.h +++ b/include/Version.h @@ -1,7 +1,7 @@ #ifndef VERSION_H #define VERSION_H -#define VERSION "1.3.4.2" +#define VERSION "1.3.4.1" // 1: graphical 128x64, 2: LCD I2C Text 4x20 #define LCD_TYPE 2 diff --git a/src/APIClient.cpp b/src/APIClient.cpp index a87d042..eb888fa 100644 --- a/src/APIClient.cpp +++ b/src/APIClient.cpp @@ -71,6 +71,7 @@ bool APIClient::uploadMeasurement(const String &filePath) { // Multipart body: head + file data + tail client.print(head); + ESP_LOGI(TAG_API, "DEBUG: Beginning file read loop"); uint8_t buffer[2048]; while (file.available()) { @@ -79,8 +80,10 @@ bool APIClient::uploadMeasurement(const String &filePath) { Watchdog::feed(); } + ESP_LOGI(TAG_API, "DEBUG: File read loop finished. Writing tail."); client.print(tail); file.close(); + ESP_LOGI(TAG_API, "DEBUG: Tail written. Waiting for response..."); // --- Parsowanie odpowiedzi --- int httpCode = 0; @@ -98,6 +101,7 @@ bool APIClient::uploadMeasurement(const String &filePath) { } Watchdog::feed(); } + ESP_LOGI(TAG_API, "DEBUG: Header parsing loop finished. httpCode: %d", httpCode); String responseBody = ""; while (client.available()) { diff --git a/src/Config.cpp b/src/Config.cpp index 1870135..f17182b 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -39,7 +39,6 @@ void ConfigManager::readConfig() { // Save config to EEPROM void ConfigManager::saveConfig() { ESP_LOGI(TAG_CONF, "SAVE CONFIG"); - EEPROM.begin(EEPROM_SIZE); EEPROM.put(1, config); EEPROM.write(0, 253); if (EEPROM.commit()) { @@ -47,6 +46,7 @@ void ConfigManager::saveConfig() { } else { ESP_LOGE(TAG_CONF, "Error save config"); } + EEPROM.end(); } void ConfigManager::generateApiKey(uint8_t *buf, size_t len) { diff --git a/src/Display.cpp b/src/Display.cpp index ea088ff..c6ce2de 100644 --- a/src/Display.cpp +++ b/src/Display.cpp @@ -158,9 +158,7 @@ void Display::showAccel(float a, float b, float c) { void Display::displayOffline(bool measure, uint8_t count, float freeSpace, long licznik, bool refresh){ if(refresh){ - oyear = 0; omonth = 0; oday = 0; - ohour = 0; omin = 0; osec = 0; - ospace = 0; oadxlcnt = 100; + oyear, omonth, oday, ohour, omin, osec, ospace, oadxlcnt = 100; _lcd->clear(); _lcd->setCursor(0, 0); } diff --git a/src/Measure.cpp b/src/Measure.cpp index 3dc6ca5..42ef797 100644 --- a/src/Measure.cpp +++ b/src/Measure.cpp @@ -78,6 +78,152 @@ bool DataCapture::captureAuto(uint32_t captureSeconds, const char * /*baseDirect } // --- main measure function --- +// bool DataCapture::capture(uint32_t captureSeconds, const char *filename) { +// Watchdog::feed(); +// if (!buffer_) { +// ESP_LOGE(TAG_CAPTURE, "No buffer - cancel."); +// display_.textStatus("Buffer error"); +// return false; +// } +// ESP_LOGI(TAG_CAPTURE, "Capture %u sec. -> %s", (unsigned)captureSeconds, filename); + +// File dataFile = _fs.open(filename, FILE_WRITE); +// if (!dataFile) { +// ESP_LOGE(TAG_CAPTURE, "Can't open file: %s", filename); +// char buf[100]; +// snprintf(buf, sizeof(buf), "Can't open: %s", filename); +// display_.textStatus(buf); +// //s2etTestingIndicator_(false, _baseDir, filename); +// return false; +// } + +// // Bufory RAMKI (po 1 próbce z każdego sensora) +// static const uint8_t MAXN = 7; // zgodnie z projektem +// int16_t X[MAXN]{}, Y[MAXN]{}, Z[MAXN]{}; +// uint32_t TS[MAXN]{}; + +// Watchdog::feed(); +// const uint32_t tStart_us = micros(); +// const uint32_t captureDuration_us = captureSeconds * 1000000UL; + +// // Czas startu (UTC) — tylko w nagłówku! +// const DateTime now = rtc_.now(); +// const int32_t unix_start = now.unixtime(); + +// // Nagłówek +// FileHeader hdr; +// memcpy(hdr.magic, "WMT", 3); +// hdr.version = 1; +// hdr.headerSize = sizeof(FileHeader); +// hdr.sampleSize = sizeof(Sample); +// hdr.timestamp = unix_start; +// hdr.reccount = 0; + +// if (dataFile.write(reinterpret_cast(&hdr), sizeof(hdr)) != sizeof(hdr)) { +// ESP_LOGE(TAG_CAPTURE, "Header write failed"); +// display_.textStatus("Header SD failed"); +// dataFile.close(); +// return false; +// } + +// measurementActive_ = true; +// bufferIndex_ = 0; +// setTestingIndicator_(true, _baseDir, filename); + +// uint32_t frames = 0; // liczba zebranych „ramek” +// const uint8_t presentCnt = adxl_.size(); // wykryte sensory (wg begin) + +// // --- Pętla akwizycji wyrównanych ramek --- +// while (measurementActive_) { +// if(isEscape()) break; // wyjście z pętli gdy OK przerwie +// uint32_t now_us = micros(); +// if ((now_us - tStart_us) >= captureDuration_us) break; + +// // 1) Czekamy aż KAŻDY obecny sensor ma >= 1 próbkę w FIFO (DATA_READY) +// while (!adxl_.availableAll()) { +// if(isEscape()) break; //????? sprawdź kHz pomiaru! +// // krótki spin; unikamy delay(1), aby nie zrywać 3.2 kHz +// // (opcjonalnie yield(); jeśli system wymaga) +// } + +// // 2) Zdejmij po 1 NAJSTARSZEJ próbce z każdego sensora (STREAM FIFO) +// const uint8_t got = adxl_.readAlignedOnce(X, Y, Z, TS); +// if (got != presentCnt) { +// // rzadki przypadek – niepełna ramka; pomiń +// continue; +// } + +// // 3) Wspólny timestamp ramki: minimalny z TS[i] +// uint32_t tmin = UINT32_MAX; +// for (uint8_t i = 0; i < MAXN; ++i) { +// if (!adxl_.isPresent(i)) continue; +// if (TS[i] < tmin) tmin = TS[i]; +// } +// const uint32_t frame_offset_us = tmin - tStart_us; + +// // 4) Zapis 7 rekordów Sample do bufora +// for (uint8_t i = 0; i < MAXN; ++i) { +// if (!adxl_.isPresent(i)) continue; + +// // Konstruuj rekord bezpośrednio w buforze (bez memcpy) +// if (bufferIndex_ + sizeof(Sample) > bufferSize_) { +// if (!flushToFile(dataFile)) { measurementActive_ = false; break; } +// } +// Sample *dst = reinterpret_cast(buffer_ + bufferIndex_); +// dst->offset = frame_offset_us; +// dst->sensor_id = i; +// dst->x = X[i]; dst->y = Y[i]; dst->z = Z[i]; +// dst->ready = true; +// bufferIndex_ += sizeof(Sample); +// } + +// if (!measurementActive_) break; +// frames++; +// // Watchdog rzadziej, żeby nie zwiększać jittera +// if ((frames & 0xFF) == 0) Watchdog::feed(); +// } // while +// // Statystyki +// ESP_LOGI(TAG_CAPTURE, "Frames: %u, sensors: %u", (unsigned)frames, (unsigned)presentCnt); +// printSamplingRate(frames, captureSeconds); // liczymy ramki/sek. + +// // Domknij bufor +// if (bufferIndex_ > 0) { +// if (!flushToFile(dataFile)) { +// Watchdog::feed(); +// ESP_LOGE(TAG_CAPTURE, "Finish save error."); +// display_.textStatus("Save SD error"); +// delay(1000); +// } +// } +// ESP_LOGI(TAG_CAPTURE, "Saved to SD successful"); +// display_.textStatus("Saved SD OK"); +// delay(700); +// // Uzupełnij nagłówek o liczbę rekordów (Sample) +// hdr.reccount = frames * presentCnt; +// dataFile.seek(0); +// dataFile.write(reinterpret_cast(&hdr), sizeof(hdr)); +// dataFile.flush(); +// dataFile.close(); + +// //setTestingIndicator_(false, _baseDir, filename); + +// bool ok = measurementActive_; +// measurementActive_ = false; + +// if (ok) { +// ESP_LOGI(TAG_CAPTURE, "Successful"); +// display_.textStatus("Successfull"); +// Watchdog::feed(); +// return true; +// } else { +// ESP_LOGE(TAG_CAPTURE, "Measurement aborted"); +// display_.textStatus("Aborted"); +// Watchdog::feed(); +// return false;f +// } +// } + +// --- main measure function --- V2 bool DataCapture::capture(uint32_t captureSeconds, const char *filename) { Watchdog::feed(); if (!buffer_) { @@ -90,12 +236,10 @@ bool DataCapture::capture(uint32_t captureSeconds, const char *filename) { const uint8_t presentCnt = adxl_.size(); const size_t frameSize = presentCnt * sizeof(Sample); - currentCapturePath_ = String(filename); - ESP_LOGI(TAG_CAPTURE, "Start RAM capture: %u sec.", (unsigned)captureSeconds); display_.textStatus("Sampling..."); - static constexpr uint8_t MAXN = ADXL345FastSPI::MAX_SENSORS; + static const uint8_t MAXN = 7; int16_t X[MAXN]{}, Y[MAXN]{}, Z[MAXN]{}; uint32_t TS[MAXN]{}; @@ -181,21 +325,16 @@ bool DataCapture::capture(uint32_t captureSeconds, const char *filename) { hdr.reccount = frames * presentCnt; // Zapis nagłówka - size_t hdrWritten = dataFile.write(reinterpret_cast(&hdr), sizeof(hdr)); - if (hdrWritten != sizeof(hdr)) { - ESP_LOGE(TAG_CAPTURE, "Header write failed!"); - dataFile.close(); - return false; - } + dataFile.write(reinterpret_cast(&hdr), sizeof(hdr)); // Zapis całego bufora PSRAM jednym ciągiem size_t written = dataFile.write(buffer_, bufferIndex_); if (written == bufferIndex_) { ESP_LOGI(TAG_CAPTURE, "SD Save Successful: %u bytes", (unsigned)written); - dataFile.flush(); // Power-loss resilience: wymuszenie zapisu na kartę + //display_.textStatus("Save OK"); display_.textStatus(basenameFromPath(filename)); - delay(500); + delay(2000); // Tutaj zrob to inaczej } else { ESP_LOGE(TAG_CAPTURE, "SD Write Error!"); display_.textStatus("SD Write Err"); @@ -203,7 +342,6 @@ bool DataCapture::capture(uint32_t captureSeconds, const char *filename) { dataFile.close(); printSamplingRate(frames, captureSeconds, filename); - currentCapturePath_ = ""; return (written == bufferIndex_); } @@ -249,7 +387,7 @@ void DataCapture::printSamplingRate(uint32_t reccount, uint32_t captureSeconds, ESP_LOGI(TAG_CAPTURE,"Rate: %.3f kHz (frames)", fs_kHz); display_.displaySampleRateSummary(reccount, captureSeconds, fs_kHz, filename); display_.textStatus("Summary"); - delay(500); + delay(2000); } // --- Zarządzanie katalogami/plikiem --- @@ -371,7 +509,7 @@ const char *DataCapture::basenameFromPath(const char *full) { const char *slash bool DataCapture::isWmtWithDigits(const char* name, uint32_t& idxOut) const { size_t nlen = strlen(name), extLen = _ext.length(); if (nlen != (size_t)_digits + extLen) return false; - if (strncmp(name + _digits, _ext.c_str(), extLen) != 0 && strncmp(name + _digits, ".upl", 4) != 0) return false; + if (strncmp(name + _digits, _ext.c_str(), extLen) != 0) return false; for (uint8_t i = 0; i < _digits; ++i) if (name[i] < '0' || name[i] > '9') return false; char buf[16]; memcpy(buf, name, _digits); buf[_digits] = '\0'; idxOut = (uint32_t)strtoul(buf, nullptr, 10); return true; @@ -395,7 +533,6 @@ uint32_t DataCapture::findHighestNumericDir() { if (!root || !root.isDirectory()) return 0; uint32_t maxDir = 0; for (File f = root.openNextFile(); f; f = root.openNextFile()) { - Watchdog::feed(); if (f.isDirectory()) { const char *nm = basenameFromPath(f.name()); if (isAllDigits(nm)) { uint32_t v = toUint(nm); if (v > maxDir) maxDir = v; } @@ -411,8 +548,7 @@ void DataCapture::scanDirForWmt(uint32_t dirNum, uint32_t &count, uint32_t &high File dir = _fs.open(path); if (!dir || !dir.isDirectory()) return; for (File f = dir.openNextFile(); f; f = dir.openNextFile()) { - Watchdog::feed(); - if (f.isDirectory()) { f.close(); continue; } + if (f.isDirectory()) { Watchdog::feed(); f.close(); continue; } const char* base = basenameFromPath(f.name()); uint32_t idx = 0; if (isWmtWithDigits(base, idx)) { count++; if (idx > highestIdx) highestIdx = idx; } @@ -453,8 +589,15 @@ void DataCapture::printLastFileInfoSerial() { snprintf(buf, sizeof(buf), "Size:%.2f KB", kb); display_.textStatus(buf); #else - ESP_LOGI(TAG_CAPTURE, "Last file: %s", info.path.c_str()); - ESP_LOGI(TAG_CAPTURE, "Size: %.2f MB", mb); + // Wariant zachowawczy dla platform bez %llu; pokazujemy rozmiar w MB/KB. + ESP_LOGI(TAG_CAPTURE, "Last file info: ")); + ESP_LOGI(TAG_CAPTURE, info.path); + ESP_LOGI(TAG_CAPTURE, "Size: ")); + ESP_LOGI(TAG_CAPTURE, mb); + //ESP_LOGI(TAG_CAPTURE, " MB")); + //Serial.print(F(" (")); + //Serial.print(kb, 2); + //Serial.println(F(" KB)")); #endif } diff --git a/src/Network.cpp b/src/Network.cpp index 1a4d80f..70b475d 100644 --- a/src/Network.cpp +++ b/src/Network.cpp @@ -272,4 +272,95 @@ bool WiFiManager::performOTAUpdate(bool allowInsecureTLS, std::function"; + html += ""; + html += "

WiFi Configuration

"; + html += "
"; + html += "
"; + html += ""; + html += ""; + html += ""; + html += ""; + html += ""; + html += ""; + html += ""; + html += "
"; + + server.send(200, "text/html", html); +} + +void WiFiManager::handleSave() { + String ssid = server.arg("ssid"); + String pass = server.arg("password"); + String captchaStr = server.arg("captcha"); + + if (captchaStr.toInt() != expectedCaptchaAnswer) { + server.send(400, "text/plain", "Incorrect Captcha. Please go back and try again."); + return; + } + + if (ssid.length() > 0) { + File f = SD.open("/wifi.txt", FILE_APPEND); + if (f) { + f.println(ssid + ";" + pass); + f.close(); + server.send(200, "text/plain", "Credentials saved successfully! The device will now restart and try to connect."); + delay(2000); + ESP.restart(); + } else { + server.send(500, "text/plain", "Failed to open wifi.txt on SD card for appending."); + } + } else { + server.send(400, "text/plain", "SSID cannot be empty."); + } +} + +void WiFiManager::handleNotFound() { + server.sendHeader("Location", String("http://") + WiFi.softAPIP().toString(), true); + server.send(302, "text/plain", ""); } \ No newline at end of file diff --git a/src/UploadManager.cpp b/src/UploadManager.cpp index dd9f15e..f23ee0a 100644 --- a/src/UploadManager.cpp +++ b/src/UploadManager.cpp @@ -16,34 +16,63 @@ String UploadManager::getCurrentTimestamp() { } void UploadManager::appendLog(const String& filePath, const String& status) { - // Legacy CSV log removed to fix O(N^2) SD card bottleneck + File f = SD.open(LOG_FILE, FILE_APPEND); + if (f) { + f.printf("%s,%s,%s\n", getCurrentTimestamp().c_str(), filePath.c_str(), status.c_str()); + f.close(); + } } bool UploadManager::isAlreadyUploaded(const String& filePath) { - // We now rely on file extensions (.wmt = pending, .upl = uploaded) - return false; + File f = SD.open(LOG_FILE, FILE_READ); + if (!f) return false; + + bool found = false; + while (f.available()) { + String line = f.readStringUntil('\n'); + line.trim(); + if (line.length() == 0) continue; + + int firstComma = line.indexOf(','); + if (firstComma < 0) continue; + int secondComma = line.indexOf(',', firstComma + 1); + if (secondComma < 0) continue; + + String logPath = line.substring(firstComma + 1, secondComma); + String logStatus = line.substring(secondComma + 1); + + if (logPath == filePath) { + if (logStatus == "OK") found = true; + else found = false; // A retry might be needed if last status wasn't OK + } + Watchdog::feed(); + } + f.close(); + return found; } void UploadManager::uploadFile(const String& filePath) { - if (WiFi.status() != WL_CONNECTED) { - ESP_LOGE(TAG_UPLOAD, "No WiFi. Cannot upload %s", filePath.c_str()); + if (isAlreadyUploaded(filePath)) { + ESP_LOGI(TAG_UPLOAD, "File %s is already uploaded.", filePath.c_str()); return; } + if (WiFi.status() != WL_CONNECTED) { + ESP_LOGE(TAG_UPLOAD, "No WiFi. Cannot upload %s", filePath.c_str()); + appendLog(filePath, "ERROR: No WiFi"); + return; + } + + ESP_LOGI(TAG_UPLOAD, "DEBUG: Before apiClient.uploadMeasurement"); bool success = apiClient.uploadMeasurement(filePath); + ESP_LOGI(TAG_UPLOAD, "DEBUG: After apiClient.uploadMeasurement. Success: %d", success); if (success) { - String newPath = filePath; - newPath.replace(".wmt", ".upl"); - if (SD.rename(filePath, newPath)) { - ESP_LOGI(TAG_UPLOAD, "Renamed %s to .upl", filePath.c_str()); - } else { - ESP_LOGE(TAG_UPLOAD, "Rename to .upl failed for %s", filePath.c_str()); - } + appendLog(filePath, "OK"); } else { if (WiFi.status() != WL_CONNECTED) { - ESP_LOGE(TAG_UPLOAD, "WiFi lost during upload"); + appendLog(filePath, "ERROR: WiFi lost during upload"); } else { - ESP_LOGE(TAG_UPLOAD, "Upload Failed"); + appendLog(filePath, "ERROR: Upload Failed"); } } } @@ -52,43 +81,42 @@ void UploadManager::processPendingUploads() { if (WiFi.status() != WL_CONNECTED) return; ESP_LOGI(TAG_UPLOAD, "Checking for pending uploads..."); + ESP_LOGI(TAG_UPLOAD, "DEBUG: findHighestNumericDir()"); uint32_t highestDir = capture_.findHighestNumericDir(); + ESP_LOGI(TAG_UPLOAD, "DEBUG: highestDir: %u", highestDir); if (highestDir == 0) return; for (uint32_t d = 1; d <= highestDir; d++) { String path = capture_.dirPath(d); + ESP_LOGI(TAG_UPLOAD, "DEBUG: Scanning dir: %s", path.c_str()); File dir = SD.open(path); if (!dir || !dir.isDirectory()) { if(dir) dir.close(); - vTaskDelay(1); // yield do IDLE0 między katalogami continue; } for (File f = dir.openNextFile(); f; f = dir.openNextFile()) { - vTaskDelay(1); // yield do IDLE0 przy każdym pliku — zapobiega głodzeniu WDT if (f.isDirectory()) { f.close(); continue; } String childPath = String(f.name()); if (!childPath.startsWith("/")) { childPath = path + "/" + childPath; } + + if (childPath.endsWith(".wmt")) { + if (!isAlreadyUploaded(childPath)) { + ESP_LOGI(TAG_UPLOAD, "Found pending file: %s", childPath.c_str()); + ESP_LOGI(TAG_UPLOAD, "DEBUG: Calling uploadFile()"); + uploadFile(childPath); + ESP_LOGI(TAG_UPLOAD, "DEBUG: Finished uploadFile(), delaying 1000ms"); + delay(1000); + } + } f.close(); - - // Pomijamy pliki .upl (już wgrane) i inne rozszerzenia - if (!childPath.endsWith(".wmt")) continue; - - // Pomijamy plik aktualnie zapisywany przez pomiar - if (childPath == capture_.getCurrentCapturePath()) continue; - Watchdog::feed(); if (WiFi.status() != WL_CONNECTED) { dir.close(); return; } - - ESP_LOGI(TAG_UPLOAD, "Found pending file: %s", childPath.c_str()); - uploadFile(childPath); - // Krótka przerwa po uploaderze — pozwala IDLE0 na reset WDT - vTaskDelay(pdMS_TO_TICKS(200)); } dir.close(); } diff --git a/src/Uploader.cpp b/src/Uploader.cpp new file mode 100644 index 0000000..2f7fa4d --- /dev/null +++ b/src/Uploader.cpp @@ -0,0 +1,79 @@ +#include "Uploader.h" + +Uploader::Uploader(Display &display) : _display(display) {} + +void Uploader::processQueue(int maxFiles) { + if (WiFi.status() != WL_CONNECTED) return; + + String caCert = loadCACert("/cert.pem"); + if (caCert == "") { + ESP_LOGE("UPLOADER", "Brak cert.pem na SD!"); + return; + } + + int sentCount = 0; + File root = SD.open("/"); + + // Przeszukiwanie folderów numerycznych stworzonych przez Measure.cpp + while (File folder = root.openNextFile()) { + if (sentCount >= maxFiles) break; + if (folder.isDirectory()) { + File dir = SD.open(folder.path()); + while (File file = dir.openNextFile()) { + if (sentCount >= maxFiles) break; + + String fileName = file.name(); + if (fileName.endsWith(".wmt")) { + _display.textStatus("SSL UPLOADING..."); + if (sendFile(String(file.path()), caCert)) { + String oldPath = String(file.path()); + String newPath = oldPath; + newPath.replace(".wmt", ".sent"); + file.close(); + SD.rename(oldPath.c_str(), newPath.c_str()); + sentCount++; + } + } + file.close(); + } + dir.close(); + } + folder.close(); + } + root.close(); +} + +bool Uploader::sendFile(String filePath, String& caCert) { + File f = SD.open(filePath, FILE_READ); + if (!f) return false; + + WiFiClientSecure client; + client.setCACert(caCert.c_str()); + HTTPClient http; + bool success = false; + + if (http.begin(client, "https://api.pwojtaszek.codes/upload")) { + http.addHeader("Content-Type", "application/octet-stream"); + http.addHeader("X-File-Name", filePath); + http.addHeader("X-Device-ID", config.hostname); + + int code = http.sendRequest("POST", &f, f.size()); + if (code == 200) { + ESP_LOGI("UPLOADER", "Upload OK: %s", filePath.c_str()); + success = true; + } else { + ESP_LOGE("UPLOADER", "Error: %d", code); + } + http.end(); + } + f.close(); + return success; +} + +String Uploader::loadCACert(const char* path) { + File f = SD.open(path); + if (!f) return ""; + String cert = f.readString(); + f.close(); + return cert; +} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 25b71d4..dfc3f60 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -24,7 +24,8 @@ SPIClass SPI_ADXL(FSPI); // SPI2 (VSPI) SPIClass SPI_SD(HSPI); // SPI3 (HSPI) - +float x, y, z = 0; // Dane odczytane z akcelerometru +String name; bool isRebootRequired = false; bool isAccelExists = false; // Czy istnieje jakiś podłączony do SPI czujnik @@ -52,23 +53,7 @@ UploadManager uploadManager(apiClient, rtc, capture); Thread wifiTestThread = Thread(); // Cykliczny test WiFi Thread offlineThread = Thread(); // Jeśli offline Thread measureThread = Thread(); // Pomiar i zapis na SD - -TaskHandle_t uploadTaskHandle = NULL; -SemaphoreHandle_t sdMutex = NULL; // Mutex chroniący dostęp do karty SD - -void uploadTaskCode(void *parameter) { - Watchdog::addThisTask(); - while(true) { - if (config.connect && WiFi.status() == WL_CONNECTED && !testingNow) { - if (xSemaphoreTake(sdMutex, pdMS_TO_TICKS(1000)) == pdTRUE) { - uploadManager.processPendingUploads(); - xSemaphoreGive(sdMutex); - } - } - Watchdog::feed(); - vTaskDelay(pdMS_TO_TICKS(5000)); - } -} +Thread uploadThread = Thread(); // Background upload //////// PROTOTYPY ///////////////// void setup(); @@ -193,9 +178,6 @@ void setup() { ESP_LOGI(TAG_MAIN, "SD Card OK"); display.print("OK"); - // Inicjalizacja mutex SD (po SD.begin) - sdMutex = xSemaphoreCreateMutex(); - ESP_LOGI(TAG_MAIN, "ADXL345 SPI3 SCK: %d, MISO: %d, MOSI: %d", CLK_ADSX, MISO_ADSX, MOSI_ADSX); SPI_ADXL.begin(CLK_ADSX, MISO_ADSX, MOSI_ADSX); @@ -242,16 +224,8 @@ void setup() { measureThread.setInterval(config.pause); // Test co X sekund if(config.connect){ - xTaskCreatePinnedToCore( - uploadTaskCode, // Funkcja zadania - "UploadTask", // Nazwa zadania - 8192, // Rozmiar stosu - NULL, // Parametr - 1, // Priorytet (1 - niski, domyślna pętla ma 1 na Core 1) - &uploadTaskHandle,// Uchwyt - 0 // Przypięcie do rdzenia 0 (Wi-Fi działa domyślnie na 0) - ); - ESP_LOGI(TAG_MAIN, "Upload task created on Core 0"); + uploadThread.onRun([]() { uploadManager.processPendingUploads(); }); + uploadThread.setInterval(60000); // Co 1 minutę sprawdzaj zaległe } // Dodanie taska loop do WDT @@ -367,19 +341,27 @@ void measure(){ snprintf(btime, sizeof(btime), "%02d:%02d:%02d", now.hour(), now.minute(), now.second()); display.initMeasure(config.measure, testingNow, runMeasure, config.pause, config.duration, config.connect, licznik, bdate, btime); ESP_LOGI(TAG_MAIN, "MEASURE RUNNING"); - xSemaphoreTake(sdMutex, portMAX_DELAY); + //delay(1000); capture.captureAuto(config.duration, "/"); - testingNow = false; Watchdog::feed(); if(!capture.isExit){ capture.printLastFileInfoSerial(); + ESP_LOGI(TAG_MAIN, "DEBUG: before readHeaderAndPrint"); capture.readHeaderAndPrint(capture.generateNextFilename().c_str()); ESP_LOGI(TAG_MAIN, "MEASURE FINISH"); } else { ESP_LOGI(TAG_MAIN, "MEASURE INTERRUPT"); + //runMeasure = false; + } + + ESP_LOGI(TAG_MAIN, "DEBUG: Checking WiFi status for upload"); + if (config.connect && WiFi.status() == WL_CONNECTED) { + ESP_LOGI(TAG_MAIN, "TRIGGER UPLOAD PENDING..."); + ESP_LOGI(TAG_MAIN, "DEBUG: Before uploadManager.processPendingUploads()"); + uploadManager.processPendingUploads(); + ESP_LOGI(TAG_MAIN, "DEBUG: After uploadManager.processPendingUploads()"); } - xSemaphoreGive(sdMutex); runMeasure = false; ESP_LOGI(TAG_MAIN, "DISPLAY Offline"); @@ -421,6 +403,18 @@ void loop() { if(settings.isPressed(3)) settingsDevice(); // DOWN if(settings.isPressed(1)) toogleMode(); // UP + if (settings.readBtnUp() && settings.readBtnOk()) { + ESP_LOGI(TAG_MAIN, "Manual AP Mode trigger"); + wifi.startCaptivePortal(); + display.clear(); + display.textCenter(1, "AP MODE"); + display.textCenter(2, "192.168.4.1"); + while(settings.readBtnUp() && settings.readBtnOk()) { + Watchdog::feed(); + delay(50); + } + } + if(testingNow) { if((runMeasure) && (settings.isPressed(2))){ runMeasure = false; @@ -432,6 +426,9 @@ void loop() { if(wifiTestThread.shouldRun() && (config.connect)) wifiTestThread.run(); + if(uploadThread.shouldRun() && config.connect) + uploadThread.run(); + if(offlineThread.shouldRun() && (!config.connect)){ offlineThread.run(); } @@ -460,6 +457,7 @@ void loop() { reboot(); } + wifi.handleClient(); Watchdog::feed(); delay(20); // 100 licznik ++;