#include "ChronoWebServer.h" #include #include #include #include #include #include #include #include "Display.h" #include static const char *SSID_KEY = "wifi_ssid"; static const char *PASSWORD_KEY = "wifi_password"; static Preferences preferences; String getWifiSsid() { String ssid = ""; if (preferences.begin("wifi", true)) { ssid = preferences.getString(SSID_KEY, ""); preferences.end(); } return ssid; } String getWifiPassword() { String password = ""; if (preferences.begin("wifi", true)) { password = preferences.getString(PASSWORD_KEY, ""); preferences.end(); } return password; } void saveWifiCredentials(const String& ssid, const String& password) { preferences.begin("wifi", false); preferences.putString(SSID_KEY, ssid); preferences.putString(PASSWORD_KEY, password); preferences.end(); } WebServer _server(80); void (*testModeCallback)(); String escapeJsonString(const char* src, int maxLen) { String out; out.reserve(maxLen * 2); for (int i = 0; i < maxLen; i++) { char c = src[i]; if (c == '\0') break; if (c == '"') { out += "\\\""; } else if (c == '\\') { out += "\\\\"; } else if ((unsigned char)c < 0x20) { continue; } else { out += c; } } return out; } // Constructor implementation ChronoWebServer::ChronoWebServer() {} void ChronoWebServer::_handleRoot() { String temp="ChronoDisplay

ChronoDisplay

Latest Reading

--
FPS
--
FPE
0
Shots
--
Avg
--
Low
--
High

Reading History

Polling: OFF
ShotFPSFPEGunPowerProjectileCalWt

Gun Profiles

NameType

Projectile Profiles

NameTypeCalWt

WiFi

Mode: ?
WiFi: --

After saving, device will restart.

OTA Update

Version: ?

Control

"; _server.send(200, "text/html", temp); } void ChronoWebServer::_handleGunProfile(){ String temp="\ \ Gun Profile\ \ \ \ \

Add Gun Profile

\
\

Name

\ \

Type

\ \
\
\ \ "; _server.send(200, "text/html", temp); } void ChronoWebServer::_handleAddGunProfile(){ if (_server.method() == HTTP_POST){ String name="Default"; GunProfile::GunProfileType type=GunProfile::PROFILE_TYPE_AIR_GUN_FAC; float caliber=0.0; float weight=0.0; if (_server.hasArg("name")){name=_server.arg("name");} if (_server.hasArg("type")){ String st=_server.arg("type"); if (strcmp(st.c_str(),"0")==0){type=GunProfile::PROFILE_TYPE_BOW_AIRSOFT;} else if (strcmp(st.c_str(),"1")==0){type=GunProfile::PROFILE_TYPE_CO2_PISTOL;} else if (strcmp(st.c_str(),"2")==0){type=GunProfile::PROFILE_TYPE_AIR_PISTOL;} else if (strcmp(st.c_str(),"3")==0){type=GunProfile::PROFILE_TYPE_AIR_GUN_UK;} else if (strcmp(st.c_str(),"4")==0){type=GunProfile::PROFILE_TYPE_AIR_GUN_FAC;} } ChronoReadingManager& chronoReadings = ChronoReadingManager::getInstance(); GunProfile p(name,type); chronoReadings.addGunProfile(p); } _server.send(200, "text/html", "ok"); } void ChronoWebServer::_handleSetGunProfile(){ if (_server.method() == HTTP_POST){ if (_server.hasArg("id")){ int id=_server.arg("id").toInt(); ChronoReadingManager& chronoReadings = ChronoReadingManager::getInstance(); chronoReadings.setGunProfile(id); //update profile with chronograph int type=chronoReadings.getProfile().getGunProfile().getPowerLevel(); ChronoBLE::changeGunType(type); //update display Display::updateProfile(chronoReadings.getProfile()); Display::refreshDisplay(); } } _server.send(200, "text/html", "ok"); } void ChronoWebServer::_handleRemoveGunProfile(){ if (_server.method() == HTTP_POST){ if (_server.hasArg("id")){ int id=_server.arg("id").toInt(); ChronoReadingManager& chronoReadings = ChronoReadingManager::getInstance(); chronoReadings.removeGunProfile(id); } } _server.send(200, "text/html", "ok"); } void ChronoWebServer::_handleGetGunProfiles(){ ChronoReadingManager& chronoReadings = ChronoReadingManager::getInstance(); LinkedList& gunProfiles=chronoReadings.getGunProfiles(); int count=gunProfiles.size(); GunProfile gp=chronoReadings.getProfile().getGunProfile(); int currentId=0; for(int i=0;i0) json+=","; json += "{\"id\":"; json += i; json += ",\"name\":\""; json += escapeJsonString(p.getName().c_str(), 49); json += "\",\"type\":"; json += p.getPowerLevel(); json += "}"; } json += "],\"current\":{\"id\":"; json += currentId; json += ",\"name\":\""; json += escapeJsonString(gp.getName().c_str(), 49); json += "\",\"type\":"; json += gp.getPowerLevel(); json += "}}"; _server.send(200, "application/json", json); } void ChronoWebServer::_handleGetProjectileProfiles(){ ChronoReadingManager& chronoReadings = ChronoReadingManager::getInstance(); LinkedList& projectileProfiles=chronoReadings.getProjectileProfiles(); int count=projectileProfiles.size(); ProjectileProfile pp=chronoReadings.getProfile().getProjectileProfile(); int currentId=0; for(int i=0;i0) json+=","; json += "{\"id\":"; json += i; json += ",\"name\":\""; json += escapeJsonString(p.getName().c_str(), 49); json += "\",\"type\":\""; json += escapeJsonString(p.getType().c_str(), 9); json += "\",\"cal\":\""; json += String(p.getCaliber(), 4); json += "\",\"weight\":\""; json += String(p.getWeight(), 4); json += "\"}"; } json += "],\"current\":{\"id\":"; json += currentId; json += ",\"name\":\""; json += escapeJsonString(pp.getName().c_str(), 49); json += "\",\"type\":\""; json += escapeJsonString(pp.getType().c_str(), 9); json += "\",\"cal\":\""; json += String(pp.getCaliber(), 4); json += "\",\"weight\":\""; json += String(pp.getWeight(), 4); json += "\"}}"; _server.send(200, "application/json", json); } void ChronoWebServer::_handleGetCurrentGunProfile(){ ChronoReadingManager& chronoReadings = ChronoReadingManager::getInstance(); GunProfile p=chronoReadings.getProfile().getGunProfile(); LinkedList& gunProfiles=chronoReadings.getGunProfiles(); int id=0; for(int i=0;i& projectileProfiles=chronoReadings.getProjectileProfiles(); int id=0; for(int i=0;i& readings=chronoReadings.getChronographReadings(); int count=readings.size(); String json; json.reserve(4096); json += "["; for (int i=count-1;i>=0;i--){ ChronoReading r=readings.get(i); if (count-1-i>0) json+=","; json += "{\"shot\":"; json += (count-i); json += ",\"fps\":"; json += r.getFPS(); json += ",\"fpe\":"; json += r.getFPE(); json += ",\"gun\":\""; json += escapeJsonString(r.getProfile().getGunProfile().getName().c_str(), 49); json += "\",\"power\":"; json += r.getProfile().getGunProfile().getPowerLevel(); json += ",\"projectile\":\""; json += escapeJsonString(r.getProfile().getProjectileProfile().getName().c_str(), 49); json += "\",\"cal\":\""; json += String(r.getProfile().getProjectileProfile().getCaliber(), 4); json += "\",\"weight\":\""; json += String(r.getProfile().getProjectileProfile().getWeight(), 4); json += "\"}"; } json += "]"; _server.send(200, "application/json", json); } void ChronoWebServer::_handleProjectileProfile(){ String temp="\ \ Projectile Profile\ \ \ \ \

Add Projectile Profile

\
\

Name

\ \

Type

\ \

Caliber

\ \

Weight

\ \
\
\ \ "; _server.send(200, "text/html", temp); } void ChronoWebServer::_handleSetProjectileProfile(){ if (_server.method() == HTTP_POST){ if (_server.hasArg("id")){ int id=_server.arg("id").toInt(); ChronoReadingManager& chronoReadings = ChronoReadingManager::getInstance(); chronoReadings.setProjectileProfile(id); Display::updateProfile(chronoReadings.getProfile()); Display::refreshDisplay(); } } _server.send(200, "text/html", "ok"); } void ChronoWebServer::_handleAddProjectileProfile(){ String message = "Got request...\n\n"; message += "URI: "; message += _server.uri(); message += "\nMethod: "; message += (_server.method() == HTTP_GET) ? "GET" : "POST"; message += "\nArguments: "; message += _server.args(); message += "\n"; for (uint8_t i = 0; i < _server.args(); i++) { message += " " + _server.argName(i) + ": " + _server.arg(i) + "\n"; } Serial.println(message); if (_server.method() == HTTP_POST){ String name=""; ProjectileProfile::ProjectileType type=ProjectileProfile::PELLET; float caliber=0.0; float weight=0.0; if (_server.hasArg("name")){name=_server.arg("name");} if (_server.hasArg("type")){ String st=_server.arg("type"); if (strcmp(st.c_str(),"slug")==0){ type=ProjectileProfile::SLUG; } } if (_server.hasArg("cal")){caliber=_server.arg("cal").toFloat();} if (_server.hasArg("weight")){weight=_server.arg("weight").toFloat();} ChronoReadingManager& chronoReadings = ChronoReadingManager::getInstance(); ProjectileProfile pp(name,type,caliber,weight); chronoReadings.addProjectileProfile(pp); } _server.send(200, "text/html", "ok"); } void ChronoWebServer::_handleRemoveProjectileProfile(){ if (_server.method() == HTTP_POST){ if (_server.hasArg("id")){ int id=_server.arg("id").toInt(); ChronoReadingManager& chronoReadings = ChronoReadingManager::getInstance(); chronoReadings.removeProjectileProfile(id); } } _server.send(200, "text/html", "ok"); } void ChronoWebServer::_handleExport(){ ChronoReadingManager& chronoReadings = ChronoReadingManager::getInstance(); LinkedList& readings=chronoReadings.getChronographReadings(); int count=readings.size(); Serial.print("exporting "); Serial.print(count); Serial.println(" readings to csv..."); //return csv data String csvData = "Shot #,FPS,FPE,Gun,Power,Projectile,Type,Caliber,Weight\n"; for (int i=0; i 0) { Serial.print("Attempting to connect to saved WiFi: "); Serial.println(savedSsid); connected = attemptStaConnection(savedSsid, savedPassword, 7000); if (connected) { Serial.println(""); Serial.print("Connected to "); Serial.println(savedSsid); Serial.print("IP address: "); Serial.println(WiFi.localIP()); } else { Serial.println(""); Serial.println("Failed to connect to saved WiFi - starting AP mode"); WiFi.disconnect(true, true); WiFi.mode(WIFI_OFF); delay(100); } } else { Serial.println("No saved WiFi credentials - starting AP mode"); } if (!connected) { setupSoftAp("ChronoDisplay", "12345678"); if (MDNS.begin("esp32")) { Serial.println("MDNS responder started"); } } _server.on("/gunprofile/add", _handleAddGunProfile); _server.on("/gunprofile/set", _handleSetGunProfile); _server.on("/gunprofile/remove", _handleRemoveGunProfile); _server.on("/gunprofile/get", _handleGetGunProfiles); _server.on("/gunprofile/current", _handleGetCurrentGunProfile); _server.on("/gunprofile", _handleGunProfile); _server.on("/projectileprofile/set", _handleSetProjectileProfile); _server.on("/projectileprofile/add", _handleAddProjectileProfile); _server.on("/projectileprofile/remove", _handleRemoveProjectileProfile); _server.on("/projectileprofile/get", _handleGetProjectileProfiles); _server.on("/projectileprofile/current", _handleGetCurrentProjectileProfile); _server.on("/projectileprofile", _handleProjectileProfile); _server.on("/readings", _handleGetReadings); _server.on("/export", _handleExport); _server.on("/reset", _handleReset); _server.on("/format", _handleFormat); _server.on("/testmode", _handleTestMode); _server.on("/ota", HTTP_POST, [](){handleOtaUpdateEnd();}, [](){handleOtaUpdate();}); _server.on("/wifi", HTTP_GET, _handleWifi); _server.on("/wifisave", HTTP_POST, _handleWifiSave); _server.on("/clearwifi", HTTP_POST, _handleClearWifi); _server.on("/wifimode", HTTP_GET, _handleWifiMode); _server.on("/version", HTTP_GET, _handleVersion); _server.on("/initdata", HTTP_GET, _handleInitData); _server.on("/", _handleRoot); _server.begin(); } void ChronoWebServer::process(){ _server.handleClient(); } void ChronoWebServer::_handleVersion() { String versionJson = String("{'version':'") + CHRONO_VERSION + "','major':" + String(CHRONO_VERSION_MAJOR) + ",'minor':" + String(CHRONO_VERSION_MINOR) + ",'patch':" + String(CHRONO_VERSION_PATCH) + "}"; _server.send(200, "application/json", versionJson); } void ChronoWebServer::_handleWifi() { String ssid = getWifiSsid(); _server.send(200, "text/plain", ssid); } void ChronoWebServer::_handleWifiSave() { if (_server.method() == HTTP_POST) { String ssid = ""; String password = ""; if (_server.hasArg("ssid")) { ssid = _server.arg("ssid"); } if (_server.hasArg("password")) { password = _server.arg("password"); } saveWifiCredentials(ssid, password); _server.send(200, "text/html", "ok"); delay(1000); ESP.restart(); } } void ChronoWebServer::_handleClearWifi() { if (_server.method() == HTTP_POST) { preferences.begin("wifi", false); preferences.clear(); preferences.end(); _server.send(200, "text/html", "ok"); delay(1000); ESP.restart(); } } void ChronoWebServer::_handleWifiMode() { String mode; if (WiFi.getMode() == WIFI_STA) { mode = "STA"; } else if (WiFi.getMode() == WIFI_AP) { mode = "AP"; } else if (WiFi.getMode() == WIFI_AP_STA) { mode = "AP+STA"; } else { mode = "NONE"; } _server.send(200, "text/plain", mode); } void ChronoWebServer::_handleInitData(){ String json; json.reserve(4096); json += "{\"version\":{\"version\":\""; json += CHRONO_VERSION; json += "\",\"major\":"; json += CHRONO_VERSION_MAJOR; json += ",\"minor\":"; json += CHRONO_VERSION_MINOR; json += ",\"patch\":"; json += CHRONO_VERSION_PATCH; json += "},\"wifiSsid\":\""; String ssid = getWifiSsid(); json += escapeJsonString(ssid.c_str(), ssid.length()); json += "\",\"wifiMode\":\""; if (WiFi.getMode() == WIFI_STA) json += "STA"; else if (WiFi.getMode() == WIFI_AP) json += "AP"; else if (WiFi.getMode() == WIFI_AP_STA) json += "AP+STA"; else json += "NONE"; json += "\",\"gunProfiles\":["; ChronoReadingManager& chronoReadings = ChronoReadingManager::getInstance(); LinkedList& gunProfiles = chronoReadings.getGunProfiles(); int gunCount = gunProfiles.size(); for (int i = 0; i < gunCount; i++) { GunProfile p = gunProfiles.get(i); if (i > 0) json += ","; json += "{\"id\":"; json += i; json += ",\"name\":\""; json += escapeJsonString(p.getName().c_str(), 49); json += "\",\"type\":"; json += p.getPowerLevel(); json += "}"; } json += "],\"projectileProfiles\":["; LinkedList& projectileProfiles = chronoReadings.getProjectileProfiles(); int projCount = projectileProfiles.size(); for (int i = 0; i < projCount; i++) { ProjectileProfile p = projectileProfiles.get(i); if (i > 0) json += ","; json += "{\"id\":"; json += i; json += ",\"name\":\""; json += escapeJsonString(p.getName().c_str(), 49); json += "\",\"type\":\""; json += escapeJsonString(p.getType().c_str(), 9); json += "\",\"cal\":\""; json += String(p.getCaliber(), 4); json += "\",\"weight\":\""; json += String(p.getWeight(), 4); json += "\"}"; } json += "],\"currentGunProfile\":{"; GunProfile gp = chronoReadings.getProfile().getGunProfile(); int gunId = 0; for (int i = 0; i < gunCount; i++) { GunProfile g = gunProfiles.get(i); if (g.getName() == gp.getName() && g.getPowerLevel() == gp.getPowerLevel()) { gunId = i; break; } } json += "\"id\":"; json += gunId; json += ",\"name\":\""; json += escapeJsonString(gp.getName().c_str(), 49); json += "\",\"type\":"; json += gp.getPowerLevel(); json += "},\"currentProjectileProfile\":{"; ProjectileProfile pp = chronoReadings.getProfile().getProjectileProfile(); int projId = 0; for (int i = 0; i < projCount; i++) { ProjectileProfile p = projectileProfiles.get(i); if (p.getName() == pp.getName() && p.getType() == pp.getType() && p.getCaliber() == pp.getCaliber() && p.getWeight() == pp.getWeight()) { projId = i; break; } } json += "\"id\":"; json += projId; json += ",\"name\":\""; json += escapeJsonString(pp.getName().c_str(), 49); json += "\",\"type\":\""; json += escapeJsonString(pp.getType().c_str(), 9); json += "\",\"cal\":\""; json += String(pp.getCaliber(), 4); json += "\",\"weight\":\""; json += String(pp.getWeight(), 4); json += "\"}"; json += "}"; _server.send(200, "application/json", json); }