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.
161 lines
4.7 KiB
161 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); |
|
}
|
|
|