Files
250928_esp32_spi_sdcard_ads…/transmit.h
byun 2dd7f9177f 00004 monitor 창 graph연결 버튼 추가, 핸드폰 자동화면 변경
여전히 메인 웹페이지에서 monitor, Transmit 밖에 선택지가 없는데 다시한번 누락되어 있는 부분 확인해줘, 그리고 핸드폰 에서 웹페이지 접속시 가로창 넘게 표시되어 이미지가 짤리는데 핸드폰 화면에 맞추어 글자와 이미지들이 들어갈 수 있게 자동 조절하게 해줄 수 있어? 특히 전송 웹페이지에 data byte(hex) 입력 칸들이 화면에 다 안들어가서 짤린상태로 전체 값들을 넣을 수 가 없어
2025-10-05 16:36:18 +00:00

510 lines
18 KiB
C

#ifndef TRANSMIT_H
#define TRANSMIT_H
const char transmit_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>CAN Transmitter</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
min-height: 100vh;
padding: 10px;
}
.container {
max-width: 1200px;
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, #f093fb 0%, #f5576c 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;
}
.nav a:hover { background: #34495e; }
.nav a.active { background: #3498db; }
.content { padding: 15px; }
.message-form {
background: #f8f9fa;
padding: 20px;
border-radius: 10px;
margin-bottom: 20px;
}
.form-row {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 12px;
margin-bottom: 12px;
}
.form-group {
display: flex;
flex-direction: column;
}
.form-group label {
font-weight: 600;
margin-bottom: 5px;
color: #333;
font-size: 0.9em;
}
.form-group input, .form-group select {
padding: 10px;
border: 2px solid #ddd;
border-radius: 5px;
font-size: 0.95em;
font-family: 'Courier New', monospace;
}
.form-group input:focus, .form-group select:focus {
outline: none;
border-color: #f093fb;
}
.data-bytes {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(45px, 1fr));
gap: 8px;
max-width: 100%;
}
.data-bytes input {
text-align: center;
text-transform: uppercase;
width: 100%;
min-width: 45px;
padding: 10px 5px;
}
.btn {
padding: 12px 20px;
border: none;
border-radius: 5px;
font-size: 0.95em;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
}
.btn-primary {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
color: white;
}
.btn-success {
background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
color: white;
}
.btn-danger {
background: linear-gradient(135deg, #eb3349 0%, #f45c43 100%);
color: white;
}
.btn:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(0,0,0,0.3); }
.message-list {
background: #f8f9fa;
border-radius: 10px;
padding: 15px;
}
.message-item {
background: white;
padding: 12px;
margin-bottom: 10px;
border-radius: 8px;
display: grid;
grid-template-columns: 90px 70px 1fr auto;
gap: 10px;
align-items: center;
border-left: 4px solid #f093fb;
}
.message-item.active {
border-left-color: #38ef7d;
background: #f0fff4;
}
.message-id {
font-weight: 700;
color: #f5576c;
font-family: 'Courier New', monospace;
font-size: 0.9em;
}
.message-type {
padding: 4px 8px;
border-radius: 5px;
font-size: 0.75em;
font-weight: 600;
text-align: center;
}
.type-std { background: #3498db; color: white; }
.type-ext { background: #9b59b6; color: white; }
.message-data {
font-family: 'Courier New', monospace;
color: #666;
font-size: 0.85em;
word-break: break-all;
}
.message-interval {
color: #888;
font-size: 0.8em;
}
.message-controls {
display: flex;
gap: 5px;
flex-wrap: wrap;
}
.btn-small {
padding: 6px 12px;
font-size: 0.8em;
}
h2 {
color: #333;
margin: 20px 0 15px 0;
padding-bottom: 8px;
border-bottom: 3px solid #f093fb;
font-size: 1.3em;
}
.status-bar {
background: #2c3e50;
color: white;
padding: 10px 15px;
border-radius: 5px;
margin-bottom: 15px;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 0.9em;
}
.status-bar span { font-weight: 600; }
@media (max-width: 768px) {
body { padding: 5px; }
.header h1 { font-size: 1.5em; }
.content { padding: 10px; }
.form-row { grid-template-columns: 1fr; }
.data-bytes {
grid-template-columns: repeat(4, 1fr);
gap: 6px;
}
.data-bytes input {
min-width: 40px;
font-size: 0.9em;
padding: 8px 4px;
}
.message-item {
grid-template-columns: 1fr;
gap: 8px;
}
.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;
}
h2 {
font-size: 1.1em;
}
.status-bar {
flex-direction: column;
gap: 8px;
text-align: center;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>CAN Transmitter</h1>
<p>Send CAN Messages</p>
</div>
<div class="nav">
<a href="/">Monitor</a>
<a href="/transmit" class="active">Transmit</a>
<a href="/graph">Graph</a>
</div>
<div class="content">
<div class="status-bar">
<span id="connection-status">Disconnected</span>
<span id="tx-count">Sent: 0</span>
</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>
</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>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-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>
<script>
let ws;
let messages = [];
let txCount = 0;
function initWebSocket() {
ws = new WebSocket('ws://' + window.location.hostname + ':81');
ws.onopen = function() {
console.log('WebSocket connected');
document.getElementById('connection-status').innerHTML = 'Connected';
};
ws.onclose = function() {
console.log('WebSocket disconnected');
document.getElementById('connection-status').innerHTML = 'Disconnected';
setTimeout(initWebSocket, 3000);
};
ws.onmessage = function(event) {
const data = JSON.parse(event.data);
if (data.type === 'txStatus') {
txCount = data.count;
document.getElementById('tx-count').textContent = 'Sent: ' + txCount;
}
};
}
function addMessage() {
const id = document.getElementById('can-id').value.toUpperCase();
const type = document.getElementById('msg-type').value;
const dlc = parseInt(document.getElementById('dlc').value);
const interval = parseInt(document.getElementById('interval').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('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'));
}
const msg = {
id: id,
type: type,
dlc: dlc,
data: data,
interval: interval,
active: false
};
messages.push(msg);
updateMessageList();
}
function sendOnce() {
const id = document.getElementById('can-id').value.toUpperCase();
const type = document.getElementById('msg-type').value;
const dlc = parseInt(document.getElementById('dlc').value);
const data = [];
for (let i = 0; i < 8; i++) {
data.push(document.getElementById('d' + i).value.toUpperCase().padStart(2, '0'));
}
sendCanMessage(id, type, dlc, data);
}
function sendCanMessage(id, type, dlc, data) {
const cmd = {
cmd: 'sendCan',
id: id,
type: type,
dlc: dlc,
data: data.join('')
};
ws.send(JSON.stringify(cmd));
}
function updateMessageList() {
const list = document.getElementById('message-list');
if (messages.length === 0) {
list.innerHTML = '<p style="text-align: center; color: #666; font-size: 0.9em;">No messages</p>';
return;
}
list.innerHTML = '';
messages.forEach((msg, index) => {
const item = document.createElement('div');
item.className = 'message-item' + (msg.active ? ' active' : '');
item.innerHTML =
'<div class="message-id">0x' + msg.id + '</div>' +
'<div class="message-type type-' + msg.type + '">' + msg.type.toUpperCase() + '</div>' +
'<div class="message-data">' + msg.data.slice(0, msg.dlc).join(' ') + '</div>' +
'<div class="message-controls">' +
'<button class="btn ' + (msg.active ? 'btn-danger' : 'btn-success') + ' btn-small" onclick="toggleMessage(' + index + ')">' +
(msg.active ? 'Stop' : 'Start') +
'</button>' +
'<button class="btn btn-danger btn-small" onclick="deleteMessage(' + index + ')">Delete</button>' +
'</div>';
list.appendChild(item);
});
}
function toggleMessage(index) {
messages[index].active = !messages[index].active;
const cmd = {
cmd: messages[index].active ? 'startMsg' : 'stopMsg',
index: index,
id: messages[index].id,
type: messages[index].type,
dlc: messages[index].dlc,
data: messages[index].data.join(''),
interval: messages[index].interval
};
ws.send(JSON.stringify(cmd));
updateMessageList();
}
function deleteMessage(index) {
if (messages[index].active) {
ws.send(JSON.stringify({cmd: 'stopMsg', index: index}));
}
messages.splice(index, 1);
updateMessageList();
}
function startAll() {
messages.forEach((msg, index) => {
if (!msg.active) {
toggleMessage(index);
}
});
}
function stopAll() {
ws.send(JSON.stringify({cmd: 'stopAll'}));
messages.forEach(msg => msg.active = false);
updateMessageList();
}
function clearAll() {
if (confirm('Clear all messages?')) {
stopAll();
messages = [];
updateMessageList();
}
}
document.querySelectorAll('input[id^="d"]').forEach(input => {
input.addEventListener('input', function() {
this.value = this.value.toUpperCase().replace(/[^0-9A-F]/g, '');
});
});
document.getElementById('can-id').addEventListener('input', function() {
this.value = this.value.toUpperCase().replace(/[^0-9A-F]/g, '');
});
initWebSocket();
</script>
</body>
</html>
)rawliteral";
#endif