New firmware - with host mockup for testing
This commit is contained in:
commit
512ec482f7
|
@ -0,0 +1,2 @@
|
|||
# Default ignored files
|
||||
/workspace.xml
|
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module classpath="CMake" type="CPP_MODULE" version="4" />
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CMakeWorkspace" PROJECT_DIR="$PROJECT_DIR$" />
|
||||
<component name="JavaScriptSettings">
|
||||
<option name="languageLevel" value="ES6" />
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/firmware.iml" filepath="$PROJECT_DIR$/.idea/firmware.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,4 @@
|
|||
cmake_minimum_required (VERSION 2.6)
|
||||
project (pooltrainer_firmware)
|
||||
|
||||
add_executable(test sessiontest.cpp)
|
|
@ -0,0 +1,40 @@
|
|||
#include <string>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <arpa/inet.h>
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
#define assert(EXPRESSION, MSG) ((EXPRESSION) ? (void)0 : _assert(#EXPRESSION, #MSG, __FILE__, __LINE__))
|
|
@ -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;
|
|
@ -0,0 +1,28 @@
|
|||
#include <FS.h>
|
||||
#include <ESP8266WebServer.h>
|
||||
|
||||
|
||||
class ESP8266HttpMsgPackWriter {
|
||||
public:
|
||||
HttpWriterAdaptor(ESP8266WebServer
|
||||
* o)
|
||||
:
|
||||
obj_(o) {}
|
||||
|
||||
void write(const char *data, uint32_t size) {
|
||||
obj_->sendContent_P(data, size);
|
||||
}
|
||||
|
||||
private:
|
||||
ESP8266WebServer *obj_;
|
||||
};
|
||||
|
||||
|
||||
template<typename SessionChunk_T>
|
||||
void saveSessionChunkToFile(const SessionChunk_T &chunk, const String &fileName) {
|
||||
String startTimeStr(chunk.getStartTime());
|
||||
File f = SPIFFS.open(fileName, "w");
|
||||
StreamingMsgPackEncoder<File> encoder(&f);
|
||||
chunk.serialize(encoder);
|
||||
f.close();
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
|
||||
// WIFI Parameters
|
||||
const char* WIFI_SSID = "RepeaterWZ";
|
||||
const char* WIFI_PASSWD = "Bau3rWLAN";
|
||||
const char* HOSTNAME = "swimtrainer";
|
||||
const bool CORS_HEADER = true;
|
||||
|
||||
// HX711 connection
|
||||
const int LOADCELL_DOUT_PIN = D2;
|
||||
const int LOADCELL_SCK_PIN = D3;
|
||||
const int LED_PIN = D1;
|
||||
|
||||
// Measurement parameters
|
||||
const int DELAY = 100; // interval in ms between measurements
|
||||
const int SESSION_SIZE = 1024*8; // how many data points are added to the session
|
||||
const byte MEASUREMENT_AVG_COUNT = 1; // averages over this many consecutive AD-converter reads
|
||||
const byte TARE_AVG_COUNT = 50; // number of measurements in tare-phase (to find 0 )
|
||||
const int DIVIDER = 128; // uint32 measurements are divided by this factor, before stored in uint16_t
|
|
@ -0,0 +1,138 @@
|
|||
#include "HX711.h"
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <WiFiUdp.h> // for NTP
|
||||
#include <NTPClient.h> // for NTP
|
||||
#include <ESP8266WebServer.h>
|
||||
#include <FS.h>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
|
||||
int16_t compressMeasurement(int32_t value) {
|
||||
return (int16_t)(measurement / DIVIDER)
|
||||
}
|
||||
|
||||
|
||||
|
||||
HX711 scale;
|
||||
WiFiUDP ntpUDP;
|
||||
NTPClient timeClient(ntpUDP, "pool.ntp.org");
|
||||
TrainingSession session;
|
||||
ESP8266WebServer webServer(80);
|
||||
|
||||
bool makeMeasurement(long & measurementOut)
|
||||
{
|
||||
if (scale.is_ready())
|
||||
{
|
||||
measurementOut = scale.get_value(MEASUREMENT_AVG_COUNT);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void setup()
|
||||
{
|
||||
digitalWrite(LED_PIN, HIGH);
|
||||
|
||||
// Serial
|
||||
Serial.begin(115200);
|
||||
while(!Serial) {}
|
||||
|
||||
// wifi
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.hostname(HOSTNAME);
|
||||
WiFi.begin(WIFI_SSID, WIFI_PASSWD);
|
||||
|
||||
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());
|
||||
|
||||
timeClient.begin();
|
||||
timeClient.update();
|
||||
|
||||
// initialize cell
|
||||
scale.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN);
|
||||
scale.tare( TARE_AVG_COUNT );
|
||||
|
||||
// NTP
|
||||
session.init( &timeClient );
|
||||
Serial.print("Initialized NTP client: ");
|
||||
Serial.println(timeClient.getEpochTime());
|
||||
|
||||
// webserver
|
||||
webServer.on("/api/session", [] () {
|
||||
session.send(&webServer);
|
||||
});
|
||||
webServer.on("/api/save", [] () {
|
||||
webServer.send(200, "text/plain", session.saveToFileSystem());
|
||||
});
|
||||
webServer.on("/api/tare", [] () {
|
||||
scale.tare( TARE_AVG_COUNT );
|
||||
webServer.send(200, "text/plain", "OK");
|
||||
});
|
||||
webServer.on("/", HTTP_GET, [](){
|
||||
Serial.println("index.html requested");
|
||||
File file = SPIFFS.open("/index.html", "r");
|
||||
size_t sent = webServer.streamFile(file, "text/html");
|
||||
file.close();
|
||||
});
|
||||
webServer.on("/swimtrainer.webmanifest", HTTP_GET, [](){
|
||||
File file = SPIFFS.open("/swimtrainer.webmanifest", "r");
|
||||
size_t sent = webServer.streamFile(file, "application/manifest+json");
|
||||
file.close();
|
||||
});
|
||||
|
||||
|
||||
|
||||
webServer.begin();
|
||||
Serial.println("Webserver started");
|
||||
|
||||
// flash file system
|
||||
if(!SPIFFS.begin()){
|
||||
Serial.println("An Error has occurred while mounting SPIFFS");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void loop()
|
||||
{
|
||||
const long cycleStart = millis();
|
||||
|
||||
|
||||
digitalWrite(LED_PIN, HIGH);
|
||||
|
||||
|
||||
long measurement = 0;
|
||||
if(makeMeasurement(measurement))
|
||||
{
|
||||
session.addPoint(measurement);
|
||||
} else {
|
||||
Serial.println("Measurement skipped - cell not ready");
|
||||
}
|
||||
|
||||
webServer.handleClient();
|
||||
|
||||
const long cycleDuration = millis() - cycleStart;
|
||||
if( cycleDuration <= DELAY)
|
||||
{
|
||||
delay(DELAY - cycleDuration);
|
||||
}
|
||||
else
|
||||
{
|
||||
Serial.print("Skipping measurement, cycle duration was ");
|
||||
Serial.println(cycleDuration);
|
||||
const long skipped = (cycleDuration / DELAY);
|
||||
//for(int i=0; i < skipped; ++i)
|
||||
// session.addPoint(0xFFFFFFFE);
|
||||
|
||||
delay(DELAY * (skipped + 1) - cycleDuration);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
|
||||
class Adaptor {
|
||||
public:
|
||||
Adaptor(const String &fileName) {
|
||||
static const String baseDirectory(".");
|
||||
auto fullFileName = baseDirectory + fileName;
|
||||
fptr = fopen(fullFileName.c_str(), "wb");
|
||||
}
|
||||
|
||||
~Adaptor() {
|
||||
fclose(fptr);
|
||||
}
|
||||
|
||||
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 Adaptor(fileName);
|
||||
encoder_ = new StreamingMsgPackEncoder <Adaptor>(adaptor_);
|
||||
}
|
||||
~MockStorageWriter(){
|
||||
delete adaptor_;
|
||||
delete encoder_;
|
||||
}
|
||||
MockStorageWriter(const MockStorageWriter &) = delete;
|
||||
|
||||
StreamingMsgPackEncoder<Adaptor> &encoder() { return *encoder_; }
|
||||
|
||||
private:
|
||||
Adaptor * adaptor_;
|
||||
StreamingMsgPackEncoder <Adaptor> * 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;
|
||||
};
|
|
@ -0,0 +1,113 @@
|
|||
#include "SessionChunk.h"
|
||||
|
||||
|
||||
template<typename Measurement_T, typename Reader, typename Writer, uint_t CHUNK_SIZE>
|
||||
class Session {
|
||||
public:
|
||||
typedef SessionChunk<Measurement_T, CHUNK_SIZE> Chunk_T;
|
||||
|
||||
Session()
|
||||
: currentChunk(&chunks[0]),
|
||||
otherChunk(&chunks[1]) {}
|
||||
|
||||
void init(uint32_t epochStartTime) {
|
||||
currentChunk = &chunks[0];
|
||||
currentChunk->init(epochStartTime, 0);
|
||||
}
|
||||
|
||||
bool addPoint(Measurement_T measurement) {
|
||||
const bool successful = currentChunk->addPoint(measurement);
|
||||
if (!successful) {
|
||||
rotate();
|
||||
const bool secondInsertSuccess = currentChunk->addPoint(measurement);
|
||||
assert(secondInsertSuccess, "Session: insertion after rotation failed");
|
||||
//TODO check that there is place for file - remove old files
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void serialize(StreamingMsgPackEncoder<T> & encoder, uint32_t startIdx) const
|
||||
{
|
||||
const uint32_t lastIdx = currentChunk->getStartIndex() + currentChunk->numMeasurements();
|
||||
if( lastIdx <= startIdx) {
|
||||
encoder.sendArray(nullptr, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
Chunk_T::sendHeader(encoder, currentChunk->getStartTime(), startIdx);
|
||||
encoder.sendArrayHeader(lastIdx - startIdx);
|
||||
while(startIdx < lastIdx)
|
||||
startIdx = serializeChunk(encoder, startIdx);
|
||||
assert(startIdx == lastIdx, "Not all data was sent");
|
||||
}
|
||||
|
||||
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());
|
||||
Writer writer( fileName );
|
||||
chunk->serialize(writer.encoder());
|
||||
};
|
||||
|
||||
template< typename T>
|
||||
uint32_t serializeChunk(StreamingMsgPackEncoder<T> & encoder, uint32_t startIdx) {
|
||||
assert( startIdx < currentChunk->getStartIndex() + currentChunk->numMeasurements(),
|
||||
"serializeChunk: invalid startIdx" );
|
||||
|
||||
if( startIdx >= currentChunk->getStartIndex() ) {
|
||||
encoder.sendArrayPartialContents( currentChunk->getDataPointer(), currentChunk->numMeasurements() );
|
||||
return currentChunk->getStartIndex() + currentChunk->numMeasurements();
|
||||
} else if( startIdx >= otherChunk->getStartIndex() && otherChunkFilled() ) {
|
||||
encoder.sendArrayPartialContents( otherChunk->getDataPointer(), otherChunk->numMeasurements() );
|
||||
assert( otherChunk->numMeasurements(), CHUNK_SIZE );
|
||||
return otherChunk->getStartIndex() + otherChunk->numMeasurements();
|
||||
} else {
|
||||
if( encoder.getSizeCountMode() ) {
|
||||
encoder.sendArrayPartialContents(nullptr, CHUNK_SIZE);
|
||||
} else {
|
||||
const uint32_t chunkNr = startIdx / CHUNK_SIZE;
|
||||
const auto chunkFileName = (chunkNr, currentChunk->getStartTime());
|
||||
Reader reader(chunkFileName);
|
||||
reader.seek(Chunk_T::valueOffset());
|
||||
|
||||
const uint32_t PART_SIZE = 32;
|
||||
static_assert( PART_SIZE < CHUNK_SIZE && CHUNK_SIZE % PART_SIZE == 0);
|
||||
|
||||
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.sendArrayPartialContents(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;
|
||||
};
|
|
@ -0,0 +1,108 @@
|
|||
#include "StreamingMsgPackEncoder.h"
|
||||
|
||||
|
||||
template<typename Measurement_T, uint_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 + startIndex, nextFree - startIndex);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void serialize(StreamingMsgPackEncoder<T> & encoder, uint32_t start, uint32_t end) const
|
||||
{
|
||||
if( start < this->startIndex )
|
||||
start = this->startIndex;
|
||||
if( end == 0 ) {
|
||||
end = start + this->nextFree;
|
||||
}
|
||||
|
||||
sendHeader(encoder, sessionStartTime, start);
|
||||
|
||||
bool sendEmpty =
|
||||
(start >= end) ||
|
||||
(end <= this->startIndex) ||
|
||||
(start >= (this->startIndex + this->nextFree));
|
||||
if( sendEmpty ) {
|
||||
encoder.sendArray(nullptr, 0);
|
||||
} else {
|
||||
const uint32_t idxStart = (start - this->startIndex);
|
||||
const uint32_t length = min(nextFree, end - start);
|
||||
encoder.sendArray(values + idxStart, length);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static uint32_t valueOffset()
|
||||
{
|
||||
StreamingMsgPackEncoder<T> encoder(nullptr);
|
||||
encoder.setSizeCountMode(true);
|
||||
sendHeader(encoder, 0, 0);
|
||||
return encoder.getContentLength();
|
||||
}
|
||||
|
||||
Measurement_T * getDataPointer() {
|
||||
return values;
|
||||
}
|
||||
|
||||
const Measurement_T * getDataPointer() const {
|
||||
return values;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static void sendHeader(StreamingMsgPackEncoder<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];
|
||||
};
|
|
@ -0,0 +1,36 @@
|
|||
#pragma once
|
||||
#include "StreamingMsgPackEncoder.h"
|
||||
#include <FS.h>
|
||||
|
||||
|
||||
class SpiffsStorageWriter {
|
||||
public:
|
||||
SpiffsStorageWriter(const String &fileName) :
|
||||
f_(SPIFFS.open(fileName, "w")),
|
||||
encoder_(&f_) {}
|
||||
|
||||
StreamingMsgPackEncoder<File> &encoder() { return encoder_; }
|
||||
|
||||
private:
|
||||
File f_;
|
||||
StreamingMsgPackEncoder<File> encoder_;
|
||||
};
|
||||
|
||||
|
||||
class SpiffsStorageReader
|
||||
{
|
||||
public:
|
||||
SpiffsBackendReader(const String &fileName) :
|
||||
f_(SPIFFS.open(fileName, "w"))
|
||||
{}
|
||||
|
||||
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_;
|
||||
};
|
|
@ -0,0 +1,126 @@
|
|||
|
||||
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';
|
||||
|
||||
|
||||
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 + length * sizeof(T);
|
||||
}
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
void setSizeCountMode(bool sizeCountMode_=true)
|
||||
{
|
||||
sizeCountMode = sizeCountMode_;
|
||||
}
|
||||
|
||||
bool getSizeCountMode() const {
|
||||
return sizeCountMode;
|
||||
}
|
||||
|
||||
uint32_t getContentLength() const {
|
||||
return contentLength;
|
||||
}
|
||||
|
||||
private:
|
||||
Writer * writer;
|
||||
uint32_t contentLength;
|
||||
bool sizeCountMode;
|
||||
};
|
|
@ -0,0 +1,18 @@
|
|||
#include "MockDtypes.h"
|
||||
#include "MockSerial.h"
|
||||
#include "session/Session.h"
|
||||
#include "session/MockStorage.h"
|
||||
|
||||
const uint32_t SESSION_SIZE = 128;
|
||||
typedef Session<uint16_t, MockStorageReader, MockStorageWriter, SESSION_SIZE> MockSession;
|
||||
|
||||
int main(int argc, char**argv)
|
||||
{
|
||||
MockSession session;
|
||||
|
||||
for( uint16_t i=0; i < SESSION_SIZE; ++i) {
|
||||
session.addPoint(i);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
Loading…
Reference in New Issue