Spaces:
Running
Running
// HTML page for web interface | |
const char* webPage = R"rawliteral( | |
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="UTF-8"> | |
<title>Engine Management System</title> | |
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script> | |
<style> | |
body { font-family: Arial; margin: 20px; } | |
.container { max-width: 1200px; margin: 0 auto; } | |
.card { | |
border: 1px solid #ddd; | |
padding: 15px; | |
margin: 10px 0; | |
border-radius: 5px; | |
} | |
.value { font-size: 24px; font-weight: bold; } | |
.label { font-size: 14px; color: #666; } | |
.progress-bar { | |
width: 100%; | |
height: 20px; | |
background: #eee; | |
border-radius: 10px; | |
overflow: hidden; | |
} | |
.progress { | |
height: 100%; | |
background: #4CAF50; | |
transition: width 0.3s; | |
} | |
button { | |
padding: 8px 16px; | |
margin: 5px; | |
border: none; | |
border-radius: 4px; | |
background: #4CAF50; | |
color: white; | |
cursor: pointer; | |
} | |
button:hover { | |
background: #45a049; | |
} | |
input[type="number"] { | |
padding: 5px; | |
border: 1px solid #ddd; | |
border-radius: 4px; | |
width: 100px; | |
} | |
</style> | |
</head> | |
<body> | |
<div class="container"> | |
<h1>Engine Management System</h1> | |
<div class="card"> | |
<h2>Real-time Parameters</h2> | |
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px;"> | |
<div> | |
<div class="value" id="rpm">0</div> | |
<div class="label">RPM</div> | |
</div> | |
<div> | |
<div class="value" id="map">0</div> | |
<div class="label">MAP (kPa)</div> | |
</div> | |
<div> | |
<div class="value" id="tps">0</div> | |
<div class="label">TPS (%)</div> | |
</div> | |
<div> | |
<div class="value" id="lambda">0</div> | |
<div class="label">Lambda</div> | |
</div> | |
</div> | |
</div> | |
<div class="card"> | |
<h2>Learning System</h2> | |
<div> | |
<h3>Learning Progress</h3> | |
<div class="progress-bar"> | |
<div class="progress" id="fuelProgress" style="width: 0%"></div> | |
</div> | |
<div style="margin-top: 10px;"> | |
<button onclick="toggleLearning()" id="learningBtn">Enable Learning</button> | |
<button onclick="resetLearning()">Reset Learning</button> | |
</div> | |
<div style="margin-top: 20px;"> | |
<div> | |
<div class="value" id="fuelCorrection">1.00</div> | |
<div class="label">Fuel Correction Factor</div> | |
</div> | |
<div> | |
<div class="value" id="ignitionCorrection">0.0</div> | |
<div class="label">Ignition Correction (deg)</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div class="card"> | |
<h2>Idle Control</h2> | |
<div> | |
<div> | |
<div class="value" id="idlePosition">0</div> | |
<div class="label">Idle Valve Position (%)</div> | |
</div> | |
<div> | |
<div class="value" id="targetRPM">800</div> | |
<div class="label">Target RPM</div> | |
</div> | |
<div style="margin-top: 10px;"> | |
<input type="number" id="rpmInput" value="800" min="600" max="2000"> | |
<button onclick="setTargetRPM()">Set RPM</button> | |
<button onclick="calibrateIdle()">Calibrate</button> | |
</div> | |
</div> | |
</div> | |
<div class="card"> | |
<h2>Diagnostics</h2> | |
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px;"> | |
<div> | |
<div class="value" id="knockEvents">0</div> | |
<div class="label">Knock Events</div> | |
</div> | |
<div> | |
<div class="value" id="engineTemp">0</div> | |
<div class="label">Engine Temp (C)</div> | |
</div> | |
<div> | |
<div class="value" id="batteryVoltage">0</div> | |
<div class="label">Battery Voltage (V)</div> | |
</div> | |
</div> | |
</div> | |
<div class="card"> | |
<h2>Settings</h2> | |
<div> | |
<button onclick="saveSettings()">Save Settings</button> | |
<button onclick="loadSettings()">Load Settings</button> | |
<button onclick="exportSettings()">Export Settings</button> | |
<button onclick="importSettings()">Import Settings</button> | |
</div> | |
</div> | |
</div> | |
<script> | |
var rpmData = { | |
x: [], | |
y: [], | |
type: "scatter", | |
name: "RPM" | |
}; | |
var mapData = { | |
x: [], | |
y: [], | |
type: "scatter", | |
name: "MAP" | |
}; | |
Plotly.newPlot("rpmChart", [rpmData], { | |
title: "RPM History", | |
xaxis: { title: "Time" }, | |
yaxis: { title: "RPM" } | |
}); | |
Plotly.newPlot("mapChart", [mapData], { | |
title: "MAP History", | |
xaxis: { title: "Time" }, | |
yaxis: { title: "kPa" } | |
}); | |
function updateData() { | |
fetch("/status") | |
.then(response => response.json()) | |
.then(data => { | |
document.getElementById("rpm").textContent = Math.round(data.rpm); | |
document.getElementById("map").textContent = data.map.toFixed(1); | |
document.getElementById("tps").textContent = data.tps.toFixed(1); | |
document.getElementById("lambda").textContent = data.lambda.toFixed(2); | |
document.getElementById("fuelProgress").style.width = data.learning_progress + "%"; | |
document.getElementById("fuelCorrection").textContent = data.fuel_correction.toFixed(2); | |
document.getElementById("ignitionCorrection").textContent = data.ignition_correction.toFixed(1); | |
document.getElementById("idlePosition").textContent = data.idle_position.toFixed(1); | |
document.getElementById("targetRPM").textContent = data.target_rpm; | |
document.getElementById("knockEvents").textContent = data.knock_events; | |
document.getElementById("engineTemp").textContent = data.temp.toFixed(1); | |
document.getElementById("batteryVoltage").textContent = data.voltage.toFixed(1); | |
const now = new Date(); | |
Plotly.extendTraces("rpmChart", { | |
x: [[now]], | |
y: [[data.rpm]] | |
}, [0]); | |
Plotly.extendTraces("mapChart", { | |
x: [[now]], | |
y: [[data.map]] | |
}, [0]); | |
}); | |
} | |
function toggleLearning() { | |
fetch("/learning/toggle", { method: "POST" }) | |
.then(response => response.json()) | |
.then(data => { | |
alert(data.enabled ? "Learning enabled" : "Learning disabled"); | |
}); | |
} | |
function resetLearning() { | |
if (confirm("Are you sure you want to reset learning data?")) { | |
fetch("/learning/reset", { method: "POST" }) | |
.then(response => response.json()) | |
.then(() => { | |
alert("Learning data reset"); | |
}); | |
} | |
} | |
function setTargetRPM() { | |
const rpm = document.getElementById("rpmInput").value; | |
fetch("/idle/rpm", { | |
method: "POST", | |
headers: { | |
"Content-Type": "application/json", | |
}, | |
body: JSON.stringify({ rpm: parseInt(rpm) }) | |
}); | |
} | |
function calibrateIdle() { | |
if (confirm("Start idle calibration?")) { | |
fetch("/idle/calibrate", { method: "POST" }) | |
.then(response => response.json()) | |
.then(() => { | |
alert("Calibration complete"); | |
}); | |
} | |
} | |
function saveSettings() { | |
fetch("/settings/save", { method: "POST" }) | |
.then(response => response.json()) | |
.then(() => { | |
alert("Settings saved"); | |
}); | |
} | |
function loadSettings() { | |
fetch("/settings/load") | |
.then(response => response.json()) | |
.then(() => { | |
alert("Settings loaded"); | |
}); | |
} | |
function exportSettings() { | |
window.location.href = "/settings/export"; | |
} | |
function importSettings() { | |
const input = document.createElement("input"); | |
input.type = "file"; | |
input.onchange = function(e) { | |
const file = e.target.files[0]; | |
const formData = new FormData(); | |
formData.append("settings", file); | |
fetch("/settings/import", { | |
method: "POST", | |
body: formData | |
}) | |
.then(response => response.json()) | |
.then(() => { | |
alert("Settings imported"); | |
}); | |
}; | |
input.click(); | |
} | |
setInterval(updateData, 1000); | |
</script> | |
</body> | |
</html> | |
)rawliteral"; | |