Worked on firmware
This commit is contained in:
parent
9889d41805
commit
6a724b284f
|
@ -0,0 +1,2 @@
|
||||||
|
.pio
|
||||||
|
CMakeListsPrivate.txt
|
|
@ -1,7 +1,16 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="CMakeWorkspace" PROJECT_DIR="$PROJECT_DIR$" />
|
<component name="CMakeWorkspace" PROJECT_DIR="$PROJECT_DIR$" />
|
||||||
<component name="JavaScriptSettings">
|
<component name="CidrRootsConfiguration">
|
||||||
<option name="languageLevel" value="ES6" />
|
<sourceRoots>
|
||||||
|
<file path="$PROJECT_DIR$/src" />
|
||||||
|
</sourceRoots>
|
||||||
|
<libraryRoots>
|
||||||
|
<file path="$PROJECT_DIR$/lib" />
|
||||||
|
<file path="$PROJECT_DIR$/.pio/libdeps" />
|
||||||
|
</libraryRoots>
|
||||||
|
<excludeRoots>
|
||||||
|
<file path="$PROJECT_DIR$/.pio" />
|
||||||
|
</excludeRoots>
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
|
@ -2,7 +2,8 @@
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ProjectModuleManager">
|
<component name="ProjectModuleManager">
|
||||||
<modules>
|
<modules>
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/firmware.iml" filepath="$PROJECT_DIR$/.idea/firmware.iml" />
|
<module fileurl="file://$PROJECT_DIR$/.idea/clion.iml" filepath="$PROJECT_DIR$/.idea/clion.iml" />
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/platformio.iml" filepath="$PROJECT_DIR$/.idea/platformio.iml" />
|
||||||
</modules>
|
</modules>
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
|
@ -1,4 +1,77 @@
|
||||||
cmake_minimum_required (VERSION 2.6)
|
# !!! WARNING !!! AUTO-GENERATED FILE, PLEASE DO NOT MODIFY IT AND USE
|
||||||
project (pooltrainer_firmware)
|
# https://docs.platformio.org/page/projectconf/section_env_build.html#build-flags
|
||||||
|
#
|
||||||
|
# If you need to override existing CMake configuration or add extra,
|
||||||
|
# please create `CMakeListsUser.txt` in the root of project.
|
||||||
|
# The `CMakeListsUser.txt` will not be overwritten by PlatformIO.
|
||||||
|
|
||||||
add_executable(test sessiontest.cpp)
|
cmake_minimum_required(VERSION 3.2)
|
||||||
|
project(firmware)
|
||||||
|
|
||||||
|
include(CMakeListsPrivate.txt)
|
||||||
|
|
||||||
|
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/CMakeListsUser.txt)
|
||||||
|
include(CMakeListsUser.txt)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_custom_target(
|
||||||
|
PLATFORMIO_BUILD ALL
|
||||||
|
COMMAND ${PLATFORMIO_CMD} -f -c clion run
|
||||||
|
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
|
)
|
||||||
|
|
||||||
|
add_custom_target(
|
||||||
|
PLATFORMIO_BUILD_VERBOSE ALL
|
||||||
|
COMMAND ${PLATFORMIO_CMD} -f -c clion run --verbose
|
||||||
|
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
|
)
|
||||||
|
|
||||||
|
add_custom_target(
|
||||||
|
PLATFORMIO_UPLOAD ALL
|
||||||
|
COMMAND ${PLATFORMIO_CMD} -f -c clion run --target upload
|
||||||
|
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
|
)
|
||||||
|
|
||||||
|
add_custom_target(
|
||||||
|
PLATFORMIO_CLEAN ALL
|
||||||
|
COMMAND ${PLATFORMIO_CMD} -f -c clion run --target clean
|
||||||
|
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
|
)
|
||||||
|
|
||||||
|
add_custom_target(
|
||||||
|
PLATFORMIO_MONITOR ALL
|
||||||
|
COMMAND ${PLATFORMIO_CMD} -f -c clion device monitor
|
||||||
|
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
|
)
|
||||||
|
|
||||||
|
add_custom_target(
|
||||||
|
PLATFORMIO_TEST ALL
|
||||||
|
COMMAND ${PLATFORMIO_CMD} -f -c clion test
|
||||||
|
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
|
)
|
||||||
|
|
||||||
|
add_custom_target(
|
||||||
|
PLATFORMIO_PROGRAM ALL
|
||||||
|
COMMAND ${PLATFORMIO_CMD} -f -c clion run --target program
|
||||||
|
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
|
)
|
||||||
|
|
||||||
|
add_custom_target(
|
||||||
|
PLATFORMIO_UPLOADFS ALL
|
||||||
|
COMMAND ${PLATFORMIO_CMD} -f -c clion run --target uploadfs
|
||||||
|
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
|
)
|
||||||
|
|
||||||
|
add_custom_target(
|
||||||
|
PLATFORMIO_UPDATE_ALL ALL
|
||||||
|
COMMAND ${PLATFORMIO_CMD} -f -c clion update
|
||||||
|
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
|
)
|
||||||
|
|
||||||
|
add_custom_target(
|
||||||
|
PLATFORMIO_REBUILD_PROJECT_INDEX ALL
|
||||||
|
COMMAND ${PLATFORMIO_CMD} -f -c clion init --ide clion
|
||||||
|
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
|
)
|
||||||
|
|
||||||
|
add_executable(${PROJECT_NAME} ${SRC_LIST})
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
#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_;
|
|
||||||
};
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
92 x 28 x 30
|
||||||
|
35x20
|
||||||
|
|
||||||
|
|
||||||
|
95x 50 x 30 (absolute min)
|
||||||
|
100 x 55 x 35
|
138
firmware.ino
138
firmware.ino
|
@ -1,138 +0,0 @@
|
||||||
#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,17 @@
|
||||||
|
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.");
|
||||||
|
}
|
||||||
|
|
||||||
|
template< typename T>
|
||||||
|
inline String toString(const T & t) {
|
||||||
|
return String(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define assert(EXPRESSION, MSG) ((EXPRESSION) ? (void)0 : _assert(#EXPRESSION, #MSG, __FILE__, __LINE__))
|
|
@ -0,0 +1,32 @@
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
|
||||||
|
class MockScale
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MockScale( uint16_t valueMin=0, uint16_t valueMax=50)
|
||||||
|
: valueMin_(valueMin), valueMax_(valueMax), currentValue_(valueMin), direction(1)
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool measure(uint16_t & measurementOut) {
|
||||||
|
currentValue_ += direction;
|
||||||
|
if ( currentValue_ >= valueMax_)
|
||||||
|
direction = -1;
|
||||||
|
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;
|
||||||
|
};
|
|
@ -0,0 +1,30 @@
|
||||||
|
#include "HX711.h"
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
|
||||||
|
template<int DIVIDER=128>
|
||||||
|
class Scale
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
bool measure(uint16_t & measurementOut) {
|
||||||
|
if (hx711_.is_ready())
|
||||||
|
{
|
||||||
|
uint32_t value = hx711_.get_value(MEASUREMENT_AVG_COUNT);
|
||||||
|
measurementOut = (int16_t)(value / DIVIDER);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void begin(uint32_t pinDOUT, uint32_t pinSCK) {
|
||||||
|
hx711_.begin(pinDOUT, pinSCK);
|
||||||
|
};
|
||||||
|
|
||||||
|
void tare(uint32_t numMeasurementsToAverage=50) {
|
||||||
|
hx711_.tare(numMeasurementsToAverage);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
HX711 hx711_;
|
||||||
|
};
|
|
@ -1,12 +1,12 @@
|
||||||
#include "SessionChunk.h"
|
#include "SessionChunk.h"
|
||||||
|
|
||||||
|
|
||||||
template<typename Measurement_T, typename Reader, typename Writer, uint_t CHUNK_SIZE>
|
template<typename Measurement_T, typename Reader, typename Writer, uint32_t CHUNK_SIZE>
|
||||||
class Session {
|
class MeasurementSession {
|
||||||
public:
|
public:
|
||||||
typedef SessionChunk<Measurement_T, CHUNK_SIZE> Chunk_T;
|
typedef SessionChunk<Measurement_T, CHUNK_SIZE> Chunk_T;
|
||||||
|
|
||||||
Session()
|
MeasurementSession()
|
||||||
: currentChunk(&chunks[0]),
|
: currentChunk(&chunks[0]),
|
||||||
otherChunk(&chunks[1]) {}
|
otherChunk(&chunks[1]) {}
|
||||||
|
|
||||||
|
@ -100,8 +100,9 @@ private:
|
||||||
reader.seek(Chunk_T::template valueOffset<T>());
|
reader.seek(Chunk_T::template valueOffset<T>());
|
||||||
|
|
||||||
const uint32_t PART_SIZE = 32;
|
const uint32_t PART_SIZE = 32;
|
||||||
static_assert( PART_SIZE < CHUNK_SIZE && CHUNK_SIZE % PART_SIZE == 0);
|
#ifndef ARDUINO
|
||||||
|
static_assert((PART_SIZE < CHUNK_SIZE) && (CHUNK_SIZE % PART_SIZE == 0));
|
||||||
|
#endif
|
||||||
Measurement_T buffer[PART_SIZE];
|
Measurement_T buffer[PART_SIZE];
|
||||||
for(uint32_t i = 0; i < CHUNK_SIZE; i += PART_SIZE)
|
for(uint32_t i = 0; i < CHUNK_SIZE; i += PART_SIZE)
|
||||||
{
|
{
|
|
@ -1,7 +1,8 @@
|
||||||
#include "StreamingMsgPackEncoder.h"
|
#include "StreamingMsgPackEncoder.h"
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
|
||||||
template<typename Measurement_T, uint_t SIZE>
|
template<typename Measurement_T, uint32_t SIZE>
|
||||||
class SessionChunk
|
class SessionChunk
|
||||||
{
|
{
|
||||||
public:
|
public:
|
|
@ -20,7 +20,7 @@ private:
|
||||||
class SpiffsStorageReader
|
class SpiffsStorageReader
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
SpiffsBackendReader(const String &fileName) :
|
SpiffsStorageReader(const String &fileName) :
|
||||||
f_(SPIFFS.open(fileName, "w"))
|
f_(SPIFFS.open(fileName, "w"))
|
||||||
{}
|
{}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
template<typename T> struct TypeToMsgPackCode{};
|
template<typename T> struct TypeToMsgPackCode{};
|
||||||
template<> struct TypeToMsgPackCode<uint8_t> { static const char CODE; };
|
template<> struct TypeToMsgPackCode<uint8_t> { static const char CODE; };
|
|
@ -0,0 +1,25 @@
|
||||||
|
;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 = d1
|
||||||
|
framework = arduino
|
||||||
|
monitor_port = /dev/ttyUSB0
|
||||||
|
upload_port = /dev/ttyUSB0
|
||||||
|
monitor_speed = 115200
|
||||||
|
lib_deps =
|
||||||
|
ESP Async WebServer
|
||||||
|
AsyncTCP
|
||||||
|
NTPClient
|
||||||
|
|
||||||
|
;[env:native]
|
||||||
|
;platform = native
|
||||||
|
;test_ignore = test_embedded
|
|
@ -0,0 +1,10 @@
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
// HX711 load cell
|
||||||
|
const int CONFIG_SCALE_DOUT_PIN = D2;
|
||||||
|
const int CONFIG_SCALE_SCK_PIN = D3;
|
||||||
|
const uint8_t CONFIG_TARE_AVG_COUNT = 50; // number of measurements in tare-phase (to find 0 )
|
||||||
|
const int CONFIG_VALUE_DIVIDER = 128; // uint32 measurements are divided by this factor, before stored in uint16_t
|
||||||
|
|
||||||
|
|
||||||
|
const uint32_t CONFIG_SESSION_CHUNK_SIZE = 1024*8 - 3 * sizeof(uint32_t);
|
|
@ -0,0 +1,5 @@
|
||||||
|
|
||||||
|
|
||||||
|
const char *CONFIG_WIFI_SSID = "WLAN";
|
||||||
|
const char *CONFIG_WIFI_PASSWORD = "Bau3rWLAN";
|
||||||
|
const char* CONFIG_HOSTNAME = "smartcords";
|
|
@ -1,14 +1,5 @@
|
||||||
|
|
||||||
// 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
|
// Measurement parameters
|
||||||
const int DELAY = 100; // interval in ms between measurements
|
const int DELAY = 100; // interval in ms between measurements
|
|
@ -0,0 +1,123 @@
|
||||||
|
// Arduino & ESP headers
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <ESP8266WiFi.h>
|
||||||
|
#include <ESPAsyncTCP.h>
|
||||||
|
#include <ESPAsyncWebServer.h>
|
||||||
|
#include <WiFiUdp.h> // for NTP
|
||||||
|
#include <NTPClient.h> // for NTP
|
||||||
|
|
||||||
|
// Own libs
|
||||||
|
#include "Dtypes.h"
|
||||||
|
#include "MockScale.h"
|
||||||
|
#include "MeasurementSession.h"
|
||||||
|
#include "SpiffsStorage.h"
|
||||||
|
|
||||||
|
// Configuration
|
||||||
|
#include "ConfigWifi.h"
|
||||||
|
#include "ConfigHardware.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:
|
||||||
|
void begin();
|
||||||
|
|
||||||
|
void startMeasurements();
|
||||||
|
void stopMeasurements();
|
||||||
|
void iteration();
|
||||||
|
private:
|
||||||
|
MockScale scale;
|
||||||
|
Session_T session;
|
||||||
|
};
|
||||||
|
|
||||||
|
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, [sessionManager](AsyncWebServerRequest * req) {
|
||||||
|
req->send(200, "text/plain", F("OK"));
|
||||||
|
sessionManager->startMeasurements();
|
||||||
|
});
|
||||||
|
server.on("/api/session/stop", HTTP_POST, [sessionManager](AsyncWebServerRequest * req) {
|
||||||
|
req->send(200, "text/plain", F("OK"));
|
||||||
|
sessionManager->stopMeasurements();
|
||||||
|
});
|
||||||
|
server.on("/api/session/data", HTTP_GET, [sessionManager](AsyncWebServerRequest * req) {
|
||||||
|
//TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
|
||||||
|
request->send(200, "text/plain", "Hello, world");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Send a GET request to <IP>/get?message=<message>
|
||||||
|
server.on("/get", HTTP_GET, [] (AsyncWebServerRequest *request) {
|
||||||
|
String message;
|
||||||
|
if (request->hasParam(PARAM_MESSAGE)) {
|
||||||
|
message = request->getParam(PARAM_MESSAGE)->value();
|
||||||
|
} else {
|
||||||
|
message = "No message sent";
|
||||||
|
}
|
||||||
|
request->send(200, "text/plain", "Hello, GET: " + message);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Send a POST request to <IP>/post with a form field message set to <message>
|
||||||
|
server.on("/post", HTTP_POST, [](AsyncWebServerRequest *request){
|
||||||
|
String message;
|
||||||
|
if (request->hasParam(PARAM_MESSAGE, true)) {
|
||||||
|
message = request->getParam(PARAM_MESSAGE, true)->value();
|
||||||
|
} else {
|
||||||
|
message = "No message sent";
|
||||||
|
}
|
||||||
|
request->send(200, "text/plain", "Hello, POST: " + message);
|
||||||
|
});
|
||||||
|
|
||||||
|
server.onNotFound(onNotFound);
|
||||||
|
|
||||||
|
server.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup()
|
||||||
|
{
|
||||||
|
// Serial
|
||||||
|
Serial.begin(115200);
|
||||||
|
while(!Serial) {}
|
||||||
|
|
||||||
|
// WiFi
|
||||||
|
WiFi.mode(WIFI_STA);
|
||||||
|
WiFi.begin(CONFIG_WIFI_SSID, CONFIG_WIFI_PASSWORD);
|
||||||
|
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.init( timeClient.getEpochTime() );
|
||||||
|
|
||||||
|
// Scale
|
||||||
|
scale.begin(CONFIG_SCALE_DOUT_PIN, CONFIG_SCALE_SCK_PIN);
|
||||||
|
scale.tare( CONFIG_TARE_AVG_COUNT );
|
||||||
|
|
||||||
|
// HTTP & Websocket server
|
||||||
|
httpSetup();
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
}
|
|
@ -1,8 +1,8 @@
|
||||||
#include "MockDtypes.h"
|
#include "MockDtypes.h"
|
||||||
#include "MockSerial.h"
|
#include "MockSerial.h"
|
||||||
#include "session/Session.h"
|
#include "MeasurementSession.h"
|
||||||
#include "session/MockStorage.h"
|
#include "MockStorage.h"
|
||||||
|
#include <unity.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
template<typename Measurement_T>
|
template<typename Measurement_T>
|
||||||
|
@ -14,14 +14,15 @@ std::vector<Measurement_T> parseMessagePack(const uint8_t * data, uint32_t &star
|
||||||
const int expectedMapSize = 3;
|
const int expectedMapSize = 3;
|
||||||
auto mapHeader = reinterpret_cast<const uint8_t*>(&data[offset]);
|
auto mapHeader = reinterpret_cast<const uint8_t*>(&data[offset]);
|
||||||
offset += 1;
|
offset += 1;
|
||||||
assert( *mapHeader == (0b10000000 | expectedMapSize), "Map Header wrong");
|
TEST_ASSERT_MESSAGE( *mapHeader == (0b10000000 | expectedMapSize), "Map Header wrong");
|
||||||
|
|
||||||
// string255: sessionStartTime
|
// string255: sessionStartTime
|
||||||
auto stringHeader = reinterpret_cast<const char*>(&data[offset++]);
|
auto stringHeader = reinterpret_cast<const char*>(&data[offset++]);
|
||||||
auto stringSize = reinterpret_cast<const uint8_t*>(&data[offset++]);
|
auto stringSize = reinterpret_cast<const uint8_t*>(&data[offset++]);
|
||||||
assert(*stringHeader == '\xd9', "String header wrong");
|
|
||||||
|
TEST_ASSERT_EQUAL(*stringHeader, '\xd9');
|
||||||
std::string sessionStartTimeStr = std::string((const char*)(&data[offset]), (size_t)(*stringSize));
|
std::string sessionStartTimeStr = std::string((const char*)(&data[offset]), (size_t)(*stringSize));
|
||||||
assert( sessionStartTimeStr == "sessionStartTime", "sessionStartTime string is wrong");
|
TEST_ASSERT( sessionStartTimeStr == "sessionStartTime");
|
||||||
offset += *stringSize;
|
offset += *stringSize;
|
||||||
|
|
||||||
//uint32
|
//uint32
|
||||||
|
@ -32,9 +33,9 @@ std::vector<Measurement_T> parseMessagePack(const uint8_t * data, uint32_t &star
|
||||||
// string255: startIndex
|
// string255: startIndex
|
||||||
stringHeader = reinterpret_cast<const char*>(&data[offset++]);
|
stringHeader = reinterpret_cast<const char*>(&data[offset++]);
|
||||||
stringSize = reinterpret_cast<const uint8_t*>(&data[offset++]);
|
stringSize = reinterpret_cast<const uint8_t*>(&data[offset++]);
|
||||||
assert(*stringHeader == '\xd9', "String header wrong");
|
TEST_ASSERT_MESSAGE(*stringHeader == '\xd9', "String header wrong");
|
||||||
std::string startIndexStr = std::string((const char*)(&data[offset]), (size_t)(*stringSize));
|
std::string startIndexStr = std::string((const char*)(&data[offset]), (size_t)(*stringSize));
|
||||||
assert( startIndexStr == "startIndex", "startIndex string is wrong");
|
TEST_ASSERT_MESSAGE( startIndexStr == "startIndex", "startIndex string is wrong");
|
||||||
offset += *stringSize;
|
offset += *stringSize;
|
||||||
|
|
||||||
//uint32
|
//uint32
|
||||||
|
@ -45,14 +46,14 @@ std::vector<Measurement_T> parseMessagePack(const uint8_t * data, uint32_t &star
|
||||||
// string255: values
|
// string255: values
|
||||||
stringHeader = reinterpret_cast<const char*>(&data[offset++]);
|
stringHeader = reinterpret_cast<const char*>(&data[offset++]);
|
||||||
stringSize = reinterpret_cast<const uint8_t*>(&data[offset++]);
|
stringSize = reinterpret_cast<const uint8_t*>(&data[offset++]);
|
||||||
assert(*stringHeader == '\xd9', "String header wrong");
|
TEST_ASSERT_MESSAGE(*stringHeader == '\xd9', "String header wrong");
|
||||||
std::string valueStr = std::string((const char*)(&data[offset]), (size_t)(*stringSize));
|
std::string valueStr = std::string((const char*)(&data[offset]), (size_t)(*stringSize));
|
||||||
assert( valueStr == "values", "values string is wrong");
|
TEST_ASSERT_MESSAGE( valueStr == "values", "values string is wrong");
|
||||||
offset += *stringSize;
|
offset += *stringSize;
|
||||||
|
|
||||||
// vector
|
// vector
|
||||||
auto vectorHeader = reinterpret_cast<const char*>(&data[offset++]);
|
auto vectorHeader = reinterpret_cast<const char*>(&data[offset++]);
|
||||||
assert( *vectorHeader == '\xc9', "Vector header wrong");
|
TEST_ASSERT_MESSAGE( *vectorHeader == '\xc9', "Vector header wrong");
|
||||||
size_t vectorLength = ntohl(*reinterpret_cast<const uint32_t*>(&data[offset])) / sizeof(Measurement_T);
|
size_t vectorLength = ntohl(*reinterpret_cast<const uint32_t*>(&data[offset])) / sizeof(Measurement_T);
|
||||||
offset += 4;
|
offset += 4;
|
||||||
offset += 1; // jump over type
|
offset += 1; // jump over type
|
||||||
|
@ -68,12 +69,12 @@ void testSessionChunkAdd()
|
||||||
SessionChunk<uint16_t, size> chunk;
|
SessionChunk<uint16_t, size> chunk;
|
||||||
for( uint16_t i=0; i < size; ++i) {
|
for( uint16_t i=0; i < size; ++i) {
|
||||||
bool res = chunk.addPoint(i);
|
bool res = chunk.addPoint(i);
|
||||||
assert(res, "Adding point failed");
|
TEST_ASSERT_MESSAGE(res, "Adding point failed");
|
||||||
assert( chunk.numMeasurements() == i+1, "Number of measurements reported wrong");
|
TEST_ASSERT_MESSAGE( chunk.numMeasurements() == i+1, "Number of measurements reported wrong");
|
||||||
}
|
}
|
||||||
bool res = chunk.addPoint(0);
|
bool res = chunk.addPoint(0);
|
||||||
assert(!res, "Full chunk was not detected");
|
TEST_ASSERT_MESSAGE(!res, "Full chunk was not detected");
|
||||||
assert(chunk.numMeasurements() == size, "Point appears to be added");
|
TEST_ASSERT_MESSAGE(chunk.numMeasurements() == size, "Point appears to be added");
|
||||||
}
|
}
|
||||||
|
|
||||||
void testSessionChunkGetterSetter()
|
void testSessionChunkGetterSetter()
|
||||||
|
@ -83,8 +84,8 @@ void testSessionChunkGetterSetter()
|
||||||
const uint32_t time = 244213;
|
const uint32_t time = 244213;
|
||||||
const uint32_t startIdx = 131;
|
const uint32_t startIdx = 131;
|
||||||
chunk.init(time, startIdx);
|
chunk.init(time, startIdx);
|
||||||
assert( chunk.getStartIndex() == startIdx, "Start Index wrong");
|
TEST_ASSERT_MESSAGE( chunk.getStartIndex() == startIdx, "Start Index wrong");
|
||||||
assert( chunk.getStartTime() == time, "Start time wrong");
|
TEST_ASSERT_MESSAGE( chunk.getStartTime() == time, "Start time wrong");
|
||||||
}
|
}
|
||||||
|
|
||||||
void testSessionChunkSerialization()
|
void testSessionChunkSerialization()
|
||||||
|
@ -98,8 +99,8 @@ void testSessionChunkSerialization()
|
||||||
chunk.init(startTime, startIndex);
|
chunk.init(startTime, startIndex);
|
||||||
for( uint16_t i=0; i < fillSize; ++i) {
|
for( uint16_t i=0; i < fillSize; ++i) {
|
||||||
bool res = chunk.addPoint(i);
|
bool res = chunk.addPoint(i);
|
||||||
assert(res, "Adding point failed");
|
TEST_ASSERT_MESSAGE(res, "Adding point failed");
|
||||||
assert( chunk.numMeasurements() == i+1, "Number of measurements reported wrong");
|
TEST_ASSERT_MESSAGE( chunk.numMeasurements() == i+1, "Number of measurements reported wrong");
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<uint8_t> data;
|
std::vector<uint8_t> data;
|
||||||
|
@ -109,16 +110,16 @@ void testSessionChunkSerialization()
|
||||||
uint32_t readStartTime=0;
|
uint32_t readStartTime=0;
|
||||||
uint32_t readStartIndex=0;
|
uint32_t readStartIndex=0;
|
||||||
auto result = parseMessagePack<uint16_t>(&data[0], readStartTime, readStartIndex);
|
auto result = parseMessagePack<uint16_t>(&data[0], readStartTime, readStartIndex);
|
||||||
assert(startIndex == readStartIndex && startTime == readStartTime, "");
|
TEST_ASSERT_MESSAGE(startIndex == readStartIndex && startTime == readStartTime, "");
|
||||||
assert(result.size() == fillSize, "Wrong result array size");
|
TEST_ASSERT_MESSAGE(result.size() == fillSize, "Wrong result array size");
|
||||||
for( uint16_t i=0; i < fillSize; ++i) {
|
for( uint16_t i=0; i < fillSize; ++i) {
|
||||||
assert(result[i] == i, "Wrong array contents");
|
TEST_ASSERT_MESSAGE(result[i] == i, "Wrong array contents");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void testSession() {
|
void testSession() {
|
||||||
const uint32_t SESSION_SIZE = 128;
|
const uint32_t SESSION_SIZE = 128;
|
||||||
typedef Session<uint16_t, MockStorageReader, MockStorageWriter, SESSION_SIZE> MockSession;
|
typedef MeasurementSession<uint16_t, MockStorageReader, MockStorageWriter, SESSION_SIZE> MockSession;
|
||||||
|
|
||||||
const uint32_t startTime = 194842;
|
const uint32_t startTime = 194842;
|
||||||
const uint_t fillSize = SESSION_SIZE * 4 + 7;
|
const uint_t fillSize = SESSION_SIZE * 4 + 7;
|
||||||
|
@ -136,19 +137,40 @@ void testSession() {
|
||||||
uint32_t readStartTime=0;
|
uint32_t readStartTime=0;
|
||||||
uint32_t readStartIndex=0;
|
uint32_t readStartIndex=0;
|
||||||
auto result = parseMessagePack<uint16_t>(&data[0], readStartTime, readStartIndex);
|
auto result = parseMessagePack<uint16_t>(&data[0], readStartTime, readStartIndex);
|
||||||
assert(readStartIndex == 0 && startTime == readStartTime, "");
|
TEST_ASSERT_MESSAGE(readStartIndex == 0 && startTime == readStartTime, "");
|
||||||
assert(result.size() == fillSize, "Wrong result array size");
|
TEST_ASSERT_MESSAGE(result.size() == fillSize, "Wrong result array size");
|
||||||
for( uint16_t i=0; i < fillSize; ++i) {
|
for( uint16_t i=0; i < fillSize; ++i) {
|
||||||
assert(result[i] == i, "Wrong array contents");
|
TEST_ASSERT_MESSAGE(result[i] == i, "Wrong array contents");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void allTests()
|
||||||
|
{
|
||||||
|
UNITY_BEGIN();
|
||||||
|
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)
|
int main(int argc, char**argv)
|
||||||
{
|
{
|
||||||
testSessionChunkAdd();
|
allTests();
|
||||||
testSessionChunkGetterSetter();
|
|
||||||
testSessionChunkSerialization();
|
|
||||||
testSession();
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,20 @@
|
||||||
|
-> setup platform.io based development with tests
|
||||||
|
- re-write tests with unity
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- backend
|
||||||
|
/api/session/start
|
||||||
|
/api/session/finish
|
||||||
|
/api/session/data?startIndex=25 (returns nothing if no session is active)
|
||||||
|
// by default only the in-memory data is reported back
|
||||||
|
|
||||||
|
/api/sessionhistory/ -> list in webdav fashion
|
||||||
|
/api/sessionhistory/sessionid?startIndex -> returns full session data
|
||||||
|
// delete file with method
|
||||||
|
|
||||||
|
|
||||||
|
// auto start session? ringbuffer with last n measurements
|
||||||
|
// detect spikes -> more than s spikes -> start session
|
||||||
|
// auto stop session if one full chunk is empty
|
||||||
|
|
Loading…
Reference in New Issue