Files
PI_mikrokontroler_2/src/Measure.cpp
2026-05-10 20:46:23 +02:00

605 lines
20 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#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
uint32_t start_wait = millis();
while (!adxl_.availableAll()) {
if (isEscape()) break;
if (millis() - start_wait > 500) {
ESP_LOGE(TAG_CAPTURE, "Sensor data timeout - aborting capture.");
measurementActive_ = false;
break;
}
Watchdog::feed();
yield();
}
if (!measurementActive_) break;
const uint8_t got = adxl_.readAlignedOnce(X, Y, Z, TS);
if (got == 0 || 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
}