561 lines
20 KiB
C++
561 lines
20 KiB
C++
#include "ChronoWebServer.h"
|
|
#include <WebServer.h>
|
|
#include <ProjectileProfile.h>
|
|
#include <ChronoReadingManager.h>
|
|
#include <FileManager.h>
|
|
#include <ChronoProfile.h>
|
|
#include <GunProfile.h>
|
|
#include "Display.h"
|
|
|
|
const char *ssid = "routeguy";
|
|
const char *password = "wrangleR621!";
|
|
WebServer _server(80);
|
|
void (*testModeCallback)();
|
|
|
|
// Constructor implementation
|
|
ChronoWebServer::ChronoWebServer() {}
|
|
void ChronoWebServer::_handleRoot() {
|
|
String temp="<html>\
|
|
<head>\
|
|
<title>FX Chronograph Display Settings</title>\
|
|
<style>\
|
|
body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; Color: #000088; }\
|
|
.clickable-row {cursor: pointer;}\
|
|
.clickable-row:hover {background-color: #f0f0f0;}\
|
|
</style>\
|
|
</head>\
|
|
<body>\
|
|
<script>\
|
|
function addGunProfile() {window.location.href = '/gunprofile';}\
|
|
function addProjectileProfile() {window.location.href = '/projectileprofile';}\
|
|
function setProfile(type,id){\
|
|
const formData = new URLSearchParams();\
|
|
formData.append('id', id);\
|
|
var url='/projectileprofile/set';\
|
|
if (type=='gun'){url='/gunprofile/set';}\
|
|
fetch(url, {\
|
|
method: 'POST',\
|
|
body: formData\
|
|
})\
|
|
.then(response => response.text())\
|
|
.then(data => {console.log(data); window.location.href = '/';})\
|
|
.catch(error => console.error('Error:', error));\
|
|
}\
|
|
function exportData(){\
|
|
const link = document.createElement('a');\
|
|
link.href = '/export';\
|
|
link.setAttribute('download', 'data.csv');\
|
|
document.body.appendChild(link);\
|
|
link.click();\
|
|
document.body.removeChild(link);\
|
|
}\
|
|
function reset(){\
|
|
fetch('/reset', {\
|
|
method: 'GET',\
|
|
})\
|
|
.then(response => response.text())\
|
|
.then(data => console.log(data))\
|
|
.catch(error => console.error('Error:', error));\
|
|
}\
|
|
function format(){\
|
|
fetch('/format', {\
|
|
method: 'GET',\
|
|
})\
|
|
.then(response => response.text())\
|
|
.then(data => console.log(data))\
|
|
.catch(error => console.error('Error:', error));\
|
|
}\
|
|
function toggleTestMode(){\
|
|
fetch('/testmode', {\
|
|
method: 'GET',\
|
|
})\
|
|
.then(response => response.text())\
|
|
.then(data => console.log(data))\
|
|
.catch(error => console.error('Error:', error));\
|
|
}\
|
|
function doOta(){\
|
|
const fileInput = document.getElementById('otaFile');\
|
|
if (!fileInput.files.length) {\
|
|
alert('Please select a firmware file');\
|
|
return;\
|
|
}\
|
|
const formData = new FormData();\
|
|
formData.append('otaFile', fileInput.files[0]);\
|
|
fetch('/ota', {\
|
|
method: 'POST',\
|
|
body: formData\
|
|
})\
|
|
.then(response => response.text())\
|
|
.then(data => {\
|
|
console.log(data);\
|
|
alert('OTA update in progress... Device will restart.');\
|
|
window.location.href = '/';\
|
|
})\
|
|
.catch(error => console.error('Error:', error));\
|
|
}\
|
|
document.addEventListener('DOMContentLoaded', function() {\
|
|
const rows = document.querySelectorAll('.clickable-row');\
|
|
rows.forEach(row => {\
|
|
row.addEventListener('click', function() {\
|
|
const type = this.dataset.type;\
|
|
const id = this.dataset.id;\
|
|
if (id) {\
|
|
setProfile(type,id);\
|
|
}\
|
|
});\
|
|
});\
|
|
const removeButtons = document.querySelectorAll('.remove-button');\
|
|
removeButtons.forEach(button => {\
|
|
button.addEventListener('click', (event) => {\
|
|
event.stopPropagation();\
|
|
const type = event.target.dataset.type;\
|
|
const id = event.target.dataset.id;\
|
|
var url='/projectileprofile/remove';\
|
|
if (type === 'gun') {\
|
|
url='/gunprofile/remove';\
|
|
}\
|
|
const formData = new URLSearchParams();\
|
|
formData.append('id', id);\
|
|
fetch(url, {\
|
|
method: 'POST',\
|
|
body: formData\
|
|
})\
|
|
.then(response => response.text())\
|
|
.then(data => {console.log(data); window.location.href = '/';})\
|
|
.catch(error => console.error('Error:', error));\
|
|
});\
|
|
});\
|
|
});\
|
|
</script>\
|
|
<h1>ChronoDisplay Settings</h1>\
|
|
<div style='margin-top:50px; position:relative;'>\
|
|
<h3>Control Panel</h3>\
|
|
<button onclick='exportData()'>Export</button>\
|
|
<button onclick='reset()'>Reset</button>\
|
|
<button onclick='format()'>Format</button>\
|
|
<button onclick='toggleTestMode()'>Toggle Test Mode</button>\
|
|
<button onclick='doOta()'>OTA Update</button>\
|
|
</div>\
|
|
<div style='margin-top:20px; padding: 10px; border: 1px solid #999;'>\
|
|
<h3>OTA Firmware Update</h3>\
|
|
<input type='file' id='otaFile' accept='.bin,.bin'></input>\
|
|
<p style='font-size: 12px; color: #666;'>Select a .bin firmware file to update over-the-air</p>\
|
|
</div>\
|
|
<div style='margin-top:100px; position:relative;'>\
|
|
<h3>Gun Profiles</h3>\
|
|
<table style='width:600px; min-height:100px; border-collapse: collapse;'>\
|
|
<tr style='border-bottom:1px solid black;'><th>Name</th><th>Type</th><th></th></tr>";
|
|
ChronoReadingManager& chronoReadings = ChronoReadingManager::getInstance();
|
|
|
|
String currentGunName=chronoReadings.getProfile().getGunProfile().getName();
|
|
int currentGunPowerLevel=chronoReadings.getProfile().getGunProfile().getPowerLevel();
|
|
LinkedList<GunProfile>& gprofiles= chronoReadings.getGunProfiles();
|
|
for(int i=0;i<gprofiles.size();i++){
|
|
String name=gprofiles.get(i).getName();
|
|
String type="";
|
|
int powerlevel=gprofiles.get(i).getPowerLevel();
|
|
if (powerlevel==0){type="Bow or Airsoft";}
|
|
else if (powerlevel==1){type="CO2 Pistol";}
|
|
else if (powerlevel==2){type="Air Pistol";}
|
|
else if (powerlevel==3){type="Air Gun UK";}
|
|
else if (powerlevel==4){type="FAC";}
|
|
if (strcmp(name.c_str(),currentGunName.c_str())==0 && currentGunPowerLevel==powerlevel){
|
|
temp +="<tr class='clickable-row' data-type='gun' data-id='"+String(i)+"' style='background-color:#b0b0b0;'>";
|
|
}
|
|
else{
|
|
temp +="<tr class='clickable-row' data-type='gun' data-id='"+String(i)+"'>";
|
|
}
|
|
temp +="<td>"+name+"</td><td>"+type+"</td><td><button class='remove-button' data-type='gun' data-id='"+String(i)+"'>Remove</button></td></tr>";
|
|
}
|
|
|
|
temp +="</table>\
|
|
<button onclick='addGunProfile()' style='position:absolute; top:10px; left:575px;'>Add</button>\
|
|
</div>\
|
|
<div style='margin-top:100px; position:relative;'>\
|
|
<h3>Projectile Profiles</h3>\
|
|
<table style='width:600px; min-height:100px; border-collapse: collapse;'>\
|
|
<tr style='border-bottom:1px solid black;'><th>Name</th><th>Type</th><th>Caliber</th><th>Weight</th><th></th></tr>";
|
|
String currentProjectileProfileName=chronoReadings.getProfile().getProjectileProfile().getName();
|
|
float currentProjectileProfileWeight=chronoReadings.getProfile().getProjectileProfile().getWeight();
|
|
LinkedList<ProjectileProfile>& profiles= chronoReadings.getProjectileProfiles();
|
|
for(int i=0;i<profiles.size();i++){
|
|
String name=profiles.get(i).getName();
|
|
String type=profiles.get(i).getType();
|
|
String cal=String(profiles.get(i).getCaliber());
|
|
float fweight=profiles.get(i).getWeight();
|
|
String weight=String(fweight);
|
|
if (strcmp(name.c_str(),currentProjectileProfileName.c_str())==0 && fweight==currentProjectileProfileWeight){
|
|
temp +="<tr class='clickable-row' data-type='projectile' data-id='"+String(i)+"' style='background-color:#b0b0b0;'>";
|
|
}
|
|
else{
|
|
temp +="<tr class='clickable-row' data-type='projectile' data-id='"+String(i)+"'>";
|
|
}
|
|
temp +="<td>"+name+"</td><td>"+type+"</td><td>"+cal+"</td><td>"+weight+"</td><td><button class='remove-button' data-type='projectile' data-id='"+String(i)+"'>Remove</button></td></tr>";
|
|
}
|
|
|
|
|
|
temp +="</table>\
|
|
<button onclick='addProjectileProfile()' style='position:absolute; top:10px; left:575px;'>Add</button>\
|
|
</div>\
|
|
</body>\
|
|
</html>";
|
|
_server.send(200, "text/html", temp);
|
|
}
|
|
void ChronoWebServer::_handleGunProfile(){
|
|
String temp="<html>\
|
|
<head>\
|
|
<title>Gun Profile</title>\
|
|
<style>\
|
|
body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; Color: #000088; }\
|
|
</style>\
|
|
</head>\
|
|
<body>\
|
|
<script>\
|
|
function addProfile() {\
|
|
var name=document.getElementById('name').value;\
|
|
var type=document.getElementById('type').value;\
|
|
const formData = new URLSearchParams();\
|
|
formData.append('name', name);\
|
|
formData.append('type', type);\
|
|
fetch('/gunprofile/add', {\
|
|
method: 'POST',\
|
|
body: formData\
|
|
})\
|
|
.then(response => response.text())\
|
|
.then(data => {console.log(data); window.location.href = '/';})\
|
|
.catch(error => console.error('Error:', error));\
|
|
}\
|
|
</script>\
|
|
<h1>Add Gun Profile</h1>\
|
|
<div>\
|
|
<h3>Name</h3>\
|
|
<input id='name' style='width:200px;'></input>\
|
|
<h3>Type</h3>\
|
|
<select id='type'>\
|
|
<option value='0'>Bow or Airsoft</option>\
|
|
<option value='1'>CO2 Pistol</option>\
|
|
<option value='2'>Air Pistol</option>\
|
|
<option value='3'>Air Gun UK</option>\
|
|
<option value='4'>FAC</option>\
|
|
</select>\
|
|
<div style='margin-top:50px;'><button onclick='addProfile()'>Add Profile</button></div>\
|
|
</div>\
|
|
</body>\
|
|
</html>";
|
|
_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::_handleProjectileProfile(){
|
|
String temp="<html>\
|
|
<head>\
|
|
<title>Projectile Profile</title>\
|
|
<style>\
|
|
body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; Color: #000088; }\
|
|
</style>\
|
|
</head>\
|
|
<body>\
|
|
<script>\
|
|
function addProfile() {\
|
|
var name=document.getElementById('name').value;\
|
|
var type=document.getElementById('type').value;\
|
|
var cal=document.getElementById('caliber').value;\
|
|
var weight=document.getElementById('weight').value;\
|
|
const formData = new URLSearchParams();\
|
|
formData.append('name', name);\
|
|
formData.append('type', type);\
|
|
formData.append('cal', cal);\
|
|
formData.append('weight', weight);\
|
|
fetch('/projectileprofile/add', {\
|
|
method: 'POST',\
|
|
body: formData\
|
|
})\
|
|
.then(response => response.text())\
|
|
.then(data => {console.log(data); window.location.href = '/';})\
|
|
.catch(error => console.error('Error:', error));\
|
|
}\
|
|
</script>\
|
|
<h1>Add Projectile Profile</h1>\
|
|
<div>\
|
|
<h3>Name</h3>\
|
|
<input id='name' style='width:200px;'></input>\
|
|
<h3>Type</h3>\
|
|
<select id='type'>\
|
|
<option value='pellet'>Pellet</option>\
|
|
<option value='slug'>Slug</option>\
|
|
</select>\
|
|
<h3>Caliber</h3>\
|
|
<input id='caliber' style='width:50px;'></input>\
|
|
<h3>Weight</h3>\
|
|
<input id='weight' style='width:50px;'></input>\
|
|
<div style='margin-top:50px;'><button onclick='addProfile()'>Add Profile</button></div>\
|
|
</div>\
|
|
</body>\
|
|
</html>";
|
|
_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<ChronoReading>& 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<count;i++){
|
|
ChronoReading r=readings.get(i);
|
|
String shotNum=String(i+1);
|
|
String fps=String(r.getFPS());
|
|
String fpe=String(r.getFPE());
|
|
String gun=r.getProfile().getGunProfile().getName();
|
|
String power=String(r.getProfile().getGunProfile().getPowerLevel());
|
|
String projectile=r.getProfile().getProjectileProfile().getName();
|
|
String ptype=r.getProfile().getProjectileProfile().getType();
|
|
String cal=String(r.getProfile().getProjectileProfile().getCaliber());
|
|
String weight=String(r.getProfile().getProjectileProfile().getWeight());
|
|
csvData += shotNum + ",";
|
|
csvData += fps + ",";
|
|
csvData += fpe + ",";
|
|
csvData += gun + ",";
|
|
csvData += power + ",";
|
|
csvData += projectile + ",";
|
|
csvData += ptype + ",";
|
|
csvData += cal + ",";
|
|
csvData += weight + "\n";
|
|
}
|
|
|
|
_server.sendHeader("Content-Disposition", "attachment; filename=data.csv");
|
|
_server.send(200, "text/csv", csvData);
|
|
|
|
|
|
//_server.send(200, "text/html", "ok");
|
|
}
|
|
void ChronoWebServer::_handleReset(){
|
|
ChronoReadingManager& chronoReadings = ChronoReadingManager::getInstance();
|
|
chronoReadings.reset();
|
|
Display::updateEntireDisplay();
|
|
Display::refreshDisplay();
|
|
_server.send(200, "text/html", "ok");
|
|
}
|
|
void ChronoWebServer::_handleFormat(){
|
|
Serial.println("formating disk, erasing all data...");
|
|
FileManager& files = FileManager::getInstance();
|
|
files.formatDisk();
|
|
_server.send(200, "text/html", "ok");
|
|
}
|
|
void ChronoWebServer::setTestModeCallback(void (*pFunc)()){
|
|
testModeCallback=pFunc;
|
|
}
|
|
void ChronoWebServer::_handleTestMode(){
|
|
Serial.println("toggling test mode....");
|
|
testModeCallback();
|
|
_server.send(200, "text/html", "ok");
|
|
}
|
|
void ChronoWebServer::_handleOta(){
|
|
HTTPMethod method = _server.method();
|
|
|
|
if (method == HTTP_GET) {
|
|
// Serve OTA update page
|
|
String temp = "<html>\
|
|
<head>\
|
|
<title>OTA Firmware Update</title>\
|
|
<style>\
|
|
body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; Color: #000088; }\
|
|
</style>\
|
|
</head>\
|
|
<body>\
|
|
<h1>OTA Firmware Update</h1>\
|
|
<p>Select a .bin firmware file to update over-the-air.</p>\
|
|
<form method='POST' action='/ota' enctype='multipart/form-data'>\
|
|
<div>\
|
|
<h3>Firmware File</h3>\
|
|
<input type='file' name='otaFile' accept='.bin'></input>\
|
|
</div>\
|
|
<div style='margin-top: 20px;'>\
|
|
<button type='submit'>Upload</button>\
|
|
</div>\
|
|
</form>\
|
|
</body>\
|
|
</html>";
|
|
_server.send(200, "text/html", temp);
|
|
}
|
|
else if (method == HTTP_POST) {
|
|
// Handle firmware upload
|
|
HTTPUpload& upload = _server.upload();
|
|
|
|
if (upload.status == UPLOAD_FILE_START) {
|
|
Serial.println("OTA: Starting update...");
|
|
// Initialize update with the size of the uploaded file
|
|
if (!Update.begin(upload.totalSize)) {
|
|
Serial.println("OTA: Error: Not enough space");
|
|
_server.send(500, "text/plain", "Not enough space");
|
|
return;
|
|
}
|
|
}
|
|
else if (upload.status == UPLOAD_FILE_WRITE) {
|
|
// Write received firmware to flash
|
|
if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
|
|
Serial.println("OTA: Error: Write failed");
|
|
_server.send(500, "text/plain", "Write failed");
|
|
return;
|
|
}
|
|
}
|
|
else if (upload.status == UPLOAD_FILE_END) {
|
|
// End of upload
|
|
if (Update.end(true)) {
|
|
Serial.printf("OTA: Update complete: %u bytes\n", upload.totalSize);
|
|
_server.send(200, "text/html", "<h1>OTA Update Successful!<br>Restarting...</h1>");
|
|
delay(1000);
|
|
ESP.restart();
|
|
} else {
|
|
Serial.println("OTA: Error: Update end failed");
|
|
_server.send(500, "text/plain", "Update failed");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
void ChronoWebServer::init(){
|
|
WiFi.mode(WIFI_STA);
|
|
WiFi.begin(ssid, password);
|
|
while (WiFi.status() != WL_CONNECTED) {
|
|
delay(500);
|
|
Serial.print(".");
|
|
}
|
|
Serial.println("");
|
|
Serial.print("Connected to ");
|
|
Serial.println(ssid);
|
|
Serial.print("IP address: ");
|
|
Serial.println(WiFi.localIP());
|
|
|
|
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", _handleGunProfile);
|
|
_server.on("/projectileprofile/set", _handleSetProjectileProfile);
|
|
_server.on("/projectileprofile/add", _handleAddProjectileProfile);
|
|
_server.on("/projectileprofile/remove", _handleRemoveProjectileProfile);
|
|
_server.on("/projectileprofile", _handleProjectileProfile);
|
|
_server.on("/export", _handleExport);
|
|
_server.on("/reset", _handleReset);
|
|
_server.on("/format", _handleFormat);
|
|
_server.on("/testmode", _handleTestMode);
|
|
_server.on("/ota", _handleOta);
|
|
_server.on("/", _handleRoot);
|
|
_server.begin();
|
|
}
|
|
void ChronoWebServer::process(){
|
|
_server.handleClient();
|
|
} |