Monitoring system for electric vehicles (log various sensors, such as consumed power, solar production, speed, slope, apparent wind, etc.)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

162 lines
4.7 KiB

#include <Arduino.h>
#include "IDECompat.h"
#include <WiFi.h>
#include <FS.h>
#include <SPIFFS.h>
#include <ESPAsyncWebServer.h>
#include "ADC.h"
#include "wifi-credentials.h"
AsyncWebServer server(80);
ADC currentSensor(36);
ADC batterySensor(39);
const int8_t speedSensorPin = 13;
const int8_t debugLedPin = 12;
const float wheelDiameterInches = 20;
const int numImpulsesPerTurn = 2;
const float wheelCircumferenceMeters = wheelDiameterInches * 0.0254f * 3.1415f / (float)numImpulsesPerTurn;
int16_t batteryVoltage = -1; // in mV
int16_t batteryCurrent = -1; // in mV
volatile bool debugLedState = true;
volatile bool speedSensorState = false;
volatile unsigned long speedSensorRiseTime = 0;
volatile unsigned long speedSensorLastImpulseTime = 0;
volatile unsigned long speedSensorLastImpulseInterval = (unsigned long)-1; // in milliseconds
void IRAM_ATTR onSpeedSensorChange(bool newState)
{
if(speedSensorState == newState) return;
unsigned long now = millis();
speedSensorState = newState;
bool magnetDetected = !speedSensorState; // the magnet closes the contact which pulls the pin low
if(magnetDetected)
{
speedSensorRiseTime = now;
}
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)
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;
speedSensorLastImpulseTime = now;
if(timeSinceLastImpulse > 30 && timeSinceLastImpulse < 4000)
{
speedSensorLastImpulseInterval = timeSinceLastImpulse;
}
else
{
speedSensorLastImpulseInterval = (unsigned long)-1;
}
}
}
void IRAM_ATTR onSpeedSensorChange() { onSpeedSensorChange(digitalRead(speedSensorPin) == HIGH); }
float getSpeed()
{
unsigned long now = millis();
unsigned long lastImpulseInterval = speedSensorLastImpulseInterval;
unsigned long lastImpulseTime = speedSensorLastImpulseTime;
unsigned long timeSinceLastImpulse = now > lastImpulseTime ? now - lastImpulseTime : (4294967295 - lastImpulseTime) + now;
unsigned long interval = timeSinceLastImpulse > lastImpulseInterval * 10 / 9 ? timeSinceLastImpulse : lastImpulseInterval;
float speed = wheelCircumferenceMeters / (float)interval * 1000.0f; // in meters per second
if(speed < 0.25f) return 0.0f; // if speed is very low (less than 1km/h) it probably means we've stopped
return speed;
}
void setup()
{
pinMode(speedSensorPin, INPUT_PULLUP);
attachInterrupt(speedSensorPin, &onSpeedSensorChange, CHANGE);
pinMode(debugLedPin, OUTPUT);
digitalWrite(debugLedPin, debugLedState ? HIGH : LOW);
Serial.begin(115200);
if(!SPIFFS.begin(false)){
Serial.println("SPIFFS Mount Failed");
return;
}
// Connect to Wi-Fi
WiFi.begin(wifi_ssid, wifi_password);
while (WiFi.status() != WL_CONNECTED)
{
delay(1000);
Serial.println("Connecting to Wifi...");
}
// Print ESP Local IP Address
Serial.print("Wifi connected, ip=");
Serial.println(WiFi.localIP());
server.on("/api/status", HTTP_GET, [](AsyncWebServerRequest *request){
int v = batteryVoltage;
int c = batteryCurrent;
int s = (int)(getSpeed() * 1000.0f + 0.5f);
char json[128];
sprintf(json, "{\"v\":%d,\"c\":%d,\"s\":%d}", v, c, s);
request->send(200, "text/json", json);
});
// 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");
// 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");
server.begin();
Serial.println("HTTP server started");
}
void loop()
{
const int numSamples = 100;
float averageV = 0.0f;
float averageC = 0.0f;
for(int sample = 0; sample < numSamples; ++sample)
{
delay(1);
float v = batterySensor.read();
float c = currentSensor.read();
averageV += v;
averageC += c;
}
averageV /= (float)numSamples;
averageC /= (float)numSamples;
if(averageV < 0.2f) averageV = 0.0f;
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 = (int16_t)(averageV * 1000.0f + 0.5f);
batteryCurrent = (int16_t)(averageC * 1000.0f + 0.5f);
delay(10);
}