ID 값 ext경우 잘못 ID읽는 부분 수정

This commit is contained in:
2025-10-17 22:51:46 +00:00
parent 6e28b48eaa
commit ce4444df2e
3 changed files with 685 additions and 98 deletions

View File

@@ -17,7 +17,7 @@ const char transmit_html[] PROGMEM = R"rawliteral(
padding: 10px;
}
.container {
max-width: 1200px;
max-width: 1400px;
margin: 0 auto;
background: white;
border-radius: 15px;
@@ -52,6 +52,39 @@ const char transmit_html[] PROGMEM = R"rawliteral(
.nav a.active { background: #3498db; }
.content { padding: 15px; }
.mode-tabs {
display: flex;
gap: 10px;
margin-bottom: 20px;
border-bottom: 3px solid #f093fb;
}
.mode-tab {
padding: 12px 25px;
background: #f8f9fa;
border: none;
border-radius: 10px 10px 0 0;
font-size: 1em;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
color: #666;
}
.mode-tab:hover {
background: #e9ecef;
}
.mode-tab.active {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
color: white;
transform: translateY(-2px);
}
.mode-content {
display: none;
}
.mode-content.active {
display: block;
}
.message-form {
background: #f8f9fa;
padding: 20px;
@@ -180,6 +213,88 @@ const char transmit_html[] PROGMEM = R"rawliteral(
font-size: 0.8em;
}
/* Sequence Mode Styles */
.sequence-builder {
background: #f8f9fa;
padding: 20px;
border-radius: 10px;
margin-bottom: 20px;
}
.sequence-step {
background: white;
padding: 15px;
margin-bottom: 10px;
border-radius: 8px;
border-left: 4px solid #667eea;
display: flex;
align-items: center;
gap: 15px;
}
.step-number {
background: #667eea;
color: white;
width: 35px;
height: 35px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
font-size: 1.1em;
flex-shrink: 0;
}
.step-type {
padding: 6px 12px;
border-radius: 5px;
font-size: 0.8em;
font-weight: 600;
flex-shrink: 0;
}
.step-message {
background: #3498db;
color: white;
}
.step-delay {
background: #f39c12;
color: white;
}
.step-content {
flex: 1;
font-family: 'Courier New', monospace;
font-size: 0.9em;
color: #333;
}
.step-controls {
display: flex;
gap: 5px;
}
.sequence-controls {
background: #e3f2fd;
padding: 15px;
border-radius: 8px;
margin-bottom: 15px;
display: flex;
gap: 15px;
align-items: center;
flex-wrap: wrap;
}
.repeat-option {
display: flex;
align-items: center;
gap: 10px;
}
.repeat-option label {
font-weight: 600;
color: #333;
}
.repeat-option input[type="number"] {
width: 80px;
padding: 8px;
border: 2px solid #ddd;
border-radius: 5px;
font-size: 0.9em;
}
h2 {
color: #333;
margin: 20px 0 15px 0;
@@ -294,6 +409,10 @@ const char transmit_html[] PROGMEM = R"rawliteral(
.preset-list {
grid-template-columns: 1fr;
}
.sequence-step {
flex-direction: column;
align-items: flex-start;
}
}
</style>
</head>
@@ -301,7 +420,7 @@ const char transmit_html[] PROGMEM = R"rawliteral(
<div class="container">
<div class="header">
<h1>CAN Transmitter</h1>
<p>Send CAN Messages</p>
<p>Send CAN Messages - Periodic & Sequence Mode</p>
</div>
<div class="nav">
@@ -316,90 +435,201 @@ 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>
<!-- Mode Tabs -->
<div class="mode-tabs">
<button class="mode-tab active" onclick="switchMode('periodic')"> Periodic Mode</button>
<button class="mode-tab" onclick="switchMode('sequence')">📋 Sequence Mode</button>
</div>
<!-- Periodic Mode -->
<div id="periodic-mode" class="mode-content active">
<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>
<div class="preset-list" id="preset-list">
<p style="text-align: center; color: #666; font-size: 0.9em;">No saved presets</p>
<h2>Add CAN Message</h2>
<div class="message-form">
<div class="form-row">
<div class="form-group">
<label>CAN ID (Hex)</label>
<input type="text" id="can-id" placeholder="123" maxlength="8">
</div>
<div class="form-group">
<label>Type</label>
<select id="msg-type">
<option value="std">Standard</option>
<option value="ext">Extended</option>
</select>
</div>
<div class="form-group">
<label>DLC</label>
<select id="dlc">
<option value="0">0</option>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
<option value="6">6</option>
<option value="7">7</option>
<option value="8" selected>8</option>
</select>
</div>
<div class="form-group">
<label>Interval (ms)</label>
<input type="number" id="interval" value="100" min="10" max="10000">
</div>
</div>
<div class="form-group" style="margin-bottom: 15px;">
<label>Send All Once - Waiting Time (ms)</label>
<input type="number" id="send-all-delay" value="10" min="0" max="1000"
style="max-width: 200px; padding: 10px; border: 2px solid #ddd; border-radius: 5px;">
<small style="color: #666; margin-top: 5px; display: block;">
Delay between messages when using "Send All Once" button
</small>
</div>
<div class="form-group" style="margin-bottom: 15px;">
<label>Data Bytes (Hex)</label>
<div class="data-bytes">
<input type="text" id="d0" placeholder="00" maxlength="2" value="00">
<input type="text" id="d1" placeholder="00" maxlength="2" value="00">
<input type="text" id="d2" placeholder="00" maxlength="2" value="00">
<input type="text" id="d3" placeholder="00" maxlength="2" value="00">
<input type="text" id="d4" placeholder="00" maxlength="2" value="00">
<input type="text" id="d5" placeholder="00" maxlength="2" value="00">
<input type="text" id="d6" placeholder="00" maxlength="2" value="00">
<input type="text" id="d7" placeholder="00" maxlength="2" value="00">
</div>
</div>
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
<button class="btn btn-primary" onclick="addMessage()">Add to List</button>
<button class="btn btn-success" onclick="sendOnce()">Send Once</button>
</div>
</div>
<h2>Message List</h2>
<div style="margin-bottom: 15px; display: flex; gap: 10px; flex-wrap: wrap;">
<button class="btn btn-success" onclick="startAll()">Start All</button>
<button class="btn btn-danger" onclick="stopAll()">Stop All</button>
<button class="btn btn-info" onclick="sendAllOnce()">📤 Send All Once</button>
<button class="btn btn-danger" onclick="clearAll()">Clear All</button>
</div>
<div class="message-list" id="message-list">
<p style="text-align: center; color: #666; font-size: 0.9em;">No messages added yet</p>
</div>
</div>
<h2>Add CAN Message</h2>
<div class="message-form">
<div class="form-row">
<div class="form-group">
<label>CAN ID (Hex)</label>
<input type="text" id="can-id" placeholder="123" maxlength="8">
</div>
<div class="form-group">
<label>Type</label>
<select id="msg-type">
<option value="std">Standard</option>
<option value="ext">Extended</option>
<!-- Sequence Mode -->
<div id="sequence-mode" class="mode-content">
<h2>Sequence Builder</h2>
<div class="sequence-controls">
<div class="repeat-option">
<label>Repeat Mode:</label>
<select id="repeat-mode" style="padding: 8px; border: 2px solid #ddd; border-radius: 5px;">
<option value="once">Once</option>
<option value="count">Count</option>
<option value="infinite">Infinite</option>
</select>
</div>
<div class="form-group">
<label>DLC</label>
<select id="dlc">
<option value="0">0</option>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
<option value="6">6</option>
<option value="7">7</option>
<option value="8" selected>8</option>
</select>
<div class="repeat-option" id="repeat-count-group" style="display:none;">
<label>Count:</label>
<input type="number" id="repeat-count" value="10" min="1" max="10000">
</div>
<div class="form-group">
<label>Interval (ms)</label>
<input type="number" id="interval" value="100" min="10" max="10000">
<button class="btn btn-success" onclick="startSequence()" id="start-seq-btn"> Start Sequence</button>
<button class="btn btn-danger" onclick="stopSequence()" id="stop-seq-btn" style="display:none;"> Stop Sequence</button>
<span id="seq-status" style="font-weight: 600; color: #667eea;"></span>
</div>
<div class="message-form">
<h3 style="margin-bottom: 15px; color: #667eea;">Add Step to Sequence</h3>
<div class="form-row">
<div class="form-group">
<label>Step Type</label>
<select id="seq-step-type" onchange="updateStepForm()">
<option value="message">CAN Message</option>
<option value="delay">Delay</option>
</select>
</div>
</div>
<div id="message-step-form">
<div class="form-row">
<div class="form-group">
<label>CAN ID (Hex)</label>
<input type="text" id="seq-can-id" placeholder="123" maxlength="8">
</div>
<div class="form-group">
<label>Type</label>
<select id="seq-msg-type">
<option value="std">Standard</option>
<option value="ext">Extended</option>
</select>
</div>
<div class="form-group">
<label>DLC</label>
<select id="seq-dlc">
<option value="0">0</option>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
<option value="6">6</option>
<option value="7">7</option>
<option value="8" selected>8</option>
</select>
</div>
</div>
<div class="form-group" style="margin-bottom: 15px;">
<label>Data Bytes (Hex)</label>
<div class="data-bytes">
<input type="text" id="seq-d0" placeholder="00" maxlength="2" value="00">
<input type="text" id="seq-d1" placeholder="00" maxlength="2" value="00">
<input type="text" id="seq-d2" placeholder="00" maxlength="2" value="00">
<input type="text" id="seq-d3" placeholder="00" maxlength="2" value="00">
<input type="text" id="seq-d4" placeholder="00" maxlength="2" value="00">
<input type="text" id="seq-d5" placeholder="00" maxlength="2" value="00">
<input type="text" id="seq-d6" placeholder="00" maxlength="2" value="00">
<input type="text" id="seq-d7" placeholder="00" maxlength="2" value="00">
</div>
</div>
</div>
<div id="delay-step-form" style="display:none;">
<div class="form-group" style="max-width: 300px;">
<label>Delay Time (ms)</label>
<input type="number" id="seq-delay" value="100" min="1" max="60000"
style="padding: 10px; border: 2px solid #ddd; border-radius: 5px;">
</div>
</div>
<div style="margin-top: 15px;">
<button class="btn btn-primary" onclick="addSequenceStep()"> Add Step</button>
</div>
</div>
<div class="form-group" style="margin-bottom: 15px;">
<label>Send All Once - Waiting Time (ms)</label>
<input type="number" id="send-all-delay" value="10" min="0" max="1000"
style="max-width: 200px; padding: 10px; border: 2px solid #ddd; border-radius: 5px;">
<small style="color: #666; margin-top: 5px; display: block;">
Delay between messages when using "Send All Once" button
</small>
<h2>Sequence Steps</h2>
<div style="margin-bottom: 15px; display: flex; gap: 10px; flex-wrap: wrap;">
<button class="btn btn-danger" onclick="clearSequence()">Clear All Steps</button>
<button class="btn btn-warning" onclick="saveSequencePreset()">💾 Save Sequence</button>
<button class="btn btn-info" onclick="loadSequencePreset()">📂 Load Sequence</button>
</div>
<div class="form-group" style="margin-bottom: 15px;">
<label>Data Bytes (Hex)</label>
<div class="data-bytes">
<input type="text" id="d0" placeholder="00" maxlength="2" value="00">
<input type="text" id="d1" placeholder="00" maxlength="2" value="00">
<input type="text" id="d2" placeholder="00" maxlength="2" value="00">
<input type="text" id="d3" placeholder="00" maxlength="2" value="00">
<input type="text" id="d4" placeholder="00" maxlength="2" value="00">
<input type="text" id="d5" placeholder="00" maxlength="2" value="00">
<input type="text" id="d6" placeholder="00" maxlength="2" value="00">
<input type="text" id="d7" placeholder="00" maxlength="2" value="00">
</div>
<div class="sequence-builder" id="sequence-list">
<p style="text-align: center; color: #666; font-size: 0.9em;">No steps added yet</p>
</div>
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
<button class="btn btn-primary" onclick="addMessage()">Add to List</button>
<button class="btn btn-success" onclick="sendOnce()">Send Once</button>
</div>
</div>
<h2>Message List</h2>
<div style="margin-bottom: 15px; display: flex; gap: 10px; flex-wrap: wrap;">
<button class="btn btn-success" onclick="startAll()">Start All</button>
<button class="btn btn-danger" onclick="stopAll()">Stop All</button>
<button class="btn btn-info" onclick="sendAllOnce()">📤 Send All Once</button>
<button class="btn btn-danger" onclick="clearAll()">Clear All</button>
</div>
<div class="message-list" id="message-list">
<p style="text-align: center; color: #666; font-size: 0.9em;">No messages added yet</p>
</div>
</div>
</div>
@@ -408,6 +638,12 @@ const char transmit_html[] PROGMEM = R"rawliteral(
let ws;
let messages = [];
let txCount = 0;
let currentMode = 'periodic';
// Sequence Mode Variables
let sequenceSteps = [];
let sequenceRunning = false;
let sequenceAbort = false;
function initWebSocket() {
ws = new WebSocket('ws://' + window.location.hostname + ':81');
@@ -432,7 +668,302 @@ const char transmit_html[] PROGMEM = R"rawliteral(
};
}
// 프리셋 관리
// Mode Switching
function switchMode(mode) {
currentMode = mode;
document.querySelectorAll('.mode-tab').forEach(tab => tab.classList.remove('active'));
document.querySelectorAll('.mode-content').forEach(content => content.classList.remove('active'));
if (mode === 'periodic') {
document.querySelectorAll('.mode-tab')[0].classList.add('active');
document.getElementById('periodic-mode').classList.add('active');
} else {
document.querySelectorAll('.mode-tab')[1].classList.add('active');
document.getElementById('sequence-mode').classList.add('active');
}
}
// Sequence Mode Functions
function updateStepForm() {
const stepType = document.getElementById('seq-step-type').value;
if (stepType === 'message') {
document.getElementById('message-step-form').style.display = 'block';
document.getElementById('delay-step-form').style.display = 'none';
} else {
document.getElementById('message-step-form').style.display = 'none';
document.getElementById('delay-step-form').style.display = 'block';
}
}
document.getElementById('repeat-mode').addEventListener('change', function() {
const mode = this.value;
const countGroup = document.getElementById('repeat-count-group');
if (mode === 'count') {
countGroup.style.display = 'flex';
} else {
countGroup.style.display = 'none';
}
});
function addSequenceStep() {
const stepType = document.getElementById('seq-step-type').value;
if (stepType === 'message') {
const id = document.getElementById('seq-can-id').value.toUpperCase();
const type = document.getElementById('seq-msg-type').value;
const dlc = parseInt(document.getElementById('seq-dlc').value);
if (!id || !/^[0-9A-F]+$/.test(id)) {
alert('Invalid CAN ID!');
return;
}
const data = [];
for (let i = 0; i < 8; i++) {
const val = document.getElementById('seq-d' + i).value.toUpperCase();
if (!/^[0-9A-F]{0,2}$/.test(val)) {
alert('Invalid data byte D' + i + '!');
return;
}
data.push(val.padStart(2, '0'));
}
sequenceSteps.push({
type: 'message',
id: id,
msgType: type,
dlc: dlc,
data: data
});
} else {
const delay = parseInt(document.getElementById('seq-delay').value);
if (delay < 1) {
alert('Delay must be at least 1ms!');
return;
}
sequenceSteps.push({
type: 'delay',
delay: delay
});
}
updateSequenceList();
}
function updateSequenceList() {
const list = document.getElementById('sequence-list');
if (sequenceSteps.length === 0) {
list.innerHTML = '<p style="text-align: center; color: #666; font-size: 0.9em;">No steps added yet</p>';
return;
}
list.innerHTML = '';
sequenceSteps.forEach((step, index) => {
const stepDiv = document.createElement('div');
stepDiv.className = 'sequence-step';
let content = '';
let stepTypeClass = '';
let stepTypeName = '';
if (step.type === 'message') {
stepTypeClass = 'step-message';
stepTypeName = 'MSG';
content = 'ID: 0x' + step.id + ' | ' + step.msgType.toUpperCase() + ' | DLC: ' + step.dlc + ' | Data: ' + step.data.join(' ');
} else {
stepTypeClass = 'step-delay';
stepTypeName = 'DELAY';
content = 'Wait ' + step.delay + ' ms';
}
stepDiv.innerHTML =
'<div class="step-number">' + (index + 1) + '</div>' +
'<div class="step-type ' + stepTypeClass + '">' + stepTypeName + '</div>' +
'<div class="step-content">' + content + '</div>' +
'<div class="step-controls">' +
'<button class="btn btn-warning btn-small" onclick="moveStepUp(' + index + ')"></button>' +
'<button class="btn btn-warning btn-small" onclick="moveStepDown(' + index + ')"></button>' +
'<button class="btn btn-danger btn-small" onclick="deleteStep(' + index + ')">Delete</button>' +
'</div>';
list.appendChild(stepDiv);
});
}
function moveStepUp(index) {
if (index > 0) {
[sequenceSteps[index], sequenceSteps[index - 1]] = [sequenceSteps[index - 1], sequenceSteps[index]];
updateSequenceList();
}
}
function moveStepDown(index) {
if (index < sequenceSteps.length - 1) {
[sequenceSteps[index], sequenceSteps[index + 1]] = [sequenceSteps[index + 1], sequenceSteps[index]];
updateSequenceList();
}
}
function deleteStep(index) {
sequenceSteps.splice(index, 1);
updateSequenceList();
}
function clearSequence() {
if (confirm('Clear all sequence steps?')) {
sequenceSteps = [];
updateSequenceList();
}
}
async function startSequence() {
if (sequenceSteps.length === 0) {
alert('No sequence steps added!');
return;
}
if (!ws || ws.readyState !== WebSocket.OPEN) {
alert('WebSocket not connected!');
return;
}
sequenceRunning = true;
sequenceAbort = false;
document.getElementById('start-seq-btn').style.display = 'none';
document.getElementById('stop-seq-btn').style.display = 'inline-block';
const repeatMode = document.getElementById('repeat-mode').value;
const repeatCount = parseInt(document.getElementById('repeat-count').value) || 1;
let totalIterations = 0;
if (repeatMode === 'once') {
totalIterations = 1;
} else if (repeatMode === 'count') {
totalIterations = repeatCount;
} else {
totalIterations = -1; // Infinite
}
let currentIteration = 0;
while (sequenceRunning && (totalIterations === -1 || currentIteration < totalIterations)) {
if (sequenceAbort) break;
currentIteration++;
if (totalIterations === -1) {
document.getElementById('seq-status').textContent = 'Running... (Iteration: ' + currentIteration + ')';
} else {
document.getElementById('seq-status').textContent = 'Running... (' + currentIteration + ' / ' + totalIterations + ')';
}
for (let i = 0; i < sequenceSteps.length; i++) {
if (sequenceAbort || !sequenceRunning) break;
const step = sequenceSteps[i];
if (step.type === 'message') {
sendCanMessage(step.id, step.msgType, step.dlc, step.data);
} else if (step.type === 'delay') {
await sleep(step.delay);
}
}
if (sequenceAbort || !sequenceRunning) break;
}
stopSequence();
}
function stopSequence() {
sequenceRunning = false;
sequenceAbort = true;
document.getElementById('start-seq-btn').style.display = 'inline-block';
document.getElementById('stop-seq-btn').style.display = 'none';
document.getElementById('seq-status').textContent = 'Stopped';
setTimeout(() => {
document.getElementById('seq-status').textContent = '';
}, 3000);
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function saveSequencePreset() {
if (sequenceSteps.length === 0) {
alert('No sequence steps to save!');
return;
}
const name = prompt('Enter sequence preset name:');
if (!name) return;
try {
let presets = JSON.parse(localStorage.getItem('sequence_presets') || '{}');
presets[name] = {
steps: JSON.parse(JSON.stringify(sequenceSteps)),
savedAt: new Date().toISOString()
};
localStorage.setItem('sequence_presets', JSON.stringify(presets));
alert('Sequence preset "' + name + '" saved!');
} catch(e) {
console.error('Failed to save sequence preset:', e);
alert('Failed to save sequence preset!');
}
}
function loadSequencePreset() {
try {
const presets = JSON.parse(localStorage.getItem('sequence_presets') || '{}');
const names = Object.keys(presets);
if (names.length === 0) {
alert('No saved sequence presets!');
return;
}
let options = 'Select a sequence preset:\n\n';
names.forEach((name, index) => {
options += (index + 1) + '. ' + name + '\n';
});
const selection = prompt(options + '\nEnter number:');
if (!selection) return;
const index = parseInt(selection) - 1;
if (index >= 0 && index < names.length) {
const name = names[index];
if (sequenceSteps.length > 0) {
if (!confirm('Current sequence will be replaced. Continue?')) {
return;
}
}
sequenceSteps = JSON.parse(JSON.stringify(presets[name].steps));
updateSequenceList();
alert('Loaded sequence preset "' + name + '"');
}
} catch(e) {
console.error('Failed to load sequence preset:', e);
alert('Failed to load sequence preset!');
}
}
// Periodic Mode Functions (기존 함수들)
function savePreset() {
const presetName = document.getElementById('preset-name').value.trim();
@@ -698,7 +1229,6 @@ const char transmit_html[] PROGMEM = R"rawliteral(
const delayMs = parseInt(document.getElementById('send-all-delay').value) || 10;
// 버튼 비활성화 및 시각적 피드백
const btn = event.target;
const originalText = btn.innerHTML;
btn.disabled = true;
@@ -706,10 +1236,8 @@ const char transmit_html[] PROGMEM = R"rawliteral(
let sentCount = 0;
// 순차적으로 메시지 전송 (딜레이 포함)
function sendNext(index) {
if (index >= messages.length) {
// 모든 메시지 전송 완료
console.log('Sent all messages once: ' + sentCount + ' messages');
btn.innerHTML = ' Sent ' + sentCount + ' msgs';
@@ -727,16 +1255,13 @@ const char transmit_html[] PROGMEM = R"rawliteral(
sendCanMessage(msg.id, msg.type, msg.dlc, msg.data);
sentCount++;
// 진행 상황 표시
btn.innerHTML = ' Sending ' + (index + 1) + '/' + messages.length;
// 다음 메시지 전송 (딜레이 후)
setTimeout(() => {
sendNext(index + 1);
}, delayMs);
}
// 첫 번째 메시지부터 시작
sendNext(0);
}
@@ -748,7 +1273,8 @@ const char transmit_html[] PROGMEM = R"rawliteral(
}
}
document.querySelectorAll('input[id^="d"]').forEach(input => {
// Input validation
document.querySelectorAll('input[id^="d"], input[id^="seq-d"]').forEach(input => {
input.addEventListener('input', function() {
this.value = this.value.toUpperCase().replace(/[^0-9A-F]/g, '');
});
@@ -758,7 +1284,10 @@ const char transmit_html[] PROGMEM = R"rawliteral(
this.value = this.value.toUpperCase().replace(/[^0-9A-F]/g, '');
});
// 페이지 로드 시 프리셋 목록 불러오기
document.getElementById('seq-can-id').addEventListener('input', function() {
this.value = this.value.toUpperCase().replace(/[^0-9A-F]/g, '');
});
window.addEventListener('load', function() {
loadPresetList();
});