Files
esp32s3_CANFD_logger/settings.h
2026-04-30 17:40:07 +00:00

231 lines
10 KiB
C++

#ifndef SETTINGS_H
#define SETTINGS_H
const char settings_html[] PROGMEM = R"rawliteral(
<!DOCTYPE html><html lang="ko"><head>
<meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title> - CANFD Logger</title>
<style>
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:'Segoe UI',sans-serif;background:#0d1117;color:#c9d1d9;font-size:13px}
nav{background:#161b22;border-bottom:1px solid #30363d;padding:8px 16px;display:flex;align-items:center;gap:12px;flex-wrap:wrap}
nav a{color:#58a6ff;text-decoration:none;padding:4px 10px;border-radius:6px;font-size:12px}
nav a:hover{background:#21262d} nav a.active{background:#1f6feb;color:#fff}
.title{color:#e6edf3;font-weight:700;font-size:15px;margin-right:8px}
.container{padding:16px;max-width:700px}
.card{background:#161b22;border:1px solid #30363d;border-radius:8px;padding:16px;margin-bottom:12px}
.card h3{font-size:12px;color:#8b949e;margin-bottom:14px;text-transform:uppercase;letter-spacing:.5px;border-bottom:1px solid #30363d;padding-bottom:8px}
.field{margin-bottom:12px}
label{display:block;font-size:12px;color:#8b949e;margin-bottom:4px}
input[type=text],input[type=password],select{width:100%;background:#21262d;border:1px solid #30363d;color:#c9d1d9;padding:7px 10px;border-radius:6px;font-size:13px}
input[type=checkbox]{width:16px;height:16px;margin-right:6px;accent-color:#1f6feb}
.row{display:flex;gap:10px;align-items:flex-end}
.row .field{flex:1}
.btn{padding:7px 16px;border:none;border-radius:6px;cursor:pointer;font-size:12px;font-weight:600;transition:.2s}
.btn-green{background:#238636;color:#fff} .btn-green:hover{background:#2ea043}
.btn-blue{background:#1f6feb;color:#fff} .btn-blue:hover{background:#388bfd}
.btn-gray{background:#21262d;color:#c9d1d9;border:1px solid #30363d}
.info{background:#051d4d;border:1px solid #1f6feb;border-radius:6px;padding:10px;font-size:12px;color:#79c0ff;margin-top:8px}
.warn{background:#3d2200;border:1px solid #9e6a03;border-radius:6px;padding:10px;font-size:12px;color:#ffa657;margin-top:8px}
.speed-table{width:100%;border-collapse:collapse;font-size:12px;margin-top:8px}
.speed-table th{background:#21262d;color:#8b949e;padding:6px 8px;text-align:left}
.speed-table td{padding:5px 8px;border-bottom:1px solid #21262d}
.speed-table tr.selected td{background:#051d4d;color:#79c0ff}
.speed-table tr:hover td{background:#1c2128;cursor:pointer}
.badge{display:inline-block;padding:2px 7px;border-radius:10px;font-size:10px;font-weight:700}
.badge-fd{background:#2d1b69;color:#a371f7} .badge-cl{background:#033a16;color:#3fb950}
#toast{position:fixed;bottom:20px;right:20px;background:#238636;color:#fff;padding:10px 16px;border-radius:8px;display:none;font-size:13px;z-index:999}
</style>
</head><body>
<nav>
<span class="title">⚡ CANFD Logger</span>
<a href="/"></a><a href="/transmit"></a><a href="/graph"></a>
<a href="/settings" class="active">설정</a>
</nav>
<div class="container">
<!-- CAN FD -->
<div class="card">
<h3>CAN FD / </h3>
<div class="field">
<label> ( )</label>
<table class="speed-table" id="speedTable">
<thead><tr><th>#</th><th></th><th> </th><th> </th><th></th></tr></thead>
<tbody id="speedBody"></tbody>
</table>
</div>
<div class="field" style="display:flex;align-items:center;gap:8px">
<input type="checkbox" id="listenOnly"><label for="listenOnly" style="display:inline;color:#c9d1d9">Listen Only ( , )</label>
</div>
<div class="info" id="speedInfo">속도 프리셋을 선택하세요.</div>
<div class="warn">⚠ 속도 변경 후 "CAN "를 눌러야 즉시 적용됩니다. SD 로깅 중에는 중지 후 변경하세요.</div>
<div style="margin-top:12px;display:flex;gap:8px">
<button class="btn btn-green" onclick="reinitCAN()">⚡ CAN 재초기화</button>
<button class="btn btn-gray" onclick="saveAndRestart()">💾 저장 후 재시작</button>
</div>
</div>
<!-- WiFi -->
<div class="card">
<h3>WiFi (AP )</h3>
<div class="row">
<div class="field"><label>AP SSID</label><input type="text" id="ssid" placeholder="CANFD_Logger"></div>
<div class="field"><label>AP 비밀번호</label><input type="password" id="password" placeholder="12345678"></div>
</div>
<div class="field" style="display:flex;align-items:center;gap:8px">
<input type="checkbox" id="staEnable" onchange="toggleSTA()"><label for="staEnable" style="display:inline;color:#c9d1d9">STA (WiFi + NTP )</label>
</div>
<div id="staFields" style="display:none">
<div class="row">
<div class="field"><label>연결할 WiFi SSID</label><input type="text" id="staSSID"></div>
<div class="field"><label>WiFi 비밀번호</label><input type="password" id="staPass"></div>
</div>
<div class="info" id="staInfo"></div>
</div>
<div style="margin-top:12px">
<button class="btn btn-blue" onclick="saveWifi()">💾 WiFi 설정 저장 (재시작 필요)</button>
</div>
</div>
<!-- -->
<div class="card">
<h3></h3>
<div style="font-size:12px;color:#8b949e;line-height:2">
<b style="color:#c9d1d9"> ()</b><br>
CAN SPI: SCK=12, MOSI=11, MISO=13, CS=10 | INT: (255)<br>
SD SDIO: CLK=39, CMD=38, D0=40, D1=41, D2=42, D3=2<br>
RTC I2C: SDA=8, SCL=18 (DS3231)<br><br>
<b style="color:#c9d1d9"></b>: OSC_20MHz ( MCP2518FD )<br>
<b style="color:#c9d1d9"></b>: ACAN2517FD by Pierre Molinaro<br>
</div>
<div style="margin-top:12px;display:flex;gap:8px;flex-wrap:wrap">
<button class="btn btn-gray" onclick="hwReset()">🔌 장치 재시작</button>
</div>
</div>
</div>
<div id="toast"></div>
<script>
const PRESETS=[
{name:'Classic 125K', arb:'125 kbps', data:'125 kbps', fd:false},
{name:'Classic 250K', arb:'250 kbps', data:'250 kbps', fd:false},
{name:'Classic 500K', arb:'500 kbps', data:'500 kbps', fd:false},
{name:'Classic 1M', arb:'1 Mbps', data:'1 Mbps', fd:false},
{name:'FD 500K/2M', arb:'500 kbps', data:'2 Mbps', fd:true},
{name:'FD 500K/4M', arb:'500 kbps', data:'4 Mbps', fd:true},
{name:'FD 1M/4M', arb:'1 Mbps', data:'4 Mbps', fd:true},
{name:'FD 1M/8M', arb:'1 Mbps', data:'8 Mbps', fd:true},
];
let selIdx=4;
let ws, reconnTimer;
function renderPresets(){
let html='';
PRESETS.forEach((p,i)=>{
let cls=i===selIdx?'class="selected"':'';
let badge=p.fd?'<span class="badge badge-fd">CAN FD</span>':'<span class="badge badge-cl">Classic</span>';
html+=`<tr ${cls} onclick="selectPreset(${i})"><td>${i}</td><td>${p.name}</td><td>${p.arb}</td><td>${p.data}</td><td>${badge}</td></tr>`;
});
document.getElementById('speedBody').innerHTML=html;
updateSpeedInfo();
}
function selectPreset(i){
selIdx=i; renderPresets();
}
function updateSpeedInfo(){
let p=PRESETS[selIdx];
document.getElementById('speedInfo').innerHTML=
`: <b>${p.name}</b> | : ${p.arb} | : ${p.data} | `+
(p.fd?'<b style="color:#a371f7">CAN FD </b>':'<b style="color:#3fb950">Classic CAN </b>');
}
function connect(){
ws=new WebSocket('ws://'+location.hostname+':81/');
ws.onopen=()=>{
clearTimeout(reconnTimer);
ws.send(JSON.stringify({cmd:'getSettings'}));
};
ws.onclose=()=>{ reconnTimer=setTimeout(connect,3000); };
ws.onmessage=e=>{
try{ let d=JSON.parse(e.data); handleMsg(d); }catch(ex){}
};
}
function handleMsg(d){
if(d.type==='settings'){
document.getElementById('ssid').value=d.ssid||'';
document.getElementById('password').value=d.password||'';
document.getElementById('staEnable').checked=!!d.staEnable;
document.getElementById('staSSID').value=d.staSSID||'';
if(d.staConnected){
document.getElementById('staInfo').textContent=': '+d.staIP;
}
if(d.speedIdx!==undefined){ selIdx=d.speedIdx; renderPresets(); }
document.getElementById('listenOnly').checked=!!d.listenOnly;
toggleSTA();
}
else if(d.type==='reinitResult'){
toast(d.success?' CAN : '+d.preset:' ','#238636');
}
else if(d.type==='settingsSaved'){
toast(' ','#238636');
}
}
function toggleSTA(){
document.getElementById('staFields').style.display=
document.getElementById('staEnable').checked?'block':'none';
}
function reinitCAN(){
ws.send(JSON.stringify({
cmd:'reinitCAN',
speedIdx:selIdx,
listenOnly:document.getElementById('listenOnly').checked
}));
}
function saveWifi(){
ws.send(JSON.stringify({
cmd:'saveSettings',
ssid:document.getElementById('ssid').value,
password:document.getElementById('password').value,
staEnable:document.getElementById('staEnable').checked,
staSSID:document.getElementById('staSSID').value,
staPassword:document.getElementById('staPass').value,
speedIdx:selIdx,
listenOnly:document.getElementById('listenOnly').checked
}));
}
function saveAndRestart(){
if(!confirm(' .')) return;
ws.send(JSON.stringify({
cmd:'saveSettings',
ssid:document.getElementById('ssid').value,
password:document.getElementById('password').value,
staEnable:document.getElementById('staEnable').checked,
staSSID:document.getElementById('staSSID').value,
staPassword:document.getElementById('staPass').value,
speedIdx:selIdx,
listenOnly:document.getElementById('listenOnly').checked
}));
setTimeout(()=>ws.send(JSON.stringify({cmd:'hwReset'})),500);
}
function hwReset(){ if(confirm('?')) ws.send(JSON.stringify({cmd:'hwReset'})); }
function toast(msg,col='#238636'){
let t=document.getElementById('toast');
t.textContent=msg; t.style.background=col; t.style.display='block';
setTimeout(()=>t.style.display='none',3000);
}
renderPresets();
connect();
</script>
</body></html>
)rawliteral";
#endif