Add Serial Terminal App & WebUi Theme

This commit is contained in:
dark98
2026-09-24 22:34:33 +01:00
parent dc3b72bdbd
commit 4bf31f908a
17 changed files with 1040 additions and 234 deletions
+115 -2
View File
@@ -14,11 +14,124 @@ static void reboot_task(void *arg)
esp_restart();
}
static const char OTA_HTML[] =
"<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"UTF-8\">"
"<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">"
"<title>ABrobot OTA Firmware Update</title>"
"<style>"
":root{--bg:#0b0f17;--card:#141b26;--border:#243042;--text:#e6edf3;--muted:#8b949e;--accent:#00d2ff;--accent2:#3a7bd5;--green:#00e676;--amber:#ffb300;--red:#ff5252;--term-bg:#080b10;}"
"*{box-sizing:border-box;margin:0;padding:0;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;}"
"body{background:var(--bg);color:var(--text);padding:14px;display:flex;justify-content:center;min-height:100vh;}"
".container{width:100%;max-width:920px;display:flex;flex-direction:column;gap:14px;}"
".header{background:linear-gradient(135deg,rgba(58,123,213,0.18),rgba(0,210,255,0.08));border:1px solid var(--border);border-radius:12px;padding:12px 18px;display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:10px;}"
".title-group{display:flex;align-items:center;gap:10px;}"
".status-dot{width:10px;height:10px;border-radius:50%;background:var(--green);box-shadow:0 0 8px var(--green);}"
".title{font-size:1.15rem;font-weight:700;letter-spacing:0.5px;background:linear-gradient(90deg,#00d2ff,#3a7bd5);-webkit-background-clip:text;-webkit-text-fill-color:transparent;}"
".nav-links{display:flex;gap:8px;align-items:center;}"
".nav-links a{color:var(--muted);text-decoration:none;font-size:0.85rem;padding:6px 12px;border-radius:6px;transition:all 0.2s;}"
".nav-links a:hover{color:var(--text);background:rgba(255,255,255,0.05);}"
".nav-links a.active{color:var(--accent);font-weight:600;background:rgba(0,210,255,0.1);border:1px solid rgba(0,210,255,0.25);}"
".card{background:var(--card);border:1px solid var(--border);border-radius:12px;padding:20px;display:flex;flex-direction:column;gap:16px;box-shadow:0 4px 16px rgba(0,0,0,0.3);}"
".card-title{font-size:1rem;font-weight:600;color:var(--text);display:flex;align-items:center;justify-content:space-between;}"
".badge{background:#1a2332;border:1px solid var(--border);border-radius:6px;padding:4px 8px;font-size:0.75rem;font-family:monospace;color:#58a6ff;}"
".dropzone{background:#0a0e14;border:2px dashed var(--border);border-radius:10px;padding:32px 20px;text-align:center;cursor:pointer;transition:all 0.2s;display:flex;flex-direction:column;align-items:center;gap:10px;}"
".dropzone:hover,.dropzone.dragover{border-color:var(--accent);background:rgba(0,210,255,0.04);box-shadow:0 0 12px rgba(0,210,255,0.15);}"
".drop-icon{font-size:2.2rem;line-height:1;}"
".drop-text{font-size:0.92rem;font-weight:600;color:var(--text);}"
".drop-subtext{font-size:0.8rem;color:var(--muted);}"
".file-info{display:none;background:#0a0e14;border:1px solid var(--border);border-radius:8px;padding:12px 16px;align-items:center;justify-content:space-between;}"
".file-meta{display:flex;flex-direction:column;gap:3px;}"
".file-name{font-size:0.9rem;font-weight:600;color:var(--accent);font-family:monospace;}"
".file-size{font-size:0.78rem;color:var(--muted);}"
"button{cursor:pointer;background:linear-gradient(180deg,#21262d,#161b22);border:1px solid #30363d;border-radius:8px;color:var(--text);padding:9px 16px;font-size:0.88rem;font-weight:600;transition:all 0.2s;display:inline-flex;align-items:center;justify-content:center;gap:6px;}"
"button:hover{background:#30363d;border-color:var(--muted);}"
"button.primary{background:linear-gradient(135deg,var(--accent2),var(--accent));border:none;color:#0a0d14;font-weight:700;padding:12px 20px;font-size:0.95rem;}"
"button.primary:hover{opacity:0.92;box-shadow:0 0 14px rgba(0,210,255,0.4);}"
"button:disabled{opacity:0.45;cursor:not-allowed;box-shadow:none;}"
".progress-wrap{display:none;flex-direction:column;gap:8px;}"
".progress-header{display:flex;justify-content:space-between;font-size:0.84rem;color:var(--muted);}"
".progress-bar{background:var(--term-bg);border:1px solid var(--border);border-radius:8px;height:24px;overflow:hidden;position:relative;}"
".progress-fill{background:linear-gradient(90deg,var(--accent2),var(--accent));height:100%;width:0%;transition:width 0.15s ease;box-shadow:0 0 10px rgba(0,210,255,0.4);}"
".progress-pct{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);font-size:0.75rem;font-weight:700;font-family:monospace;color:#fff;text-shadow:0 1px 2px rgba(0,0,0,0.8);}"
".msg-box{padding:12px 14px;border-radius:8px;font-size:0.86rem;line-height:1.45;display:none;}"
".msg-info{background:rgba(58,123,213,0.12);border:1px solid rgba(58,123,213,0.35);color:#79c0ff;}"
".msg-success{background:rgba(0,230,118,0.12);border:1px solid rgba(0,230,118,0.35);color:#7ee787;}"
".msg-error{background:rgba(255,82,82,0.12);border:1px solid rgba(255,82,82,0.35);color:#ff7b72;}"
".info-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:10px;}"
".info-item{background:#0a0e14;border:1px solid var(--border);border-radius:8px;padding:12px;display:flex;flex-direction:column;gap:4px;}"
".info-label{font-size:0.75rem;color:var(--muted);text-transform:uppercase;letter-spacing:0.5px;}"
".info-val{font-size:0.85rem;color:var(--text);font-weight:500;}"
"</style></head><body>"
"<div class=\"container\">"
"<div class=\"header\">"
" <div class=\"title-group\">"
" <div class=\"status-dot\" id=\"statusDot\"></div>"
" <div class=\"title\">ESP32-C3 Firmware Update</div>"
" </div>"
" <div class=\"nav-links\">"
" <a href=\"/\">Wi-Fi Setup</a>"
" <a href=\"/serial\">Serial Terminal</a>"
" <a href=\"/ota\" class=\"active\">OTA Update</a>"
" </div>"
"</div>"
"<div class=\"card\">"
" <div class=\"card-title\"><span>Over-The-Air Flasher</span><span class=\"badge\">OTA 2.0 Engine</span></div>"
" <form id=\"otaForm\" onsubmit=\"startUpload(event)\" style=\"display:flex;flex-direction:column;gap:16px;\">"
" <input type=\"file\" id=\"fileInput\" accept=\".bin,application/octet-stream\" style=\"display:none;\" onchange=\"fileChosen()\">"
" <div id=\"dropArea\" class=\"dropzone\" onclick=\"document.getElementById('fileInput').click()\">"
" <div class=\"drop-icon\">⚡</div>"
" <div class=\"drop-text\">Click or Drag & Drop Firmware Image (.bin)</div>"
" <div class=\"drop-subtext\">Select the compiled application binary file to flash to ESP32-C3</div>"
" </div>"
" <div id=\"fileCard\" class=\"file-info\">"
" <div class=\"file-meta\">"
" <span class=\"file-name\" id=\"fileNameText\">firmware.bin</span>"
" <span class=\"file-size\" id=\"fileSizeText\">0 KB</span>"
" </div>"
" <button type=\"button\" onclick=\"document.getElementById('fileInput').click()\">Change</button>"
" </div>"
" <div id=\"progressSection\" class=\"progress-wrap\">"
" <div class=\"progress-header\">"
" <span id=\"progressStatus\">Uploading binary...</span>"
" <span id=\"bytesTransferred\">0 KB / 0 KB</span>"
" </div>"
" <div class=\"progress-bar\">"
" <div class=\"progress-fill\" id=\"progressFill\"></div>"
" <div class=\"progress-pct\" id=\"progressPct\">0%</div>"
" </div>"
" </div>"
" <button type=\"submit\" class=\"primary\" id=\"btnUpload\" disabled>Start Firmware Update</button>"
" </form>"
" <div id=\"statusMsg\" class=\"msg-box\"></div>"
"</div>"
"<div class=\"card\">"
" <div class=\"card-title\"><span>OTA Safety Checklist</span></div>"
" <div class=\"info-grid\">"
" <div class=\"info-item\"><span class=\"info-label\">Dual Partition</span><span class=\"info-val\">Safe rollback on boot failure</span></div>"
" <div class=\"info-item\"><span class=\"info-label\">Flash Verification</span><span class=\"info-val\">SHA-256 & image headers verified</span></div>"
" <div class=\"info-item\"><span class=\"info-label\">Power Notice</span><span class=\"info-val\">Do not remove power during flash</span></div>"
" <div class=\"info-item\"><span class=\"info-label\">Auto Restart</span><span class=\"info-val\">Reboots automatically after install</span></div>"
" </div>"
"</div>"
"</div>"
"<script>"
"const fileIn=document.getElementById('fileInput'),dropArea=document.getElementById('dropArea'),fileCard=document.getElementById('fileCard'),fileName=document.getElementById('fileNameText'),fileSize=document.getElementById('fileSizeText'),progSec=document.getElementById('progressSection'),progFill=document.getElementById('progressFill'),progPct=document.getElementById('progressPct'),progStatus=document.getElementById('progressStatus'),bytesText=document.getElementById('bytesTransferred'),btnUp=document.getElementById('btnUpload'),msg=document.getElementById('statusMsg'),statusDot=document.getElementById('statusDot');"
"function showMsg(text,type){msg.className='msg-box msg-'+(type||'info');msg.textContent=text;msg.style.display='block';}"
"function formatBytes(bytes){if(bytes<1024)return bytes+' B';if(bytes<1048576)return(bytes/1024).toFixed(1)+' KB';return(bytes/1048576).toFixed(2)+' MB';}"
"function fileChosen(){if(fileIn.files.length>0){const f=fileIn.files[0];fileName.textContent=f.name;fileSize.textContent=formatBytes(f.size);dropArea.style.display='none';fileCard.style.display='flex';btnUp.disabled=false;showMsg('Ready to flash \"'+f.name+'\" ('+formatBytes(f.size)+'). Click \"Start Firmware Update\" to proceed.','info');}}"
"['dragenter','dragover'].forEach(ev=>{dropArea.addEventListener(ev,e=>{e.preventDefault();dropArea.classList.add('dragover');});});"
"['dragleave','drop'].forEach(ev=>{dropArea.addEventListener(ev,e=>{e.preventDefault();dropArea.classList.remove('dragover');});});"
"dropArea.addEventListener('drop',e=>{if(e.dataTransfer.files.length>0){fileIn.files=e.dataTransfer.files;fileChosen();}});"
"function startUpload(e){e.preventDefault();if(!fileIn.files.length)return;const file=fileIn.files[0];btnUp.disabled=true;btnUp.textContent='Flashing in Progress...';progSec.style.display='flex';progFill.style.width='0%';progPct.textContent='0%';showMsg('Uploading firmware payload... Please keep device powered on.','info');const xhr=new XMLHttpRequest();xhr.open('POST','/ota',true);xhr.setRequestHeader('Content-Type','application/octet-stream');xhr.upload.onprogress=ev=>{if(ev.lengthComputable){const pct=Math.round((ev.loaded/ev.total)*100);progFill.style.width=pct+'%';progPct.textContent=pct+'%';progStatus.textContent=pct<100?'Uploading firmware binary...':'Writing flash & verifying integrity...';bytesText.textContent=formatBytes(ev.loaded)+' / '+formatBytes(ev.total);}};"
"xhr.onload=()=>{if(xhr.status===200){progFill.style.width='100%';progPct.textContent='100%';progStatus.textContent='OTA Flash Complete!';statusDot.style.background='#00e676';showMsg('Firmware updated successfully! Device is now rebooting... Please wait 5 seconds and refresh.','success');let countdown=5;setInterval(()=>{if(countdown>0){progStatus.textContent='Rebooting in '+countdown+'s...';countdown--;}else{window.location.href='/';}},1000);}else{statusDot.style.background='#ff5252';showMsg('OTA Flash Failed: '+(xhr.responseText||'Server Error '+xhr.status),'error');btnUp.disabled=false;btnUp.textContent='Retry Firmware Update';}};"
"xhr.onerror=()=>{statusDot.style.background='#ff5252';showMsg('Upload network error occurred. Please verify Wi-Fi connection and retry.','error');btnUp.disabled=false;btnUp.textContent='Retry Firmware Update';};"
"xhr.send(file);}"
"</script></body></html>";
static esp_err_t ota_get(httpd_req_t *request)
{
const char *page = "<!doctype html><html><head><meta name=viewport content=\"width=device-width\"><title>ABrobot OTA</title><style>body{font:16px sans-serif;max-width:480px;margin:2em auto;padding:0 1em}input,button{font:inherit;padding:.6em;width:100%;box-sizing:border-box}button{margin-top:1em}progress{width:100%;margin-top:1em}</style></head><body><h1>Firmware update</h1><form id=form><input id=file type=file accept=\".bin,application/octet-stream\" required><button type=submit>Upload firmware</button></form><progress id=progress value=0 max=100 hidden></progress><p id=message></p><script>form.onsubmit=async e=>{e.preventDefault();if(!file.files.length)return;progress.hidden=false;message.textContent='Uploading...';try{let r=await fetch('/ota',{method:'POST',headers:{'Content-Type':'application/octet-stream'},body:file.files[0]});if(!r.ok)throw Error(await r.text());progress.value=100;message.textContent='Update installed. Rebooting...'}catch(e){message.textContent='Update failed: '+e.message;progress.hidden=true}}</script></body></html>";
httpd_resp_set_type(request, "text/html");
return httpd_resp_send(request, page, HTTPD_RESP_USE_STRLEN);
return httpd_resp_send(request, OTA_HTML, HTTPD_RESP_USE_STRLEN);
}
static esp_err_t ota_post(httpd_req_t *request)