ID 값 ext경우 잘못 ID읽는 부분 수정
This commit is contained in:
693
transmit.h
693
transmit.h
@@ -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();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user