Files
SC-F001/main/webpage_minified.html
Thaddeus Hughes 012d28ae14 Ironed out tons of stuff on the webserver
Logging, time sync, collapsible menus, oh my!
2025-12-29 22:21:43 -06:00

22 lines
8.3 KiB
HTML

<!doctype html><title>Control Panel</title><style>*{color:#eee;background-color:#111;font-family:sans-serif}input,button{text-align:right;box-sizing:border-box;background-color:#333;border:1px solid #666;width:100%;font-family:monospace}button{text-align:center}.changed{color:#111!important;background-color:#3d3!important}#commit_btn{color:#111;cursor:pointer;background-color:#3d3;border:none;width:100%;margin-top:10px;padding:10px;font-weight:700}#commit_btn[disabled]{color:#888;cursor:not-allowed;background-color:#444}table{border-collapse:collapse;width:100%}td{border-bottom:1px solid #222;padding:8px}tr:hover{background-color:#1a1a1a}summary{text-align:left;color:#ccc;background-color:#723;padding:.3rem;font-weight:700}</style></head><body><button disabled id=commit_btn onclick=commit_params()>Save Changes</button><table><tr><td>Schedule Start</td><td><input id=move_start onchange=changeSchedule(this) type=time></td></tr><tr><td>Schedule End</td><td><input id=move_end onchange=changeSchedule(this) type=time></td></tr><tr><td># Moves/Day</td><td><input id=num_moves min=0 onchange=changeSchedule(this) type=number></td></tr><tr><td>Move Distance</td><td><input id=drive_dist min=0 onchange=changeSchedule(this) type=number></td><td>ft</td></tr><tr><td>Jack Height</td><td><input id=jack_dist min=0 onchange=changeSchedule(this) type=number></td><td>in</td></tr><tr><td>Time</td><td><input id=in_time onchange=markChanged(this) step=1 type=datetime-local></td><td><button id=now_btn onclick=setTimeToNow()>&lt; NOW</button></td></tr><tr><td>Battery</td><td><input id=voltage readonly></td><td>V</td></tr><tr><td></td><td><button onclick="cmd('start')">START MOVE</button></td></tr><tr><td></td><td><button onclick="cmd('undo')">UNDO MOVE</button></td></tr><tr><td></td><td><button onclick="cmd('stop')" style=background-color:#800>STOP MOVE</button></td></tr></table><br><details><summary>DANGER ZONE</summary> <table><tr><td>Program RF Remote</td><td><button onclick=programRF(0) style=width:40%>Fwd</button> <button onclick=programRF(1) style=width:40%>Rev</button> <button onclick=programRF(2) style=width:40%>Up</button> <button onclick=programRF(3) style=width:40%>Down</button> <button onclick=programRF(-1)>Cancel Learning</button></td></tr><tr><td>Calibration</td><td><button id=cal_jack_btn>Jack Calibration</button> <button id=cal_drive_btn>Drive Calibration</button></td></tr><tr><td>Firmware</td><td><input accept=.bin id=firmware_file type=file> <button id=upload_btn onclick=uploadFirmware()>Upload Firmware</button></td></tr><tr><td>Log File</td><td><button id=log_btn onclick=downloadLogFile()>Download Log</button></td></tr></table> <table id=table></table></details><script>let param_values=[],param_names=[],param_units=[];function cmd(x){if(x===`start`&&!confirm(`Will begin moving - please confirm.`))return;let xhr=new XMLHttpRequest;xhr.open(`POST`,`./cmd`,!0),xhr.setRequestHeader(`Content-Type`,`application/json`),xhr.send(JSON.stringify({cmd:x})),xhr.onload=function(){console.log(xhr)}}function ge(x){return document.getElementById(x)}
// Highlight changed inputs and enable the save button
function markChanged(el){el.classList.add(`changed`),ge(`commit_btn`).disabled=!1}function toFixed(input,n){let num=parseFloat(input);return isNaN(num)?null:Number(num.toFixed(n))}
// --- 1. GET DATA ---
function fetchStatus(){let xhr=new XMLHttpRequest;xhr.open(`GET`,`./status`,!0),xhr.onload=function(){if(xhr.status===200)try{console.log(xhr.responseText);let data=JSON.parse(xhr.responseText);
// Update time field if available
if(data.time){let date=(/* @__PURE__ */ new Date(data.time*1e3)).toISOString().slice(0,19);ge(`in_time`).value=date}ge(`voltage`).value=toFixed(data.battery,2),param_values=data.values||[],param_names=data.names||[],param_units=data.units||[],data.rtc_set||(ge(`in_time`).classList.add(`error`),confirm(`Clock not set. Sync with this device's clock?`)&&(setTimeToNow(),commit_time()))}catch(e){console.error(`Error parsing JSON`,e)}
// Always render table even if request fails or data is empty
renderTable()},xhr.onerror=function(e){console.error(`Network error`,e),renderTable()},xhr.send()}function s_to_hhmm(s){return`${String(Math.floor(s/3600)).padStart(2,`0`)}:${String(Math.floor(s%3600/60)).padStart(2,`0`)}`}function renderTable(){let table=ge(`table`);
// Clear existing parameter rows (rows between index 0 and the last row)
for(;table.rows.length>0;)table.deleteRow(0);param_names.forEach((name,i)=>{let row=table.insertRow(table.rows.length);row.innerHTML=`
<td>${i}</td>
<td>${param_names[i]!==void 0&&param_names[i]!==null?param_names[i]:`null`}</td>
<td><input type="number" id="in_${i}" value="${param_values[i]!==void 0&&param_values[i]!==null?param_values[i]:`null`}" oninput="markChanged(this)"></td>
<td>${param_units[i]||``}</td>
`}),ge(`num_moves`).value=param_values[1],ge(`move_start`).value=s_to_hhmm(param_values[2]),ge(`move_end`).value=s_to_hhmm(param_values[3]),ge(`drive_dist`).value=param_values[4],ge(`jack_dist`).value=param_values[5]}function changeSchedule(e){if(markChanged(e),e.id==`num_moves`&&(ge(`in_1`).value=e.value,markChanged(ge(`in_1`))),e.id==`move_start`){let[hours,minutes]=e.value.split(`:`).map(Number);ge(`in_2`).value=hours*3600+minutes*60,markChanged(ge(`in_2`))}if(e.id==`move_end`){let[hours,minutes]=e.value.split(`:`).map(Number);ge(`in_3`).value=hours*3600+minutes*60,markChanged(ge(`in_3`))}e.id==`drive_dist`&&(ge(`in_4`).value=e.value,markChanged(ge(`in_4`))),e.id==`jack_dist`&&(ge(`in_5`).value=e.value,markChanged(ge(`in_5`)))}function setTimeToNow(){e=ge(`in_time`),markChanged(e),e.value=(/* @__PURE__ */ new Date()).toLocaleString(`sv-SE`)}function commit_time(){let xhr=new XMLHttpRequest,[datePart,timePart]=ge(`in_time`).value.split(`T`),[year,month,day]=datePart.split(`-`).map(Number),[hour,minute,second=0]=timePart.split(`:`).map(Number),epoch=Math.floor(Date.UTC(year,month-1,day,hour,minute,second)/1e3);xhr.open(`POST`,`./st`,!0),xhr.setRequestHeader(`Content-Type`,`application/json`),xhr.onload=function(){xhr.status===200&&ge(`in_time`).classList.remove(`changed`),document.querySelectorAll(`input.changed`).length===0?ge(`commit_btn`).disabled=!0:ge(`commit_btn`).disabled=!1},xhr.send(epoch.toString())}
// --- 2. POST DATA ---
function commit_params(){ge(`commit_btn`).disabled=!0;let changedInputs=document.querySelectorAll(`input.changed`);if(sp={},changedInputs.forEach(input=>{if(input.id===`in_time`)commit_time();else if(input.id.startsWith(`in_`)){
// Parameter handling
let id=input.id.split(`_`)[1],val=input.value.toLowerCase()===`null`?null:parseFloat(input.value);sp[id]=val}}),Object.keys(sp).length!==0){let xhr2=new XMLHttpRequest;xhr2.open(`POST`,`./sp`,!0),xhr2.setRequestHeader(`Content-Type`,`application/json`),xhr2.onload=function(){xhr2.status===200?changedInputs.forEach(input=>{input.id!==`in_time`&&input.classList.remove(`changed`)}):ge(`commit_btn`).disabled=!1},xhr2.send(JSON.stringify(sp))}fetchStatus()}function uploadFirmware(){let fileInput=ge(`firmware_file`);if(!fileInput.files.length){alert(`No file selected`);return}let file=fileInput.files[0],xhr=new XMLHttpRequest;xhr.open(`POST`,`./ota`,!0),xhr.setRequestHeader(`Content-Type`,`application/octet-stream`),xhr.onload=function(){xhr.status===200?alert(`Upload successful. Device may reboot.`):alert(`Upload failed: `+xhr.status)},xhr.onerror=function(){alert(`Network error during upload`)},xhr.send(file)}function programRF(i){let xhr=new XMLHttpRequest;xhr.open(`POST`,`./prf`,!0),xhr.setRequestHeader(`Content-Type`,`application/json`),xhr.send(i.toString()),xhr.status}async function downloadLogFile(){try{let response=await fetch(`./log`);if(!response.ok)throw Error(`Network response was not ok`);let blob=await response.blob(),now=/* @__PURE__ */ new Date,formattedDate=`${String(now.getDate()).padStart(2,`0`)}${[`JAN`,`FEB`,`MAR`,`APR`,`MAY`,`JUN`,`JUL`,`AUG`,`SEP`,`OCT`,`NOV`,`DEC`][now.getMonth()]}${now.getFullYear()}-${String(now.getHours()).padStart(2,`0`)}${String(now.getMinutes()).padStart(2,`0`)}`,url=URL.createObjectURL(blob),a=document.createElement(`a`);a.href=url,a.download=`storage-${formattedDate}.bin`,document.body.appendChild(a),a.click(),document.body.removeChild(a),URL.revokeObjectURL(url)}catch(error){console.error(`Download failed:`,error)}}
// Initial Load
window.onload=fetchStatus;</script></body></html>