# 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 ) ;
}