Browse Source

added data logger

master
Youen Toupin 3 years ago
parent
commit
01103d0b46
  1. 4
      ESP32/.settings/language.settings.xml
  2. 84
      ESP32/src/DataLogger.cpp
  3. 45
      ESP32/src/DataLogger.h
  4. 19
      ESP32/src/utils.cpp
  5. 4
      ESP32/src/utils.h
  6. 115
      ESP32/src/vehicle-monitor.cpp

4
ESP32/.settings/language.settings.xml

@ -5,7 +5,7 @@
<provider copy-of="extension" id="org.eclipse.cdt.ui.UserLanguageSettingsProvider"/>
<provider-reference id="org.eclipse.cdt.core.ReferencedProjectsLanguageSettingsProvider" ref="shared-provider"/>
<provider-reference id="org.eclipse.cdt.managedbuilder.core.MBSLanguageSettingsProvider" ref="shared-provider"/>
<provider class="org.eclipse.cdt.internal.build.crossgcc.CrossGCCBuiltinSpecsDetector" console="false" env-hash="-387635951242037682" id="org.eclipse.cdt.build.crossgcc.CrossGCCBuiltinSpecsDetector" keep-relative-paths="false" name="CDT Cross GCC Built-in Compiler Settings" parameter="${HOME}/.platformio/packages/toolchain-xtensa32/bin/xtensa-esp32-elf-g++ ${FLAGS} -std=gnu++11 -E -P -v -dD &quot;${INPUTS}&quot;" prefer-non-shared="true">
<provider class="org.eclipse.cdt.internal.build.crossgcc.CrossGCCBuiltinSpecsDetector" console="false" env-hash="1001601878207211985" id="org.eclipse.cdt.build.crossgcc.CrossGCCBuiltinSpecsDetector" keep-relative-paths="false" name="CDT Cross GCC Built-in Compiler Settings" parameter="${HOME}/.platformio/packages/toolchain-xtensa32/bin/xtensa-esp32-elf-g++ ${FLAGS} -std=gnu++11 -E -P -v -dD &quot;${INPUTS}&quot;" prefer-non-shared="true">
<language-scope id="org.eclipse.cdt.core.gcc"/>
<language-scope id="org.eclipse.cdt.core.g++"/>
</provider>
@ -16,7 +16,7 @@
<provider copy-of="extension" id="org.eclipse.cdt.ui.UserLanguageSettingsProvider"/>
<provider-reference id="org.eclipse.cdt.core.ReferencedProjectsLanguageSettingsProvider" ref="shared-provider"/>
<provider-reference id="org.eclipse.cdt.managedbuilder.core.MBSLanguageSettingsProvider" ref="shared-provider"/>
<provider class="org.eclipse.cdt.internal.build.crossgcc.CrossGCCBuiltinSpecsDetector" console="false" env-hash="-387635951242037682" id="org.eclipse.cdt.build.crossgcc.CrossGCCBuiltinSpecsDetector" keep-relative-paths="false" name="CDT Cross GCC Built-in Compiler Settings" parameter="${HOME}/.platformio/packages/toolchain-xtensa32/bin/xtensa-esp32-elf-g++ ${FLAGS} -std=gnu++11 -E -P -v -dD &quot;${INPUTS}&quot;" prefer-non-shared="true">
<provider class="org.eclipse.cdt.internal.build.crossgcc.CrossGCCBuiltinSpecsDetector" console="false" env-hash="1001601878207211985" id="org.eclipse.cdt.build.crossgcc.CrossGCCBuiltinSpecsDetector" keep-relative-paths="false" name="CDT Cross GCC Built-in Compiler Settings" parameter="${HOME}/.platformio/packages/toolchain-xtensa32/bin/xtensa-esp32-elf-g++ ${FLAGS} -std=gnu++11 -E -P -v -dD &quot;${INPUTS}&quot;" prefer-non-shared="true">
<language-scope id="org.eclipse.cdt.core.gcc"/>
<language-scope id="org.eclipse.cdt.core.g++"/>
</provider>

84
ESP32/src/DataLogger.cpp

@ -0,0 +1,84 @@
#include "DataLogger.h"
#include "utils.h"
#include <Preferences.h>
DataLogger DataLogger::mainLogger;
DataLogger::DataLogger()
{
}
DataLogger::~DataLogger()
{
}
void DataLogger::open()
{
if(isOpen()) return;
lastLogTime = -std::numeric_limits<float>::max();
currentTime = 0.0f;
Preferences preferences;
preferences.begin("vm-log", false);
uint16_t logFileIndex = preferences.getUShort("next", 1);
preferences.putUShort("next", logFileIndex + 1);
preferences.end();
// clear existing files until we have enough free space
const size_t requiredSpace = 300000; // in bytes
size_t totalSpace = SPIFFS.totalBytes();
auto logFolder = SPIFFS.open("/log");
auto file = logFolder.openNextFile();
while(file && SPIFFS.usedBytes() + requiredSpace > totalSpace)
{
Serial.print("Deleting old log file: ");
Serial.println(file.name());
SPIFFS.remove(file.name());
file = logFolder.openNextFile();
}
char fileName[32];
sprintf(fileName, "/log/L%05d.csv", logFileIndex);
file = SPIFFS.open(fileName, FILE_WRITE);
if(!file) Serial.println("DataLogger: failed to open file");
if(!file.print("time,speed,battery voltage,battery output current\n")) Serial.println("DataLogger: failed to write to file");
}
void DataLogger::close()
{
if(!isOpen()) return;
file.close();
}
void DataLogger::log(unsigned long timeMilliseconds, const Entry& entry)
{
if(lastLogTime == -std::numeric_limits<float>::max()) lastTimeMilliseconds = timeMilliseconds;
currentTime += (float)utils::elapsed(lastTimeMilliseconds, timeMilliseconds) / 1000.0f;
lastTimeMilliseconds = timeMilliseconds;
if((lastEntry.isDifferent(entry) || currentTime >= lastLogTime + 20.0f) && currentTime >= lastLogTime + 0.2f)
{
char line[128];
sprintf(line, "%.3f,%.3f,%.3f,%.3f\n", currentTime, entry.speed, entry.batteryVoltage, entry.batteryOutputCurrent);
file.print(line);
lastEntry = entry;
lastLogTime = currentTime;
}
}
bool DataLogger::isOpen()
{
return file;
}
const char* DataLogger::currentLogFileName()
{
return isOpen() ? file.name() : "";
}

45
ESP32/src/DataLogger.h

@ -0,0 +1,45 @@
#include <cmath>
#include <SPIFFS.h>
class DataLogger
{
public:
struct Entry
{
float batteryVoltage = 0.0f; // V
float batteryOutputCurrent = 0.0f; // A
float speed = 0.0f; // m/s
bool isDifferent(const Entry& other)
{
const float scale = speed > 0.0f ? 1.0f : 5.0f;
return
std::abs(batteryVoltage - other.batteryVoltage) > 0.1f * scale
&& std::abs(batteryOutputCurrent - other.batteryOutputCurrent) > 0.1f * scale
&& std::abs(speed - other.speed) > 0.1f;
}
};
public:
DataLogger();
~DataLogger();
static DataLogger& get() { return mainLogger; }
void open();
void close();
void log(unsigned long timeMilliseconds, const Entry& entry);
bool isOpen();
const char* currentLogFileName();
private:
static DataLogger mainLogger;
Entry lastEntry;
unsigned long lastTimeMilliseconds = -1;
float currentTime = 0.0f;
float lastLogTime = 0.0f;
File file; // @suppress("Abstract class cannot be instantiated")
};

19
ESP32/src/utils.cpp

@ -0,0 +1,19 @@
#include "utils.h"
namespace utils
{
unsigned long elapsed(unsigned long from, unsigned long to)
{
if(to >= from)
{
return to - from;
}
else
{
// if the counter overflowed, this computes the real duration
// of course it won't work if the counter made a "full turn" or more
const unsigned long biggestValue = (unsigned long)-1;
return (biggestValue - from) + to + 1;
}
}
}

4
ESP32/src/utils.h

@ -0,0 +1,4 @@
namespace utils
{
unsigned long elapsed(unsigned long from, unsigned long to);
}

115
ESP32/src/vehicle-monitor.cpp

@ -9,9 +9,13 @@
#include "ADC.h"
#include "OTA.h"
#include "DataLogger.h"
#include "utils.h"
#include "wifi-credentials.h"
#define DUMMY_DATA 0
AsyncWebServer server(80);
ADC currentSensor(36);
@ -24,13 +28,15 @@ const int numImpulsesPerTurn = 2;
const float wheelCircumferenceMeters = wheelDiameterInches * 0.0254f * 3.1415f / (float)numImpulsesPerTurn;
uint16_t batteryVoltage = 0; // in mV
uint16_t batteryCurrent = 0; // in mV
uint16_t batteryOutputCurrent = 0; // in mV
WiFiMulti wifiMulti;
wl_status_t wifi_STA_status = WL_NO_SHIELD;
unsigned long wifiConnexionBegin = 0;
const unsigned long retryWifiConnexionDelay = 60000; // in milliseconds
unsigned long stoppedSince = -1;
volatile bool debugLedState = true;
volatile bool speedSensorState = false;
@ -51,13 +57,13 @@ void IRAM_ATTR onSpeedSensorChange(bool newState)
}
else
{
unsigned long impulseDuration = now > speedSensorRiseTime ? now - speedSensorRiseTime : (4294967295 - speedSensorRiseTime) + now; // if now is lower than speedSensorRiseTime, it means millis() has overflowed (happens every 50 days)
unsigned long impulseDuration = utils::elapsed(speedSensorRiseTime, now);
if(impulseDuration > 1000) return; // impulse was too long, ignore it (maybe magnet stopped near the sensor)
debugLedState = !debugLedState;
digitalWrite(debugLedPin, debugLedState ? HIGH : LOW);
unsigned long timeSinceLastImpulse = now > speedSensorLastImpulseTime ? now - speedSensorLastImpulseTime : (4294967295 - speedSensorLastImpulseTime) + now;
unsigned long timeSinceLastImpulse = utils::elapsed(speedSensorLastImpulseTime, now);
speedSensorLastImpulseTime = now;
if(timeSinceLastImpulse > 30 && timeSinceLastImpulse < 4000)
{
@ -74,11 +80,21 @@ void IRAM_ATTR onSpeedSensorChange() { onSpeedSensorChange(digitalRead(speedSens
float getSpeed()
{
#if DUMMY_DATA
{
float result = max(0.0f, sinf((float)millis()/30000.0f)) * 7.0f;
return result < 0.25f ? 0.0f : result;
}
#endif
unsigned long now = millis();
noInterrupts();
unsigned long lastImpulseInterval = speedSensorLastImpulseInterval;
unsigned long lastImpulseTime = speedSensorLastImpulseTime;
unsigned long timeSinceLastImpulse = now > lastImpulseTime ? now - lastImpulseTime : (4294967295 - lastImpulseTime) + now;
interrupts();
unsigned long timeSinceLastImpulse = utils::elapsed(lastImpulseTime, now);
unsigned long interval = timeSinceLastImpulse > lastImpulseInterval * 10 / 9 ? timeSinceLastImpulse : lastImpulseInterval;
@ -144,18 +160,52 @@ void setup()
server.on("/api/status", HTTP_GET, [](AsyncWebServerRequest *request){
int v = batteryVoltage;
int c = batteryCurrent;
int c = batteryOutputCurrent;
int s = (int)(getSpeed() * 1000.0f + 0.5f);
const char* logFileName = DataLogger::get().currentLogFileName();
if(String(logFileName).startsWith("/log/")) logFileName += 5;
int totalSize = (int)(SPIFFS.totalBytes() / 1000);
int usedSize = (int)(SPIFFS.usedBytes() / 1000);
char json[128];
sprintf(json, "{\"v\":%d,\"c\":%d,\"s\":%d}", v, c, s);
sprintf(json, "{\"v\":%d,\"c\":%d,\"s\":%d,\"log\":\"%s\",\"tot\":%d,\"used\":%d}", v, c, s, logFileName, totalSize, usedSize);
request->send(200, "text/json", json);
});
server.on("/api/log/list", HTTP_GET, [](AsyncWebServerRequest *request){
String json;
json = "{\"files\":[";
auto logFolder = SPIFFS.open("/log");
auto file = logFolder.openNextFile();
bool first = true;
while(file)
{
if(!first) json += ",";
json += "{\"n\":\"/api";
json += file.name();
json += "\",\"s\":";
json += file.size();
json += "}";
first = false;
file = logFolder.openNextFile();
}
json += "]}";
request->send(200, "text/json", json.c_str());
});
// Special case to send index.html without caching
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ request->send(SPIFFS, "/www/index.html", "text/html"); });
server.serveStatic("/index.html", SPIFFS, "/www/index.html");
// Log files (not cached)
server.serveStatic("/api/log", SPIFFS, "/log/");
// Other static files are cached (index.html knows whether to ignore caching or not for each file)
server.serveStatic("/", SPIFFS, "/www/").setCacheControl("max-age=5184000");
@ -163,10 +213,8 @@ void setup()
Serial.println("HTTP server started");
}
void loop()
void handle_wifi_connection()
{
OTA.handle();
wl_status_t newWifiStatus = WiFi.status();
if(newWifiStatus != wifi_STA_status)
{
@ -201,11 +249,14 @@ void loop()
if(wifi_STA_status != WL_CONNECTED)
{
unsigned long now = millis();
unsigned long elapsed = now > wifiConnexionBegin ? now - wifiConnexionBegin : (4294967295 - wifiConnexionBegin) + now;
unsigned long elapsed = utils::elapsed(wifiConnexionBegin, now);
if(elapsed > retryWifiConnexionDelay)
connectWifi();
}
}
void handle_ADC_measures()
{
const int numSamples = 100;
float averageV = 0.0f;
@ -228,9 +279,49 @@ void loop()
averageV *= 27.000f; // account for voltage divider to retrieve the input voltage
averageC = max(0.0f, averageC - 2.5f) / 0.0238f; // convert voltage to current, according to the sensor linear relation
// TODO: mutex ?
batteryVoltage = (uint16_t)(averageV * 1000.0f + 0.5f);
batteryCurrent = (uint16_t)(averageC * 1000.0f + 0.5f);
batteryOutputCurrent = (uint16_t)(averageC * 1000.0f + 0.5f);
#if DUMMY_DATA
batteryVoltage = (uint16_t)((float)random(4000, 4020) / 100.0f * 1000.0f + 0.5f);
batteryOutputCurrent = (uint16_t)(max(0.0f, sinf((float)millis()/30000.0f)) * 25.0f * 1000.0f + 0.5f);
#endif
}
void loop()
{
OTA.handle();
handle_wifi_connection();
handle_ADC_measures();
unsigned long now = millis();
static DataLogger::Entry entry;
entry.batteryVoltage = (float)batteryVoltage / 1000.0f;
entry.batteryOutputCurrent = (float)batteryOutputCurrent / 1000.0f;
entry.speed = getSpeed();
if(entry.speed > 0.0f)
{
stoppedSince = -1;
if(!DataLogger::get().isOpen())
{
Serial.println("Starting DataLogger");
DataLogger::get().open();
}
}
else
{
if(stoppedSince == -1)
{
stoppedSince = now;
}
else if(utils::elapsed(stoppedSince, now) > 5 * 60 * 1000)
{
Serial.println("Stopping DataLogger");
DataLogger::get().close();
}
}
DataLogger::get().log(now, entry);
delay(10);
}

Loading…
Cancel
Save