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.

232 lines
6.4 KiB

#include <Arduino.h>
#include "IDECompat.h"
#include <WiFi.h>
#include <WiFiMulti.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;
uint16_t batteryVoltage = 0; // in mV
uint16_t batteryCurrent = 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
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 connectWifi()
{
wifiMulti = WiFiMulti();
const int numSSIDs = sizeof(wifi_STA_credentials)/sizeof(wifi_STA_credentials[0]);
if(numSSIDs > 0)
{
Serial.println("Connecting to wifi...");
for(int idx = 0; idx < numSSIDs; ++idx)
{
wifiMulti.addAP(wifi_STA_credentials[idx].SSID, wifi_STA_credentials[idx].password);
}
wifiConnexionBegin = millis();
wifiMulti.run();
}
}
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;
}
// Set WiFi mode to both AccessPoint and Station
WiFi.mode(WIFI_AP_STA);
// Create the WiFi Access Point
if(wifi_AP_ssid != nullptr)
{
Serial.println("Creating wifi access point...");
WiFi.softAP(wifi_AP_ssid, wifi_AP_password);
Serial.print("Wifi access point created, SSID=");
Serial.print(wifi_AP_ssid);
Serial.print(", IP=");
Serial.println(WiFi.softAPIP());
}
// Also connect as a station (if the configured remote access point is in range)
connectWifi();
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()
{
wl_status_t newWifiStatus = WiFi.status();
if(newWifiStatus != wifi_STA_status)
{
if(newWifiStatus == WL_CONNECTED)
{
Serial.print("Connected to wifi (");
Serial.print(WiFi.SSID().c_str());
Serial.print("), ip=");
Serial.println(WiFi.localIP());
}
else if(newWifiStatus == WL_DISCONNECTED)
{
char codeStr[16];
sprintf(codeStr, "%d", (int)newWifiStatus);
Serial.print("Lost wifi connexion (");
Serial.print(codeStr);
Serial.println(")");
connectWifi();
}
else
{
char codeStr[16];
sprintf(codeStr, "%d", (int)newWifiStatus);
Serial.print("Wifi state: ");
Serial.println(codeStr);
}
wifi_STA_status = newWifiStatus;
}
if(wifi_STA_status != WL_CONNECTED)
{
unsigned long now = millis();
unsigned long elapsed = now > wifiConnexionBegin ? now - wifiConnexionBegin : (4294967295 - wifiConnexionBegin) + now;
if(elapsed > retryWifiConnexionDelay)
connectWifi();
}
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 = (uint16_t)(averageV * 1000.0f + 0.5f);
batteryCurrent = (uint16_t)(averageC * 1000.0f + 0.5f);
delay(10);
}