Restructured full repo

This commit is contained in:
Martin Bauer
2020-06-05 21:41:16 +02:00
parent daa2454e71
commit 9bf3287944
46 changed files with 1913 additions and 107 deletions

View File

@@ -0,0 +1,20 @@
#pragma once
inline void _assert(const char* expression, const char* message, const char* file, int line)
{
Serial.print("Assert ");
Serial.print(file);
Serial.print(" : ");
Serial.print(line);
Serial.print(" '");
Serial.print(expression);
Serial.println("' failed.");
Serial.println(message);
}
template< typename T>
inline String toString(const T & t) {
return String(t);
}
#define assert_msg(EXPRESSION, MSG) ((EXPRESSION) ? (void)0 : _assert(#EXPRESSION, #MSG, __FILE__, __LINE__))

View File

@@ -0,0 +1,181 @@
#pragma once
#ifdef PLATFORM_ESP32
#include "SPIFFS.h"
namespace portablefs
{
using File = ::File;
class Dir
{
public:
Dir() {}
Dir(const String &path)
: root_(SPIFFS.open(path))
{
//next();
}
bool next()
{
file_ = root_.openNextFile();
return file_;
}
bool isFile()
{
return !file_.isDirectory();
}
bool isDirectory()
{
return file_.isDirectory();
}
String fileName() const
{
return file_.name();
}
size_t fileSize() const
{
return file_.size();
}
private:
File root_;
File file_;
};
inline Dir openDir(const String &path)
{
return Dir(path);
}
inline File open(const char *name, const char *mode)
{
return SPIFFS.open(name, mode);
}
inline bool exists(const char *name)
{
return SPIFFS.exists(name);
}
inline bool remove(const char *name)
{
return SPIFFS.remove(name);
}
inline bool mkdir(const char *name)
{
return SPIFFS.mkdir(name);
}
} // namespace portablefs
#endif
#ifdef PLATFORM_ESP8266
#include <FS.h>
namespace portablefs
{
using Dir;
} // namespace portablefs
#endif
#ifdef PLATFORM_NATIVE
#include <string>
#include <fstream>
#include <filesystem>
#include "MockDtypes.h"
namespace fs = std::filesystem;
namespace portablefs
{
const std::string basePath = "./base";
class Dir
{
public:
Dir() {}
Dir(const String &path)
: it_(fs::directory_iterator(path).begin()),
end_(fs::directory_iterator(path).end()),
firstIncremented_(false)
{
}
bool next()
{
if (!firstIncremented_)
firstIncremented_ = true;
else
++it_;
return it_ != end_;
}
bool isFile()
{
return file.is_regular_file();
}
bool isDirectory()
{
return it_.is_directory();
}
String fileName() const
{
return it_.path().filename().string();
}
size_t fileSize() const
{
return it_.file_size();
}
private:
fs::directory_iterator it_;
fs::directory_iterator end_;
bool firstIncremented_;
};
inline Dir openDir(const String &path)
{
return Dir(path);
}
inline File open(const char *name, const char *mode)
{
if(mode == "r")
return fopen()
return SPIFFS.open(name, mode);
}
inline bool exists(const char *name)
{
return SPIFFS.exists(name);
}
inline bool remove(const char *name)
{
return SPIFFS.remove(name);
}
inline bool mkdir(const char *name)
{
return SPIFFS.mkdir(name);
}
} // namespace portablefs
#endif

View File

@@ -0,0 +1,46 @@
#pragma once
#include <string>
#include <cstdint>
#include <cstring>
#include <arpa/inet.h>
#include <algorithm>
#include <iostream>
#include <sstream>
#include <string>
typedef uint32_t uint_t;
typedef std::string String;
typedef uint8_t byte;
typedef const char * PGM_P;
using std::min;
using std::max;
inline uint32_t strlen_P(PGM_P str) {
return std::strlen(str);
}
inline const char * F( const char * in) { return in; }
inline void _assert(const char* expression, const char* message, const char* file, int line)
{
std::cerr << "Assert " << file << ":" << line << " '" << expression << "' failed." << std::endl;
std::cerr << message << std::endl;
abort();
}
template< typename T>
inline std::string toString(const T & t) {
std::stringstream stream;
stream << t;
return stream.str();
}
class String : public std::string
{};
#define assert(EXPRESSION, MSG) ((EXPRESSION) ? (void)0 : _assert(#EXPRESSION, #MSG, __FILE__, __LINE__))

View File

@@ -0,0 +1,18 @@
#pragma once
#include <iostream>
class SerialMock {
public:
template< typename T>
static inline void print(const T & str) {
std::cout << str;
}
template< typename T>
static inline void println(const T & str) {
std::cout << str << std::endl;
}
};
static SerialMock Serial;

View File

@@ -0,0 +1,34 @@
#include <cstdint>
class MockScale
{
public:
MockScale( uint16_t valueMin=0, uint16_t valueMax=10)
: valueMin_(valueMin), valueMax_(valueMax), currentValue_(valueMin), direction(1)
{}
bool measure(uint16_t & measurementOut) {
currentValue_ += direction;
if ( currentValue_ >= valueMax_) {
direction = -1;
valueMax_ += 2;
}
else if ( currentValue_ <= valueMin_ )
direction = +1;
measurementOut = currentValue_;
return true;
}
void begin(uint32_t , uint32_t ) {
};
void tare(uint32_t ) {
}
private:
uint16_t valueMin_;
uint16_t valueMax_;
uint16_t currentValue_;
int direction;
};

View File

@@ -0,0 +1,35 @@
#include "HX711.h"
#include "ConfigHardware.h"
#include <cstdint>
template<int DIVIDER=128>
class Scale
{
public:
bool measure(uint16_t & measurementOut) {
if (hx711_.is_ready())
{
long value = hx711_.read_average(CONFIG_MEASUREMENT_AVG_COUNT) - offset_;
if(value > 0)
measurementOut = (int16_t)(value / DIVIDER);
else
measurementOut = 0;
return true;
}
else
return false;
}
void begin(uint32_t pinDOUT, uint32_t pinSCK) {
hx711_.begin(pinDOUT, pinSCK);
};
void tare(uint32_t numMeasurementsToAverage=50) {
offset_ = hx711_.read_average(numMeasurementsToAverage);
}
private:
HX711 hx711_;
long offset_ = 0;
};

View File

@@ -0,0 +1,135 @@
#include "SessionChunk.h"
template<typename Measurement_T, typename Reader, typename Writer, uint32_t CHUNK_SIZE>
class MeasurementSession {
public:
typedef SessionChunk<Measurement_T, CHUNK_SIZE> Chunk_T;
MeasurementSession()
: currentChunk(&chunks[0]),
otherChunk(&chunks[1]) {}
void init(uint32_t epochStartTime) {
currentChunk = &chunks[0];
otherChunk = &chunks[1];
currentChunk->init(epochStartTime, 0);
otherChunk->init(0, 0);
}
bool addPoint(Measurement_T measurement) {
const bool successful = currentChunk->addPoint(measurement);
if (!successful) {
Serial.println("Starting session rotate");
rotate();
const bool secondInsertSuccess = currentChunk->addPoint(measurement);
assert_msg(secondInsertSuccess, "Session: insertion after rotation failed");
// TODO check that there is place for file - remove old files
}
return true;
}
void finalize() {
if( otherChunkFilled() )
saveChunkToFile(otherChunk);
if( currentChunk->numMeasurements() > 0) {
saveChunkToFile(currentChunk);
}
currentChunk->init(0, 0);
otherChunk->init(0, 0);
}
template<typename Encoder_T>
void serialize(Encoder_T & encoder, uint32_t startIdx) const
{
const uint32_t lastIdx = currentChunk->getStartIndex() + currentChunk->numMeasurements();
if( lastIdx <= startIdx) {
encoder.template sendArray<Measurement_T>(nullptr, 0);
return;
}
Chunk_T::sendHeader(encoder, currentChunk->getStartTime(), startIdx);
encoder.template sendArrayHeader<Measurement_T>(lastIdx - startIdx);
while(startIdx < lastIdx)
startIdx = serializeChunk(encoder, startIdx);
assert_msg(startIdx == lastIdx, "Not all data was sent");
}
uint32_t getStartTime() const {
return currentChunk->getStartTime();
}
private:
void rotate() {
if( otherChunkFilled() )
saveChunkToFile(otherChunk);
swapChunks();
currentChunk->init(otherChunk->getStartTime(), otherChunk->getStartIndex() + CHUNK_SIZE);
}
bool otherChunkFilled() const {
return otherChunk->numMeasurements() > 0;
}
void swapChunks() {
Chunk_T *tmp = currentChunk;
currentChunk = otherChunk;
otherChunk = tmp;
}
void saveChunkToFile(Chunk_T *chunk) const {
const uint32_t chunkNr = chunk->getStartIndex() / CHUNK_SIZE;
const auto fileName = chunkFileName(chunkNr, chunk->getStartTime());
Serial.print("Writing session to file ");
Serial.println(fileName);
Writer writer(fileName);
chunk->serialize(writer.encoder());
};
template< typename Encoder_T>
uint32_t serializeChunk(Encoder_T & encoder, uint32_t startIdx) const {
assert_msg( startIdx < currentChunk->getStartIndex() + currentChunk->numMeasurements(),
"serializeChunk: invalid startIdx" );
if( startIdx >= currentChunk->getStartIndex() ) {
const auto localStartIdx = startIdx - currentChunk->getStartIndex();
const auto numElements = currentChunk->numMeasurements() - localStartIdx;
assert_msg(numElements <= currentChunk->numMeasurements(), "Internal problem in serializeChunk");
encoder.sendArrayPartialContents( currentChunk->getDataPointer() + localStartIdx, numElements );
return currentChunk->getStartIndex() + currentChunk->numMeasurements();
} else if( startIdx >= otherChunk->getStartIndex() && otherChunkFilled() ) {
encoder.sendArrayPartialContents( otherChunk->getDataPointer(), otherChunk->numMeasurements() );
assert_msg( otherChunk->numMeasurements(), CHUNK_SIZE );
return otherChunk->getStartIndex() + otherChunk->numMeasurements();
} else {
if( encoder.getSizeCountMode() ) {
encoder.template sendArrayPartialContents<Measurement_T>(nullptr, CHUNK_SIZE);
} else {
const uint32_t chunkNr = startIdx / CHUNK_SIZE;
const auto chunkFileNameStr = chunkFileName(chunkNr, currentChunk->getStartTime());
Reader reader(chunkFileNameStr);
reader.seek(Chunk_T::valueOffset());
const uint32_t PART_SIZE = 32;
#ifndef ARDUINO
static_assert((PART_SIZE < CHUNK_SIZE) && (CHUNK_SIZE % PART_SIZE == 0));
#endif
Measurement_T buffer[PART_SIZE];
for(uint32_t i = 0; i < CHUNK_SIZE; i += PART_SIZE)
{
reader.readBytes((char*) buffer, sizeof(Measurement_T) * PART_SIZE);
encoder.template sendArrayPartialContents<Measurement_T>(buffer, PART_SIZE);
}
}
return startIdx + CHUNK_SIZE;
}
}
static String chunkFileName(uint32_t chunkNr, uint32_t startTime) {
return("/dat/" + toString(startTime) + "_" + toString(chunkNr));
}
Chunk_T chunks[2];
Chunk_T *currentChunk;
Chunk_T *otherChunk;
};

View File

@@ -0,0 +1,90 @@
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <vector>
class VectorAdaptor {
public:
VectorAdaptor(std::vector<uint8_t> * v) : v_(v) {}
void write(const char *data, uint32_t size) {
v_->insert(v_->end(), data, data + size );
}
private:
std::vector<uint8_t> * v_;
};
class FilePtrAdaptor {
public:
FilePtrAdaptor(const String &fileName) {
static const String baseDirectory(".");
auto fullFileName = baseDirectory + fileName;
fptr = fopen(fullFileName.c_str(), "wb");
if (fptr == NULL) {
printf("fopen of %s failed, errno = %d - %s\n", fullFileName.c_str(), errno, strerror(errno));
exit(1);
}
}
~FilePtrAdaptor() {
fclose(fptr);
}
FilePtrAdaptor(const FilePtrAdaptor &) = delete;
void operator=(const FilePtrAdaptor &) = delete;
void write(const char *data, uint32_t size) {
fwrite(data, size, 1, fptr);
}
private:
FILE * fptr;
};
class MockStorageWriter {
public:
MockStorageWriter(const String &fileName) {
adaptor_ = new FilePtrAdaptor(fileName);
encoder_ = new StreamingMsgPackEncoder <FilePtrAdaptor>(adaptor_);
}
~MockStorageWriter(){
delete adaptor_;
delete encoder_;
}
MockStorageWriter(const MockStorageWriter &) = delete;
StreamingMsgPackEncoder<FilePtrAdaptor> &encoder() { return *encoder_; }
private:
FilePtrAdaptor * adaptor_;
StreamingMsgPackEncoder <FilePtrAdaptor> * encoder_;
};
class MockStorageReader {
public:
MockStorageReader(const String &fileName)
{
static const String baseDirectory(".");
auto fullFileName = baseDirectory + fileName;
fptr = fopen(fullFileName.c_str(), "rb");
}
uint32_t readBytes(char *buffer, size_t length) {
return fread(buffer, length, 1, fptr);
}
bool seek(uint32_t pos) {
auto ret = fseek(fptr, pos, SEEK_SET);
return ret == 0;
}
private:
FILE * fptr;
};

View File

@@ -0,0 +1,85 @@
#include "StreamingMsgPackEncoder.h"
#include <cstdint>
template<typename Measurement_T, uint32_t SIZE>
class SessionChunk
{
public:
SessionChunk()
: nextFree(0), sessionStartTime(0), startIndex(0)
{}
void init(uint32_t epochStartTime, uint32_t startIdx)
{
nextFree = 0;
sessionStartTime = epochStartTime;
startIndex = startIdx;
}
uint32_t getStartTime() const {
return sessionStartTime;
}
uint32_t getStartIndex() const {
return startIndex;
}
uint32_t numMeasurements() const {
return nextFree;
}
bool addPoint(Measurement_T measurement)
{
if( nextFree >= SIZE)
return false;
values[nextFree] = measurement;
nextFree++;
return true;
}
template<typename T>
void serialize(StreamingMsgPackEncoder<T> & encoder) const
{
sendHeader(encoder, sessionStartTime, startIndex);
encoder.sendArray(values, nextFree);
}
static uint32_t valueOffset()
{
StreamingMsgPackEncoder<DummyWriter> encoder(nullptr);
encoder.setSizeCountMode(true);
sendHeader(encoder, 0, 0);
encoder.template sendArrayHeader<Measurement_T>(0);
return encoder.getContentLength();
}
Measurement_T * getDataPointer() {
return values;
}
const Measurement_T * getDataPointer() const {
return values;
}
template<typename Encoder_T>
static void sendHeader(Encoder_T & encoder, uint32_t sessionStartTime, uint32_t startIndex)
{
encoder.sendMap16(3);
encoder.sendString255("sessionStartTime");
encoder.sendInt(sessionStartTime);
encoder.sendString255("startIndex");
encoder.sendInt(startIndex);
encoder.sendString255("values");
}
private:
uint32_t nextFree = 0;
uint32_t sessionStartTime;
uint32_t startIndex;
Measurement_T values[SIZE];
};

View File

@@ -0,0 +1,90 @@
#pragma once
#include "StreamingMsgPackEncoder.h"
#include "FilesystemAbstraction.h"
#ifdef USE_ESP32
struct WriterAdaptor
{
File * f;
void write(const char * ptr, size_t size) {
f->write(reinterpret_cast<const uint8_t *>(ptr), size);
}
};
class SpiffsStorageWriter {
public:
SpiffsStorageWriter(const String &fileName) :
f_(SPIFFS.open(fileName, "w")),
adaptor_{&f_},
encoder_(&adaptor_),
fileName_(fileName)
{
bool success = f_;
Serial.println(success);
}
~SpiffsStorageWriter() {
f_.close();
Serial.println(fileName_);
Serial.println(SPIFFS.exists(fileName_));
}
StreamingMsgPackEncoder<WriterAdaptor> &encoder() { return encoder_; }
private:
File f_;
WriterAdaptor adaptor_;
StreamingMsgPackEncoder<WriterAdaptor> encoder_;
String fileName_;
};
#else
class SpiffsStorageWriter {
public:
SpiffsStorageWriter(const String &fileName) :
f_(SPIFFS.open(fileName, "w")),
encoder_(&f_),
fileName_(fileName)
{
bool success = f_;
Serial.println(success);
}
~SpiffsStorageWriter() {
f_.close();
Serial.println(fileName_);
Serial.println(SPIFFS.exists(fileName_));
}
StreamingMsgPackEncoder<File> &encoder() { return encoder_; }
private:
File f_;
StreamingMsgPackEncoder<File> encoder_;
String fileName_;
};
#endif
class SpiffsStorageReader
{
public:
SpiffsStorageReader(const String &fileName) :
f_(SPIFFS.open(fileName, "r"))
{}
~SpiffsStorageReader() {
f_.close();
}
uint32_t readBytes(char *buffer, size_t length) {
return f_.readBytes(buffer, length);
}
bool seek(uint32_t pos) {
return f_.seek(pos);
}
private:
File f_;
};

View File

@@ -0,0 +1,262 @@
#pragma once
template<typename T> struct TypeToMsgPackCode{};
template<> struct TypeToMsgPackCode<uint8_t> { static const char CODE; };
template<> struct TypeToMsgPackCode<uint16_t>{ static const char CODE; };
template<> struct TypeToMsgPackCode<uint32_t>{ static const char CODE; };
template<> struct TypeToMsgPackCode<int8_t> { static const char CODE; };
template<> struct TypeToMsgPackCode<int16_t> { static const char CODE; };
template<> struct TypeToMsgPackCode<int32_t> { static const char CODE; };
const char TypeToMsgPackCode<uint8_t>::CODE = '\xcc';
const char TypeToMsgPackCode<uint16_t>::CODE = '\xcd';
const char TypeToMsgPackCode<uint32_t>::CODE = '\xce';
const char TypeToMsgPackCode<int8_t>::CODE = '\xd0';
const char TypeToMsgPackCode<int16_t>::CODE = '\xd1';
const char TypeToMsgPackCode<int32_t>::CODE = '\xd2';
struct DummyWriter {
void write(const void*, uint32_t) {}
};
class CopyWriter {
public:
CopyWriter(uint8_t * bufferToWrite)
: bufferToWrite_( bufferToWrite )
{}
void write(const void* data, uint32_t length) {
memcpy(bufferToWrite_, data, length);
bufferToWrite_ += length;
}
private:
uint8_t * bufferToWrite_;
};
template<typename Writer>
class StreamingMsgPackEncoder
{
public:
StreamingMsgPackEncoder(Writer * writer_)
: writer(writer_), contentLength(0), sizeCountMode(false)
{}
void sendMap16(byte size)
{
if( sizeCountMode )
{
contentLength += 1;
}
else
{
size |= 0b10000000;
writer->write((const char*)(&size), 1);
}
}
void sendString255(PGM_P s)
{
auto len = strlen_P(s);
if( len >= 255 ) {
Serial.println(F("ERROR: StreamingMsgPackEncoder::string255 - string too long"));
return;
}
byte castedLen = (byte)(len);
if( sizeCountMode )
{
contentLength += 2 + castedLen;
}
else
{
writer->write("\xd9", 1);
writer->write((const char*)&castedLen, 1);
writer->write(s, len);
}
}
template<typename T>
void sendInt(T value)
{
if( sizeCountMode )
{
contentLength += 1 + sizeof(T);
}
else
{
if( sizeof(T) == 4 )
value = htonl(value);
else if( sizeof(T) == 2)
value = htons(value);
writer->write(&TypeToMsgPackCode<T>::CODE, 1);
writer->write((const char*)&value, sizeof(T));
}
}
template<typename T>
void sendArray(const T * data, uint32_t length)
{
sendArrayHeader<T>(length);
sendArrayPartialContents(data, length);
}
template<typename T>
void sendArrayHeader(uint32_t length)
{
if( sizeCountMode )
{
contentLength += 1 + sizeof(uint32_t) + 1;
}
else
{
uint32_t nlength = htonl(length * sizeof(T));
writer->write("\xc9", 1); // ext dtype since typed arrays are not supported by msgpack
writer->write((char*)(&nlength), sizeof(uint32_t) );
writer->write(&TypeToMsgPackCode<T>::CODE, 1); // put code for type here, this is not part of msgpack but custom
}
}
template<typename T>
void sendArrayPartialContents(T * data, uint32_t length)
{
if( !sizeCountMode ) {
writer->write((char*)(data), length * sizeof(T));
} else {
contentLength += sizeof(T) * length;
}
}
void setSizeCountMode(bool sizeCountMode_=true)
{
sizeCountMode = sizeCountMode_;
}
bool getSizeCountMode() const {
return sizeCountMode;
}
uint32_t getContentLength() const {
return contentLength;
}
void resetContentLength() {
contentLength = 0;
}
private:
Writer * writer;
uint32_t contentLength;
bool sizeCountMode;
};
template<typename Writer>
class ChunkedStreamingMsgPackEncoder
{
public:
ChunkedStreamingMsgPackEncoder(Writer * writer_, uint32_t offset, uint32_t maxSize)
: encoder_(writer_), sentBytes_(0), maxBytes_(maxSize), offsetToStart_(offset), sendingFinished_(false)
{}
void sendMap16(byte size) {
sendIfSpaceLeft([&]() { encoder_.sendMap16(size); });
}
void sendString255(PGM_P s) {
sendIfSpaceLeft([&]() { encoder_.sendString255(s); });
}
template<typename T>
void sendInt(T value) {
sendIfSpaceLeft([&]() { encoder_.template sendInt<T>(value); });
}
template<typename T>
void sendArray(const T * data, uint32_t length)
{
sendArrayHeader<T>(length);
sendArrayPartialContents(data, length);
}
template<typename T>
void sendArrayHeader(uint32_t length) {
sendIfSpaceLeft([&]() { encoder_.template sendArrayHeader<T>(length); });
}
template<typename T>
void sendArrayPartialContents(T * data, uint32_t length) {
if( sendingFinished_ ) {
return;
}
uint32_t elementsToSkip = 0;
if( sentBytes_ < offsetToStart_ ) {
elementsToSkip = (offsetToStart_ - sentBytes_) / sizeof(T);
assert_msg((offsetToStart_ - sentBytes_) % sizeof(T) == 0,
"Looks like previous sent operation send fraction of an element.");
}
if( elementsToSkip >= length) {
sentBytes_ += sizeof(T) * length;
return;
} else {
sentBytes_ += sizeof(T) * elementsToSkip;
const uint32_t elementsRemaining = length - elementsToSkip;
const uint32_t maxElementsToSend = (maxBytes_ - sentBytes_) / sizeof(T);
const uint32_t elementsToSend = min(elementsRemaining, maxElementsToSend);
if( elementsToSend == 0 ) {
sendingFinished_ = true;
return;
} else {
encoder_.sendArrayPartialContents(data + elementsToSkip, elementsToSend);
sentBytes_ += sizeof(T) * elementsToSend;
if( elementsRemaining > elementsToSend ) {
sendingFinished_ = true;
}
}
}
}
uint32_t sentBytes() const {
return sentBytes_;
}
bool getSizeCountMode() const {
return false;
}
private:
template<typename T, typename... Args>
void sendIfSpaceLeft(T sendFunction) {
if( sendingFinished_ ) {
return;
}
encoder_.setSizeCountMode(true);
encoder_.resetContentLength();
sendFunction();
auto sizeRequired = encoder_.getContentLength();
encoder_.setSizeCountMode(false);
if( sentBytes_ < offsetToStart_ ) {
// already sent
sentBytes_ += sizeRequired;
assert_msg( sentBytes_ <= offsetToStart_, "Partial sending not supported by this function" );
return;
}
if( sentBytes_ + sizeRequired <= maxBytes_ ) {
sendFunction();
sentBytes_ += sizeRequired;
} else {
sendingFinished_ = true;
}
}
StreamingMsgPackEncoder<Writer> encoder_;
uint32_t sentBytes_;
uint32_t maxBytes_;
uint32_t offsetToStart_;
bool sendingFinished_;
};

View File

@@ -0,0 +1,192 @@
#include "UserDB.h"
#include "FilesystemAbstraction.h"
const String userDir = "/u/";
template <class ForwardIterator, class T>
ForwardIterator lower_bound(ForwardIterator first, ForwardIterator last, const T &val)
{
ForwardIterator it;
iterator_traits<ForwardIterator>::difference_type count, step;
count = distance(first, last);
while (count > 0)
{
it = first;
step = count / 2;
advance(it, step);
if (*it < val)
{
first = ++it;
count -= step + 1;
}
else
count = step;
}
return first;
}
static String userFileName(const String &userName)
{
return userDir + userName;
}
bool User::load(const String &name)
{
name_ = name;
assert(name_.length() > 0);
const auto fileName = userFileName(name_);
if (!portablefs::exists(fileName.c_str()))
return false;
auto file = portablefs::open(fileName.c_str(), "r");
size_t sessionsInFile;
file.read((uint8_t *)&sessionsInFile, sizeof(numSessions_));
init(name, sessionsInFile * 2);
size_t expectedSize = sizeof(SessionIdType) * numSessions_;
auto bytesRead = file.read((uint8_t *)sessionIds_, expectedSize);
numSessions_ = sessionsInFile;
assert(expectedSize == bytesRead);
}
void User::init(const String &name, size_t sessionAllocateSize)
{
if (sessionIds_ != nullptr)
{
free(sessionIds_);
sessionIds_ = nullptr;
}
name_ = name;
numSessionsAllocated_ = sessionAllocateSize;
sessionIds_ = (SessionIdType *)ps_malloc(sizeof(SessionIdType) * numSessionsAllocated_);
numSessions_ = 0;
}
void User::save()
{
if (!portablefs::exists(userDir.c_str()))
portablefs::mkdir(userDir.c_str());
auto file = portablefs::open(userFileName(name_).c_str(), "w");
file.write((uint8_t *)&numSessions_, sizeof(numSessions_));
file.write((uint8_t *)sessionIds_, sizeof(SessionIdType) * numSessions_);
}
void User::freeResources()
{
if (sessionIds_ != nullptr)
{
free(sessionIds_);
sessionIds_ = nullptr;
}
}
void User::remove()
{
portablefs::remove(userFileName(name_).c_str());
freeResources();
name_ = "";
numSessions_ = 0;
numSessionsAllocated_ = 0;
}
void User::growSessionArrayIfNecessary()
{
assert(numSessions_ <= numSessionsAllocated_);
if (numSessions_ < numSessionsAllocated_)
return;
numSessionsAllocated_ *= 2;
sessionIds_ = (SessionIdType *)ps_realloc(sessionIds_, numSessionsAllocated_);
assert(numSessions_ < numSessionsAllocated_);
}
void User::insertSession(SessionIdType newSessionId)
{
growSessionArrayIfNecessary();
assert(numSessionsAllocated_ > numSessions_);
SessionIdType *insertPos = lower_bound(sessionIds_, sessionIds_ + numSessions_, newSessionId);
const size_t moveStartIdx = sessionIds_ + numSessions_ - insertPos;
for (size_t i = numSessions_ - 1; i >= moveStartIdx; --i)
sessionIds_[i + 1] = sessionIds_[i];
sessionIds_[moveStartIdx] = newSessionId;
numSessions_ += 1;
}
bool User::removeSession(SessionIdType sessionIdToRemove)
{
SessionIdType *removePos = lower_bound(sessionIds_, sessionIds_ + numSessions_, sessionIdToRemove);
const size_t removeIdx = sessionIds_ + numSessions_ - removePos;
if (sessionIds_[removeIdx] != sessionIdToRemove)
return false;
for (size_t i = removeIdx; i < numSessions_ - 1; ++i)
sessionIds_[i] = sessionIds_[i + 1];
numSessions_ -= 1;
return true;
}
// --------------------------------------------------------------------------------------------------------------------
User *UserStorage::getUserInfo(const String &userName)
{
// index 0 is the unassigned user
for (size_t i = 1; i < numUsers_; ++i)
if (users_[i].name() == userName)
return &users_[i];
return nullptr;
}
User *UserStorage::getUnassignedUser()
{
return &users_[0];
}
User *UserStorage::addNewUser(const String &userName)
{
if (numUsers_ >= MAX_USERS)
return nullptr;
auto userIdx = numUsers_;
numUsers_++;
assert(numUsers_ < MAX_USERS);
users_[userIdx].init(userName);
}
bool UserStorage::deleteUser(const String &userName)
{
User *userPtr = getUserInfo(userName);
if (userPtr == nullptr)
return false;
size_t userIdx = userPtr - users_;
userPtr->remove();
assert(numUsers_ > 0);
if (userIdx != numUsers_ - 1)
users_[userIdx] = users_[numUsers_ - 1];
}
void UserStorage::fillFromFileSystem()
{
for (size_t i = 0; i < numUsers_; ++i)
users_[i].freeResources();
numUsers_ = 1;
users_[0].load("_unassigned");
portablefs::Dir d(userDir);
while (d.next())
{
if (d.isFile() && d.fileName()[0] != '_')
{
users_[numUsers_].load(d.fileName());
++numUsers_;
}
}
}

View File

@@ -0,0 +1,73 @@
#pragma once
#ifndef PLATFORM_NATIVE
#include <Arduino.h>
#endif
using SessionIdType = uint32_t;
constexpr size_t MAX_USERS = 64;
constexpr size_t INITIAL_SESSIONS_PER_USER = 128;
struct User
{
public:
User() : numSessions_(0), numSessionsAllocated_(0), sessionIds_(nullptr) {}
void init(const String &name, size_t sessionAllocateSize = INITIAL_SESSIONS_PER_USER);
void freeResources();
bool load(const String &name);
void save();
void remove();
bool valid() const { return sessionIds_ != nullptr; }
void insertSession(SessionIdType sessionId);
bool removeSession(SessionIdType sessionId);
bool hasSession(SessionIdType sessionId) const;
const String &name() const { return name_; }
// session access
SessionIdType *sessionBegin() { return sessionIds_; }
SessionIdType *sessionEnd() { return sessionIds_ + numSessions_; }
const SessionIdType *sessionBegin() const { return sessionIds_; }
const SessionIdType *sessionEnd() const { return sessionIds_ + numSessions_; }
private:
void growSessionArrayIfNecessary();
String name_;
size_t numSessions_;
size_t numSessionsAllocated_;
SessionIdType *sessionIds_;
};
class UserStorage
{
public:
UserStorage()
: numUsers_(0)
{
fillFromFileSystem();
}
User *getUserInfo(const String &userName);
User *getUnassignedUser();
User *addNewUser(const String &userName);
bool deleteUser(const String &userName);
User *begin() { return &users_[0]; }
User *end() { return &users_[numUsers_]; }
const User *begin() const { return &users_[0]; }
const User *end() const { return &users_[numUsers_]; }
private:
void fillFromFileSystem();
User users_[MAX_USERS];
size_t numUsers_;
};

View File

@@ -0,0 +1,233 @@
#include <ESPAsyncWebServer.h>
#include "FilesystemAbstraction.h"
#define FLASH_TEXT(name) const char *name
namespace webdav_constants
{
FLASH_TEXT(MULTISTATUS_START) = "<?xml version=\"1.0\" ?><D:multistatus xmlns:D=\"DAV:\">";
FLASH_TEXT(MULTISTATUS_END) = "</D:multistatus>";
FLASH_TEXT(RESPONSE_START) = "<D:response>";
FLASH_TEXT(RESPONSE_END) = "</D:response>\n";
FLASH_TEXT(HREF_START) = "<D:href>";
FLASH_TEXT(HREF_END) = "</D:href>";
FLASH_TEXT(PROPSTAT_START) = "<D:propstat>";
FLASH_TEXT(PROPSTAT_END) = "</D:propstat>";
FLASH_TEXT(PROP_START) = "<D:prop>";
FLASH_TEXT(PROP_END) = "</D:prop>";
FLASH_TEXT(RESOURCETYPE_START) = "<D:resourcetype>";
FLASH_TEXT(RESOURCETYPE_END) = "</D:resourcetype>";
FLASH_TEXT(RESOURCE_COLLECTION) = "<D:collection/>";
FLASH_TEXT(HTTP_204_NO_CONTENT) = "HTTP/1.1 204 No Content";
FLASH_TEXT(CONTENTLEN_START) = "<D:getcontentlength>";
FLASH_TEXT(CONTENTLEN_END) = "</D:getcontentlength>";
FLASH_TEXT(CREATEDATE_START) = "<D:creationdate>";
FLASH_TEXT(CREATEDATE_END) = "</D:creationdate>";
FLASH_TEXT(MODDATE_START) = "<D:getlastmodified>";
FLASH_TEXT(MODDATE_END) = "</D:getlastmodified>";
FLASH_TEXT(STATUS_OK) = "<D:status>HTTP/1.1 200 OK</D:status>";
} // namespace webdav_constants
class WebdavFileListCallback
{
public:
WebdavFileListCallback(const String &path)
: path_(path), headerWritten_(false), finished_(false)
{
dir_ = portablefs::openDir(path);
}
size_t operator()(uint8_t *buffer, size_t maxLen, size_t index)
{
Serial.print("index ");
Serial.println(index);
using namespace webdav_constants;
uint8_t *bufferStart = buffer;
if (finished_)
return 0;
if (!headerWritten_)
{
toBuffer(buffer, MULTISTATUS_START);
headerWritten_ = true;
}
bool fileFound = false;
while (dir_.next())
{
if (isFirstFileOfTrainingGroup())
{
fileFound = true;
break;
}
}
if (fileFound)
{
//toBuffer(buffer, path_.c_str());
toBuffer(buffer, RESPONSE_START);
toBuffer(buffer, HREF_START);
const auto fileName = dir_.fileName();
const auto fileNameWithoutDir = fileName.substring(fileName.lastIndexOf("/") + 1);
String fileBaseName = fileNameWithoutDir.substring(0, fileNameWithoutDir.indexOf('_'));
fileBaseName += ".st";
toBuffer(buffer, fileBaseName.c_str());
toBuffer(buffer, HREF_END);
toBuffer(buffer, PROPSTAT_START);
toBuffer(buffer, PROP_START);
if (dir_.isDirectory())
{
toBuffer(buffer, RESOURCETYPE_START);
toBuffer(buffer, RESOURCE_COLLECTION);
toBuffer(buffer, RESOURCETYPE_END);
}
else
{
toBuffer(buffer, CONTENTLEN_START);
String fileSizeStr(getFileSize(fileName));
toBuffer(buffer, fileSizeStr.c_str());
toBuffer(buffer, CONTENTLEN_END);
}
toBuffer(buffer, PROP_END);
toBuffer(buffer, STATUS_OK);
toBuffer(buffer, PROPSTAT_END);
toBuffer(buffer, webdav_constants::RESPONSE_END);
}
else
{
toBuffer(buffer, MULTISTATUS_END);
finished_ = true;
}
size_t bytesWritten = buffer - bufferStart;
assert_msg(bytesWritten < maxLen, "Written too much!");
//Serial.print("Bytes written ");
//Serial.println(bytesWritten);
//Serial.print("Max bytes ");
//Serial.println(maxLen);
return bytesWritten;
}
private:
bool isFirstFileOfTrainingGroup()
{
return !dir_.isDirectory() && dir_.fileName().endsWith("_0");
}
size_t getFileSize(const String &fileZero)
{
size_t size = 0;
auto fileBase = fileZero.substring(0, fileZero.indexOf('_'));
auto newDirInstance = portablefs::openDir(path_);
while (newDirInstance.next())
if (newDirInstance.isFile() && newDirInstance.fileName().startsWith(fileBase))
size += newDirInstance.fileSize();
return size;
}
void toBuffer(uint8_t *&buffer, const char *text)
{
auto len = strlen(text);
memcpy(buffer, text, len);
buffer += len;
}
portablefs::Dir dir_;
const String path_;
bool headerWritten_;
bool finished_;
};
bool deleteMeasurementFiles(const String &stName, const String &folder)
{
String baseName = folder + "/" + stName.substring(0, stName.indexOf("."));
int counter = 0;
{
auto d = portablefs::openDir(folder);
while (d.next())
if (d.isFile() && d.fileName().startsWith(baseName))
++counter;
}
if (counter == 0)
return false;
for (int i = 0; i < counter; ++i)
{
const String pathToDelete = baseName + "_" + String(i);
if (!SPIFFS.remove(pathToDelete))
return false;
}
return true;
}
class SpiffsWebDavHandler : public AsyncWebHandler
{
public:
SpiffsWebDavHandler(const String &prefix, const String &folder)
: prefix_(prefix), folder_(folder)
{
}
virtual bool canHandle(AsyncWebServerRequest *request) override final
{
Serial.print("Can handle for url : ");
Serial.println(request->url());
return request->url().startsWith(prefix_);
}
virtual void handleRequest(AsyncWebServerRequest *request) override final
{
if (request->url() == prefix_ + "/" && (request->method() == HTTP_GET || request->method() == HTTP_PROPFIND))
{
// send chunked response - it is too large to send in one go
auto response = request->beginChunkedResponse("application/xml",
WebdavFileListCallback(folder_));
request->send(response);
} /*
else if(request->url() == prefix_ + "/" && request->method() == HTTP_GET) {
AsyncResponseStream * response = request->beginResponseStream("text/plain", 1460*10);
Dir dir = SPIFFS.openDir(folder_);
Serial.print("Opening folder ");
Serial.println(folder_);
while (dir.next()) {
Serial.print(" File: ");
Serial.println(dir.fileName());
response->println(dir.fileName());
}
request->send(response);
}*/
else if (request->method() == HTTP_GET)
{
auto path = folder_ + request->url().substring(prefix_.length());
if (SPIFFS.exists(path))
request->send(SPIFFS, path, "application/x-msgpack");
else
request->send(404, "text/plain", "Webdav: File not found");
}
else if (request->method() == HTTP_DELETE)
{
auto stFileName = request->url().substring(prefix_.length() + 1);
Serial.print("HTTP_DELETE for ");
Serial.println(stFileName);
bool deleteSuccessful = deleteMeasurementFiles(stFileName, folder_);
if (deleteSuccessful)
request->send(204, "text/plain", "Success");
else
request->send(404, "text/plain", "Webdav: File to delete not found");
}
else
{
request->send(404, "text/plain", "Webdav: Invalid request");
}
}
virtual bool isRequestHandlerTrivial() override final { return false; }
private:
String prefix_;
String folder_;
};

27
firmware/platformio.ini Normal file
View File

@@ -0,0 +1,27 @@
;PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[env:d1]
platform = espressif8266
board = esp_wroom_02
framework = arduino
monitor_port = /dev/ttyUSB0
upload_port = /dev/ttyUSB0
monitor_speed = 115200
build_flags = -Wl,-Teagle.flash.2m1m.ld
lib_deps =
https://github.com/mabau/ESPAsyncWebServer.git
AsyncTCP
NTPClient
[env:native]
platform = native
test_ignore = test_embedded
build_flags = -g

View File

@@ -0,0 +1,29 @@
#pragma once
#include <cstdint>
//#define _HW_V_20
// HX711 load cell
#ifdef USE_ESP32
#ifdef _HW_V_20
const int CONFIG_SCALE_DOUT_PIN = 23;
const int CONFIG_SCALE_SCK_PIN = 22;
#else
const int CONFIG_SCALE_DOUT_PIN = 22;
const int CONFIG_SCALE_SCK_PIN = 23;
#endif
#else
const int CONFIG_SCALE_DOUT_PIN = D2;
const int CONFIG_SCALE_SCK_PIN = D3;
#endif
const uint8_t CONFIG_MEASUREMENT_AVG_COUNT = 1; // number of measurements in normal phase
const uint8_t CONFIG_TARE_AVG_COUNT = 6; // number of measurements in tare-phase (to find 0 )
const int CONFIG_MEASURE_DELAY = 100; // interval in ms between measurements
//const int CONFIG_VALUE_DIVIDER = 8; // uint32 measurements are divided by this factor, before stored in uint16_t
const int CONFIG_VALUE_DIVIDER = 256; // uint32 measurements are divided by this factor, before stored in uint16_t
const uint32_t CONFIG_SESSION_CHUNK_SIZE = 1024; //1024*8 - 16 * sizeof(uint32_t);

View File

@@ -0,0 +1,5 @@
const char *CONFIG_WIFI_SSID = "WLAN";
const char *CONFIG_WIFI_PASSWORD = "Bau3rWLAN";
const char* CONFIG_HOSTNAME = "smartswim";

View File

@@ -0,0 +1,75 @@
#ifdef USE_ESP32
#include "SPIFFS.h"
#else
#include <FS.h>
#endif
inline void printDeviceInfo()
{
/*
FSInfo fs_info;
SPIFFS.info(fs_info);
float fileTotalKB = (float)fs_info.totalBytes / 1024.0;
float fileUsedKB = (float)fs_info.usedBytes / 1024.0;
float flashChipSize = (float)ESP.getFlashChipSize() / 1024.0 / 1024.0;
float realFlashChipSize = (float)ESP.getFlashChipRealSize() / 1024.0 / 1024.0;
float flashFreq = (float)ESP.getFlashChipSpeed() / 1000.0 / 1000.0;
FlashMode_t ideMode = ESP.getFlashChipMode();
Serial.printf("\n#####################\n");
Serial.printf("__________________________\n\n");
Serial.println("Firmware: ");
Serial.printf(" Chip Id: %08X\n", ESP.getChipId());
Serial.print(" Core version: ");
Serial.println(ESP.getCoreVersion());
Serial.print(" SDK version: ");
Serial.println(ESP.getSdkVersion());
Serial.print(" Boot version: ");
Serial.println(ESP.getBootVersion());
Serial.print(" Boot mode: ");
Serial.println(ESP.getBootMode());
Serial.printf("__________________________\n\n");
Serial.println("Flash chip information: ");
Serial.printf(" Flash chip Id: %08X (for example: Id=001640E0 Manuf=E0, Device=4016 (swap bytes))\n", ESP.getFlashChipId());
Serial.printf(" Sketch thinks Flash RAM is size: ");
Serial.print(flashChipSize);
Serial.println(" MB");
Serial.print(" Actual size based on chip Id: ");
Serial.print(realFlashChipSize);
Serial.println(" MB ... given by (2^( Device - 1) / 8 / 1024");
Serial.print(" Flash frequency: ");
Serial.print(flashFreq);
Serial.println(" MHz");
Serial.printf(" Flash write mode: %s\n", (ideMode == FM_QIO ? "QIO" : ideMode == FM_QOUT ? "QOUT" : ideMode == FM_DIO ? "DIO" : ideMode == FM_DOUT ? "DOUT" : "UNKNOWN"));
Serial.printf("__________________________\n\n");
Serial.println("File system (SPIFFS): ");
Serial.print(" Total KB: ");
Serial.print(fileTotalKB);
Serial.println(" KB");
Serial.print(" Used KB: ");
Serial.print(fileUsedKB);
Serial.println(" KB");
Serial.printf(" Block size: %u\n", fs_info.blockSize);
Serial.printf(" Page size: %u\n", fs_info.pageSize);
Serial.printf(" Maximum open files: %u\n", fs_info.maxOpenFiles);
Serial.printf(" Maximum path length: %u\n\n", fs_info.maxPathLength);
String str = "";
Dir dir = SPIFFS.openDir("/dat");
while (dir.next())
{
str += dir.fileName();
str += " / ";
str += dir.fileSize();
str += "\r\n";
}
Serial.print(str);
*/
}

View File

@@ -0,0 +1,239 @@
#define USE_ESP32
// Arduino & ESP headers
#include <Arduino.h>
#ifdef USE_ESP32
#include <WiFi.h>
#else
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#endif
#include <ESPAsyncWebServer.h>
#include <WiFiUdp.h> // for NTP
#include <NTPClient.h> // for NTP
// Own libs
#include "Dtypes.h"
#include "MockScale.h"
#include "Scale.h"
#include "MeasurementSession.h"
#include "SpiffsStorage.h"
#include "DeviceInfoLog.h"
// Configuration
#include "ConfigWifi.h"
#include "ConfigHardware.h"
#include "AsyncWebDav.h"
AsyncWebServer server(80);
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "pool.ntp.org");
typedef MeasurementSession<uint16_t, SpiffsStorageReader, SpiffsStorageWriter, CONFIG_SESSION_CHUNK_SIZE> Session_T;
template<typename Session_T>
class SessionManager
{
public:
SessionManager() : measuring_(false), lastCallTime_(0)
{}
void begin() {
scale.begin(CONFIG_SCALE_DOUT_PIN, CONFIG_SCALE_SCK_PIN);
scale.tare( CONFIG_TARE_AVG_COUNT );
session.init( timeClient.getEpochTime() );
}
void startMeasurements() {
measuring_ = true;
lastCallTime_ = 0;
session.init( timeClient.getEpochTime() );
}
void stopMeasurements() {
measuring_ = false;
session.finalize();
}
bool isMeasuring() const {
return measuring_;
}
void iteration() {
if( ! measuring_ ) {
//Serial.println("Disabled");
return;
}
uint16_t measurement=-1;
scale.measure(measurement);
session.addPoint(measurement);
Serial.print("Measurement: ");
Serial.println(measurement);
if( lastCallTime_ != 0) {
const long cycleDuration = millis() - lastCallTime_;
if( cycleDuration <= CONFIG_MEASURE_DELAY)
{
delay(CONFIG_MEASURE_DELAY - cycleDuration);
}
else
{
const long skipped = (cycleDuration / CONFIG_MEASURE_DELAY);
//Serial.printf("Warning: measurements skipped: %d, cycleDuration %d", skipped, cycleDuration);
for(int i=0; i < skipped; ++i)
session.addPoint(measurement);
delay(CONFIG_MEASURE_DELAY * (skipped + 1) - cycleDuration);
}
}
lastCallTime_ = millis();
}
Session_T & getSession() { return session; }
private:
Scale<CONFIG_VALUE_DIVIDER> scale;
//MockScale scale;
Session_T session;
bool measuring_;
long lastCallTime_;
};
SessionManager<Session_T> sessionManager;
void onNotFound(AsyncWebServerRequest *request) {
request->send(404, "text/plain", "Not found");
}
template<typename Session_T>
void httpSetup(SessionManager<Session_T> * sessionManager)
{
server.on("/api/session/start", HTTP_POST | HTTP_GET, [sessionManager](AsyncWebServerRequest * req) {
AsyncWebServerResponse *response = req->beginResponse(200, "text/plain", F("OK"));
response->addHeader("Access-Control-Allow-Origin", "*");
req->send(response);
//req->send(200, "text/plain", F("OK"));
sessionManager->startMeasurements();
Serial.println("Started measurements");
});
server.on("/api/session/stop", HTTP_POST | HTTP_GET, [sessionManager](AsyncWebServerRequest * req) {
AsyncWebServerResponse *response = req->beginResponse(200, "text/plain", F("OK"));
response->addHeader("Access-Control-Allow-Origin", "*");
req->send(response);
//req->send(200, "text/plain", F("OK"));
sessionManager->stopMeasurements();
Serial.println("Stopped measurements");
});
server.on("/api/session/data", HTTP_GET, [sessionManager](AsyncWebServerRequest * req) {
uint32_t startIdx = 0;
if( req->hasParam("startIdx") ) {
startIdx = req->getParam("startIdx")->value().toInt();
}
Serial.print("Data request, start index: ");
Serial.println(startIdx);
StreamingMsgPackEncoder<DummyWriter> encoderToDetermineSize(nullptr);
encoderToDetermineSize.setSizeCountMode(true);
sessionManager->getSession().serialize(encoderToDetermineSize, startIdx);
auto totalSize = encoderToDetermineSize.getContentLength();
Serial.print("Sending started of total size ");
Serial.println(totalSize);
auto callback = [=](uint8_t *buffer, size_t maxLen, size_t index) -> size_t {
CopyWriter copyWriter(buffer);
ChunkedStreamingMsgPackEncoder<CopyWriter> encoder(&copyWriter, index, index + maxLen);
sessionManager->getSession().serialize(encoder, startIdx);
return encoder.sentBytes() - index;
};
AsyncWebServerResponse *response = req->beginResponse("application/x-msgpack", totalSize, callback);
response->addHeader("Access-Control-Allow-Origin", "*");
auto sessionId = sessionManager->getSession().getStartTime();
response->addHeader("content-disposition", "attachment; filename=\"" + String(sessionId) + ".st\"");
req->send(response);
});
server.addHandler(new SpiffsWebDavHandler("/webdav", "/dat"));
server.onNotFound(onNotFound);
server.begin();
}
void listDir(fs::FS &fs, const char * dirname, uint8_t levels){
Serial.printf("Listing directory: %s\r\n", dirname);
File root = fs.open(dirname);
if(!root){
Serial.println("- failed to open directory");
return;
}
if(!root.isDirectory()){
Serial.println(" - not a directory");
return;
}
File file = root.openNextFile();
while(file){
if(file.isDirectory()){
Serial.print(" DIR : ");
Serial.println(file.name());
if(levels){
listDir(fs, file.name(), levels -1);
}
} else {
Serial.print(" FILE: ");
Serial.print(file.name());
Serial.print("\tSIZE: ");
Serial.println(file.size());
}
file = root.openNextFile();
}
}
void setup()
{
// Serial
Serial.begin(115200);
while(!Serial) {}
Serial.println(" ");
Serial.println("----- New start -----");
// File system
bool spiffsResult = SPIFFS.begin(true);
Serial.printf("Spiffs begin %d\n", spiffsResult);
printDeviceInfo();
// WiFi
WiFi.mode(WIFI_STA);
WiFi.begin(CONFIG_WIFI_SSID, CONFIG_WIFI_PASSWORD);
#ifdef USE_ESP32
WiFi.setHostname(CONFIG_HOSTNAME);
#else
WIFI.hostname(CONFIG_HOSTNAME);
#endif
Serial.print(F("\n\n"));
Serial.println(F("Waiting for WIFI connection..."));
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
}
Serial.print(F("Connected to WiFi. IP:"));
Serial.println(WiFi.localIP());
// NTP
timeClient.begin();
timeClient.update();
// Session
sessionManager.begin();
// HTTP & Websocket server
httpSetup(&sessionManager);
Serial.println("Spiffs listing:");
listDir(SPIFFS, "/", 3);
}
void loop() {
sessionManager.iteration();
}

View File

@@ -0,0 +1,241 @@
#include "MockDtypes.h"
#include "MockSerial.h"
#include "MeasurementSession.h"
#include "MockStorage.h"
#include <unity.h>
#include <vector>
#include <iostream>
template<typename Measurement_T>
std::vector<Measurement_T> parseMessagePack(const uint8_t * data, uint32_t &startTime, uint32_t &startIndex)
{
int offset = 0;
// map header
const int expectedMapSize = 3;
auto mapHeader = reinterpret_cast<const uint8_t*>(&data[offset]);
offset += 1;
TEST_ASSERT_MESSAGE( *mapHeader == (0b10000000 | expectedMapSize), "Map Header wrong");
// string255: sessionStartTime
auto stringHeader = reinterpret_cast<const char*>(&data[offset++]);
auto stringSize = reinterpret_cast<const uint8_t*>(&data[offset++]);
TEST_ASSERT_EQUAL(*stringHeader, '\xd9');
std::string sessionStartTimeStr = std::string((const char*)(&data[offset]), (size_t)(*stringSize));
TEST_ASSERT( sessionStartTimeStr == "sessionStartTime");
offset += *stringSize;
//uint32
auto intCode = reinterpret_cast<const uint8_t*>(&data[offset++]);
startTime = ntohl(*reinterpret_cast<const uint32_t*>(&data[offset]));
offset += 4;
// string255: startIndex
stringHeader = reinterpret_cast<const char*>(&data[offset++]);
stringSize = reinterpret_cast<const uint8_t*>(&data[offset++]);
TEST_ASSERT_MESSAGE(*stringHeader == '\xd9', "String header wrong");
std::string startIndexStr = std::string((const char*)(&data[offset]), (size_t)(*stringSize));
TEST_ASSERT_MESSAGE( startIndexStr == "startIndex", "startIndex string is wrong");
offset += *stringSize;
//uint32
intCode = reinterpret_cast<const uint8_t*>(&data[offset++]);
startIndex = ntohl(*reinterpret_cast<const uint32_t*>(&data[offset]));
offset += 4;
// string255: values
stringHeader = reinterpret_cast<const char*>(&data[offset++]);
stringSize = reinterpret_cast<const uint8_t*>(&data[offset++]);
TEST_ASSERT_MESSAGE(*stringHeader == '\xd9', "String header wrong");
std::string valueStr = std::string((const char*)(&data[offset]), (size_t)(*stringSize));
TEST_ASSERT_MESSAGE( valueStr == "values", "values string is wrong");
offset += *stringSize;
// vector
auto vectorHeader = reinterpret_cast<const char*>(&data[offset++]);
TEST_ASSERT_MESSAGE( *vectorHeader == '\xc9', "Vector header wrong");
size_t vectorLength = ntohl(*reinterpret_cast<const uint32_t*>(&data[offset])) / sizeof(Measurement_T);
offset += 4;
offset += 1; // jump over type
auto vectorData = reinterpret_cast<const Measurement_T *>(&data[offset]);
return std::vector<Measurement_T>(vectorData, vectorData + vectorLength);
}
void testSessionChunkAdd()
{
const uint_t size = 16;
SessionChunk<uint16_t, size> chunk;
for( uint16_t i=0; i < size; ++i) {
bool res = chunk.addPoint(i);
TEST_ASSERT_MESSAGE(res, "Adding point failed");
TEST_ASSERT_MESSAGE( chunk.numMeasurements() == i+1, "Number of measurements reported wrong");
}
bool res = chunk.addPoint(0);
TEST_ASSERT_MESSAGE(!res, "Full chunk was not detected");
TEST_ASSERT_MESSAGE(chunk.numMeasurements() == size, "Point appears to be added");
}
void testSessionChunkGetterSetter()
{
const uint_t size = 16;
SessionChunk<uint16_t, size> chunk;
const uint32_t time = 244213;
const uint32_t startIdx = 131;
chunk.init(time, startIdx);
TEST_ASSERT_MESSAGE( chunk.getStartIndex() == startIdx, "Start Index wrong");
TEST_ASSERT_MESSAGE( chunk.getStartTime() == time, "Start time wrong");
}
void testSessionChunkSerialization()
{
const uint_t size = 16;
const uint32_t startTime = 194232;
const uint32_t startIndex = 1314;
const uint_t fillSize = 12;
SessionChunk<uint16_t, size> chunk;
chunk.init(startTime, startIndex);
for( uint16_t i=0; i < fillSize; ++i) {
bool res = chunk.addPoint(i);
TEST_ASSERT_MESSAGE(res, "Adding point failed");
TEST_ASSERT_MESSAGE( chunk.numMeasurements() == i+1, "Number of measurements reported wrong");
}
std::vector<uint8_t> data;
VectorAdaptor adaptor( &data );
StreamingMsgPackEncoder<VectorAdaptor> encoder(&adaptor);
chunk.serialize(encoder);
uint32_t readStartTime=0;
uint32_t readStartIndex=0;
auto result = parseMessagePack<uint16_t>(&data[0], readStartTime, readStartIndex);
TEST_ASSERT_MESSAGE(startIndex == readStartIndex && startTime == readStartTime, "");
TEST_ASSERT_MESSAGE(result.size() == fillSize, "Wrong result array size");
for( uint16_t i=0; i < fillSize; ++i) {
TEST_ASSERT_MESSAGE(result[i] == i, "Wrong array contents");
}
}
void testSession() {
const uint32_t SESSION_SIZE = 128;
typedef MeasurementSession<uint16_t, MockStorageReader, MockStorageWriter, SESSION_SIZE> MockSession;
const uint32_t startTime = 194842;
const uint_t fillSize = SESSION_SIZE * 4 + 7;
MockSession session;
session.init(startTime);
for (uint16_t i = 0; i < fillSize; ++i) {
session.addPoint(i);
}
std::vector<uint8_t> data;
VectorAdaptor adaptor( &data );
StreamingMsgPackEncoder<VectorAdaptor> encoder(&adaptor);
session.serialize(encoder, 0);
uint32_t readStartTime=0;
uint32_t readStartIndex=0;
auto result = parseMessagePack<uint16_t>(&data[0], readStartTime, readStartIndex);
TEST_ASSERT_MESSAGE(readStartIndex == 0 && startTime == readStartTime, "");
TEST_ASSERT_MESSAGE(result.size() == fillSize, "Wrong result array size");
for( uint16_t i=0; i < fillSize; ++i) {
TEST_ASSERT_MESSAGE(result[i] == i, "Wrong array contents");
}
}
void testPartialSessionSerialization() {
const uint32_t SESSION_SIZE = 1024*8 - 16 * sizeof(uint32_t);
typedef MeasurementSession<uint16_t, MockStorageReader, MockStorageWriter, SESSION_SIZE> MockSession;
const uint32_t startTime = 194842;
const uint_t fillSize = 4937 + 81;
MockSession session;
session.init(startTime);
for (uint16_t i = 0; i < fillSize; ++i) {
session.addPoint(i);
}
std::vector<uint8_t> data;
VectorAdaptor adaptor( &data );
StreamingMsgPackEncoder<VectorAdaptor> encoder(&adaptor);
encoder.setSizeCountMode(true);
session.serialize(encoder, 0);
auto totalSize = encoder.getContentLength();
std::vector<uint32_t> splits = {953, totalSize};
//std::vector<uint32_t> splits = {totalSize};
uint32_t written = 0;
data.clear();
for(auto & split : splits) {
ChunkedStreamingMsgPackEncoder<VectorAdaptor> encoder(&adaptor, written, split);
session.serialize(encoder, 0);
written = encoder.sentBytes();
}
TEST_ASSERT(written == totalSize);
uint32_t readStartTime=0;
uint32_t readStartIndex=0;
auto result = parseMessagePack<uint16_t>(&data[0], readStartTime, readStartIndex);
TEST_ASSERT_MESSAGE(readStartIndex == 0 && startTime == readStartTime, "");
TEST_ASSERT_MESSAGE(result.size() == fillSize, "Wrong result array size");
for( uint16_t i=0; i < fillSize; ++i) {
TEST_ASSERT_MESSAGE(result[i] == i, "Wrong array contents");
}
}
void testPartialSessionSerializationEmptyArray() {
const uint32_t SESSION_SIZE = 1024*8 - 16 * sizeof(uint32_t);
typedef MeasurementSession<uint16_t, MockStorageReader, MockStorageWriter, SESSION_SIZE> MockSession;
const uint32_t startTime = 194842;
const uint_t fillSize = 4937 + 81;
std::vector<uint8_t> data;
VectorAdaptor adaptor( &data );
MockSession session;
session.init(startTime);
ChunkedStreamingMsgPackEncoder<VectorAdaptor> encoder(&adaptor, 0, 6);
session.serialize(encoder, 0);
auto written = encoder.sentBytes();
TEST_ASSERT(written == 6);
}
void allTests()
{
UNITY_BEGIN();
RUN_TEST(testPartialSessionSerializationEmptyArray);
RUN_TEST(testPartialSessionSerialization);
RUN_TEST(testSessionChunkAdd);
RUN_TEST(testSessionChunkGetterSetter);
RUN_TEST(testSessionChunkSerialization);
RUN_TEST(testSession);
UNITY_END();
}
#ifdef ARDUINO
void setup() {
// NOTE!!! Wait for >2 secs
// if board doesn't support software reset via Serial.DTR/RTS
delay(2000);
allTests();
}
void loop() {
}
#else
int main(int argc, char**argv)
{
allTests();
return 0;
}
#endif