149 lines
5.1 KiB
C++
149 lines
5.1 KiB
C++
#include "Dtypes.h"
|
|
#include "SessionChunk.h"
|
|
#include "FilesystemAbstraction.h"
|
|
|
|
template <typename Measurement_T, uint32_t MAX_SIZE>
|
|
class SimpleMeasurementSession
|
|
{
|
|
public:
|
|
using ChunkT = SessionChunk<Measurement_T, MAX_SIZE>;
|
|
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)
|
|
Serial.println("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 <typename Encoder_T>
|
|
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();
|
|
Serial.printf(" -------- Array header offset ---- %u\n", 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!
|
|
Serial.printf("%ld saveToFileSystem start()\n", millis());
|
|
deleteUntilBytesFree(CONFIG_SESSION_MAX_SIZE);
|
|
Serial.printf("%ld after deleteUntilBytesFree()\n", 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));
|
|
|
|
Serial.printf("%ld Incr Save: before header patch\n", millis());
|
|
file.seek(arrayHeaderOffset);
|
|
|
|
StreamingMsgPackEncoder<portablefs::File> encoder(&file);
|
|
encoder.template sendArrayHeader<Measurement_T>(numMeasurements);
|
|
Serial.printf("%ld total measurements up to now %u\n", millis(), numMeasurements);
|
|
}
|
|
else
|
|
{
|
|
Serial.println("First save");
|
|
auto file = portablefs::open(filename.c_str(), "w");
|
|
StreamingMsgPackEncoder<portablefs::File> encoder(&file);
|
|
chunk->serialize(encoder);
|
|
}
|
|
Serial.printf("%ld saveToFileSystem done-------------\n", millis());
|
|
}
|
|
|
|
void deleteUntilBytesFree(size_t requiredSpace)
|
|
{
|
|
auto freeBytes = portablefs::totalBytes() - portablefs::usedBytes();
|
|
while (freeBytes < requiredSpace)
|
|
{
|
|
uint32_t nextSessionToDelete = 0;
|
|
|
|
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);
|
|
auto sessionId = fileNameWithoutDir.toInt();
|
|
if (sessionId < nextSessionToDelete)
|
|
{
|
|
nextSessionToDelete = sessionId;
|
|
filenameToDelete = dir.fileName();
|
|
}
|
|
}
|
|
}
|
|
assert(nextSessionToDelete > 0);
|
|
Serial.printf("Removing old session %s to make space\n", filenameToDelete.c_str());
|
|
portablefs::remove(filenameToDelete.c_str());
|
|
auto newFreeBytes = portablefs::totalBytes() - portablefs::usedBytes();
|
|
assert(newFreeBytes > freeBytes);
|
|
freeBytes = newFreeBytes;
|
|
}
|
|
}
|
|
|
|
ChunkT *chunk;
|
|
uint32_t saveInterval_;
|
|
};
|