#include "Dtypes.h" #include "SessionChunk.h" #include "FilesystemAbstraction.h" #include "Logger.h" template class SimpleMeasurementSession { public: using ChunkT = SessionChunk; using MeasurementType = Measurement_T; // save interval in number of measurements (by default every minute) SimpleMeasurementSession(uint32_t saveInterval = 10 * 20) : chunk(nullptr), saveInterval_(saveInterval) { } ~SimpleMeasurementSession() { if (chunk != nullptr) free(chunk); } void init(uint32_t epochStartTime) { if (chunk == nullptr) { // psram allocation doesn't seem to work in constructor chunk = (ChunkT *)heap_caps_malloc(sizeof(ChunkT), MALLOC_CAP_SPIRAM); new (chunk) ChunkT(); // placement new to init chunk } chunk->init(epochStartTime, 0); } bool addPoint(Measurement_T measurement) { bool success = chunk->addPoint(measurement); if (success && (chunk->numMeasurements() % saveInterval_) == 0) saveToFileSystem(); if (!success) LOG_WARNING("Failed to add point"); return success; } void finalize() { if (numMeasurements() > 0) saveToFileSystem(); chunk->init(0, 0); } uint32_t getStartTime() const { return chunk->getStartTime(); } uint32_t numMeasurements() const { return chunk->numMeasurements(); } template void serialize(Encoder_T &encoder, uint32_t startIdx) const { ChunkT::sendHeader(encoder, chunk->getStartTime(), startIdx); auto numElementsToSend = chunk->numMeasurements() - startIdx; encoder.sendArray(chunk->getDataPointer() + startIdx, numElementsToSend); } Measurement_T *getDataPointer() { return chunk->getDataPointer(); } private: void saveToFileSystem() { static const uint32_t arrayHeaderOffset = ChunkT::arrayHeaderOffset(); const uint32_t numMeasurements = chunk->numMeasurements(); // todo: check this! free doesn't mean that the file writing actually works ok // use error codes of write instead? anyway: test it! LOG_INFO("%ld saveToFileSystem start", millis()); deleteUntilBytesFree(CONFIG_SESSION_MAX_SIZE); LOG_INFO(" %ld after deleteUntilBytesFree()", millis()); String filename = String(CONFIG_DATA_PATH) + "/" + String(chunk->getStartTime()); if (portablefs::exists(filename.c_str())) { auto file = portablefs::open(filename.c_str(), "r+"); file.seek(0, SeekEnd); size_t existingMeasurements = (file.size() - ChunkT::valueOffset()) / sizeof(Measurement_T); size_t measurementsToWrite = numMeasurements - existingMeasurements; Measurement_T *startPtr = chunk->getDataPointer() + existingMeasurements; file.write((uint8_t *)(startPtr), measurementsToWrite * sizeof(Measurement_T)); file.seek(arrayHeaderOffset); StreamingMsgPackEncoder encoder(&file); encoder.template sendArrayHeader(numMeasurements); } else { auto file = portablefs::open(filename.c_str(), "w"); StreamingMsgPackEncoder encoder(&file); chunk->serialize(encoder); } LOG_INFO(" %ld saveToFileSystem done", millis()); } void deleteUntilBytesFree(size_t requiredSpace) { auto freeBytes = portablefs::totalBytes() - portablefs::usedBytes(); while (freeBytes < requiredSpace) { uint32_t nextSessionToDelete = uint32_t(-1); auto dir = portablefs::openDir(CONFIG_DATA_PATH); String filenameToDelete; while (dir.next()) { if (dir.isFile()) { const auto fileName = dir.fileName(); const auto fileNameWithoutDir = fileName.substring(fileName.lastIndexOf("/") + 1); uint32_t sessionId = fileNameWithoutDir.toInt(); if (sessionId < nextSessionToDelete) { nextSessionToDelete = sessionId; filenameToDelete = dir.fileName(); } } } assert(nextSessionToDelete > 0); assert(nextSessionToDelete < uint32_t(-1)); LOG_INFO("Removing old session %s to make space", filenameToDelete.c_str()); portablefs::remove(filenameToDelete.c_str()); auto newFreeBytes = portablefs::totalBytes() - portablefs::usedBytes(); assert(newFreeBytes > freeBytes); freeBytes = newFreeBytes; } } ChunkT *chunk; uint32_t saveInterval_; };