Files
Microtracking/platformio_microtracking/include/web_pages.h
T

208 lines
12 KiB
C

#pragma once
#include <Arduino.h>
static const char INDEX_HTML[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ESP32 Logger & Stepper Control</title>
<style>
body { font-family: Arial, sans-serif; margin: 15px; background: #f8f9fa; color: #333; }
h2, h3 { margin-top: 10px; margin-bottom: 8px; }
.container { display:flex; flex-wrap:wrap; gap:20px; margin-top:15px; }
.column { flex:1; min-width:320px; max-width:48%; background: #ffffff; padding: 15px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
@media (max-width:850px){ .column{ max-width:100%; } }
.param-group { margin-bottom:8px; display: flex; align-items: center; justify-content: space-between; }
.param-group label { flex: 1; }
.param-group input { width: 90px; padding: 4px; border: 1px solid #ccc; border-radius: 4px; }
.param-group button { margin-left: 6px; padding: 4px 10px; cursor: pointer; }
button { padding: 6px 12px; margin: 3px 2px; cursor: pointer; border-radius: 4px; border: 1px solid #007bff; background: #007bff; color: white; font-weight: bold; }
button:hover { background: #0056b3; }
.btn-danger { background: #dc3545; border-color: #dc3545; }
.btn-danger:hover { background: #bd2130; }
.btn-secondary { background: #6c757d; border-color: #6c757d; }
.btn-secondary:hover { background: #5a6268; }
#log { height:260px; overflow-y:auto; border:1px solid #ccc; background:#1e1e1e; color:#00ff66; padding:8px; font-family: monospace; font-size: 13px; border-radius: 4px; }
canvas { background: #ffffff; border-radius: 4px; }
.input-coord { width: 140px; padding: 5px; margin: 3px; border: 1px solid #ccc; border-radius: 4px; }
</style>
<script>
var ws = new WebSocket("ws://" + location.hostname + ":81/");
ws.onmessage = function(event){
var data = event.data;
if (data.startsWith("LOG:")) {
var log = document.getElementById("log");
log.innerHTML += data.substring(4) + "<br>";
log.scrollTop = log.scrollHeight;
} else if (data.startsWith("POS:")) {
var parts = data.split(":");
if (parts.length === 3) {
document.getElementById("posXDisplay").innerText = parts[1];
document.getElementById("posYDisplay").innerText = parts[2];
updatePlot(parts[1], parts[2]);
}
}
};
function updatePlot(x, y){
var canvas = document.getElementById("positionPlot");
var ctx = canvas.getContext("2d");
var w = canvas.width, h = canvas.height, m=40, maxX=106000, maxY=104000;
ctx.clearRect(0,0,w,h);
ctx.strokeStyle="#ccc"; ctx.lineWidth=1;
for (var i=0;i<=10;i++){
var xPos=m+(i*(w-2*m)/10);
ctx.beginPath(); ctx.moveTo(xPos,m); ctx.lineTo(xPos,h-m); ctx.stroke();
ctx.fillStyle="#555"; ctx.font="11px Arial"; ctx.fillText(Math.round(i*maxX/10), xPos-12, h-m+15);
var yPos=m+(i*(h-2*m)/10);
ctx.beginPath(); ctx.moveTo(m,yPos); ctx.lineTo(w-m,yPos); ctx.stroke();
ctx.fillText(Math.round((10-i)*maxY/10), m-35, yPos+4);
}
ctx.strokeStyle="#000"; ctx.lineWidth=2;
ctx.beginPath(); ctx.moveTo(m,m); ctx.lineTo(m,h-m); ctx.lineTo(w-m,h-m); ctx.stroke();
var plotX=m+(x/maxX)*(w-2*m);
var plotY=h-m-(y/maxY)*(h-2*m);
ctx.fillStyle="red"; ctx.beginPath(); ctx.arc(plotX,plotY,6,0,2*Math.PI); ctx.fill();
}
function sendCommand(s){ fetch('/command?status='+s).catch(console.error); }
function setCoords(){
var x=document.getElementById("posX").value;
var y=document.getElementById("posY").value;
fetch('/setCoords?x='+x+'&y='+y).catch(console.error);
}
function setCurrentPos(){
var x=document.getElementById("posX").value;
var y=document.getElementById("posY").value;
if(x===""||y===""){ alert('Fill X and Y'); return; }
fetch('/command?status=6&x='+x+'&y='+y).catch(console.error);
}
function setLPFBeta(){ var v=document.getElementById("lpfBeta").value; fetch('/setLPFBeta?beta='+v); }
function setErrMax(){ var v=document.getElementById("errMax").value; fetch('/setErrMax?err='+v); }
function setDelay(){ var v=document.getElementById("delayyy").value; fetch('/setDelay?delay='+v); }
function setMinErrMax(){ var v=document.getElementById("minErrMax").value; fetch('/setMinErrMax?minErr='+v); }
function setAbruptThreshold(){ var v=document.getElementById("abruptThreshold").value; fetch('/setAbruptThreshold?abrupt='+v); }
function setSmallThreshold(){ var v=document.getElementById("smallThreshold").value; fetch('/setSmallThreshold?small='+v); }
function setResetThreshold(){ var v=document.getElementById("resetThreshold").value; fetch('/setResetThreshold?reset='+v); }
function setPeakProximity(){ var v=document.getElementById("peakProximity").value; fetch('/setPeakProximity?peak='+v); }
function setCicleMax(){ var v=document.getElementById("cicleMax").value; fetch('/setCicleMax?cicle='+v); }
function setAxisFactor(axis){ var v=document.getElementById(axis+'Factor').value; if(!/^\d+$/.test(v)||+v<1||+v>8){alert('Factor must be an integer from 1 to 8');return;} fetch('/setAxis'+axis+'Factor?factor='+v); }
function setAzDeviation(){ var v=document.getElementById("azDeviation").value; fetch('/setAzDeviation?az='+v); }
function setTiltAngle(){ var v=document.getElementById("tiltAngle").value; fetch('/setTiltAngle?tilt='+v); }
function toggleAutoApply(){ var ck=document.getElementById("autoApply").checked ? 1 : 0; fetch('/setAutoApply?on='+ck); }
function recomputeAT(){ fetch('/recomputeAutoTune'); }
function applyAT(){ fetch('/applyAutoTune'); }
function updateNVSButton(enabled) {
var btn = document.getElementById("btnNVS");
btn.innerText = "NVS Autosave: " + (enabled ? "ACTIVADO" : "DESACTIVADO");
btn.style.background = enabled ? "#28a745" : "#6c757d";
}
function toggleNVS() {
fetch('/toggleNVS').then(r => r.json()).then(data => {
updateNVSButton(data.nvsEnabled);
}).catch(console.error);
}
window.onload=function(){
document.getElementById("lpfBeta").value="0.85";
document.getElementById("errMax").value="1500";
document.getElementById("delayyy").value="2";
document.getElementById("minErrMax").value="100";
document.getElementById("abruptThreshold").value="9000";
document.getElementById("smallThreshold").value="3000";
document.getElementById("resetThreshold").value="25000";
document.getElementById("peakProximity").value="0.015";
document.getElementById("cicleMax").value="3";
fetch('/getNVS').then(r => r.json()).then(data => {
updateNVSButton(data.nvsEnabled);
document.getElementById("cicleMax").value=data.cicleMax;
document.getElementById("XFactor").value=data.axisXFactor;
document.getElementById("YFactor").value=data.axisYFactor;
}).catch(() => updateNVSButton(true));
};
</script>
</head>
<body>
<h2>ESP32 Solar Tracker & Stepper Control</h2>
<div id="log"></div>
<div style="margin:10px 0;">
<button id="btnNVS" onclick="toggleNVS()" class="btn-secondary">NVS Autosave: (cargando...)</button>
</div>
<div class="container">
<div class="column">
<h2>Posiciones Actuales</h2>
<p><strong>X (Pasos):</strong> <span id="posXDisplay">0</span></p>
<p><strong>Y (Pasos):</strong> <span id="posYDisplay">0</span></p>
<h2>Visualización</h2>
<canvas id="positionPlot" width="380" height="380" style="border:1px solid #ccc;"></canvas>
</div>
<div class="column">
<h2>Control Manual y Coordenadas</h2>
<div>
<input type="number" id="posX" class="input-coord" placeholder="X (0 a 106000)" min="0" max="106000">
<input type="number" id="posY" class="input-coord" placeholder="Y (0 a 104000)" min="0" max="104000"><br>
<button onclick="setCoords()">Mover a Coordenadas</button>
<button onclick="setCurrentPos()" class="btn-secondary">Calibrar Pos Actual</button>
</div>
<div style="margin-top:12px;">
<h3>Parámetros de Fine-Tuning</h3>
<div class="param-group"><label>LPF Beta:</label> <input type="number" id="lpfBeta" step="0.01" min="0" max="1"> <button onclick="setLPFBeta()">Set</button></div>
<div class="param-group"><label>Error Max:</label> <input type="number" id="errMax" min="0"> <button onclick="setErrMax()">Set</button></div>
<div class="param-group"><label>Delay (s):</label> <input type="number" id="delayyy" min="0"> <button onclick="setDelay()">Set</button></div>
<div class="param-group"><label>Min Error Max:</label> <input type="number" id="minErrMax" min="0"> <button onclick="setMinErrMax()">Set</button></div>
<div class="param-group"><label>Abrupt Threshold:</label> <input type="number" id="abruptThreshold" min="0"> <button onclick="setAbruptThreshold()">Set</button></div>
<div class="param-group"><label>Small Threshold:</label> <input type="number" id="smallThreshold" min="0"> <button onclick="setSmallThreshold()">Set</button></div>
<div class="param-group"><label>Reset Threshold:</label> <input type="number" id="resetThreshold" min="0"> <button onclick="setResetThreshold()">Set</button></div>
<div class="param-group"><label>Peak Proximity (0-1):</label> <input type="number" id="peakProximity" step="0.005" min="0" max="1"> <button onclick="setPeakProximity()">Set</button></div>
<div class="param-group"><label>Cycle Max:</label> <input type="number" id="cicleMax" min="1"> <button onclick="setCicleMax()">Set</button></div>
<div class="param-group"><label>X allocation factor:</label> <input type="number" id="XFactor" min="1" max="8" value="3"> <button onclick="setAxisFactor('X')">Set</button></div>
<div class="param-group"><label>Y allocation factor:</label> <input type="number" id="YFactor" min="1" max="8" value="1"> <button onclick="setAxisFactor('Y')">Set</button></div>
<div class="param-group"><label>Azimuth Deviation (°):</label> <input type="number" id="azDeviation" step="0.01"> <button onclick="setAzDeviation()">Set</button></div>
<div class="param-group"><label>Tilt Angle (°):</label> <input type="number" id="tiltAngle" step="0.01"> <button onclick="setTiltAngle()">Set</button></div>
</div>
<div style="margin-top:12px;">
<h3>Comandos de Seguimiento</h3>
<button onclick="sendCommand(2)">Track Sun (Tiempo)</button>
<button onclick="sendCommand(3)">Fine-Tune (nomegusta)</button>
<button onclick="sendCommand(4)" class="btn-danger">Homing XY (StallGuard)</button>
<button onclick="sendCommand(5)">Mover a Centro</button>
<button onclick="sendCommand(10)" class="btn-secondary">Ver Config TMC</button>
<button onclick="sendCommand(11)">Modo Demo</button>
</div>
<div style="margin-top:12px;">
<h3>Auto-Tune Adaptativo</h3>
<div class="param-group"><label><input type="checkbox" id="autoApply" onchange="toggleAutoApply()"> Auto-apply cada 60s</label></div>
<div class="param-group">
<button onclick="recomputeAT()" class="btn-secondary">Recalcular (Log)</button>
<button onclick="applyAT()">Aplicar Umbrales</button>
</div>
</div>
<h2>Actualización de Firmware (OTA)</h2>
<form method="POST" action="/update" enctype="multipart/form-data">
<input type="file" name="update" accept=".bin">
<input type="submit" value="Subir Firmware" class="btn-secondary">
</form>
<h2>Opciones de Registro</h2>
<a href="/download" download="log.txt"><button class="btn-secondary">Descargar Log</button></a>
<a href="/clear"><button class="btn-danger">Limpiar Log</button></a>
</div>
</div>
</body>
</html>
)rawliteral";