Debug dongle test code
This commit is contained in:
429
data/index.html
Normal file
429
data/index.html
Normal file
@@ -0,0 +1,429 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>ESP32 Debug Dongle</title>
|
||||
|
||||
<!-- xterm.js from CDN -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@5.3.0/css/xterm.min.css">
|
||||
<script src="https://cdn.jsdelivr.net/npm/xterm@5.3.0/lib/xterm.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.8.0/lib/xterm-addon-fit.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-web-links@0.9.0/lib/xterm-addon-web-links.min.js"></script>
|
||||
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
||||
background: #1a1a2e;
|
||||
color: #eee;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.header {
|
||||
background: #16213e;
|
||||
padding: 10px 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid #0f3460;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
margin: 0;
|
||||
font-size: 1.2em;
|
||||
color: #e94560;
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.control-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.control-group label {
|
||||
font-size: 0.85em;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
select, button {
|
||||
background: #0f3460;
|
||||
color: #fff;
|
||||
border: 1px solid #1a1a2e;
|
||||
padding: 6px 12px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
select:hover, button:hover {
|
||||
background: #1a4a7a;
|
||||
}
|
||||
|
||||
button.danger {
|
||||
background: #e94560;
|
||||
}
|
||||
|
||||
button.danger:hover {
|
||||
background: #ff6b6b;
|
||||
}
|
||||
|
||||
.status {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.status-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.status-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: #666;
|
||||
}
|
||||
|
||||
.status-dot.connected {
|
||||
background: #4ade80;
|
||||
}
|
||||
|
||||
.status-dot.disconnected {
|
||||
background: #ef4444;
|
||||
}
|
||||
|
||||
.terminal-container {
|
||||
flex: 1;
|
||||
padding: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#terminal {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.footer {
|
||||
background: #16213e;
|
||||
padding: 8px 20px;
|
||||
font-size: 0.75em;
|
||||
color: #666;
|
||||
border-top: 1px solid #0f3460;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.controls {
|
||||
gap: 10px;
|
||||
}
|
||||
.control-group label {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1>🔌 ESP32 Debug Dongle</h1>
|
||||
<div class="controls">
|
||||
<div class="control-group">
|
||||
<label>Port:</label>
|
||||
<select id="portSelect">
|
||||
<option value="0">Internal (Debug)</option>
|
||||
<option value="1">USB Serial</option>
|
||||
<option value="2">External (Serial1)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label>Baud:</label>
|
||||
<select id="baudSelect">
|
||||
<option value="9600">9600</option>
|
||||
<option value="19200">19200</option>
|
||||
<option value="38400">38400</option>
|
||||
<option value="57600">57600</option>
|
||||
<option value="115200" selected>115200</option>
|
||||
<option value="230400">230400</option>
|
||||
<option value="460800">460800</option>
|
||||
<option value="921600">921600</option>
|
||||
</select>
|
||||
</div>
|
||||
<button onclick="clearTerminal()">Clear</button>
|
||||
<button onclick="reconnect()">Reconnect</button>
|
||||
</div>
|
||||
<div class="status">
|
||||
<div class="status-item">
|
||||
<span class="status-dot" id="wsStatus"></span>
|
||||
<span>WebSocket</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="status-dot" id="btStatus"></span>
|
||||
<span>Bluetooth</span>
|
||||
</div>
|
||||
<div class="status-item" id="wifiInfo" style="display:none;">
|
||||
<span id="wifiIcon">📶</span>
|
||||
<span id="wifiText">WiFi</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="terminal-container">
|
||||
<div id="terminal"></div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<span id="rxCount">RX: 0 bytes</span>
|
||||
<span id="txCount">TX: 0 bytes</span>
|
||||
<span id="heap">Heap: --</span>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Terminal setup
|
||||
const term = new Terminal({
|
||||
cursorBlink: true,
|
||||
fontSize: 14,
|
||||
fontFamily: 'Menlo, Monaco, "Courier New", monospace',
|
||||
theme: {
|
||||
background: '#1a1a2e',
|
||||
foreground: '#eee',
|
||||
cursor: '#e94560',
|
||||
cursorAccent: '#1a1a2e',
|
||||
selection: 'rgba(233, 69, 96, 0.3)',
|
||||
black: '#1a1a2e',
|
||||
red: '#e94560',
|
||||
green: '#4ade80',
|
||||
yellow: '#fbbf24',
|
||||
blue: '#60a5fa',
|
||||
magenta: '#c084fc',
|
||||
cyan: '#22d3d3',
|
||||
white: '#eee',
|
||||
brightBlack: '#666',
|
||||
brightRed: '#ff6b6b',
|
||||
brightGreen: '#86efac',
|
||||
brightYellow: '#fcd34d',
|
||||
brightBlue: '#93c5fd',
|
||||
brightMagenta: '#d8b4fe',
|
||||
brightCyan: '#67e8f9',
|
||||
brightWhite: '#fff'
|
||||
}
|
||||
});
|
||||
|
||||
// Addons
|
||||
const fitAddon = new FitAddon.FitAddon();
|
||||
const webLinksAddon = new WebLinksAddon.WebLinksAddon();
|
||||
|
||||
term.loadAddon(fitAddon);
|
||||
term.loadAddon(webLinksAddon);
|
||||
|
||||
// Open terminal
|
||||
term.open(document.getElementById('terminal'));
|
||||
fitAddon.fit();
|
||||
|
||||
// Resize handler
|
||||
window.addEventListener('resize', () => {
|
||||
fitAddon.fit();
|
||||
});
|
||||
|
||||
// Stats
|
||||
let rxBytes = 0;
|
||||
let txBytes = 0;
|
||||
|
||||
// WebSocket connection
|
||||
let ws = null;
|
||||
let reconnectTimer = null;
|
||||
|
||||
function connect() {
|
||||
const wsUrl = `ws://${window.location.host}/ws`;
|
||||
term.writeln(`\x1b[33mConnecting to ${wsUrl}...\x1b[0m`);
|
||||
|
||||
ws = new WebSocket(wsUrl);
|
||||
ws.binaryType = 'arraybuffer';
|
||||
|
||||
ws.onopen = () => {
|
||||
term.writeln('\x1b[32mConnected!\x1b[0m\r\n');
|
||||
updateStatus('wsStatus', true);
|
||||
|
||||
// Request initial status
|
||||
sendCommand({ cmd: 'getStatus' });
|
||||
};
|
||||
|
||||
ws.onclose = () => {
|
||||
term.writeln('\r\n\x1b[31mDisconnected\x1b[0m');
|
||||
updateStatus('wsStatus', false);
|
||||
|
||||
// Auto-reconnect after 3 seconds
|
||||
if (!reconnectTimer) {
|
||||
reconnectTimer = setTimeout(() => {
|
||||
reconnectTimer = null;
|
||||
connect();
|
||||
}, 3000);
|
||||
}
|
||||
};
|
||||
|
||||
ws.onerror = (err) => {
|
||||
term.writeln('\r\n\x1b[31mWebSocket error\x1b[0m');
|
||||
console.error('WebSocket error:', err);
|
||||
};
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
if (event.data instanceof ArrayBuffer) {
|
||||
const data = new Uint8Array(event.data);
|
||||
|
||||
// Check for command response (starts with 0x00)
|
||||
if (data.length > 0 && data[0] === 0x00) {
|
||||
const jsonStr = new TextDecoder().decode(data.slice(1));
|
||||
try {
|
||||
const msg = JSON.parse(jsonStr);
|
||||
handleResponse(msg);
|
||||
} catch (e) {
|
||||
console.error('JSON parse error:', e);
|
||||
}
|
||||
} else {
|
||||
// Regular serial data
|
||||
rxBytes += data.length;
|
||||
updateStats();
|
||||
|
||||
// Write to terminal
|
||||
const text = new TextDecoder().decode(data);
|
||||
term.write(text);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function handleResponse(msg) {
|
||||
switch (msg.type) {
|
||||
case 'status':
|
||||
document.getElementById('portSelect').value = msg.currentPort;
|
||||
document.getElementById('baudSelect').value = msg.baudSerial1;
|
||||
updateStatus('btStatus', msg.btConnected);
|
||||
document.getElementById('heap').textContent = `Heap: ${msg.freeHeap}`;
|
||||
|
||||
// Show WiFi info
|
||||
const wifiInfo = document.getElementById('wifiInfo');
|
||||
const wifiText = document.getElementById('wifiText');
|
||||
wifiInfo.style.display = 'flex';
|
||||
if (msg.wifiMode === 'station' && msg.rssi) {
|
||||
const signal = msg.rssi > -50 ? '📶' : msg.rssi > -70 ? '📶' : '📶';
|
||||
wifiText.textContent = `${msg.rssi}dBm`;
|
||||
} else {
|
||||
wifiText.textContent = 'AP Mode';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'portChanged':
|
||||
document.getElementById('portSelect').value = msg.port;
|
||||
const portNames = ['Internal', 'USB Serial', 'External'];
|
||||
term.writeln(`\r\n\x1b[33m[Switched to ${portNames[msg.port]}]\x1b[0m\r\n`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function sendCommand(cmd) {
|
||||
if (ws && ws.readyState === WebSocket.OPEN) {
|
||||
const json = JSON.stringify(cmd);
|
||||
const data = new Uint8Array(json.length + 1);
|
||||
data[0] = 0x00; // Command prefix
|
||||
for (let i = 0; i < json.length; i++) {
|
||||
data[i + 1] = json.charCodeAt(i);
|
||||
}
|
||||
ws.send(data);
|
||||
}
|
||||
}
|
||||
|
||||
function sendData(data) {
|
||||
if (ws && ws.readyState === WebSocket.OPEN) {
|
||||
const bytes = new TextEncoder().encode(data);
|
||||
txBytes += bytes.length;
|
||||
updateStats();
|
||||
ws.send(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
// Terminal input handler
|
||||
term.onData(data => {
|
||||
sendData(data);
|
||||
});
|
||||
|
||||
// UI handlers
|
||||
document.getElementById('portSelect').addEventListener('change', (e) => {
|
||||
sendCommand({ cmd: 'setPort', port: parseInt(e.target.value) });
|
||||
});
|
||||
|
||||
document.getElementById('baudSelect').addEventListener('change', (e) => {
|
||||
const port = parseInt(document.getElementById('portSelect').value);
|
||||
sendCommand({ cmd: 'setBaud', port: port, baud: parseInt(e.target.value) });
|
||||
});
|
||||
|
||||
function clearTerminal() {
|
||||
term.clear();
|
||||
rxBytes = 0;
|
||||
txBytes = 0;
|
||||
updateStats();
|
||||
}
|
||||
|
||||
function reconnect() {
|
||||
if (ws) {
|
||||
ws.close();
|
||||
}
|
||||
if (reconnectTimer) {
|
||||
clearTimeout(reconnectTimer);
|
||||
reconnectTimer = null;
|
||||
}
|
||||
setTimeout(connect, 100);
|
||||
}
|
||||
|
||||
function updateStatus(elementId, connected) {
|
||||
const el = document.getElementById(elementId);
|
||||
el.classList.toggle('connected', connected);
|
||||
el.classList.toggle('disconnected', !connected);
|
||||
}
|
||||
|
||||
function updateStats() {
|
||||
document.getElementById('rxCount').textContent = `RX: ${formatBytes(rxBytes)}`;
|
||||
document.getElementById('txCount').textContent = `TX: ${formatBytes(txBytes)}`;
|
||||
}
|
||||
|
||||
function formatBytes(bytes) {
|
||||
if (bytes < 1024) return bytes + ' B';
|
||||
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
|
||||
return (bytes / 1024 / 1024).toFixed(1) + ' MB';
|
||||
}
|
||||
|
||||
// Initial connection
|
||||
term.writeln('\x1b[1;36m╔═══════════════════════════════════════╗\x1b[0m');
|
||||
term.writeln('\x1b[1;36m║ ESP32 Debug Dongle Terminal ║\x1b[0m');
|
||||
term.writeln('\x1b[1;36m╚═══════════════════════════════════════╝\x1b[0m');
|
||||
term.writeln('');
|
||||
term.writeln('Ports:');
|
||||
term.writeln(' • Internal: ESP32 debug output (virtual serial)');
|
||||
term.writeln(' • USB Serial: Main UART (shared with USB)');
|
||||
term.writeln(' • External: Serial1 (GPIO16=RX, GPIO17=TX)');
|
||||
term.writeln('');
|
||||
|
||||
connect();
|
||||
|
||||
// Periodic status update
|
||||
setInterval(() => {
|
||||
sendCommand({ cmd: 'getStatus' });
|
||||
}, 5000);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
214
data/index_offline.html
Normal file
214
data/index_offline.html
Normal file
@@ -0,0 +1,214 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>ESP32 Debug Dongle (Offline)</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body {
|
||||
font-family: monospace;
|
||||
background: #1a1a2e;
|
||||
color: #eee;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.header {
|
||||
background: #16213e;
|
||||
padding: 10px 15px;
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
border-bottom: 1px solid #0f3460;
|
||||
}
|
||||
.header h1 { font-size: 1em; color: #e94560; }
|
||||
select, button, input {
|
||||
background: #0f3460;
|
||||
color: #fff;
|
||||
border: 1px solid #1a1a2e;
|
||||
padding: 5px 10px;
|
||||
border-radius: 3px;
|
||||
font-family: monospace;
|
||||
}
|
||||
.terminal-wrap {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
#output {
|
||||
flex: 1;
|
||||
background: #0d0d1a;
|
||||
border: 1px solid #333;
|
||||
padding: 10px;
|
||||
overflow-y: auto;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
#inputLine {
|
||||
display: flex;
|
||||
margin-top: 5px;
|
||||
}
|
||||
#input {
|
||||
flex: 1;
|
||||
background: #0d0d1a;
|
||||
border: 1px solid #333;
|
||||
color: #4ade80;
|
||||
padding: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
#input:focus { outline: 1px solid #e94560; }
|
||||
.status { font-size: 0.8em; color: #666; }
|
||||
.status.connected { color: #4ade80; }
|
||||
.green { color: #4ade80; }
|
||||
.red { color: #ef4444; }
|
||||
.yellow { color: #fbbf24; }
|
||||
.cyan { color: #22d3d3; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1>🔌 ESP32 Debug</h1>
|
||||
<select id="port">
|
||||
<option value="0">Internal</option>
|
||||
<option value="1">USB</option>
|
||||
<option value="2">External</option>
|
||||
</select>
|
||||
<select id="baud">
|
||||
<option value="9600">9600</option>
|
||||
<option value="115200" selected>115200</option>
|
||||
<option value="921600">921600</option>
|
||||
</select>
|
||||
<button onclick="clear_()">Clear</button>
|
||||
<button onclick="reconnect()">Reconnect</button>
|
||||
<span id="status" class="status">Disconnected</span>
|
||||
</div>
|
||||
<div class="terminal-wrap">
|
||||
<div id="output"></div>
|
||||
<div id="inputLine">
|
||||
<input type="text" id="input" placeholder="Type and press Enter..." autofocus>
|
||||
<button onclick="sendInput()">Send</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const output = document.getElementById('output');
|
||||
const input = document.getElementById('input');
|
||||
const status = document.getElementById('status');
|
||||
let ws = null;
|
||||
let history = [];
|
||||
let histIdx = 0;
|
||||
|
||||
function log(msg, cls='') {
|
||||
const span = document.createElement('span');
|
||||
if(cls) span.className = cls;
|
||||
span.textContent = msg;
|
||||
output.appendChild(span);
|
||||
output.scrollTop = output.scrollHeight;
|
||||
}
|
||||
|
||||
function connect() {
|
||||
log('Connecting...\\n', 'yellow');
|
||||
ws = new WebSocket('ws://' + location.host + '/ws');
|
||||
ws.binaryType = 'arraybuffer';
|
||||
|
||||
ws.onopen = () => {
|
||||
log('Connected!\\n', 'green');
|
||||
status.textContent = 'Connected';
|
||||
status.className = 'status connected';
|
||||
sendCmd({cmd:'getStatus'});
|
||||
};
|
||||
|
||||
ws.onclose = () => {
|
||||
log('Disconnected\\n', 'red');
|
||||
status.textContent = 'Disconnected';
|
||||
status.className = 'status';
|
||||
setTimeout(connect, 3000);
|
||||
};
|
||||
|
||||
ws.onmessage = (e) => {
|
||||
const data = new Uint8Array(e.data);
|
||||
if(data[0] === 0) {
|
||||
// Command response
|
||||
try {
|
||||
const json = JSON.parse(new TextDecoder().decode(data.slice(1)));
|
||||
if(json.type === 'status') {
|
||||
document.getElementById('port').value = json.currentPort;
|
||||
document.getElementById('baud').value = json.baudSerial1;
|
||||
} else if(json.type === 'portChanged') {
|
||||
log('[Port changed]\\n', 'yellow');
|
||||
}
|
||||
} catch(e) {}
|
||||
} else {
|
||||
output.appendChild(document.createTextNode(new TextDecoder().decode(data)));
|
||||
output.scrollTop = output.scrollHeight;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function sendCmd(cmd) {
|
||||
if(ws && ws.readyState === 1) {
|
||||
const json = JSON.stringify(cmd);
|
||||
const arr = new Uint8Array(json.length + 1);
|
||||
arr[0] = 0;
|
||||
for(let i=0; i<json.length; i++) arr[i+1] = json.charCodeAt(i);
|
||||
ws.send(arr);
|
||||
}
|
||||
}
|
||||
|
||||
function sendData(str) {
|
||||
if(ws && ws.readyState === 1) {
|
||||
ws.send(new TextEncoder().encode(str));
|
||||
}
|
||||
}
|
||||
|
||||
function sendInput() {
|
||||
const val = input.value;
|
||||
if(val) {
|
||||
history.push(val);
|
||||
histIdx = history.length;
|
||||
sendData(val + '\\r\\n');
|
||||
log('> ' + val + '\\n', 'cyan');
|
||||
input.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
function clear_() {
|
||||
output.innerHTML = '';
|
||||
}
|
||||
|
||||
function reconnect() {
|
||||
if(ws) ws.close();
|
||||
}
|
||||
|
||||
input.addEventListener('keydown', (e) => {
|
||||
if(e.key === 'Enter') sendInput();
|
||||
else if(e.key === 'ArrowUp' && histIdx > 0) {
|
||||
histIdx--;
|
||||
input.value = history[histIdx];
|
||||
} else if(e.key === 'ArrowDown' && histIdx < history.length) {
|
||||
histIdx++;
|
||||
input.value = history[histIdx] || '';
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('port').onchange = (e) => {
|
||||
sendCmd({cmd:'setPort', port:parseInt(e.target.value)});
|
||||
};
|
||||
|
||||
document.getElementById('baud').onchange = (e) => {
|
||||
const port = parseInt(document.getElementById('port').value);
|
||||
sendCmd({cmd:'setBaud', port:port, baud:parseInt(e.target.value)});
|
||||
};
|
||||
|
||||
log('ESP32 Debug Dongle - Offline Version\\n', 'cyan');
|
||||
log('(Lightweight terminal without xterm.js)\\n\\n');
|
||||
connect();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user