Files
SC-F001/main/webpage_minified.html
Thaddeus Hughes 179a6ae23d bluetooth
2026-02-17 16:53:21 -06:00

10 lines
24 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;border-radius:5px;width:100%;margin:5px}input[type=text],input[type=number],input[type=time]{border:1px solid #ba965b;border-radius:5px;font-family:monospace}input[readonly]{background-color:#e4e4e4;border-radius:5px}button{text-align:center;border:1px solid #ba965b;border-radius:5px}.changed,#commit_btn{color:#fff!important;background-color:#2a493d!important}#cancel_btn{color:#fff!important;background-color:#723!important}#commit_btn,#cancel_btn{cursor:pointer;border:none;width:45%;margin-top:10px;padding:10px;font-weight:700}#commit_btn[disabled],#cancel_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}#popup-overlay{z-index:1000;background-color:#000000b3;justify-content:center;align-items:center;width:100%;height:100%;display:none;position:fixed;top:0;left:0}#popup-content{color:#fff;text-align:center;background-color:#2a493d;border-radius:10px;max-width:400px;padding:30px;box-shadow:0 4px 6px #0000004d}#popup-content h2{color:#fff;background-color:#2a493d;margin-top:0}#popup-content p{color:#fff;background-color:#2a493d;font-size:1.1rem}@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}}#popup-buttons{background-color:#2a493d;flex-wrap:wrap;justify-content:center;gap:10px;margin-top:20px;display:flex}#popup-buttons button{color:#2f2f2f;cursor:pointer;background-color:#efede9;border:1px solid #ba965b;border-radius:5px;min-width:100px;padding:10px 20px;font-weight:700}#popup-buttons button:hover,#popup-buttons button.primary{color:#fff;background-color:#ba965b}#popup-buttons button.primary:hover{background-color:#8a7045}#popup-input-container{background-color:#2a493d;margin:20px 0}#popup-input{color:#2f2f2f;text-align:center;background-color:#efede9;border:1px solid #ba965b;border-radius:5px;width:100%;padding:10px;font-size:1.1rem}.sqbtn{width:35%;padding:30px;font-weight:700}</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=UX_TIME readonly step=1 type=datetime-local> <button id=now_btn onclick=setTimeToNow()>Sync Time</button></td></tr><tr><td>Schedule Start</td><td><input id=UX_MOVE_START onchange=changeSchedule(this) type=time></td></tr><tr><td>Schedule End</td><td><input id=UX_MOVE_END onchange=changeSchedule(this) type=time></td></tr><tr><td># Moves/Day</td><td><input id=UX_NUM_MOVES min=0 onchange=changeSchedule(this) type=number></td></tr><tr><td>Next Move At</td><td><input id=UX_NEXT_ALARM readonly type=datetime-local></td></tr><tr><td>Remain. Distance (ft)</td><td><input id=UX_REM_DIST onchange=handleRemainingDistChange(this) type=number></td></tr><tr><td>Move Distance (ft)</td><td><input id=UX_DRIVE_DIST min=0 onchange=changeSchedule(this) type=number></td></tr><tr><td>Jack Height (in)</td><td><input id=UX_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=cancel_btn onclick=location.reload();>Discard</button><button disabled id=commit_btn onclick=commitParams()>Save Changes</button><br><br><details><summary>REMOTE CONTROL</summary> <br> <button onmousedown="startRemote('fwd', event)" ontouchstart="startRemote('fwd', event)" class=sqbtn onmouseleave=stopRemote() onmouseup=stopRemote() ontouchend=stopRemote()>FWD</button> <button onmousedown="startRemote('rev', event)" ontouchstart="startRemote('rev', event)" class=sqbtn onmouseleave=stopRemote() onmouseup=stopRemote() ontouchend=stopRemote()>REV</button> <button onmousedown="startRemote('up', event)" ontouchstart="startRemote('up', event)" class=sqbtn onmouseleave=stopRemote() onmouseup=stopRemote() ontouchend=stopRemote()>UP</button> <button onmousedown="startRemote('down', event)" ontouchstart="startRemote('down', event)" class=sqbtn onmouseleave=stopRemote() onmouseup=stopRemote() ontouchend=stopRemote()>DOWN</button> <button onmousedown="startRemote('aux', event)" ontouchstart="startRemote('aux', event)" class=sqbtn onmouseleave=stopRemote() onmouseup=stopRemote() ontouchend=stopRemote()>AUX</button></details><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>Current Version</td><td><input id=version readonly></td></tr><tr><td>Firmware</td><td><input accept=.bin id=firmware_file style=display:none 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> <button onclick="sendCommand('sleep')" class=cmd style=color:#fff;background-color:#237>SLEEP</button></details></div></div><div id=popup-overlay><div id=popup-content><h2 id=popup-title></h2><p id=popup-message></p><div id=popup-input-container style=display:none><input id=popup-input></div><div id=popup-buttons></div></div></div><script>let data={},paramTableCreated=!1,pollInterval=null,modalResolve=null;const ge=id=>document.getElementById(id);function showModal(title,message,buttons=[],options={}){let overlay=document.getElementById(`popup-overlay`),titleEl=document.getElementById(`popup-title`),messageEl=document.getElementById(`popup-message`),buttonsEl=document.getElementById(`popup-buttons`),inputContainer=document.getElementById(`popup-input-container`),inputEl=document.getElementById(`popup-input`);titleEl.textContent=title,messageEl.innerHTML=message,options.showInput?(inputContainer.style.display=`block`,inputEl.type=options.inputType||`text`,inputEl.value=options.inputValue||``,inputEl.placeholder=options.inputPlaceholder||``,setTimeout(()=>inputEl.focus(),100)):inputContainer.style.display=`none`,buttonsEl.innerHTML=``;let buttonElements=[];buttons.forEach(btn=>{let button=document.createElement(`button`);button.textContent=btn.text,btn.primary&&button.classList.add(`primary`),button.onclick=()=>{let result=options.showInput?{button:btn.value,input:inputEl.value}:btn.value;modalResolve&&=(modalResolve(result),null),hideModal(),btn.callback&&btn.callback(result)},buttonsEl.appendChild(button),buttonElements.push({button,btn})});let keyHandler=e=>{if(e.key===`Enter`){e.preventDefault();let primaryBtn=buttonElements.find(b=>b.btn.primary);primaryBtn?primaryBtn.button.click():buttonElements.length>0&&buttonElements[buttonElements.length-1].button.click()}else if(e.key===`Escape`){e.preventDefault();let cancelBtn=buttonElements.find(b=>!b.btn.primary);cancelBtn?cancelBtn.button.click():buttonElements.length>0&&buttonElements[0].button.click()}};window.modalKeyHandler&&document.removeEventListener(`keydown`,window.modalKeyHandler),window.modalKeyHandler=keyHandler,document.addEventListener(`keydown`,keyHandler),overlay.style.display=`flex`}function hideModal(){let overlay=document.getElementById(`popup-overlay`);overlay.style.display=`none`,modalResolve&&=(modalResolve(!1),null),window.modalKeyHandler&&(document.removeEventListener(`keydown`,window.modalKeyHandler),window.modalKeyHandler=null)}function modalConfirm(message,title=`Confirm`){return new Promise(resolve=>{modalResolve=resolve,showModal(title,message,[{text:`Cancel`,value:!1},{text:`OK`,value:!0,primary:!0}])})}function modalAlert(message,title=`Notice`){return new Promise(resolve=>{modalResolve=resolve,showModal(title,message,[{text:`OK`,value:!0,primary:!0}])})}function modalPrompt(message,title=`Input`,defaultValue=``){return new Promise(resolve=>{modalResolve=result=>{result&&result.button?resolve(result.input):resolve(null)},showModal(title,message,[{text:`Cancel`,value:!1},{text:`OK`,value:!0,primary:!0}],{showInput:!0,inputType:`text`,inputValue:defaultValue,inputPlaceholder:``})})}function showRebootModal(){showModal(`Device Rebooting`,`Page will refresh in <span id="popup-countdown">5</span> seconds...`,[]);let countdown=5,countdownInterval=setInterval(()=>{countdown--;let countdownEl=document.getElementById(`popup-countdown`);countdownEl&&(countdownEl.textContent=countdown),countdown<=0&&(clearInterval(countdownInterval),location.reload())},1e3)}function showRTCSyncModal(){showModal(`Time Not Set`,`The device clock needs to be synchronized.`,[{text:`Sync Time`,value:!0,primary:!0,callback:async()=>{setTimeToNow(),await commitParams()}}])}const warnedOfEOT=!1;function showEOTModal(){warnedOfEOT||(warnedOfEOT=!0,showModal(`Out of Travel`,`Device cannot move until more travel distance is allowed.`,[{text:`OK`,value:!0,primary:!0}]))}let intervalId=null;function remote(command){sendCommand(command);try{navigator.vibrate(200)}catch{}}function startRemote(command,event){event&&event.preventDefault(),intervalId&&clearInterval(intervalId),remote(command),intervalId=setInterval(()=>{remote(command)},150)}function stopRemote(){intervalId&&=(clearInterval(intervalId),null)}async function sendCommand(cmdName){if(!(cmdName===`start`&&!await modalConfirm(`Will begin moving - please confirm.`))&&!(cmdName===`reboot`&&!await modalConfirm(`Device will reboot - clearing clock and distance. Are you sure?`))&&!(cmdName===`sleep`&&!await modalConfirm(`Device will sleep. Are you sure?`)))try{let response=await fetch(`./post`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({cmd:cmdName})});if(!response.ok){await modalAlert(`Command '${cmdName}' failed: ${response.status} ${response.statusText}`);return}cmdName===`reboot`?showRebootModal():setTimeout(fetchStatus,200)}catch(e){console.log(e),await modalAlert(`Network error: ${e.message}`)}}async function calibrate(type){let cmdName=type===`jack`?`cal_jack`:`cal_drive`;if(await modalConfirm(`This will calibrate the ${type}. Continue?`))try{let response=await fetch(`./post`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({cmd:cmdName})});if(!response.ok){await modalAlert(`Calibration failed: ${response.status} ${response.statusText}`);return}await modalAlert(`${type.charAt(0).toUpperCase()+type.slice(1)} calibration started.`),setTimeout(fetchStatus,200)}catch(e){await modalAlert(`Network error: ${e.message}`)}}function setTimeToNow(){let now=/* @__PURE__ */ new Date,input=ge(`UX_TIME`);input.value=`${now.getFullYear()}-${String(now.getMonth()+1).padStart(2,`0`)}-${String(now.getDate()).padStart(2,`0`)}T${String(now.getHours()).padStart(2,`0`)}:${String(now.getMinutes()).padStart(2,`0`)}:${String(now.getSeconds()).padStart(2,`0`)}`,markChanged(input)}function markChanged(el){el.classList.add(`changed`),ge(`commit_btn`).disabled=!1,ge(`cancel_btn`).disabled=!1}function handleRemainingDistChange(input){markChanged(input)}const scheduleInputs=[`MOVE_START`,`MOVE_END`,`NUM_MOVES`,`DRIVE_DIST`,`JACK_DIST`];function changeSchedule(ux_input){if(param_input=ge(`PARAM_${ux_input.id.substring(3)}`),markChanged(ux_input),markChanged(param_input),ux_input.type===`time`){let[hours,minutes]=ux_input.value.split(`:`).map(Number);param_input.value=(hours*60+minutes)*60}else param_input.value=parseFloat(ux_input.value)||0}function updateScheduleInputs(){for(let i=0;i<scheduleInputs.length;i++)if(ux_input=ge(`UX_${scheduleInputs[i]}`),param_input=ge(`PARAM_${scheduleInputs[i]}`),!(!ux_input||!param_input)&&!(ux_input.classList.contains(`changed`)||document.activeElement===ux_input))if(ux_input.type===`time`){let hours=Math.floor(param_input.value/3600),minutes=Math.floor(param_input.value%3600/60);ux_input.value=`${String(hours).padStart(2,`0`)}:${String(minutes).padStart(2,`0`)}`}else ux_input.value=param_input.value}async function commitParams(){let changedInputs=document.querySelectorAll(`input.changed`);if(changedInputs.length===0)return;let payload={};for(let input of changedInputs){let id=input.id;if(id===`UX_TIME`){let dt=new Date(input.value),year=dt.getFullYear(),month=dt.getMonth(),day=dt.getDate(),hours=dt.getHours(),minutes=dt.getMinutes(),seconds=dt.getSeconds();payload.time=Math.floor(Date.UTC(year,month,day,hours,minutes,seconds)/1e3)}else if(id===`UX_REM_DIST`)payload.remaining_dist=parseFloat(input.value)||0;else if(id.startsWith(`PARAM_`)){let paramName=id.substring(6),value=input.value;input.type===`number`&&(value=parseFloat(input.value)||0),payload.parameters||={},payload.parameters[paramName]=value}}try{console.log(`Sending data:`,payload);let response=await fetch(`./post`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify(payload)});if(!response.ok){await modalAlert(`Failed to save changes: ${response.status} ${response.statusText}`);return}changedInputs.forEach(input=>input.classList.remove(`changed`)),ge(`commit_btn`).disabled=!0,ge(`cancel_btn`).disabled=!0,setTimeout(fetchStatus,200)}catch(e){await modalAlert(`Network error: ${e.message}`)}}async function fetchStatus(){try{let response=await fetch(`./get`);if(!response.ok){console.error(`Failed to fetch status:`,response.status);return}data=await response.json(),console.log(`Got data:`,data),updateUI()}catch(e){console.error(`Error fetching status:`,e)}}function updateUI(){data.rtc_set||showRTCSyncModal(),data.msg!==void 0&&(ge(`msg`).value=data.msg),data.voltage!==void 0&&(ge(`voltage`).value=data.voltage.toFixed(2)),(data.build_version||data.build_date)&&(ge(`version`).value=data.build_version+` (`+data.build_date+`)`);let timeInput=ge(`UX_TIME`);if(data.time!==void 0&&!timeInput.classList.contains(`changed`)&&document.activeElement!==timeInput){let dt=/* @__PURE__ */ new Date(data.time*1e3);timeInput.value=`${dt.getUTCFullYear()}-${String(dt.getUTCMonth()+1).padStart(2,`0`)}-${String(dt.getUTCDate()).padStart(2,`0`)}T${String(dt.getUTCHours()).padStart(2,`0`)}:${String(dt.getUTCMinutes()).padStart(2,`0`)}:${String(dt.getUTCSeconds()).padStart(2,`0`)}`}let timeOutput=ge(`UX_NEXT_ALARM`);if(data.next_alarm!==void 0){let dt=/* @__PURE__ */ new Date(data.next_alarm*1e3);timeOutput.value=`${dt.getUTCFullYear()}-${String(dt.getUTCMonth()+1).padStart(2,`0`)}-${String(dt.getUTCDate()).padStart(2,`0`)}T${String(dt.getUTCHours()).padStart(2,`0`)}:${String(dt.getUTCMinutes()).padStart(2,`0`)}:${String(dt.getUTCSeconds()).padStart(2,`0`)}`}data.parameters&&(updateParamTable(),updateScheduleInputs());let remainingDistInput=ge(`UX_REM_DIST`);data.remaining_dist!==void 0&&!remainingDistInput.classList.contains(`changed`)&&document.activeElement!==remainingDistInput&&(remainingDistInput.value=data.remaining_dist.toFixed(1))}function updateParamTable(){let table=ge(`table`),sortedParams=Object.entries(data.parameters).sort((a,b)=>a[0].localeCompare(b[0]));if(paramTableCreated)for(let[key,value]of sortedParams){let input=ge(`PARAM_${key}`);if(input)!input.classList.contains(`changed`)&&document.activeElement!==input&&(input.value=value);else{paramTableCreated=!1,updateParamTable();return}}else{table.innerHTML=``;for(let[key,value]of sortedParams){let row=table.insertRow(),cell1=row.insertCell(0),cell2=row.insertCell(1);cell1.textContent=key;let input=document.createElement(`input`);input.type=typeof value==`string`?`text`:`number`,input.id=`PARAM_${key}`,input.value=value,input.onchange=function(){markChanged(this)},cell2.appendChild(input)}document.querySelectorAll(`input`).forEach(input=>{input.addEventListener(`click`,function(){this.readOnly!=1&&this.select()})}),paramTableCreated=!0}}async function uploadFirmware(){let fileInput=ge(`firmware_file`);fileInput.click(),await new Promise(resolve=>{fileInput.onchange=()=>resolve()});let file=fileInput.files[0];if(file){if(!file.name.endsWith(`.bin`)){await modalAlert(`Please select a .bin file`);return}if(await modalConfirm(`Upload firmware file "${file.name}"?\n\nDevice will reboot after upload.`))try{let arrayBuffer=await file.arrayBuffer();showModal(`Uploading Firmware`,`<div style="background-color: #2a493d;">
<div style="background-color: #2a493d; margin-bottom: 10px;">Uploading ${file.name}...</div>
<div style="background-color: #2a493d; font-size: 2rem; font-weight: bold;" id="upload-progress">0%</div>
</div>`,[]);let xhr=new XMLHttpRequest;xhr.upload.addEventListener(`progress`,e=>{if(e.lengthComputable){let percentComplete=Math.round(e.loaded/e.total*100),progressEl=document.getElementById(`upload-progress`);progressEl&&(progressEl.textContent=percentComplete+`%`)}});let uploadPromise=new Promise((resolve,reject)=>{xhr.addEventListener(`load`,()=>{xhr.status>=200&&xhr.status<300?resolve():reject(/* @__PURE__ */ Error(`Upload failed: ${xhr.status} ${xhr.statusText}`))}),xhr.addEventListener(`error`,()=>{reject(/* @__PURE__ */ Error(`Network error during upload`))}),xhr.addEventListener(`abort`,()=>{reject(/* @__PURE__ */ Error(`Upload cancelled`))})});xhr.open(`POST`,`./ota`),xhr.setRequestHeader(`Content-Type`,`application/octet-stream`),xhr.send(arrayBuffer),await uploadPromise,showModal(`Firmware Update`,`Firmware uploaded successfully! Device is rebooting...<br>Page will refresh in <span id="popup-countdown">5</span> seconds...`,[]);let countdown=5,countdownInterval=setInterval(()=>{countdown--;let countdownEl=document.getElementById(`popup-countdown`);countdownEl&&(countdownEl.textContent=countdown),countdown<=0&&(clearInterval(countdownInterval),location.reload())},1e3)}catch(e){hideModal(),await modalAlert(`Error: ${e.message}`)}}}function programRF(i){fetch(`./post`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({cmd:`rf_learn`,channel:i})})}async function programRFSequence(){let buttonNames=[`Forward`,`Reverse`,`Up`,`Down`],learnedCodes=[null,null,null,null];if(await modalConfirm(`This will program all 4 RF remote buttons in sequence.
Press OK to begin, then follow the prompts.`))try{let response=await fetch(`./post`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({cmd:`rf_clear_temp`})});if(!response.ok){await modalAlert(`Failed to clear RF temp storage`);return}if(response=await fetch(`./post`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({cmd:`rf_disable`})}),!response.ok){await modalAlert(`Failed to disable RF controls`);return}for(let i=0;i<4;i++){await fetch(`./post`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({cmd:`rf_learn`,channel:i})}),await new Promise(resolve=>setTimeout(resolve,100)),await modalAlert(`Button ${i+1}/4: ${buttonNames[i]}\n\nPress the ${buttonNames[i]} button on your remote once now.\n\nPress OK when done (or just press OK to leave unprogrammed).`,`Program Button`),await fetch(`./post`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({cmd:`rf_learn`,channel:-1})});try{let response=await fetch(`./post`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({cmd:`rf_status`})});if(!response.ok)console.error(`Error checking RF status:`,response.status),learnedCodes[i]=0;else{let data=await response.json();learnedCodes[i]=data.codes?data.codes[i]:0}}catch(e){console.error(`Error checking RF status:`,e),learnedCodes[i]=0}learnedCodes[i]===0&&await fetch(`./post`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({cmd:`rf_set_temp`,index:i,code:0})})}for(let i=0;i<4;i++){let input=ge(`PARAM_KEYCODE_${i}`);input&&(input.value=learnedCodes[i],markChanged(input))}let parameters={KEYCODE_0:learnedCodes[0],KEYCODE_1:learnedCodes[1],KEYCODE_2:learnedCodes[2],KEYCODE_3:learnedCodes[3]};if(response=await fetch(`./post`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({parameters})}),response.ok){for(let i=0;i<4;i++){let input=ge(`PARAM_KEYCODE_${i}`);input&&input.classList.remove(`changed`)}document.querySelectorAll(`input.changed`).length===0&&(ge(`commit_btn`).disabled=!0,ge(`cancel_btn`).disabled=!0)}else await modalAlert(`Failed to save RF codes: ${response.status} ${response.statusText}`);await fetch(`./post`,{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]===0?summary+=`${buttonNames[i]}: - <br/>`:summary+=`${buttonNames[i]}: ${learnedCodes[i]}<br/>`;await modalAlert(summary),await fetchStatus()}catch(e){await modalAlert(`RF programming error: ${e.message}`)}}async function calibrate(axis){try{let response=await fetch(`./post`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({cmd:`cal_${axis}_start`})});if(!response.ok){await modalAlert(`Failed to start ${axis} calibration: ${response.status}`);return}let amt=await modalPrompt(`Press button on mover. Press button again to stop it.
Then, type the actual travelled distance in inches:`,`Calibration`);if(amt===null||amt===``){await modalAlert(`Calibration cancelled`);return}let distance=parseFloat(amt);if(isNaN(distance)||distance<=0){await modalAlert(`Invalid distance entered. Please enter a positive number.`);return}if(response=await fetch(`./post`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({cmd:`cal_get`})}),!response.ok){await modalAlert(`Failed to get calibration data: ${response.status}`);return}let calData=await response.json();if(axis===`drive`){let ke=calData.e/(distance/12),kt=calData.t/(distance/12)*1.2,keInput=ge(`PARAM_DRIVE_KE`),ktInput=ge(`PARAM_DRIVE_KT`);keInput?(keInput.value=ke.toFixed(2),markChanged(keInput)):console.error(`Could not find PARAM_DRIVE_KE`),ktInput?(ktInput.value=Math.round(kt),markChanged(ktInput)):console.error(`Could not find PARAM_DRIVE_KT`),keInput||ktInput?await modalAlert(`Drive calibration complete!\n\n${keInput?`KE(steps/ft):${ke.toFixed(2)}\n`:``}${ktInput?`KT(timeout ms/ft):${Math.round(kt)}\n`:``}\nPlease save changes.`):await modalAlert(`Drive calibration failed: Could not find DRIVE_KE or DRIVE_KT parameters.`)}else if(axis===`jack`){let kt=calData.t/distance,ktInput=ge(`PARAM_JACK_KT`);ktInput?(ktInput.value=Math.round(kt),markChanged(ktInput),await modalAlert(`Jack calibration complete!\n\nKT (timeout ms/in): ${Math.round(kt)}\n\nPlease save changes.`)):(console.error(`Could not find PARAM_JACK_KT`),await modalAlert(`Jack calibration failed: Could not find JACK_KT parameter.`))}}catch(e){await modalAlert(`Calibration error: ${e.message}`)}}async function downloadLogFile(){try{let response=await fetch(`./log`);if(!response.ok){await modalAlert(`Failed to download log file: ${response.status} ${response.statusText}`);return}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){await modalAlert(`Error downloading log file: ${error.message}`)}}function startPolling(){fetchStatus(),pollInterval=setInterval(fetchStatus,3e3)}function stopPolling(){pollInterval&&=(clearInterval(pollInterval),null)}document.addEventListener(`visibilitychange`,function(){document.hidden?stopPolling():(fetchStatus(),pollInterval=setInterval(fetchStatus,3e3))}),window.onload=function(){ge(`commit_btn`).disabled=!0,ge(`cancel_btn`).disabled=!0,startPolling()};</script></body></html>