diff --git a/include/ADXL345FastSPI.h b/include/ADXL345FastSPI.h index 271422b..407c5ad 100644 --- a/include/ADXL345FastSPI.h +++ b/include/ADXL345FastSPI.h @@ -6,6 +6,8 @@ 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 }; @@ -56,7 +58,7 @@ public: uint8_t refreshActiveMask(); private: - static constexpr uint8_t MAX_NUM = 4; // MAX Ilość ADXL345 + static constexpr uint8_t MAX_NUM = MAX_SENSORS; SPIClass* spi_ = &SPI; ADXL345FreshSPI dev_[MAX_NUM]; diff --git a/include/Config.h b/include/Config.h index e23d9d4..08abafa 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 - uint16_t pause; // Pomiar co sekund + uint32_t pause; // Pomiar co milisekund (ms) uint8_t duration; // Czas pomiaru w sekundach 1-25 char S0[12]; // nazwy czujników 1-8 char S1[12]; @@ -53,6 +53,7 @@ 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 ea62c8b..40c5953 100644 --- a/include/Measure.h +++ b/include/Measure.h @@ -99,7 +99,7 @@ private: bool ispress = (GPIO.in & (1UL << BTN_OK)) == 0; if(ispress) { isExit = true; - measurementActive_; + measurementActive_ = false; display_.textStatus("Cancelling. Wait!"); } return ispress; diff --git a/include/Version.h b/include/Version.h index d06b09f..5314c9e 100644 --- a/include/Version.h +++ b/include/Version.h @@ -1,7 +1,7 @@ #ifndef VERSION_H #define VERSION_H -#define VERSION "1.3.4.1" +#define VERSION "1.3.4.2" // 1: graphical 128x64, 2: LCD I2C Text 4x20 #define LCD_TYPE 2 diff --git a/releases/v1.3.4.2/src/UploadManager.cpp b/releases/v1.3.4.2/src/UploadManager.cpp index 235ed6e..e48ff1b 100644 --- a/releases/v1.3.4.2/src/UploadManager.cpp +++ b/releases/v1.3.4.2/src/UploadManager.cpp @@ -16,59 +16,34 @@ String UploadManager::getCurrentTimestamp() { } void UploadManager::appendLog(const String& filePath, const String& status) { - 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(); - } + // Legacy CSV log removed to fix O(N^2) SD card bottleneck } bool UploadManager::isAlreadyUploaded(const String& filePath) { - 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; + // We now rely on file extensions (.wmt = pending, .upl = uploaded) + return false; } void UploadManager::uploadFile(const String& filePath) { if (WiFi.status() != WL_CONNECTED) { ESP_LOGE(TAG_UPLOAD, "No WiFi. Cannot upload %s", filePath.c_str()); - appendLog(filePath, "ERROR: No WiFi"); return; } bool success = apiClient.uploadMeasurement(filePath); if (success) { - appendLog(filePath, "OK"); String newPath = filePath; newPath.replace(".wmt", ".upl"); - SD.rename(filePath, newPath); + 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()); + } } else { if (WiFi.status() != WL_CONNECTED) { - appendLog(filePath, "ERROR: WiFi lost during upload"); + ESP_LOGE(TAG_UPLOAD, "WiFi lost during upload"); } else { - appendLog(filePath, "ERROR: Upload Failed"); + ESP_LOGE(TAG_UPLOAD, "Upload Failed"); } } } diff --git a/releases/v1.3.4.2/src/main.cpp b/releases/v1.3.4.2/src/main.cpp index f36985c..25b71d4 100644 --- a/releases/v1.3.4.2/src/main.cpp +++ b/releases/v1.3.4.2/src/main.cpp @@ -369,7 +369,7 @@ void measure(){ ESP_LOGI(TAG_MAIN, "MEASURE RUNNING"); xSemaphoreTake(sdMutex, portMAX_DELAY); capture.captureAuto(config.duration, "/"); - xSemaphoreGive(sdMutex); + testingNow = false; Watchdog::feed(); if(!capture.isExit){ @@ -379,6 +379,7 @@ void measure(){ } else { ESP_LOGI(TAG_MAIN, "MEASURE INTERRUPT"); } + xSemaphoreGive(sdMutex); runMeasure = false; ESP_LOGI(TAG_MAIN, "DISPLAY Offline"); diff --git a/src/Config.cpp b/src/Config.cpp index f17182b..1870135 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -39,6 +39,7 @@ 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()) { @@ -46,7 +47,6 @@ 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 c6ce2de..ea088ff 100644 --- a/src/Display.cpp +++ b/src/Display.cpp @@ -158,7 +158,9 @@ 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, omonth, oday, ohour, omin, osec, ospace, oadxlcnt = 100; + oyear = 0; omonth = 0; oday = 0; + ohour = 0; omin = 0; osec = 0; + ospace = 0; oadxlcnt = 100; _lcd->clear(); _lcd->setCursor(0, 0); } diff --git a/src/Measure.cpp b/src/Measure.cpp index 556d937..3dc6ca5 100644 --- a/src/Measure.cpp +++ b/src/Measure.cpp @@ -78,152 +78,6 @@ 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_) { @@ -241,7 +95,7 @@ bool DataCapture::capture(uint32_t captureSeconds, const char *filename) { ESP_LOGI(TAG_CAPTURE, "Start RAM capture: %u sec.", (unsigned)captureSeconds); display_.textStatus("Sampling..."); - static const uint8_t MAXN = 7; + static constexpr uint8_t MAXN = ADXL345FastSPI::MAX_SENSORS; int16_t X[MAXN]{}, Y[MAXN]{}, Z[MAXN]{}; uint32_t TS[MAXN]{}; @@ -327,16 +181,21 @@ bool DataCapture::capture(uint32_t captureSeconds, const char *filename) { hdr.reccount = frames * presentCnt; // Zapis nagłówka - dataFile.write(reinterpret_cast(&hdr), sizeof(hdr)); + 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; + } // 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); - //display_.textStatus("Save OK"); + dataFile.flush(); // Power-loss resilience: wymuszenie zapisu na kartę display_.textStatus(basenameFromPath(filename)); - delay(2000); // Tutaj zrob to inaczej + delay(500); } else { ESP_LOGE(TAG_CAPTURE, "SD Write Error!"); display_.textStatus("SD Write Err"); @@ -390,7 +249,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(2000); + delay(500); } // --- Zarządzanie katalogami/plikiem --- @@ -594,15 +453,8 @@ void DataCapture::printLastFileInfoSerial() { snprintf(buf, sizeof(buf), "Size:%.2f KB", kb); display_.textStatus(buf); #else - // 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)")); + ESP_LOGI(TAG_CAPTURE, "Last file: %s", info.path.c_str()); + ESP_LOGI(TAG_CAPTURE, "Size: %.2f MB", mb); #endif } diff --git a/src/UploadManager.cpp b/src/UploadManager.cpp index 235ed6e..e48ff1b 100644 --- a/src/UploadManager.cpp +++ b/src/UploadManager.cpp @@ -16,59 +16,34 @@ String UploadManager::getCurrentTimestamp() { } void UploadManager::appendLog(const String& filePath, const String& status) { - 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(); - } + // Legacy CSV log removed to fix O(N^2) SD card bottleneck } bool UploadManager::isAlreadyUploaded(const String& filePath) { - 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; + // We now rely on file extensions (.wmt = pending, .upl = uploaded) + return false; } void UploadManager::uploadFile(const String& filePath) { if (WiFi.status() != WL_CONNECTED) { ESP_LOGE(TAG_UPLOAD, "No WiFi. Cannot upload %s", filePath.c_str()); - appendLog(filePath, "ERROR: No WiFi"); return; } bool success = apiClient.uploadMeasurement(filePath); if (success) { - appendLog(filePath, "OK"); String newPath = filePath; newPath.replace(".wmt", ".upl"); - SD.rename(filePath, newPath); + 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()); + } } else { if (WiFi.status() != WL_CONNECTED) { - appendLog(filePath, "ERROR: WiFi lost during upload"); + ESP_LOGE(TAG_UPLOAD, "WiFi lost during upload"); } else { - appendLog(filePath, "ERROR: Upload Failed"); + ESP_LOGE(TAG_UPLOAD, "Upload Failed"); } } } diff --git a/src/main.cpp b/src/main.cpp index e05c010..25b71d4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -24,8 +24,7 @@ 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 @@ -55,15 +54,19 @@ 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) { - uploadManager.processPendingUploads(); + if (xSemaphoreTake(sdMutex, pdMS_TO_TICKS(1000)) == pdTRUE) { + uploadManager.processPendingUploads(); + xSemaphoreGive(sdMutex); + } } Watchdog::feed(); - vTaskDelay(pdMS_TO_TICKS(5000)); // wait 5 seconds before checking again + vTaskDelay(pdMS_TO_TICKS(5000)); } } @@ -190,6 +193,9 @@ 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); @@ -361,8 +367,9 @@ 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"); - //delay(1000); + xSemaphoreTake(sdMutex, portMAX_DELAY); capture.captureAuto(config.duration, "/"); + testingNow = false; Watchdog::feed(); if(!capture.isExit){ @@ -372,6 +379,7 @@ void measure(){ } else { ESP_LOGI(TAG_MAIN, "MEASURE INTERRUPT"); } + xSemaphoreGive(sdMutex); runMeasure = false; ESP_LOGI(TAG_MAIN, "DISPLAY Offline");