# include <Arduino.h>
# include "IDECompat.h"
# include <WiFi.h>
# include <WiFiMulti.h>
# include <FS.h>
# include <SPIFFS.h>
# include <ESPAsyncWebServer.h>
# 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 ) ;
}