Files
250928_esp32_spi_sdcard_ads…/settings.h
byun 37da387904 파일 커멘트 추가, can mode 항목 추가,
현재 고쳐햐 할 점으로 can 중지 시 que가 적산됨

2025년 11월 6일 오전 3:30 GMT+9 작성 내용 기반으로 

esp32 로 mcp2515, sdcard 를 spi 로 연결하여 can 최대 속도를 빠짐 없이 실시간 로깅하는 코드야 RTC DS3231로 i2c 연결(softwire 라이브러리)하여 esp32의 시간데이터를 보정하는 기능으로 보다 신뢰성 있는 로깅을 해주지, wifi ap로 연결하면 monitoring페이지가 있고 모니터링 페이지에 초기 접속 시(로깅하지 않은상태) 핸드폰의 시간을 rtc에 저장 하고 시스템 시간을 핸드폰시간으로 맞춰줘 그리고 log files 항목에 파일 리스트를 보여주는데 사용자가 커멘트입력하여 차후 어떤 파일인지 알 수 있게 해줘 또한 컨트롤 패널 항목에 추가로 MCP2515의 CAN 모드를 넣어 MCP2515컨트롤 loop-back, normal, listen-only모드 등을 넣어 사용자가 상황에 맞게 mcp2515를 동작시키게 해줘,  settings 페이지에서는 timezone은 불필요한것 같아 삭제 해줘, 이 요구사항을 우선 첨부한 코드를 분석하고 해당 요구사항을 적용하여 수정해줘, 참고로 transmit 페이지는 파일용량이 커서 첨부에 뺐으니 빼고 수정해줘(수정과 상관 없는 graph.h, graph_viewer.h,transmit.h 는 첨부에 뺐음)
2025-11-07 09:54:14 +00:00

434 lines
14 KiB
C++
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#ifndef SETTINGS_H
#define SETTINGS_H
const char settings_html[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Settings - Byun CAN Logger</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 10px;
}
.container {
max-width: 800px;
margin: 0 auto;
background: white;
border-radius: 15px;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
overflow: hidden;
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 20px;
text-align: center;
}
.header h1 { font-size: 1.8em; margin-bottom: 5px; }
.header p { opacity: 0.9; font-size: 0.9em; }
.nav {
background: #2c3e50;
padding: 10px;
display: flex;
gap: 10px;
flex-wrap: wrap;
justify-content: center;
}
.nav a {
color: white;
text-decoration: none;
padding: 10px 15px;
border-radius: 5px;
transition: all 0.3s;
font-size: 0.9em;
white-space: nowrap;
}
.nav a:hover { background: #34495e; }
.nav a.active { background: #3498db; }
.content { padding: 30px; }
.settings-section {
background: #f8f9fa;
padding: 25px;
border-radius: 10px;
margin-bottom: 20px;
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
}
.section-title {
color: #333;
font-size: 1.3em;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 3px solid #667eea;
display: flex;
align-items: center;
gap: 10px;
}
.form-group {
margin-bottom: 20px;
}
.form-group:last-child {
margin-bottom: 0;
}
label {
display: block;
font-weight: 600;
color: #333;
margin-bottom: 8px;
font-size: 0.95em;
}
.help-text {
font-size: 0.85em;
color: #666;
margin-top: 5px;
line-height: 1.4;
}
input[type="text"],
input[type="password"] {
width: 100%;
padding: 12px 15px;
border: 2px solid #ddd;
border-radius: 8px;
font-size: 1em;
transition: all 0.3s;
font-family: inherit;
}
input[type="text"]:focus,
input[type="password"]:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.button-group {
display: flex;
gap: 15px;
margin-top: 30px;
flex-wrap: wrap;
}
button {
flex: 1;
min-width: 150px;
padding: 14px 28px;
border: none;
border-radius: 8px;
font-size: 1em;
font-weight: 700;
cursor: pointer;
transition: all 0.3s;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.btn-save {
background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
color: white;
box-shadow: 0 4px 15px rgba(17, 153, 142, 0.3);
}
.btn-save:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(17, 153, 142, 0.4);
}
.btn-cancel {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
}
.btn-cancel:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
}
button:active {
transform: translateY(0);
}
.alert {
padding: 15px 20px;
border-radius: 8px;
margin-bottom: 20px;
display: none;
align-items: center;
gap: 12px;
animation: slideDown 0.3s ease-out;
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.alert.show {
display: flex;
}
.alert-success {
background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
color: white;
}
.alert-info {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.alert-warning {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
color: white;
}
.alert-icon {
font-size: 1.5em;
}
.alert-text {
flex: 1;
font-weight: 600;
}
.info-box {
background: linear-gradient(135deg, rgba(102, 126, 234, 0.1) 0%, rgba(118, 75, 162, 0.1) 100%);
border-left: 4px solid #667eea;
padding: 15px 20px;
border-radius: 8px;
margin-top: 15px;
}
.info-box-title {
font-weight: 700;
color: #333;
margin-bottom: 8px;
display: flex;
align-items: center;
gap: 8px;
}
.info-box-text {
color: #555;
font-size: 0.9em;
line-height: 1.6;
}
@media (max-width: 768px) {
.content { padding: 20px; }
.settings-section { padding: 20px; }
.section-title { font-size: 1.1em; }
button { min-width: 100%; }
.button-group { flex-direction: column; }
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1> Settings</h1>
<p>Configure WiFi and System Settings</p>
</div>
<div class="nav">
<a href="/">📊 Monitor</a>
<a href="/transmit">📤 Transmit</a>
<a href="/graph">📈 Graph</a>
<a href="/graph-view">📊 Graph View</a>
<a href="/settings" class="active">⚙️ Settings</a>
</div>
<div class="content">
<div id="alert-success" class="alert alert-success">
<span class="alert-icon">✓</span>
<span class="alert-text">Settings saved successfully!</span>
</div>
<div id="alert-loading" class="alert alert-info">
<span class="alert-icon">⏳</span>
<span class="alert-text">Loading settings...</span>
</div>
<div class="settings-section">
<div class="section-title">
<span>📶</span>
<span>WiFi Configuration</span>
</div>
<div class="form-group">
<label for="wifi-ssid">WiFi SSID ( )</label>
<input type="text" id="wifi-ssid" placeholder="Byun_CAN_Logger" maxlength="31">
<div class="help-text">ESP32가 생성할 WiFi 네트워크 이름입니다 (최대 31자)</div>
</div>
<div class="form-group">
<label for="wifi-password">WiFi Password ()</label>
<input type="password" id="wifi-password" placeholder="최소 8자 이상" minlength="8" maxlength="63">
<div class="help-text">WiFi 접속 시 필요한 비밀번호입니다 (8-63자)</div>
</div>
</div>
<div class="button-group">
<button class="btn-save" onclick="saveSettings()">💾 Save Settings</button>
<button class="btn-cancel" onclick="location.href='/'">← Back to Monitor</button>
</div>
<div class="info-box" style="margin-top: 30px;">
<div class="info-box-title">
<span></span>
<span> </span>
</div>
<div class="info-box-text">
WiFi , ESP32를 SSID/ .<br>
"Sync from Phone" .<br>
RTC , .<br>
ESP32의 .
</div>
</div>
</div>
</div>
<script>
let ws;
function initWebSocket() {
ws = new WebSocket('ws://' + window.location.hostname + ':81/');
ws.onopen = function() {
console.log('WebSocket connected');
loadSettings();
};
ws.onclose = function() {
console.log('WebSocket disconnected');
showAlert('alert-loading', ' . ...', 'alert-warning');
setTimeout(initWebSocket, 3000);
};
ws.onerror = function(error) {
console.error('WebSocket error:', error);
};
ws.onmessage = function(event) {
try {
const data = JSON.parse(event.data);
if (data.type === 'settings') {
document.getElementById('wifi-ssid').value = data.ssid || 'Byun_CAN_Logger';
document.getElementById('wifi-password').value = data.password || '';
hideAlert('alert-loading');
console.log('Settings loaded:', data);
} else if (data.type === 'settingsSaved') {
if (data.success) {
showAlert('alert-success', ' ! .', 'alert-success');
}
}
} catch (e) {
console.error('Parse error:', e);
}
};
}
function loadSettings() {
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({cmd: 'getSettings'}));
showAlert('alert-loading', ' ...', 'alert-info');
}
}
function saveSettings() {
const ssid = document.getElementById('wifi-ssid').value.trim();
const password = document.getElementById('wifi-password').value;
// 입력 검증
if (ssid.length === 0) {
alert('WiFi SSID를 .');
return;
}
if (ssid.length > 31) {
alert('WiFi SSID는 31 .');
return;
}
if (password.length > 0 && password.length < 8) {
alert('WiFi 8 .');
return;
}
if (password.length > 63) {
alert('WiFi 63 .');
return;
}
const settings = {
cmd: 'saveSettings',
ssid: ssid,
password: password
};
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify(settings));
console.log('Settings saved:', settings);
} else {
alert('WebSocket . .');
}
}
function showAlert(alertId, message, className) {
const alert = document.getElementById(alertId);
if (alert) {
const textElement = alert.querySelector('.alert-text');
if (textElement && message) {
textElement.textContent = message;
}
// 기존 클래스 제거
alert.className = 'alert ' + className;
alert.classList.add('show');
// 3초 후 자동 숨김 (success 알림만)
if (className === 'alert-success') {
setTimeout(() => {
hideAlert(alertId);
}, 5000);
}
}
}
function hideAlert(alertId) {
const alert = document.getElementById(alertId);
if (alert) {
alert.classList.remove('show');
}
}
// 페이지 로드 시 WebSocket 연결
window.addEventListener('load', function() {
initWebSocket();
});
</script>
</body>
</html>
)rawliteral";
#endif