Browse Source

Possibility for the client (for example a smartphone) to send current date and time

Added possibility to send GPS coordinates as well, but not enabled yet
because browsers require an HTTPS connection to enable the geolocation
API
master
Youen 7 months ago
parent
commit
359f3b4cd4
  1. 18
      ESP32/src/DataLogger.cpp
  2. 12
      ESP32/src/DataLogger.h
  3. 77
      ESP32/src/vehicle-monitor.cpp
  4. 106
      WebApp/src/monitor-api.ts
  5. 1
      WebApp/src/pages/dashboard/dashboard-page.tsx
  6. 7
      WebApp/src/pages/raw-data/raw-data-page.tsx

18
ESP32/src/DataLogger.cpp

@ -16,7 +16,7 @@ DataLogger::~DataLogger()
{
}
void DataLogger::open()
void DataLogger::open(const char* currentDateTime)
{
if(isOpen()) return;
@ -51,7 +51,10 @@ void DataLogger::open()
file = SPIFFS.open(fileName, FILE_WRITE);
if(!isOpen()) Serial.println("DataLogger: failed to open file");
if(!file.print("time,speed,battery voltage,battery output current,temperature,altitude\n")) Serial.println("DataLogger: failed to write to file");
char metadata[64];
sprintf(metadata, "start time: %s\n", currentDateTime == nullptr || currentDateTime[0] == 0 ? "NA" : currentDateTime);
if(!file.print(metadata)) Serial.println("DataLogger: failed to write to file");
if(!file.print("time,speed,battery voltage,battery output current,temperature,altitude,latitude,longitude\n")) Serial.println("DataLogger: failed to write to file");
}
void DataLogger::close()
@ -69,7 +72,16 @@ void DataLogger::log(unsigned long timeMilliseconds, const Entry& entry)
if((lastEntry.isDifferent(entry) || currentTime >= lastLogTime + 20.0f) && currentTime >= lastLogTime + 0.2f)
{
char line[128];
sprintf(line, "%.3f,%.3f,%.3f,%.3f,%.1f,%.1f\n", currentTime, entry.speed, entry.batteryVoltage, entry.batteryOutputCurrent, entry.temperature, entry.altitude);
char coords[32];
if(entry.latitude < -900.0f || !gpsCoordsEnabled)
{
sprintf(coords, "NA,NA");
}
else
{
sprintf(coords, "%.5f,%.5f", entry.latitude, entry.longitude);
}
sprintf(line, "%.3f,%.3f,%.3f,%.3f,%.1f,%.1f,%s\n", currentTime, entry.speed, entry.batteryVoltage, entry.batteryOutputCurrent, entry.temperature, entry.altitude, coords);
file.print(line);
if(currentTime >= lastFlushTime + 10.0f)

12
ESP32/src/DataLogger.h

@ -11,6 +11,8 @@ public:
float speed = 0.0f; // m/s
float temperature = 0.0f; // in °C
float altitude = 0.0f; // in m
float latitude = -1000.0f;
float longitude = -1000.0f;
bool isDifferent(const Entry& other)
{
@ -20,7 +22,9 @@ public:
|| std::abs(batteryOutputCurrent - other.batteryOutputCurrent) > 0.1f * scale
|| std::abs(speed - other.speed) > 0.1f
|| std::abs(temperature - other.temperature) > 0.5f * scale
|| std::abs(altitude - other.altitude) > 0.5f * scale;
|| std::abs(altitude - other.altitude) > 0.5f * scale
|| std::abs(latitude - other.latitude) > 0.0001f
|| std::abs(longitude - other.longitude) > 0.0001f;
}
};
@ -30,13 +34,15 @@ public:
static DataLogger& get() { return mainLogger; }
void open();
void open(const char* currentDateTime = nullptr);
void close();
void log(unsigned long timeMilliseconds, const Entry& entry);
bool isOpen();
const char* currentLogFileName();
void enableGPSCoordinates(bool enable) { gpsCoordsEnabled = enable; }
private:
static DataLogger mainLogger;
@ -47,4 +53,6 @@ private:
float lastFlushTime = 0.0f;
File file; // @suppress("Abstract class cannot be instantiated")
bool gpsCoordsEnabled = false;
};

77
ESP32/src/vehicle-monitor.cpp

@ -18,7 +18,8 @@
#include <limits>
#define DUMMY_DATA 1
#define DUMMY_DATA 0
#define ENABLE_GPS_COORDINATES 1
AsyncWebServer server(80);
@ -39,6 +40,10 @@ uint16_t batteryVoltage = 0; // in mV
uint16_t batteryOutputCurrent = 0; // in mV
int16_t temperature = 0; // in tenth of °C
int32_t altitude = 0; // in mm above sea level (can be negative if below sea level, or depending on atmospheric conditions)
float latitude = -1000.0f; // in decimal degrees
float longitude = -1000.0f; // in decimal degrees
float gpsAltitude = -1000.0f; // in meters, above sea level
char realtime[32] = {0}; // UTC date and time, in format YYYY-MM-DDTHH:mm:ss.sssZ
// current trip
uint32_t tripDistance = 0; // in meters
@ -191,6 +196,10 @@ void setup()
OTA.begin();
#if ENABLE_GPS_COORDINATES
DataLogger::get().enableGPSCoordinates(true);
#endif
Wire.begin(I2C_SDA, I2C_SCL);
pressureSensor.begin(Wire);
@ -230,10 +239,54 @@ void setup()
int usedSize = (int)(SPIFFS.usedBytes() / 1000);
char json[256];
sprintf(json, "{\"v\":%d,\"c\":%d,\"s\":%d,\"td\":%d,\"ttt\":%d,\"tmt\":%d,\"tae\":%d,\"tme\":%d,\"temp\":%d,\"alt\":%d,\"log\":\"%s\",\"tot\":%d,\"used\":%d}", v, c, s, td, ttt, tmt, tae, tme, temp, alt, logFileName, totalSize, usedSize);
sprintf(json, "{\"v\":%d,\"c\":%d,\"s\":%d,\"td\":%d,\"ttt\":%d,\"tmt\":%d,\"tae\":%d,\"tme\":%d,\"temp\":%d,\"alt\":%d,\"log\":\"%s\",\"tot\":%d,\"used\":%d,\"lat\":%.5f,\"lng\":%.5f,\"d\":\"%s\"}", v, c, s, td, ttt, tmt, tae, tme, temp, alt, logFileName, totalSize, usedSize, latitude, longitude, realtime);
request->send(200, "text/json", json);
});
server.on("/api/info", HTTP_POST, [](AsyncWebServerRequest *request){
//DebugLog.println("/api/info");
/*int params = request->params();
for(int i=0;i<params;i++){
AsyncWebParameter* p = request->getParam(i);
DebugLog.print(p->name().c_str());
DebugLog.print("=");
DebugLog.println(p->value().c_str());
}*/
AsyncWebParameter* latitudeParam = request->getParam("lat", true);
AsyncWebParameter* longitudeParam = request->getParam("lng", true);
AsyncWebParameter* altitudeParam = request->getParam("alt", true);
if(latitudeParam != nullptr && longitudeParam != nullptr)
{
char *ending = nullptr;
latitude = strtof(latitudeParam->value().c_str(), &ending);
if (*ending != 0)
latitude = -1000.0f;
longitude = strtof(longitudeParam->value().c_str(), &ending);
if (*ending != 0)
longitude = -1000.0f;
//DebugLog.print("lat="); DebugLog.print(latitude); DebugLog.print(" lng="); DebugLog.println(longitude);
if(altitudeParam != nullptr)
{
gpsAltitude = strtof(altitudeParam->value().c_str(), &ending);
if (*ending != 0)
gpsAltitude = -1000.0f;
//DebugLog.print("alt="); DebugLog.println(gpsAltitude);
}
}
AsyncWebParameter* timeParam = request->getParam("time", true);
if(timeParam != nullptr)
{
strcpy(realtime, timeParam->value().c_str());
//DebugLog.print("time="); DebugLog.println(realtime);
}
request->send(200);
});
server.on("/api/log/list", HTTP_GET, [](AsyncWebServerRequest *request){
String json;
@ -435,13 +488,16 @@ void loop()
entry.temperature = (float)temperature / 10.0f;
entry.altitude = (float)altitude / 1000.0f;
entry.latitude = latitude;
entry.longitude = longitude;
if(entry.speed > 0.0f)
{
stoppedSince = -1;
if(!DataLogger::get().isOpen())
{
DebugLog.println("Starting DataLogger");
DataLogger::get().open();
DataLogger::get().open(realtime);
tripDistance = 0;
tripMovingTime = 0;
tripTotalTime = 0;
@ -486,8 +542,16 @@ void loop()
uint32_t altitudeMillimeters = (uint32_t)(entry.altitude * 1000.0f + 0.5f);
static uint32_t lastLoopAltitude = altitudeMillimeters;
uint32_t altitudeChange = altitudeMillimeters - lastLoopAltitude;
lastLoopAltitude = altitudeMillimeters;
uint32_t clampedPositiveAltitudeChange = 0;
if(altitudeMillimeters > lastLoopAltitude + 300)
{
clampedPositiveAltitudeChange = altitudeMillimeters - lastLoopAltitude;
lastLoopAltitude = altitudeMillimeters;
}
else if(lastLoopAltitude > 300 && altitudeMillimeters < lastLoopAltitude - 300)
{
lastLoopAltitude = altitudeMillimeters;
}
if(isOnTrip)
{
@ -495,8 +559,7 @@ void loop()
if(isMoving) tripMovingTime += newSeconds;
tripDistance += newMeters;
if(altitudeChange > 0)
tripAscendingElevation += altitudeChange;
tripAscendingElevation += clampedPositiveAltitudeChange;
static float remainingEnergy = 0.0f;
float newEnergy = entry.batteryVoltage * entry.batteryOutputCurrent * ((float)dt / 1000.0f) + remainingEnergy;

106
WebApp/src/monitor-api.ts

@ -10,7 +10,10 @@ export interface Status {
tripAscendingElevation: number; // in meters
tripMotorEnergy: number; // in Watt-hour
temperature: number; // in Celcius degrees
latitude?: number; // latitude (decimal degrees)
longitude?: number; // longitude (decimal degrees)
altitude: number; // in meters above sea level
dateTime?: Date; // current date and time (UTC)
};
interface ApiStatus {
@ -24,11 +27,29 @@ interface ApiStatus {
tme: number; // trip motor energy (Wh)
temp: number; // temperature (tenth of °C)
alt: number; // altitude (mm)
lat: number; // latitude (in decimal degrees), or -1000 if unknown
lng: number; // longitude (in decimal degrees), or -1000 if unknown
d: string; // current UTC date and time, in format "YYYY-MM-DDTHH:mm:ss.sssZ", or empty string if unknown
}
export interface Info {
latitude: number; // latitude (decimal degrees)
longitude: number; // longitude (decimal degrees)
altitude: number; // altitude (from sea level, in meters)
time: Date; // UTC date and time
}
interface ApiInfo {
lat?: number; // latitude (decimal degrees)
lng?: number; // longitude (decimal degrees)
alt?: number; // altitude (from sea level, in meters)
time?: string; // UTC date and time, in format "YYYY-MM-DDTHH:mm:ss.sssZ"
}
export class MonitorApi {
private mockServer: boolean;
private lastStatus: Status = null;
private lastKnownPosition: GeolocationPosition = null;
private static api: MonitorApi = null;
private lastFetchTime = 0;
@ -52,6 +73,81 @@ export class MonitorApi {
getStatus() { return this.lastStatus; }
async autoUpdateInfo(): Promise<void> {
if (navigator.geolocation) {
let updatePosition = (position: GeolocationPosition) => {
this.lastKnownPosition = position;
};
const options = {
enableHighAccuracy: true,
timeout: 6000,
maximumAge: 3000,
};
//navigator.geolocation.getCurrentPosition(updatePosition, () => updatePosition(null), options);
}
const defaultPosition : GeolocationPosition = {
timestamp: 0,
coords: {
latitude: -1000,
longitude: -1000,
accuracy: -1,
altitude: -1000,
altitudeAccuracy: -1,
heading: null,
speed: null
}
};
let position = this.lastKnownPosition == null ? defaultPosition : this.lastKnownPosition;
let info: Info = {
latitude: position.coords.latitude,
longitude: position.coords.longitude,
altitude: position.coords.altitude,
time: new Date()
};
await this.postInfo(info);
}
async postInfo(info: Info): Promise<void> {
let apiInfo: ApiInfo = {
lat: info.latitude,
lng: info.longitude,
alt: info.altitude,
time: info.time.toISOString()
};
if(this.mockServer) {
await new Promise(resolve => setTimeout(resolve, 200));
} else {
await new Promise<void>((resolve, error) => {
let request = new XMLHttpRequest();
request.onreadystatechange = () => {
if(request.readyState == 4) {
if(request.status == 200) {
resolve();
}
else {
error();
}
}
};
let urlEncodedData = "";
for(let name in apiInfo) {
if(urlEncodedData != "") urlEncodedData += "&";
urlEncodedData += encodeURIComponent(name)+'='+encodeURIComponent((<any>apiInfo)[name]);
}
request.open('POST', '/api/info', true);
request.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
request.send(urlEncodedData);
});
}
}
async fetchStatus(): Promise<Status> {
let apiStatus: ApiStatus;
@ -94,7 +190,10 @@ export class MonitorApi {
tae: Math.round(this.mockedTripAscendingElevation*10.0),
tme: Math.round(this.mockedTripMotorEnergy*10.0),
temp: Math.round((Math.cos(t*0.2)+1.0)*0.5 * 400 - 100),
alt: Math.round(altitude * 1000)
lat: -1000,
lng: -1000,
alt: Math.round(altitude * 1000),
d: ''
};
} else {
apiStatus = await new Promise<ApiStatus>((resolve, error) => {
@ -124,7 +223,10 @@ export class MonitorApi {
tripAscendingElevation: apiStatus.tae / 10,
tripMotorEnergy: apiStatus.tme / 10,
temperature: apiStatus.temp / 10,
altitude: apiStatus.alt / 1000
latitude: apiStatus.lat <= -900 ? null : apiStatus.lat,
longitude: apiStatus.lng <= -900 ? null : apiStatus.lng,
altitude: apiStatus.alt / 1000,
dateTime: apiStatus.d == '' ? null : new Date(apiStatus.d)
};
return this.lastStatus;

1
WebApp/src/pages/dashboard/dashboard-page.tsx

@ -49,6 +49,7 @@ export class DashboardPage extends Page {
}
async refresh() {
await MonitorApi.get().autoUpdateInfo();
let newStatus = await MonitorApi.get().fetchStatus();
if(this.status == null)
m.redraw();

7
WebApp/src/pages/raw-data/raw-data-page.tsx

@ -18,6 +18,7 @@ export class RawDataPage extends Page {
}
async refresh() {
await MonitorApi.get().autoUpdateInfo();
this.status = await MonitorApi.get().fetchStatus();
m.redraw();
if(this.autoRefresh)
@ -35,6 +36,12 @@ export class RawDataPage extends Page {
<p>Vitesse : {(this.status.speed * 3.6).toFixed(1)}km/h</p>
<p>Temperature : {this.status.temperature.toFixed(1)}°C</p>
<p>Altitude : {this.status.altitude.toFixed(1)}m</p>
{this.status.latitude ?
<p>Lat {this.status.latitude.toFixed(5)}° Lng {this.status.latitude.toFixed(5)}°</p>
: null}
{this.status.dateTime ?
<p>Time : {this.status.dateTime.toString()}</p>
: null}
</div>
: <p>Chargement...</p>;
}

Loading…
Cancel
Save