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
232 lines
6.4 KiB
3 years ago
|
#include <Arduino.h>
|
||
3 years ago
|
#include "IDECompat.h"
|
||
3 years ago
|
|
||
3 years ago
|
#include <WiFi.h>
|
||
3 years ago
|
#include <WiFiMulti.h>
|
||
3 years ago
|
#include <FS.h>
|
||
|
#include <SPIFFS.h>
|
||
3 years ago
|
#include <ESPAsyncWebServer.h>
|
||
|
|
||
3 years ago
|
#include "ADC.h"
|
||
|
|
||
3 years ago
|
#include "wifi-credentials.h"
|
||
|
|
||
|
AsyncWebServer server(80);
|
||
|
|
||
3 years ago
|
ADC currentSensor(36);
|
||
|
ADC batterySensor(39);
|
||
|
const int8_t speedSensorPin = 13;
|
||
|
const int8_t debugLedPin = 12;
|
||
3 years ago
|
|
||
3 years ago
|
const float wheelDiameterInches = 20;
|
||
|
const int numImpulsesPerTurn = 2;
|
||
|
const float wheelCircumferenceMeters = wheelDiameterInches * 0.0254f * 3.1415f / (float)numImpulsesPerTurn;
|
||
|
|
||
3 years ago
|
uint16_t batteryVoltage = 0; // in mV
|
||
|
uint16_t batteryCurrent = 0; // in mV
|
||
3 years ago
|
|
||
3 years ago
|
WiFiMulti wifiMulti;
|
||
|
wl_status_t wifi_STA_status = WL_NO_SHIELD;
|
||
|
unsigned long wifiConnexionBegin = 0;
|
||
|
const unsigned long retryWifiConnexionDelay = 60000; // in milliseconds
|
||
|
|
||
3 years ago
|
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)
|
||
3 years ago
|
{
|
||
3 years ago
|
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
|
||
3 years ago
|
{
|
||
3 years ago
|
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
|
||
3 years ago
|
{
|
||
3 years ago
|
speedSensorLastImpulseInterval = (unsigned long)-1;
|
||
3 years ago
|
}
|
||
|
}
|
||
3 years ago
|
}
|
||
3 years ago
|
|
||
3 years ago
|
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;
|
||
3 years ago
|
}
|
||
|
|
||
3 years ago
|
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();
|
||
|
}
|
||
|
}
|
||
|
|
||
3 years ago
|
void setup()
|
||
|
{
|
||
3 years ago
|
pinMode(speedSensorPin, INPUT_PULLUP);
|
||
|
attachInterrupt(speedSensorPin, &onSpeedSensorChange, CHANGE);
|
||
|
|
||
|
pinMode(debugLedPin, OUTPUT);
|
||
|
digitalWrite(debugLedPin, debugLedState ? HIGH : LOW);
|
||
|
|
||
3 years ago
|
Serial.begin(115200);
|
||
3 years ago
|
|
||
3 years ago
|
if(!SPIFFS.begin(false)){
|
||
|
Serial.println("SPIFFS Mount Failed");
|
||
|
return;
|
||
|
}
|
||
|
|
||
3 years ago
|
// Set WiFi mode to both AccessPoint and Station
|
||
|
WiFi.mode(WIFI_AP_STA);
|
||
|
|
||
|
// Create the WiFi Access Point
|
||
|
if(wifi_AP_ssid != nullptr)
|
||
3 years ago
|
{
|
||
3 years ago
|
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());
|
||
3 years ago
|
}
|
||
|
|
||
3 years ago
|
// Also connect as a station (if the configured remote access point is in range)
|
||
|
connectWifi();
|
||
3 years ago
|
|
||
3 years ago
|
server.on("/api/status", HTTP_GET, [](AsyncWebServerRequest *request){
|
||
3 years ago
|
int v = batteryVoltage;
|
||
3 years ago
|
int c = batteryCurrent;
|
||
|
int s = (int)(getSpeed() * 1000.0f + 0.5f);
|
||
|
|
||
3 years ago
|
char json[128];
|
||
3 years ago
|
sprintf(json, "{\"v\":%d,\"c\":%d,\"s\":%d}", v, c, s);
|
||
3 years ago
|
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");
|
||
3 years ago
|
|
||
|
server.begin();
|
||
|
Serial.println("HTTP server started");
|
||
3 years ago
|
}
|
||
|
|
||
3 years ago
|
void loop()
|
||
|
{
|
||
3 years ago
|
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();
|
||
|
}
|
||
|
|
||
3 years ago
|
const int numSamples = 100;
|
||
3 years ago
|
|
||
3 years ago
|
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();
|
||
3 years ago
|
|
||
3 years ago
|
averageV += v;
|
||
|
averageC += c;
|
||
|
}
|
||
|
|
||
|
averageV /= (float)numSamples;
|
||
|
averageC /= (float)numSamples;
|
||
3 years ago
|
|
||
3 years ago
|
if(averageV < 0.2f) averageV = 0.0f;
|
||
3 years ago
|
|
||
3 years ago
|
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 ?
|
||
3 years ago
|
batteryVoltage = (uint16_t)(averageV * 1000.0f + 0.5f);
|
||
|
batteryCurrent = (uint16_t)(averageC * 1000.0f + 0.5f);
|
||
3 years ago
|
|
||
|
delay(10);
|
||
3 years ago
|
}
|