Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>ESP32 Serial Communication</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| .serial-console { | |
| height: 300px; | |
| overflow-y: auto; | |
| background-color: #1a202c; | |
| color: #e2e8f0; | |
| padding: 1rem; | |
| border-radius: 0.375rem; | |
| font-family: monospace; | |
| white-space: pre-wrap; | |
| } | |
| .blink { | |
| animation: blink 1s step-end infinite; | |
| } | |
| @keyframes blink { | |
| from, to { opacity: 1; } | |
| 50% { opacity: 0.5; } | |
| } | |
| .toast { | |
| animation: fadeIn 0.3s, fadeOut 0.3s 2.7s; | |
| } | |
| @keyframes fadeIn { | |
| from { opacity: 0; transform: translateY(20px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| @keyframes fadeOut { | |
| from { opacity: 1; transform: translateY(0); } | |
| to { opacity: 0; transform: translateY(20px); } | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-100 min-h-screen"> | |
| <div class="container mx-auto px-4 py-8 max-w-4xl"> | |
| <div class="bg-white rounded-xl shadow-md overflow-hidden p-6"> | |
| <div class="flex items-center mb-8"> | |
| <div class="p-3 rounded-full bg-blue-100 text-blue-600 mr-4"> | |
| <i class="fas fa-microchip text-2xl"></i> | |
| </div> | |
| <div> | |
| <h1 class="text-3xl font-bold text-gray-800">ESP32 Serial Communication</h1> | |
| <p class="text-gray-600">Connect to your CYD ESP32-2432S028 device via Web Serial API</p> | |
| </div> | |
| </div> | |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8"> | |
| <div class="bg-blue-50 rounded-lg p-4 border border-blue-200"> | |
| <h3 class="font-semibold text-blue-800 mb-2">Connection Status</h3> | |
| <div id="connectionStatus" class="flex items-center"> | |
| <div class="h-3 w-3 rounded-full bg-gray-400 mr-2"></div> | |
| <span>Disconnected</span> | |
| </div> | |
| <div id="portInfo" class="mt-2 text-sm text-gray-600 hidden"> | |
| <div>Device: <span id="portName" class="font-medium">-</span></div> | |
| <div>Baud Rate: <span id="currentBaudRate" class="font-medium">-</span></div> | |
| </div> | |
| </div> | |
| <div class="bg-gray-50 rounded-lg p-4 border border-gray-200"> | |
| <h3 class="font-semibold text-gray-800 mb-2">Data Statistics</h3> | |
| <div class="grid grid-cols-2 gap-2 text-sm"> | |
| <div>Bytes Sent: <span id="bytesSent" class="font-medium">0</span></div> | |
| <div>Bytes Received: <span id="bytesReceived" class="font-medium">0</span></div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="mb-6"> | |
| <div class="flex flex-wrap gap-3 mb-4"> | |
| <button id="connectBtn" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition flex items-center"> | |
| <i class="fas fa-plug mr-2"></i> Connect | |
| </button> | |
| <button id="disconnectBtn" disabled class="px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 transition flex items-center"> | |
| <i class="fas fa-unlink mr-2"></i> Disconnect | |
| </button> | |
| <div class="flex items-center ml-auto"> | |
| <label for="baudRate" class="mr-2 text-sm text-gray-700">Baud Rate:</label> | |
| <select id="baudRate" class="border border-gray-300 rounded px-3 py-1"> | |
| <option>9600</option> | |
| <option>19200</option> | |
| <option selected>115200</option> | |
| <option>57600</option> | |
| <option>38400</option> | |
| </select> | |
| </div> | |
| </div> | |
| <div class="mb-4"> | |
| <label for="dataInput" class="block text-sm font-medium text-gray-700 mb-1">Send Command</label> | |
| <div class="flex"> | |
| <input type="text" id="dataInput" placeholder="Enter command to send" | |
| class="flex-1 border border-gray-300 rounded-l-lg px-4 py-2 focus:ring-blue-500 focus:border-blue-500"> | |
| <button id="sendBtn" disabled class="px-4 py-2 bg-blue-600 text-white rounded-r-lg hover:bg-blue-700"> | |
| <i class="fas fa-paper-plane mr-1"></i> Send | |
| </button> | |
| </div> | |
| </div> | |
| <div> | |
| <div class="flex justify-between items-center mb-1"> | |
| <label class="block text-sm font-medium text-gray-700">Serial Console</label> | |
| <button id="clearConsoleBtn" class="text-sm text-blue-600 hover:text-blue-800"> | |
| <i class="fas fa-trash-alt mr-1"></i> Clear | |
| </button> | |
| </div> | |
| <div id="serialConsole" class="serial-console"> | |
| <div class="text-gray-400">Waiting for connection...</div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="bg-yellow-50 border-l-4 border-yellow-400 p-4 mb-6"> | |
| <div class="flex"> | |
| <div class="flex-shrink-0"> | |
| <i class="fas fa-info-circle text-yellow-400 mt-1 mr-3"></i> | |
| </div> | |
| <div> | |
| <h3 class="text-sm font-medium text-yellow-800">Important Notes</h3> | |
| <div class="mt-2 text-sm text-yellow-700"> | |
| <p>• The Web Serial API requires Chrome/Edge 89+ or Opera 76+</p> | |
| <p>• Must be used in a secure context (HTTPS or localhost)</p> | |
| <p>• Requires user interaction to initiate connection</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div id="toastContainer" class="fixed bottom-4 right-4 space-y-2"></div> | |
| <script> | |
| // DOM elements | |
| const connectBtn = document.getElementById('connectBtn'); | |
| const disconnectBtn = document.getElementById('disconnectBtn'); | |
| const sendBtn = document.getElementById('sendBtn'); | |
| const dataInput = document.getElementById('dataInput'); | |
| const baudRate = document.getElementById('baudRate'); | |
| const serialConsole = document.getElementById('serialConsole'); | |
| const clearConsoleBtn = document.getElementById('clearConsoleBtn'); | |
| const bytesSent = document.getElementById('bytesSent'); | |
| const bytesReceived = document.getElementById('bytesReceived'); | |
| const connectionStatus = document.getElementById('connectionStatus'); | |
| const portInfo = document.getElementById('portInfo'); | |
| const portName = document.getElementById('portName'); | |
| const currentBaudRate = document.getElementById('currentBaudRate'); | |
| const toastContainer = document.getElementById('toastContainer'); | |
| // Serial port variables | |
| let port = null; | |
| let reader = null; | |
| let writer = null; | |
| let keepReading = true; | |
| let sentBytes = 0; | |
| let receivedBytes = 0; | |
| // Check Web Serial API support | |
| function checkSupport() { | |
| if (!('serial' in navigator)) { | |
| showToast('Web Serial API not supported in this browser', 'error'); | |
| connectBtn.disabled = true; | |
| connectBtn.innerHTML = '<i class="fas fa-times-circle mr-2"></i> Not Supported'; | |
| } | |
| } | |
| // Connect to serial port | |
| async function connect() { | |
| try { | |
| // Request port access | |
| port = await navigator.serial.requestPort(); | |
| // Open the port | |
| const selectedBaudRate = parseInt(baudRate.value); | |
| await port.open({ baudRate: selectedBaudRate }); | |
| // Update UI | |
| updateConnectionStatus(true); | |
| portName.textContent = port.getInfo().usbProductId ? | |
| `USB Device (${port.getInfo().usbProductId})` : 'Serial Port'; | |
| currentBaudRate.textContent = selectedBaudRate; | |
| portInfo.classList.remove('hidden'); | |
| // Clear console and add welcome message | |
| clearConsole(); | |
| addToConsole('Connected to serial port', 'info'); | |
| addToConsole(`Baud rate: ${selectedBaudRate}`, 'info'); | |
| // Start reading from the port | |
| readFromPort(); | |
| showToast('Successfully connected to device', 'success'); | |
| } catch (err) { | |
| if (err.name === 'NotFoundError') { | |
| showToast('No port selected by user', 'warning'); | |
| } else { | |
| showToast('Connection error: ' + err.message, 'error'); | |
| console.error('Connection error:', err); | |
| } | |
| } | |
| } | |
| // Disconnect from serial port | |
| async function disconnect() { | |
| try { | |
| keepReading = false; | |
| if (reader) { | |
| await reader.cancel(); | |
| } | |
| if (port) { | |
| await port.close(); | |
| } | |
| // Update UI | |
| updateConnectionStatus(false); | |
| addToConsole('Disconnected from serial port', 'info'); | |
| showToast('Disconnected from device', 'info'); | |
| } catch (err) { | |
| showToast('Disconnection error: ' + err.message, 'error'); | |
| console.error('Disconnection error:', err); | |
| } | |
| } | |
| // Read data from serial port | |
| async function readFromPort() { | |
| keepReading = true; | |
| try { | |
| while (port.readable && keepReading) { | |
| reader = port.readable.getReader(); | |
| try { | |
| while (keepReading) { | |
| const { value, done } = await reader.read(); | |
| if (done) { | |
| break; | |
| } | |
| // Process received data | |
| const textDecoder = new TextDecoder(); | |
| const text = textDecoder.decode(value); | |
| receivedBytes += text.length; | |
| bytesReceived.textContent = receivedBytes; | |
| // Add to console with timestamp | |
| addToConsole(text, 'received'); | |
| } | |
| } catch (err) { | |
| showToast('Error reading data: ' + err.message, 'error'); | |
| console.error('Read error:', err); | |
| } finally { | |
| reader.releaseLock(); | |
| } | |
| } | |
| } catch (err) { | |
| showToast('Port error: ' + err.message, 'error'); | |
| console.error('Port error:', err); | |
| disconnect(); | |
| } | |
| } | |
| // Send data to serial port | |
| async function sendData() { | |
| if (!port || !port.writable) { | |
| showToast('Port is not connected', 'error'); | |
| return; | |
| } | |
| const data = dataInput.value.trim(); | |
| if (!data) { | |
| showToast('Please enter data to send', 'warning'); | |
| return; | |
| } | |
| try { | |
| writer = port.writable.getWriter(); | |
| const textEncoder = new TextEncoder(); | |
| const encodedData = textEncoder.encode(data + '\n'); | |
| await writer.write(encodedData); | |
| sentBytes += encodedData.length; | |
| bytesSent.textContent = sentBytes; | |
| // Add to console as sent data | |
| addToConsole(data, 'sent'); | |
| dataInput.value = ''; | |
| showToast('Data sent successfully', 'success'); | |
| } catch (err) { | |
| showToast('Error sending data: ' + err.message, 'error'); | |
| console.error('Send error:', err); | |
| } finally { | |
| if (writer) { | |
| writer.releaseLock(); | |
| } | |
| } | |
| } | |
| // Add message to console | |
| function addToConsole(message, type) { | |
| const now = new Date(); | |
| const timestamp = `[${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}:${now.getSeconds().toString().padStart(2, '0')}] `; | |
| // Create message element | |
| const messageElement = document.createElement('div'); | |
| // Style based on message type | |
| if (type === 'info') { | |
| messageElement.className = 'text-blue-400'; | |
| messageElement.innerHTML = timestamp + message; | |
| } else if (type === 'sent') { | |
| messageElement.className = 'text-green-400'; | |
| messageElement.innerHTML = timestamp + '> ' + message; | |
| } else { // received | |
| messageElement.className = 'text-white'; | |
| messageElement.innerHTML = timestamp + message.replace(/\n/g, '<br>'); | |
| } | |
| // Remove "waiting for connection" message if it's there | |
| if (serialConsole.firstChild && serialConsole.firstChild.textContent === 'Waiting for connection...') { | |
| serialConsole.removeChild(serialConsole.firstChild); | |
| } | |
| // Add message to console | |
| serialConsole.appendChild(messageElement); | |
| serialConsole.scrollTop = serialConsole.scrollHeight; | |
| } | |
| // Clear console | |
| function clearConsole() { | |
| serialConsole.innerHTML = '<div class="text-gray-400">Waiting for connection...</div>'; | |
| } | |
| // Update connection status UI | |
| function updateConnectionStatus(isConnected) { | |
| if (isConnected) { | |
| connectionStatus.innerHTML = '<div class="h-3 w-3 rounded-full bg-green-500 mr-2"></div><span>Connected</span>'; | |
| connectBtn.disabled = true; | |
| disconnectBtn.disabled = false; | |
| sendBtn.disabled = false; | |
| } else { | |
| connectionStatus.innerHTML = '<div class="h-3 w-3 rounded-full bg-gray-400 mr-2"></div><span>Disconnected</span>'; | |
| connectBtn.disabled = false; | |
| disconnectBtn.disabled = true; | |
| sendBtn.disabled = true; | |
| portInfo.classList.add('hidden'); | |
| port = null; | |
| } | |
| } | |
| // Show toast notification | |
| function showToast(message, type) { | |
| const toast = document.createElement('div'); | |
| toast.className = `toast px-4 py-2 rounded-md shadow-md text-white ${ | |
| type === 'success' ? 'bg-green-500' : | |
| type === 'error' ? 'bg-red-500' : | |
| type === 'warning' ? 'bg-yellow-500' : 'bg-blue-500' | |
| }`; | |
| toast.textContent = message; | |
| // Add to container and auto-remove after animation | |
| toastContainer.appendChild(toast); | |
| setTimeout(() => { | |
| toast.remove(); | |
| }, 3000); | |
| } | |
| // Event listeners | |
| connectBtn.addEventListener('click', connect); | |
| disconnectBtn.addEventListener('click', disconnect); | |
| sendBtn.addEventListener('click', sendData); | |
| clearConsoleBtn.addEventListener('click', clearConsole); | |
| dataInput.addEventListener('keypress', (e) => { | |
| if (e.key === 'Enter') { | |
| sendData(); | |
| } | |
| }); | |
| // Initialize | |
| checkSupport(); | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=drbaker171/sp" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |