transmit 창 메세지 리스트 저장불러오기 기능 추가, monitor 창 canspeed 유지기능 추가
This commit is contained in:
49
index.h
49
index.h
@@ -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>
|
||||
|
||||
216
transmit.h
216
transmit.h
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user