@@ -9,8 +9,10 @@ const char index_html[] PROGMEM = R"rawliteral(
99 * { margin: 0; padding: 0; box-sizing: border-box; }
1010 body { font-family: Arial, sans-serif; background: #1a1a1a; color: #fff; padding: 20px; }
1111 .container { max-width: 1200px; margin: 0 auto; }
12- h1 { margin-bottom: 20px; color: #4CAF50; }
13- .card { background: #2a2a2a; padding: 20px; margin-bottom: 20px; border-radius: 8px; }
12+ h1 { color: #4CAF50; font-size: 20px; }
13+ .header-row { display: flex; align-items: center; gap: 30px; margin-bottom: 15px; }
14+ .header-row h1 { margin: 0; }
15+ .card { background: #2a2a2a; padding: 15px; margin-bottom: 15px; border-radius: 8px; }
1416 .card h2 { margin-bottom: 15px; color: #4CAF50; font-size: 18px; }
1517 .stepper-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 15px; }
1618 .pin-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 10px; max-height: 400px; overflow-y: auto; }
@@ -64,14 +66,13 @@ const char index_html[] PROGMEM = R"rawliteral(
6466</head>
6567<body>
6668 <div class="container">
67- <h1>ESP32 Stepper Control</h1>
68-
69- <div class="card">
70- <h2>System Status <span class="connection-status" id="wsStatus"></span></h2>
71- <div class="system-info">
72- <div>IP: <span id="ip">-</span></div>
73- <div>Free Heap: <span id="heap">-</span> bytes</div>
74- <div>Uptime: <span id="uptime">-</span></div>
69+ <div class="header-row">
70+ <h1>ESP32 Stepper Control</h1>
71+ <div class="system-info" style="display:flex;gap:20px;align-items:center">
72+ <span class="connection-status" id="wsStatus"></span>
73+ <span>IP: <strong id="ip">-</strong></span>
74+ <span>Heap: <strong id="heap">-</strong></span>
75+ <span>Up: <strong id="uptime">-</strong></span>
7576 </div>
7677 </div>
7778
@@ -105,24 +106,8 @@ const char index_html[] PROGMEM = R"rawliteral(
105106 </div>
106107
107108 <div class="card">
108- <h2>Quick Commands</h2>
109- <div class="controls">
110- <input type="number" id="stepperId" placeholder="Stepper ID" value="0" min="0">
111- <input type="number" id="moveValue" placeholder="Steps" value="1000">
112- <button class="btn-primary" onclick="sendCommand('move')">Move</button>
113- <button class="btn-primary" onclick="sendCommand('move_to')">Move To</button>
114- <button class="btn-secondary" onclick="sendCommand('stop')">Stop</button>
115- <button class="btn-danger" onclick="sendCommand('force_stop')">Force Stop</button>
116- </div>
117- </div>
118-
119- <div class="card">
120- <h2>GPIO Pins</h2>
121- <div class="controls" style="margin-bottom:15px">
122- <button class="btn-secondary" onclick="loadPins()">Refresh Pins</button>
123- <button class="btn-secondary" id="togglePinBtn" onclick="togglePinView()">Show All Pins</button>
124- </div>
125- <div id="pins" class="pin-grid">Loading...</div>
109+ <h2>GPIO Pins <span style="font-weight:normal;color:#888;font-size:12px">(click IN/OUT to toggle mode, click value to toggle 0/1)</span></h2>
110+ <div id="pins" style="display:grid;grid-template-columns:repeat(8,1fr);gap:5px;font-size:11px">Loading...</div>
126111 </div>
127112
128113 <div class="card">
@@ -375,12 +360,6 @@ const char index_html[] PROGMEM = R"rawliteral(
375360 }
376361 }
377362
378- async function sendCommand(command) {
379- const stepperId = parseInt(document.getElementById('stepperId').value);
380- const value = parseInt(document.getElementById('moveValue').value);
381- await sendStepperCommand(stepperId, command, value);
382- }
383-
384363 async function updateSystemStatus() {
385364 try {
386365 const response = await fetch('/api/status');
@@ -406,14 +385,6 @@ const char index_html[] PROGMEM = R"rawliteral(
406385 updateSystemStatus();
407386 setInterval(updateSystemStatus, 5000);
408387
409- let pinViewExpanded = false;
410-
411- function togglePinView() {
412- pinViewExpanded = !pinViewExpanded;
413- document.getElementById('pins').classList.toggle('expanded', pinViewExpanded);
414- document.getElementById('togglePinBtn').textContent = pinViewExpanded ? 'Hide All Pins' : 'Show All Pins';
415- }
416-
417388 async function loadPins() {
418389 try {
419390 const response = await fetch('/api/pins');
@@ -492,39 +463,43 @@ const char index_html[] PROGMEM = R"rawliteral(
492463 return;
493464 }
494465
495- container.innerHTML = usablePins.map(p => {
496- const typeStr = p.type === 1 ? 'IN' : p.type === 2 ? 'OUT' : '-';
497- const valueClass = p.value === 1 ? 'high' : 'low';
498- const valueStr = p.value === -1 ? '-' : p.value.toString();
499- const assignedStr = p.is_assigned ? ` (S${p.assigned_to})` : '';
500- const itemClass = p.is_reserved ? 'pin-item reserved' : (p.is_assigned ? 'pin-item assigned' : 'pin-item');
466+ let html = '';
467+ for (const p of usablePins) {
468+ const canOutput = p.capabilities & 2;
469+ const isOutput = p.type === 2;
470+ const modeStr = isOutput ? 'OUT' : 'IN';
471+ const value = p.value === -1 ? '-' : p.value;
472+ const isAssigned = p.is_assigned;
473+ const bgColor = isOutput && value === 1 ? '#4CAF50' : '#333';
474+ const opacity = isAssigned ? '0.5' : '1';
475+ const modeCursor = (isAssigned || !canOutput) ? 'default' : 'pointer';
476+ const assignedTitle = isAssigned ? ` (S${p.assigned_to})` : '';
477+ const inputOnly = !canOutput ? ' (IN only)' : '';
501478
502- return `
503- <div class="${itemClass}">
504- <div class="pin-header">
505- <span class="pin-num">GPIO${p.pin}</span>
506- <span class="pin-value ${valueClass}">${valueStr}</span>
507- </div>
508- <div style="color:#888;font-size:11px">${p.name}${assignedStr}</div>
509- <div class="pin-actions">
510- <select id="mode-${p.pin}" onchange="setPinMode(${p.pin})">
511- <option value="none" ${p.type === 0 ? 'selected' : ''}>-</option>
512- <option value="input" ${p.type === 1 ? 'selected' : ''}>IN</option>
513- <option value="output" ${p.type === 2 ? 'selected' : ''}>OUT</option>
514- </select>
515- <button class="btn-secondary" onclick="togglePin(${p.pin})" ${p.type !== 2 ? 'disabled' : ''}>Toggle</button>
479+ html += `
480+ <div style="background:${bgColor};padding:5px;border-radius:3px;text-align:center;opacity:${opacity}"
481+ title="GPIO${p.pin}${assignedTitle}${inputOnly}">
482+ <div style="font-weight:bold;font-size:10px;color:#888">${p.pin}</div>
483+ <div style="display:flex;justify-content:center;gap:8px;font-weight:bold">
484+ <span style="cursor:${modeCursor};color:${canOutput ? '#fff' : '#888'}"
485+ onclick="${isAssigned || !canOutput ? '' : `cyclePinMode(${p.pin})`}">${modeStr}</span>
486+ <span style="cursor:${isOutput && !isAssigned ? 'pointer' : 'default'};color:${isOutput && value === 1 ? '#fff' : '#888'}"
487+ onclick="${isOutput && !isAssigned ? `togglePin(${p.pin})` : ''}">${value}</span>
516488 </div>
517489 </div>
518490 `;
519- }).join('');
491+ }
492+ container.innerHTML = html;
520493 }
521494
522- async function setPinMode(pin) {
523- const mode = document.getElementById(`mode-${pin}`).value;
524- if (mode === 'none') return;
495+ async function cyclePinMode(pin) {
496+ const pinData = gpioPins.find(p => p.pin === pin);
497+ if (!pinData) return;
498+
499+ const newMode = pinData.type === 2 ? 'input' : 'output';
525500
526501 try {
527- const params = new URLSearchParams({ mode: mode , value: '0' });
502+ const params = new URLSearchParams({ mode: newMode , value: '0' });
528503 await fetch(`/api/pins/${pin}`, {
529504 method: 'POST',
530505 headers: {'Content-Type': 'application/x-www-form-urlencoded'},
0 commit comments