Debug dongle test code

This commit is contained in:
2025-12-09 18:40:35 +11:00
commit fc84cfa66a
8 changed files with 1568 additions and 0 deletions

429
data/index.html Normal file
View 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
View 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>