Wgranie zmian do repozytorium

This commit is contained in:
2026-05-10 16:46:04 +02:00
commit f171113450
1607 changed files with 254616 additions and 0 deletions

View File

@@ -0,0 +1,594 @@
#include <Measure.h>
static const char *TAG_CAPTURE = "CAPTURE";
// Był na sztywno przydzielony bufor 128 KB. Zmieniam tak, aby zaalokować 4 MB lub 6 MB (trzeba zostawić resztę na potrzeby systemu).
DataCapture::DataCapture(ADXL345FastSPI &adxl, Display &display, RTC_DS3231 &rtc, fs::FS &storage, size_t bufferSize)
: adxl_(adxl), display_(display), rtc_(rtc), _fs(storage){
// Ustawiamy duży bufor dla PSRAM (np. 4 MB = 4194304 bajty)
// 4 * 1024 * 1024 / 12 bajtów = ~349 525 próbek
// Możesz to przekazać jako parametr lub wpisać na sztywno:
if (bufferSize < 4 * 1024 * 1024) bufferSize = 4 * 1024 * 1024;
bufferSize_ = bufferSize;
#if defined(ESP32)
// ps_malloc jest kluczowy dla PSRAM. Wymuszamy alokację w zewnętrznym PSRAM
buffer_ = (uint8_t*)ps_malloc(bufferSize_);
#endif
if (!buffer_) buffer_ = (uint8_t*)malloc(bufferSize_);
if (!buffer_) {
ESP_LOGE(TAG_CAPTURE, "No mem for buffer (%u B)", (unsigned)bufferSize_);
} else {
ESP_LOGI(TAG_CAPTURE, "Capture buffer: %u B", (unsigned)bufferSize_);
}
}
DataCapture::~DataCapture() {
if (buffer_) { free(buffer_); buffer_ = nullptr; }
}
void DataCapture::stop() { measurementActive_ = false; }
// --- ZAPIS BUFORA NA SD ---
bool DataCapture::flushToFile(File& f) {
if (bufferIndex_ == 0) return true;
Watchdog::feed();
size_t written = f.write(buffer_, bufferIndex_);
if (written != bufferIndex_) {
ESP_LOGE(TAG_CAPTURE, "SD save error (written=%u, expected=%u)", (unsigned)written, (unsigned)bufferIndex_);
display_.textStatus("SD save error");
bufferIndex_ = 0;
return false;
}
bufferIndex_ = 0;
return true;
}
// CRC8 (zostawione nieużywane domyślnie)
uint8_t DataCapture::crc8(const uint8_t* data, size_t len) {
uint8_t crc = 0x00;
for (size_t i = 0; i < len; ++i) {
crc ^= data[i];
for (uint8_t b = 0; b < 8; ++b) {
if (crc & 0x80) crc = (crc << 1) ^ 0x07; else crc <<= 1;
}
}
return crc;
}
void DataCapture::setTestingIndicator_(bool on, String myDir, String myFile) {
display_.displayOnOffM(on, myDir, myFile);
}
// --- automatyczna nazwa pliku ---
bool DataCapture::captureAuto(uint32_t captureSeconds, const char * /*baseDirectory*/) {
isExit = false;
Watchdog::feed();
String path = allocateNextFilePath();
if (path.isEmpty()) {
ESP_LOGE(TAG_CAPTURE, "Error generate file name in %s", _baseDir.c_str());
display_.textStatus("Error file");
return false;
}
ESP_LOGI(TAG_CAPTURE, "Autogenerate file: %s", path.c_str());
display_.textStatus(path.c_str());
Watchdog::feed();
return capture(captureSeconds, path.c_str());
}
// --- 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<const uint8_t*>(&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<Sample*>(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<const uint8_t*>(&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_) {
ESP_LOGE(TAG_CAPTURE, "No buffer - cancel.");
display_.textStatus("Buffer error");
return false;
}
// Obliczamy ile danych maksymalnie może wejść do bufora
const uint8_t presentCnt = adxl_.size();
const size_t frameSize = presentCnt * sizeof(Sample);
ESP_LOGI(TAG_CAPTURE, "Start RAM capture: %u sec.", (unsigned)captureSeconds);
display_.textStatus("Sampling...");
static const uint8_t MAXN = 7;
int16_t X[MAXN]{}, Y[MAXN]{}, Z[MAXN]{};
uint32_t TS[MAXN]{};
measurementActive_ = true;
bufferIndex_ = 0;
uint32_t frames = 0;
const uint32_t tStart_us = micros();
const uint32_t captureDuration_us = captureSeconds * 1000000UL;
const DateTime now_start = rtc_.now(); // Czas dla nagłówka
// --- KRYTYCZNA PĘTLA POMIAROWA (ZERO SD) ---
while (measurementActive_) {
if(isEscape()) break;
uint32_t now_us = micros();
if ((now_us - tStart_us) >= captureDuration_us) break;
// Sprawdzenie czy mamy miejsce w PSRAM na kolejną pełną ramkę
if (bufferIndex_ + frameSize > bufferSize_) {
ESP_LOGW(TAG_CAPTURE, "PSRAM Buffer Full! Stopping.");
break;
}
// Czekamy na dane z sensora
while (!adxl_.availableAll()) {
if(isEscape()) break;
}
const uint8_t got = adxl_.readAlignedOnce(X, Y, Z, TS);
if (got != presentCnt) continue;
// Wspólny timestamp ramki
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;
// Zapis do PSRAM
for (uint8_t i = 0; i < MAXN; ++i) {
if (!adxl_.isPresent(i)) continue;
Sample *dst = reinterpret_cast<Sample*>(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);
}
frames++;
if ((frames & 0x3FF) == 0) Watchdog::feed(); // Rzadziej, by nie siać jittera
} // --- KONIEC PĘTLI POMIAROWEJ ---
measurementActive_ = false;
ESP_LOGI(TAG_CAPTURE, "Capture finished. Samples in RAM: %u. Writing to SD...", (unsigned)frames);
display_.textStatus("Saving to SD...");
// Dopiero teraz otwieramy plik na SD
File dataFile = _fs.open(filename, FILE_WRITE);
if (!dataFile) {
ESP_LOGE(TAG_CAPTURE, "Can't open SD file!");
display_.textStatus("SD Open Error");
return false;
}
// Przygotowanie nagłówka
FileHeader hdr;
memcpy(hdr.magic, "WMT", 3);
hdr.version = 1;
hdr.headerSize = sizeof(FileHeader);
hdr.sampleSize = sizeof(Sample);
hdr.timestamp = now_start.unixtime();
hdr.reccount = frames * presentCnt;
// Zapis nagłówka
dataFile.write(reinterpret_cast<const uint8_t*>(&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);
//display_.textStatus("Save OK");
display_.textStatus(basenameFromPath(filename));
delay(2000); // Tutaj zrob to inaczej
} else {
ESP_LOGE(TAG_CAPTURE, "SD Write Error!");
display_.textStatus("SD Write Err");
}
dataFile.close();
printSamplingRate(frames, captureSeconds, filename);
return (written == bufferIndex_);
}
// --- Narzędzia SD i pierdoły ---
String DataCapture::unixToDateTime(uint32_t ts) {
DateTime dt(ts);
char buf[25];
snprintf(buf, sizeof(buf), "%04u-%02u-%02u %02u:%02u:%02u",
dt.year(), dt.month(), dt.day(), dt.hour(), dt.minute(), dt.second());
return String(buf);
}
void DataCapture::readHeaderAndPrint(const char *path) {
Watchdog::feed();
File f = SD.open(path, FILE_READ);
if (!f) { ESP_LOGE(TAG_CAPTURE,"Error open file %s", path); return; }
FileHeader hdr;
size_t rd = f.read((uint8_t*)&hdr, sizeof(hdr));
f.close();
if (rd != sizeof(hdr)) { ESP_LOGE(TAG_CAPTURE,"Header error"); return; }
if (memcmp(hdr.magic, "WMT", 3) != 0) { ESP_LOGE(TAG_CAPTURE,"File signature error"); return; }
ESP_LOGI(TAG_CAPTURE,"===== WMT file header =====");
ESP_LOGI(TAG_CAPTURE,"Signature: %.3s", hdr.magic);
ESP_LOGI(TAG_CAPTURE,"Version: %u", hdr.version);
ESP_LOGI(TAG_CAPTURE,"Header size:%u B", hdr.headerSize);
ESP_LOGI(TAG_CAPTURE,"Sample size:%u B", hdr.sampleSize);
ESP_LOGI(TAG_CAPTURE,"Timestamp: %d", hdr.timestamp);
ESP_LOGI(TAG_CAPTURE,"Reccount: %u", hdr.reccount);
ESP_LOGI(TAG_CAPTURE,"===========================");
}
void DataCapture::printSamplingRate(uint32_t reccount, uint32_t captureSeconds, String filename) {
Watchdog::feed();
if (captureSeconds == 0) { ESP_LOGW(TAG_CAPTURE, "Sampling time = 0!"); return; }
float fs = (float)reccount / (float)captureSeconds; // Hz (ramki/s)
float fs_kHz = fs / 1000.0f;
ESP_LOGI(TAG_CAPTURE,"Frames: %u", (unsigned)reccount);
ESP_LOGI(TAG_CAPTURE,"Time: %u s", (unsigned)captureSeconds);
ESP_LOGI(TAG_CAPTURE,"Rate: %.3f kHz (frames)", fs_kHz);
display_.displaySampleRateSummary(reccount, captureSeconds, fs_kHz, filename);
display_.textStatus("Summary");
delay(2000);
}
// --- Zarządzanie katalogami/plikiem ---
String DataCapture::allocateNextFilePath() {
Watchdog::feed();
uint32_t highestDir = findHighestNumericDir();
if (highestDir == 0) {
highestDir = 1;
if (!ensureDir(highestDir)) return String();
}
uint32_t count = 0, highestIdx = 0;
scanDirForWmt(highestDir, count, highestIdx);
uint32_t targetDir = highestDir;
uint32_t nextIdx = 0;
if (count > _maxFilesPerDir) {
targetDir = highestDir + 1;
if (!ensureDir(targetDir)) return String();
nextIdx = 1;
} else {
nextIdx = (highestIdx == 0) ? 1 : (highestIdx + 1);
}
return joinPath(dirPath(targetDir), makeIndexedName(nextIdx));
}
String DataCapture::generateNextFilename() {
Watchdog::feed();
uint32_t highestDir = findHighestNumericDir();
if (highestDir == 0) return String();
uint32_t count = 0, highestIdx = 0;
scanDirForWmt(highestDir, count, highestIdx);
ESP_LOGI(TAG_CAPTURE, "highestDir %u, count %u, highestIdx %u", (unsigned)highestDir, (unsigned)count, (unsigned)highestIdx);
if (highestIdx == 0) return String();
return joinPath(dirPath(highestDir), makeIndexedName(highestIdx));
}
FileInfo DataCapture::getLastFileInfo() {
FileInfo info{String(), 0u, false};
Watchdog::feed();
String lastPath = generateNextFilename();
if (lastPath.isEmpty()) return info;
if (_fs.exists(lastPath)) {
File f = _fs.open(lastPath, FILE_READ);
if (f) {
info.exists = true;
info.path = lastPath;
info.size = f.size();
f.close();
}
}
return info;
}
bool DataCapture::deleteAllOnSD() {
Watchdog::feed();
File root = _fs.open(_baseDir);
if (!root || !root.isDirectory()) return false;
for (File e = root.openNextFile(); e; e = root.openNextFile()) {
Watchdog::feed();
String child = String(e.name());
e.close();
if (!recursiveDelete(child)) return false;
}
root.close();
return true;
}
SpaceInfo DataCapture::freeSpaceMB() {
Watchdog::feed();
SpaceInfo si{0.0, "UNKNOWN"};
#if defined(ESP32)
uint64_t total = SD.totalBytes();
uint64_t used = SD.usedBytes();
if (total >= used) {
uint64_t freeB = total - used;
const double oneGB = 1024.0 * 1024.0 * 1024.0;
const double oneMB = 1024.0 * 1024.0;
if (freeB >= (uint64_t)oneGB) {
si.value = (double)freeB / oneGB; si.unit = "GB";
} else {
si.value = (double)freeB / oneMB; si.unit = "MB";
}
}
#endif
return si;
}
float DataCapture::freeSpaceFloat(bool* isGB) {
Watchdog::feed();
SpaceInfo si = freeSpaceMB();
if (!si.unit || strcmp(si.unit, "UNKNOWN") == 0) {
if (isGB) *isGB = false;
return -1.0f;
}
if (isGB) *isGB = (strcmp(si.unit, "GB") == 0);
return static_cast<float>(si.value);
}
// --- helpery pomocnicze ---
bool DataCapture::isAllDigits(const char *s) {
if (!s || !*s) return false;
while (*s) { if (*s < '0' || *s > '9') return false; ++s; }
return true;
}
uint32_t DataCapture::toUint(const char *s) {
uint32_t v = 0; while (*s) { v = v*10u + uint32_t(*s - '0'); ++s; } return v;
}
String DataCapture::dirPath(uint32_t dirNum) const { return joinPath(_baseDir, String(dirNum)); }
const char *DataCapture::basenameFromPath(const char *full) { const char *slash = strrchr(full, '/'); return slash ? slash + 1 : full; }
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) 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;
}
String DataCapture::makeIndexedName(uint32_t idx) const {
char fmt[8]; snprintf(fmt, sizeof(fmt), "%%0%ulu", (unsigned long)_digits);
char num[24]; snprintf(num, sizeof(num), fmt, (unsigned long)idx);
return String(num) + _ext;
}
String DataCapture::joinPath(const String &a, const String &b) { if (a.endsWith("/")) return a + b; return a + "/" + b; }
bool DataCapture::ensureDir(uint32_t dirNum) {
String path = dirPath(dirNum);
if (_fs.exists(path)) { File f = _fs.open(path); bool ok = f && f.isDirectory(); if (f) f.close(); return ok; }
return _fs.mkdir(path);
}
uint32_t DataCapture::findHighestNumericDir() {
File root = _fs.open(_baseDir);
if (!root || !root.isDirectory()) return 0;
uint32_t maxDir = 0;
for (File f = root.openNextFile(); f; f = root.openNextFile()) {
if (f.isDirectory()) {
const char *nm = basenameFromPath(f.name());
if (isAllDigits(nm)) { uint32_t v = toUint(nm); if (v > maxDir) maxDir = v; }
}
}
return maxDir;
}
void DataCapture::scanDirForWmt(uint32_t dirNum, uint32_t &count, uint32_t &highestIdx) {
Watchdog::feed();
count = 0; highestIdx = 0;
String path = dirPath(dirNum);
File dir = _fs.open(path);
if (!dir || !dir.isDirectory()) return;
for (File f = dir.openNextFile(); f; f = dir.openNextFile()) {
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; }
f.close();
}
}
bool DataCapture::recursiveDelete(const String &path) {
File e = _fs.open(path);
if (!e) return false;
if (!e.isDirectory()) { e.close(); return _fs.remove(path); }
// katalog
for (File c = e.openNextFile(); c; c = e.openNextFile()) {
String child = String(c.name()); c.close();
if (!recursiveDelete(child)) { e.close(); return false; }
}
e.close();
if (path == _baseDir) return true; // nie usuwamy katalogu bazowego
return _fs.rmdir(path);
}
void DataCapture::printLastFileInfoSerial() {
Watchdog::feed();
const FileInfo info = getLastFileInfo();
if (!info.exists) {
ESP_LOGE(TAG_CAPTURE, "No .wmt file exists. Last file not exists");
return;
}
const double kb = static_cast<double>(info.size) / 1024.0;
const double mb = kb / 1024.0;
#if defined(ESP32)
// ESP32 wspiera Serial.printf z %llu
ESP_LOGI(TAG_CAPTURE, "Last file: %s", info.path.c_str());
ESP_LOGI(TAG_CAPTURE, "Size: %llu B (%.2f KB, %.2f MB)", static_cast<unsigned long long>(info.size), kb, mb);
char buf[100];
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)"));
#endif
}