#include #include "IDECompat.h" #include #include #include #include #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); }