transmit 창 메세지 리스트 저장불러오기 기능 추가, monitor 창 canspeed 유지기능 추가

This commit is contained in:
2025-10-06 17:16:28 +00:00
parent 1bf6186305
commit d629b076db
2 changed files with 247 additions and 18 deletions

49
index.h
View File

@@ -238,6 +238,7 @@ const char index_html[] PROGMEM = R"rawliteral(
<option value="3" selected>1 Mbps</option>
</select>
<button onclick="setCanSpeed()">Apply</button>
<span id="speed-status" style="color: #11998e; font-size: 0.85em; font-weight: 600;"></span>
</div>
<div class="control-row">
<button onclick="refreshFiles()">Refresh Files</button>
@@ -274,21 +275,40 @@ const char index_html[] PROGMEM = R"rawliteral(
let canMessages = {};
let messageOrder = [];
// CAN 속도 설정 저장 및 복원
// CAN 속도 이름 매핑
const speedNames = ['125 Kbps', '250 Kbps', '500 Kbps', '1 Mbps'];
// CAN 속도 설정 저장
function saveCanSpeed() {
const speed = document.getElementById('can-speed').value;
try {
localStorage.setItem('canSpeed', speed);
} catch(e) {}
console.log('Saved CAN speed:', speedNames[speed]);
} catch(e) {
console.error('Failed to save CAN speed:', e);
}
}
// CAN 속도 설정 복원
function loadCanSpeed() {
try {
const savedSpeed = localStorage.getItem('canSpeed');
if (savedSpeed !== null) {
document.getElementById('can-speed').value = savedSpeed;
console.log('Restored CAN speed:', speedNames[savedSpeed]);
// 복원되었음을 표시
const statusSpan = document.getElementById('speed-status');
if (statusSpan) {
statusSpan.textContent = '(Restored: ' + speedNames[savedSpeed] + ')';
setTimeout(() => {
statusSpan.textContent = '';
}, 3000);
}
}
} catch(e) {}
} catch(e) {
console.error('Failed to load CAN speed:', e);
}
}
function initWebSocket() {
@@ -461,7 +481,25 @@ const char index_html[] PROGMEM = R"rawliteral(
function setCanSpeed() {
const speed = document.getElementById('can-speed').value;
const speedName = speedNames[speed];
ws.send(JSON.stringify({cmd: 'setSpeed', speed: parseInt(speed)}));
// 설정 저장
saveCanSpeed();
// 적용 완료 표시
const statusSpan = document.getElementById('speed-status');
if (statusSpan) {
statusSpan.textContent = ' Applied: ' + speedName;
statusSpan.style.color = '#11998e';
setTimeout(() => {
statusSpan.textContent = '';
}, 3000);
}
console.log('CAN speed set to:', speedName);
}
function refreshFiles() {
@@ -480,6 +518,11 @@ const char index_html[] PROGMEM = R"rawliteral(
window.location.href = '/download?file=' + encodeURIComponent(filename);
}
// 페이지 로드 시 저장된 CAN speed 복원
window.addEventListener('load', function() {
loadCanSpeed();
});
initWebSocket();
setTimeout(() => { refreshFiles(); }, 2000);
</script>

View File

@@ -119,6 +119,10 @@ const char transmit_html[] PROGMEM = R"rawliteral(
background: linear-gradient(135deg, #eb3349 0%, #f45c43 100%);
color: white;
}
.btn-warning {
background: linear-gradient(135deg, #f2994a 0%, #f2c94c 100%);
color: white;
}
.btn:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(0,0,0,0.3); }
.message-list {
@@ -162,10 +166,6 @@ const char transmit_html[] PROGMEM = R"rawliteral(
font-size: 0.85em;
word-break: break-all;
}
.message-interval {
color: #888;
font-size: 0.8em;
}
.message-controls {
display: flex;
gap: 5px;
@@ -197,6 +197,63 @@ const char transmit_html[] PROGMEM = R"rawliteral(
}
.status-bar span { font-weight: 600; }
.preset-manager {
background: #fff3cd;
padding: 15px;
border-radius: 10px;
margin-bottom: 15px;
border-left: 4px solid #f2994a;
}
.preset-controls {
display: flex;
gap: 10px;
align-items: center;
flex-wrap: wrap;
margin-bottom: 10px;
}
.preset-controls input {
flex: 1;
min-width: 200px;
padding: 8px;
border: 2px solid #f2994a;
border-radius: 5px;
font-size: 0.9em;
}
.preset-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 10px;
margin-top: 10px;
}
.preset-item {
background: white;
padding: 10px;
border-radius: 5px;
border: 2px solid #f2c94c;
display: flex;
justify-content: space-between;
align-items: center;
transition: all 0.3s;
}
.preset-item:hover {
transform: translateX(5px);
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.preset-name {
font-weight: 600;
color: #333;
font-size: 0.9em;
}
.preset-info {
font-size: 0.75em;
color: #666;
margin-top: 3px;
}
.preset-buttons {
display: flex;
gap: 5px;
}
@media (max-width: 768px) {
body { padding: 5px; }
.header h1 { font-size: 1.5em; }
@@ -218,17 +275,6 @@ const char transmit_html[] PROGMEM = R"rawliteral(
.message-controls {
justify-content: flex-start;
}
.control-row {
flex-direction: column;
align-items: stretch;
}
.control-row > * {
width: 100%;
}
.nav {
padding: 8px;
gap: 5px;
}
.nav a {
padding: 8px 10px;
font-size: 0.85em;
@@ -241,6 +287,9 @@ const char transmit_html[] PROGMEM = R"rawliteral(
gap: 8px;
text-align: center;
}
.preset-list {
grid-template-columns: 1fr;
}
}
</style>
</head>
@@ -263,6 +312,17 @@ const char transmit_html[] PROGMEM = R"rawliteral(
<span id="tx-count">Sent: 0</span>
</div>
<h2>Message List Presets</h2>
<div class="preset-manager">
<div class="preset-controls">
<input type="text" id="preset-name" placeholder="Enter preset name...">
<button class="btn btn-warning" onclick="savePreset()">Save Current List</button>
</div>
<div class="preset-list" id="preset-list">
<p style="text-align: center; color: #666; font-size: 0.9em;">No saved presets</p>
</div>
</div>
<h2>Add CAN Message</h2>
<div class="message-form">
<div class="form-row">
@@ -358,6 +418,127 @@ const char transmit_html[] PROGMEM = R"rawliteral(
};
}
// 프리셋 관리
function savePreset() {
const presetName = document.getElementById('preset-name').value.trim();
if (!presetName) {
alert('Please enter a preset name!');
return;
}
if (messages.length === 0) {
alert('No messages to save!');
return;
}
try {
let presets = JSON.parse(localStorage.getItem('tx_presets') || '{}');
if (presets[presetName] && !confirm('Preset "' + presetName + '" already exists. Overwrite?')) {
return;
}
presets[presetName] = {
messages: JSON.parse(JSON.stringify(messages)),
savedAt: new Date().toISOString(),
count: messages.length
};
localStorage.setItem('tx_presets', JSON.stringify(presets));
document.getElementById('preset-name').value = '';
loadPresetList();
alert('Preset "' + presetName + '" saved successfully!');
} catch(e) {
console.error('Failed to save preset:', e);
alert('Failed to save preset!');
}
}
function loadPreset(presetName) {
try {
const presets = JSON.parse(localStorage.getItem('tx_presets') || '{}');
const preset = presets[presetName];
if (!preset) {
alert('Preset not found!');
return;
}
if (messages.length > 0 && !confirm('Current message list will be replaced. Continue?')) {
return;
}
stopAll();
messages = JSON.parse(JSON.stringify(preset.messages));
messages.forEach(msg => msg.active = false);
updateMessageList();
alert('Loaded preset "' + presetName + '" with ' + preset.count + ' messages');
} catch(e) {
console.error('Failed to load preset:', e);
alert('Failed to load preset!');
}
}
function deletePreset(presetName) {
if (!confirm('Delete preset "' + presetName + '"?')) {
return;
}
try {
let presets = JSON.parse(localStorage.getItem('tx_presets') || '{}');
delete presets[presetName];
localStorage.setItem('tx_presets', JSON.stringify(presets));
loadPresetList();
} catch(e) {
console.error('Failed to delete preset:', e);
alert('Failed to delete preset!');
}
}
function loadPresetList() {
const presetListDiv = document.getElementById('preset-list');
try {
const presets = JSON.parse(localStorage.getItem('tx_presets') || '{}');
const presetNames = Object.keys(presets);
if (presetNames.length === 0) {
presetListDiv.innerHTML = '<p style="text-align: center; color: #666; font-size: 0.9em;">No saved presets</p>';
return;
}
presetListDiv.innerHTML = '';
presetNames.sort().forEach(name => {
const preset = presets[name];
const item = document.createElement('div');
item.className = 'preset-item';
const savedDate = new Date(preset.savedAt);
const dateStr = savedDate.toLocaleDateString() + ' ' + savedDate.toLocaleTimeString();
item.innerHTML =
'<div>' +
'<div class="preset-name">' + name + '</div>' +
'<div class="preset-info">' + preset.count + ' messages | ' + dateStr + '</div>' +
'</div>' +
'<div class="preset-buttons">' +
'<button class="btn btn-success btn-small" onclick="loadPreset(\'' + name + '\')">Load</button>' +
'<button class="btn btn-danger btn-small" onclick="deletePreset(\'' + name + '\')">Delete</button>' +
'</div>';
presetListDiv.appendChild(item);
});
} catch(e) {
console.error('Failed to load preset list:', e);
presetListDiv.innerHTML = '<p style="text-align: center; color: #e74c3c; font-size: 0.9em;">Error loading presets</p>';
}
}
function addMessage() {
const id = document.getElementById('can-id').value.toUpperCase();
const type = document.getElementById('msg-type').value;
@@ -501,6 +682,11 @@ const char transmit_html[] PROGMEM = R"rawliteral(
this.value = this.value.toUpperCase().replace(/[^0-9A-F]/g, '');
});
// 페이지 로드 시 프리셋 목록 불러오기
window.addEventListener('load', function() {
loadPresetList();
});
initWebSocket();
</script>
</body>