Initial import of Microtracking project
This commit is contained in:
commit
60720c388c
2265 files changed
+1464696
No files matched your search
@@ -0,0 +1,75 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
|
||||
#ifndef PCB_ID
|
||||
#error "Define PCB_ID in platformio.ini, for example -DPCB_ID=0x01"
|
||||
#endif
|
||||
|
||||
static_assert(std::is_integral<decltype(PCB_ID)>::value, "PCB_ID must be an integer");
|
||||
static_assert(PCB_ID > 0 && PCB_ID <= 0xffffffffffffULL, "PCB_ID must be between 0x01 and 0xffffffffffff");
|
||||
|
||||
constexpr char cloudIdHexDigit(unsigned shift) {
|
||||
return "0123456789abcdef"[(static_cast<std::uint64_t>(PCB_ID) >> shift) & 0x0f];
|
||||
}
|
||||
|
||||
// Fixed 12-digit representation accepted by the existing hub registry.
|
||||
constexpr char CLOUD_DEVICE_ID[] = {
|
||||
'e', 's', 'p', '3', '2', '-',
|
||||
cloudIdHexDigit(44), cloudIdHexDigit(40), cloudIdHexDigit(36),
|
||||
cloudIdHexDigit(32), cloudIdHexDigit(28), cloudIdHexDigit(24),
|
||||
cloudIdHexDigit(20), cloudIdHexDigit(16), cloudIdHexDigit(12),
|
||||
cloudIdHexDigit(8), cloudIdHexDigit(4), cloudIdHexDigit(0), '\0'
|
||||
};
|
||||
|
||||
#if !defined(CLOUD_DEVICE_TOKEN) && __has_include("cloud_credentials.h")
|
||||
#include "cloud_credentials.h"
|
||||
#endif
|
||||
|
||||
#ifndef CLOUD_DEVICE_TOKEN
|
||||
#error "Define CLOUD_DEVICE_TOKEN in include/cloud_credentials.h; see cloud_credentials.example.h"
|
||||
#else
|
||||
constexpr bool cloudTokenIsHex(const char* token, unsigned index = 0) {
|
||||
return index == 64 ? token[index] == '\0' :
|
||||
(((token[index] >= '0' && token[index] <= '9') ||
|
||||
(token[index] >= 'a' && token[index] <= 'f')) && cloudTokenIsHex(token, index + 1));
|
||||
}
|
||||
static_assert(sizeof(CLOUD_DEVICE_TOKEN) == 65 && cloudTokenIsHex(CLOUD_DEVICE_TOKEN),
|
||||
"CLOUD_DEVICE_TOKEN must be the 64 lowercase hex characters issued for this PCB_ID");
|
||||
#endif
|
||||
|
||||
// ISRG Root X1: https://letsencrypt.org/certs/isrgrootx1.pem (expires 2035).
|
||||
// Both WSS and OTA verify the hostname/certificate; a valid NTP clock is needed.
|
||||
constexpr char CLOUD_ROOT_CA_PEM[] = R"PEM(-----BEGIN CERTIFICATE-----
|
||||
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
|
||||
-----END CERTIFICATE-----
|
||||
)PEM";
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
// Copy to cloud_credentials.h and use the 64-character token issued by the hub.
|
||||
// Never commit the provisioned token.
|
||||
#define CLOUD_DEVICE_TOKEN "REPLACE_WITH_64_LOWERCASE_HEX_CHARACTERS_FROM_THE_HUB"
|
||||
@@ -0,0 +1,133 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
|
||||
// Guard RAII: toma el mutex al entrar al alcance y lo libera al salir.
|
||||
// Garantiza emparejamiento lock/unlock incluso con return/early-exit.
|
||||
// No copiable: un handle tomado debe liberarse exactamente una vez.
|
||||
struct ScopedMutexGuard {
|
||||
SemaphoreHandle_t _m = nullptr;
|
||||
ScopedMutexGuard(SemaphoreHandle_t m) : _m(m) {
|
||||
if (_m && xSemaphoreTake(_m, portMAX_DELAY) != pdTRUE) {
|
||||
Serial.println("[FATAL] xSemaphoreTake fallo (mutex destruido?)");
|
||||
_m = nullptr; // no liberar lo que no se tomo
|
||||
}
|
||||
}
|
||||
~ScopedMutexGuard() {
|
||||
if (_m && xSemaphoreGive(_m) != pdTRUE) {
|
||||
Serial.println("[FATAL] xSemaphoreGive fallo (mutex no tomado?)");
|
||||
}
|
||||
}
|
||||
ScopedMutexGuard(const ScopedMutexGuard&) = delete;
|
||||
ScopedMutexGuard& operator=(const ScopedMutexGuard&) = delete;
|
||||
// Move: transfiere la posesion (evita doble liberacion).
|
||||
ScopedMutexGuard(ScopedMutexGuard&& other) : _m(other._m) { other._m = nullptr; }
|
||||
};
|
||||
|
||||
// Maximo de bytes de log copiados fuera de PSRAM hacia el heap interno
|
||||
// (String no usa PSRAM; el heap interno es ~320KB).
|
||||
constexpr size_t LOG_SEND_CHUNK = 60 * 1024;
|
||||
|
||||
// ==============================================================================
|
||||
// CONFIGURACIÓN GENERAL Y HARDWARE (Waveshare ESP32-S3-Nano)
|
||||
// ==============================================================================
|
||||
|
||||
#define VERBO 0
|
||||
|
||||
// --- Version de PCB: seleccionada mediante build_flags ---
|
||||
#ifndef PCB_VERSION
|
||||
#error "Define PCB_VERSION=1 o PCB_VERSION=2 en platformio.ini"
|
||||
#endif
|
||||
|
||||
// --- Pines Motores Paso a Paso (GPIO nativos ESP32-S3) ---
|
||||
#if PCB_VERSION == 1
|
||||
constexpr int MOTOR_X_STEP_PIN = 48;
|
||||
constexpr int MOTOR_X_DIRECTION_PIN = 47;
|
||||
#elif PCB_VERSION == 2
|
||||
constexpr int MOTOR_X_STEP_PIN = 8;
|
||||
constexpr int MOTOR_X_DIRECTION_PIN = 7;
|
||||
#else
|
||||
#error "PCB_VERSION no soportada: utiliza 1 o 2"
|
||||
#endif
|
||||
constexpr int MOTOR_Y_STEP_PIN = 6;
|
||||
constexpr int MOTOR_Y_DIRECTION_PIN = 5;
|
||||
|
||||
constexpr int PIN_MOTOR_EN = 21;
|
||||
constexpr int PIN_VOC = 18;
|
||||
|
||||
// --- Pines I2C (ADS122C04) ---
|
||||
constexpr int SDA_PIN = 11;
|
||||
constexpr int SCL_PIN = 12;
|
||||
|
||||
// --- Parámetros de UART para TMC2209 ---
|
||||
constexpr long TMC_SERIAL_BAUD_RATE = 250000;
|
||||
constexpr uint8_t TMC_REPLY_DELAY = 15;
|
||||
|
||||
// Configuración Driver X (Motor 0)
|
||||
constexpr uint8_t RUN_CURRENT_PERCENT0 = 29;
|
||||
constexpr int32_t VELOCITY0 = 4200;
|
||||
constexpr uint8_t STALL_GUARD_THRESHOLD0 = 200;
|
||||
|
||||
// Configuración Driver Y (Motor 1)
|
||||
constexpr uint8_t RUN_CURRENT_PERCENT1 = 29;
|
||||
constexpr int32_t VELOCITY1 = 4200;
|
||||
constexpr uint8_t STALL_GUARD_THRESHOLD1 = 200;
|
||||
|
||||
// Límites de carrera mecánicos
|
||||
constexpr int32_t LIMITX = -120000;
|
||||
constexpr int32_t LIMITY = -116000;
|
||||
constexpr int32_t HIST_VAL = 100;
|
||||
|
||||
// Velocidades de operación normal
|
||||
constexpr float MOTOR_MAX_SPEED = 6200.0f;
|
||||
constexpr float MOTOR_ACCELERATION = 9000.0f;
|
||||
|
||||
// --- Red WiFi y NTP (WPA2-Enterprise / PEAP) ---
|
||||
extern const char* WIFI_SSID;
|
||||
extern const char* WIFI_EAP_IDENTITY;
|
||||
extern const char* WIFI_EAP_USERNAME;
|
||||
extern const char* WIFI_EAP_PASSWORD;
|
||||
|
||||
constexpr const char* NTP_SERVER = "pool.ntp.org";
|
||||
constexpr long GMT_OFFSET_SEC = -6 * 3600; // UTC-6 (Aguascalientes)
|
||||
constexpr int DAYLIGHT_OFFSET_SEC = 0;
|
||||
|
||||
// --- Coordenadas Geográficas (Aguascalientes) ---
|
||||
constexpr float SITE_LATITUDE = 21.88f; // °N
|
||||
constexpr float SITE_LONGITUDE = -102.29f; // °E (negativo = Oeste)
|
||||
|
||||
// --- Capacidad de Log en PSRAM ---
|
||||
constexpr size_t PSRAM_LOG_CAPACITY = 2 * 1024 * 1024; // 2 MB
|
||||
|
||||
// --- NVS (Persistencia de coordenadas) ---
|
||||
constexpr int32_t NVS_THRESHOLD = 1000;
|
||||
constexpr unsigned long NVS_CHECK_PERIOD_MS = 500;
|
||||
|
||||
// --- Servidor Cloud / Docker Hub (WebSocket Inverso) ---
|
||||
constexpr bool CLOUD_SYNC_ENABLED = true;
|
||||
extern const char* CLOUD_SERVER_HOST; // Definido en main.cpp
|
||||
constexpr uint16_t CLOUD_SERVER_PORT = 443;
|
||||
constexpr const char* CLOUD_SERVER_PATH = "/ws/esp32";
|
||||
|
||||
// --- F5: Re-alineación astronómica periódica --------------------------------
|
||||
constexpr uint32_t F5_DRIFT_PERIOD_MS = 300000UL; // 5 min entre chequeos de deriva
|
||||
constexpr int32_t F5_DRIFT_THRESHOLD = 2000; // Pasos máx. de deriva antes de re-alinear
|
||||
|
||||
// --- OTA / version de firmware ---------------------------------------------
|
||||
// Se reporta al hub al (re)conectar como linea "VER:<version>". El hub la
|
||||
// compara con la imagen subida; cuando hay diferencia (o auto-update ON) manda
|
||||
// "OTA:<url>" por el canal WSS para que el dispositivo descargue + flashee +
|
||||
// se reinicie con la nueva firmware. Sube esta version antes de cada build.
|
||||
#define FIRMWARE_VERSION "1.1.5-ws"
|
||||
// 1.1.4: corrige dC, rampa cerca del pico y escape de la zona muerta.
|
||||
// 1.1.3: corrige el terminador duplicado del encabezado de autenticacion WebSocket.
|
||||
// 1.1.2: PCB_ID configurable, identidad/token por tarjeta y TLS validado para cloud/OTA.
|
||||
// la 1.0.1 cambie valores de control del tuning engine para mejorar la estabilidad y evitar sobrepasos.
|
||||
// la 1.0.2 agrega soporte OTA y comandos remotos desde el hub (Fine-Tune, Set Coords, etc.)
|
||||
// la 1.0.3 corrige el orden de los ejes en la visualización del canvas y en el log de posición enviada al hub.
|
||||
// la 1.1.0: F1 reset CmaxN, F2 auto-arranque, F3 anti-oscilación, F4 filtro picos, F5 re-alineación, F6 telemetría
|
||||
// la 1.1.1: mutex en buffer de log y movimiento (evita corrupción por acceso concurrente),
|
||||
// elimina objetos FlexyStepper/TMC2209 duplicados y aplica extensión de signo
|
||||
// (24 bits) a los canales del ADC: V/P/C ya no se leen como ~2^24 de noche.
|
||||
@@ -0,0 +1,207 @@
|
||||
#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";
|
||||
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
// Copy to wifi_credentials.h and configure locally. Never commit credentials.
|
||||
const char* WIFI_SSID = "YOUR_WIFI_SSID";
|
||||
const char* WIFI_EAP_IDENTITY = "YOUR_EAP_IDENTITY";
|
||||
const char* WIFI_EAP_USERNAME = "YOUR_EAP_USERNAME";
|
||||
const char* WIFI_EAP_PASSWORD = "YOUR_EAP_PASSWORD";
|
||||
Reference in new issue
Block a user