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"; | |