Restructured full repo
This commit is contained in:
20
firmware/lib/basic/Dtypes.h
Normal file
20
firmware/lib/basic/Dtypes.h
Normal 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__))
|
||||
181
firmware/lib/basic/FilesystemAbstraction.h
Normal file
181
firmware/lib/basic/FilesystemAbstraction.h
Normal 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
|
||||
46
firmware/lib/basic/MockDtypes.h
Normal file
46
firmware/lib/basic/MockDtypes.h
Normal 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__))
|
||||
18
firmware/lib/basic/MockSerial.h
Normal file
18
firmware/lib/basic/MockSerial.h
Normal 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;
|
||||
34
firmware/lib/scale/MockScale.h
Normal file
34
firmware/lib/scale/MockScale.h
Normal 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;
|
||||
};
|
||||
35
firmware/lib/scale/Scale.h
Normal file
35
firmware/lib/scale/Scale.h
Normal 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;
|
||||
};
|
||||
135
firmware/lib/session/MeasurementSession.h
Normal file
135
firmware/lib/session/MeasurementSession.h
Normal 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;
|
||||
};
|
||||
90
firmware/lib/session/MockStorage.h
Normal file
90
firmware/lib/session/MockStorage.h
Normal 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;
|
||||
};
|
||||
85
firmware/lib/session/SessionChunk.h
Normal file
85
firmware/lib/session/SessionChunk.h
Normal 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];
|
||||
};
|
||||
90
firmware/lib/session/SpiffsStorage.h
Normal file
90
firmware/lib/session/SpiffsStorage.h
Normal 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_;
|
||||
};
|
||||
262
firmware/lib/session/StreamingMsgPackEncoder.h
Normal file
262
firmware/lib/session/StreamingMsgPackEncoder.h
Normal 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_;
|
||||
};
|
||||
192
firmware/lib/userdb/UserDB.cpp
Normal file
192
firmware/lib/userdb/UserDB.cpp
Normal 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_;
|
||||
}
|
||||
}
|
||||
}
|
||||
73
firmware/lib/userdb/UserDB.h
Normal file
73
firmware/lib/userdb/UserDB.h
Normal 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_;
|
||||
};
|
||||
233
firmware/lib/webdav/AsyncWebDav.h
Normal file
233
firmware/lib/webdav/AsyncWebDav.h
Normal 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_;
|
||||
};
|
||||
Reference in New Issue
Block a user