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:
parent
fde8f27f70
commit
359f3b4cd4
@ -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)
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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…
Reference in New Issue
Block a user