Files
SC-F001/main/webpage_minified.html
2025-12-30 20:47:25 -06:00

10 lines
12 KiB
HTML

<!doctype html><title>Control Panel</title><meta content="width=device-width,initial-scale=1.0" name=viewport><style>#wrapper{text-align:center;box-sizing:border-box}#content{max-width:500px;margin:auto;padding:0 10px}body{text-align:center;margin:0;padding:0}*{color:#2f2f2f;background-color:#fff;font-family:Noto Sans,Verdana,sans-serif;font-size:1.2rem}input,button{text-align:right;box-sizing:border-box;background-color:#efede9;border:1px solid #ba965b;border-radius:5px;width:100%}input[type=text],input[type=number]{font-family:monospace}button{text-align:center}.changed,#commit_btn{color:#fff!important;background-color:#2a493d!important}#commit_btn{cursor:pointer;border:none;width:100%;margin-top:10px;padding:10px;font-weight:700}#commit_btn[disabled]{color:#888;cursor:not-allowed;background-color:#444!important}table{border-collapse:collapse;text-align:left;width:100%}td{border-bottom:1px solid #efede9;padding:8px}summary{text-align:left;color:#fff;background-color:#723;border-radius:5px;padding:.3rem;font-weight:700}.cmd{border:none;font-size:1.5rem}#msg{text-align:center}h1{font-size:2.5rem}@media screen and (width<=350px){#content{max-width:100%;padding:0 5px}table tr td{box-sizing:border-box;width:100%;display:block}table tr{margin-bottom:10px;display:block}}</style></head><body><div id=wrapper><div id=content><h1>CluckCommand</h1><table><tr><td><button onclick="sendCommand('start')" class=cmd>START</button></td><td><button onclick="sendCommand('stop')" class=cmd style=color:#fff;background-color:#723>STOP</button></td><td><button onclick="sendCommand('undo')" class=cmd>UNDO</button></td></tr></table><table><tr><td colspan=3><input id=msg readonly></td></tr><tr><td colspan=3><input id=in_time onchange=markChanged(this) step=1 type=datetime-local> <button id=now_btn onclick=setTimeToNow()>Sync Time</button></td></tr><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>Remain. Distance (ft)</td><td><input id=remaining_dist onchange=markChanged(this) type=number></td></tr><tr><td>Move Distance (ft)</td><td><input id=drive_dist min=0 onchange=changeSchedule(this) type=number></td></tr><tr><td>Jack Height (in)</td><td><input id=jack_dist min=0 onchange=changeSchedule(this) type=number></td></tr><tr><td>Battery (V)</td><td><input id=voltage readonly></td></tr><tr><td>Program RF Remote</td><td><button onclick=programRFSequence()>Program All Buttons</button></td></tr></table><button disabled id=commit_btn onclick=commitParams()>Save Changes</button><br><br><details><summary>DANGER ZONE</summary> <table><tr><td>Calibration</td><td><button onclick="calibrate('jack')">Jack Calibration</button> <button onclick="calibrate('drive')">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> <button onclick="sendCommand('reboot')" class=cmd style=color:#fff;background-color:#723>REBOOT</button></details></div></div><script>let paramValues=[],paramNames=[],paramUnits=[];const ge=id=>document.getElementById(id);async function sendCommand(cmdName){cmdName===`start`&&!confirm(`Will begin moving - please confirm.`)||cmdName===`reboot`&&!confirm(`Device will reboot - clearing clock and distance. Are you sure?`)||await fetch(`./cmd`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({cmd:cmdName})})}async function calibrate(axis){await fetch(`./cmd`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({cmd:`cal_${axis}_start`})});let amt=prompt(`Press button on mover. Press button again to stop it. Then, type in actual travelled distance here in inches:`);if(!isNaN(parseFloat(amt))){let response=await fetch(`./cmd`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({cmd:`cal_get`})});if(response.ok){let data=await response.json();axis===`drive`?(markChanged(ge(`in_6`)).value=data.e/amt,markChanged(ge(`in_7`)).value=data.t/amt*1.2):markChanged(ge(`in_8`)).value=data.t/amt}}}function markChanged(el){return el.classList.add(`changed`),ge(`commit_btn`).disabled=!1,el}function toFixed(input,n){let num=parseFloat(input);return isNaN(num)?null:Number(num.toFixed(n))}async function fetchStatus(){try{let response=await fetch(`./status`);if(!response.ok)return;let data=await response.json();if(console.log(data),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),ge(`remaining_dist`).value=toFixed(data.remaining_dist,2),ge(`msg`).value=data.msg,paramValues=data.values||[],paramNames=data.names||[],paramUnits=data.units||[],ge(`commit_btn`).disabled=!0,data.rtc_set||(ge(`in_time`).classList.add(`error`),confirm(`Clock not set. Sync with this device's clock?`)&&(setTimeToNow(),commitTime()))}catch(e){console.error(`Error parsing JSON`,e)}renderTable()}function secondsToHHMM(seconds){return`${String(Math.floor(seconds/3600)).padStart(2,`0`)}:${String(Math.floor(seconds%3600/60)).padStart(2,`0`)}`}function renderTable(){let table=ge(`table`);for(;table.rows.length>0;)table.deleteRow(0);paramNames.forEach((name,i)=>{let row=table.insertRow(table.rows.length),pname=paramNames[i]!==void 0&&paramNames[i]!==null?paramNames[i]:`null`,pval=paramValues[i]!==void 0&&paramValues[i]!==null?paramValues[i]:`null`;row.innerHTML=`
<td>${i}</td>
<td>${pname}</td>
<td><input type="${typeof pval==`number`?`number`:`text`}" id="in_${i}" value="${pval}" oninput="markChanged(this)"></td>
<td>${paramUnits[i]||``}</td>
`});for(let input of document.getElementsByTagName(`input`))input.addEventListener(`click`,e=>{e.target.select()});ge(`num_moves`).value=paramValues[1],ge(`move_start`).value=secondsToHHMM(paramValues[2]),ge(`move_end`).value=secondsToHHMM(paramValues[3]),ge(`drive_dist`).value=paramValues[4],ge(`jack_dist`).value=paramValues[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(){let e=ge(`in_time`);markChanged(e),e.value=(/* @__PURE__ */ new Date()).toLocaleString(`sv-SE`)}async function commitTime(){let[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);(await fetch(`./st`,{method:`POST`,headers:{"Content-Type":`application/json`},body:epoch.toString()})).ok&&ge(`in_time`).classList.remove(`changed`),document.querySelectorAll(`input.changed`).length===0?ge(`commit_btn`).disabled=!0:ge(`commit_btn`).disabled=!1}async function commitParams(){ge(`commit_btn`).disabled=!0;let changedInputs=document.querySelectorAll(`input.changed`),sp={};for(let input of changedInputs)if(input.id===`in_time`)await commitTime();else if(input.id===`remaining_dist`){let x=parseFloat(ge(`remaining_dist`).value);(await fetch(`./cmd`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({cmd:`remaining_dist`,amt:x})})).ok&&ge(`remaining_dist`).classList.remove(`changed`)}else if(input.id.startsWith(`in_`)){let id=input.id.split(`_`)[1];sp[id]=input.value,input.type===`number`&&(sp[id]=input.value.toLowerCase()===`null`?null:parseFloat(input.value))}Object.keys(sp).length!==0&&((await fetch(`./sp`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify(sp)})).ok?changedInputs.forEach(input=>{input.id!==`in_time`&&input.classList.remove(`changed`)}):ge(`commit_btn`).disabled=!1),await fetchStatus()}async function uploadFirmware(){let fileInput=ge(`firmware_file`);if(!fileInput.files.length){alert(`No file selected`);return}let file=fileInput.files[0];try{let response=await fetch(`./ota`,{method:`POST`,headers:{"Content-Type":`application/octet-stream`},body:file});response.ok?alert(`Upload successful. Device may reboot.`):alert(`Upload failed: `+response.status)}catch{alert(`Network error during upload`)}}function programRF(i){fetch(`./cmd`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({cmd:`rfp`,channel:i})})}async function programRFSequence(){let buttonNames=[`Forward`,`Reverse`,`Up`,`Down`],learnedCodes=[null,null,null,null];if(!confirm(`This will program all 4 RF remote buttons in sequence.
Press OK to begin, then follow the prompts.`))return;await fetch(`./cmd`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({cmd:`rf_clear_temp`})}),await fetch(`./cmd`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({cmd:`rf_disable`})});for(let i=0;i<4;i++){programRF(i),await new Promise(resolve=>setTimeout(resolve,100)),alert(`Button ${i+1}/4: ${buttonNames[i]}\n\nPress the ${buttonNames[i]} button on your remote now.\n\nPress OK when done (or just press OK to leave unprogrammed).`),programRF(-1);try{learnedCodes[i]=(await(await fetch(`./cmd`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({cmd:`rf_status`})})).json()).codes[i]}catch(e){console.error(`Error checking RF status:`,e),learnedCodes[i]=-1}learnedCodes[i]===-1&&await fetch(`./cmd`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({cmd:`rf_set_temp`,index:i,code:-1})})}for(let i=0;i<4;i++){let input=ge(`in_${9+i}`);input&&(input.value=learnedCodes[i],markChanged(input))}let sp={};for(let i=0;i<4;i++)sp[9+i]=learnedCodes[i];if((await fetch(`./sp`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify(sp)})).ok){for(let i=0;i<4;i++){let input=ge(`in_${9+i}`);input&&input.classList.remove(`changed`)}document.querySelectorAll(`input.changed`).length===0&&(ge(`commit_btn`).disabled=!0)}await fetch(`./cmd`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({cmd:`rf_enable`})});let summary=`RF Remote Programming Complete!
`;for(let i=0;i<4;i++)learnedCodes[i]===-1?summary+=`${buttonNames[i]}: Not programmed\n`:summary+=`${buttonNames[i]}: ${learnedCodes[i]}\n`;alert(summary),await fetchStatus()}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)}}window.onload=fetchStatus;</script></body></html>