#include #include "IDECompat.h" #include #include #include #include #include #include "ADC.h" #include "OTA.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(); OTA.begin(); 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() { OTA.handle(); 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); }