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
This commit is contained in:
Youen 2024-05-24 19:31:09 +02:00
parent fde8f27f70
commit 359f3b4cd4
6 changed files with 207 additions and 14 deletions

View File

@ -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)

View File

@ -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;
};

View File

@ -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;

View File

@ -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;

View File

@ -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();

View File

@ -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>;
}