esp / index.html
drbaker171's picture
Add 1 files
15e8115 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Permissions-Policy" content="serial=(self)">
<title>ESP32/Arduino Code Uploader</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/monaco-editor@0.43.0/min/vs/loader.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
#editor {
height: 400px;
border: 1px solid #e5e7eb;
border-radius: 0.375rem;
}
#serialMonitor {
height: 200px;
overflow-y: auto;
background-color: #1e293b;
color: #f8fafc;
font-family: monospace;
padding: 0.5rem;
border-radius: 0.375rem;
}
.tab-active {
border-bottom: 2px solid #3b82f6;
color: #3b82f6;
}
.progress-bar {
transition: width 0.3s ease;
}
.serial-line {
margin: 0;
padding: 0;
line-height: 1.2;
}
.blink {
animation: blink 1s step-end infinite;
}
@keyframes blink {
from, to { opacity: 1 }
50% { opacity: 0.5 }
}
.port-option {
display: flex;
align-items: center;
}
.port-icon {
margin-right: 8px;
color: #4b5563;
}
.permission-modal {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0,0,0,0.5);
z-index: 1000;
justify-content: center;
align-items: center;
}
.permission-content {
background-color: white;
padding: 2rem;
border-radius: 0.5rem;
max-width: 500px;
width: 90%;
}
.browser-warning {
background-color: #fef3c7;
border-left: 4px solid #f59e0b;
padding: 1rem;
margin-bottom: 1rem;
}
</style>
</head>
<body class="bg-gray-50 min-h-screen">
<!-- Permission Request Modal -->
<div id="permissionModal" class="permission-modal">
<div class="permission-content">
<h2 class="text-xl font-bold mb-4">Serial Port Access Required</h2>
<p class="mb-4">To connect to your device, you need to grant permission to access serial ports. Please click the button below and select your device from the browser's prompt.</p>
<div class="flex flex-col space-y-4">
<button id="requestPermissionBtn" class="bg-blue-600 hover:bg-blue-700 text-white py-2 px-6 rounded-md flex items-center justify-center">
<i class="fas fa-key mr-2"></i> Grant Permission
</button>
<button id="learnMoreBtn" class="text-blue-600 hover:text-blue-800 py-2 px-6 rounded-md flex items-center justify-center">
<i class="fas fa-info-circle mr-2"></i> Learn More About Serial Access
</button>
</div>
</div>
</div>
<div class="container mx-auto px-4 py-8">
<header class="mb-8">
<h1 class="text-3xl font-bold text-gray-800 flex items-center">
<i class="fas fa-microchip mr-3 text-blue-500"></i>
ESP32/Arduino Code Uploader
</h1>
<p class="text-gray-600 mt-2">Upload and manage your sketches for ESP32 and Arduino devices</p>
</header>
<!-- Browser Warning (hidden by default) -->
<div id="browserWarning" class="browser-warning hidden">
<div class="flex items-start">
<div class="flex-shrink-0">
<i class="fas fa-exclamation-triangle text-yellow-600"></i>
</div>
<div class="ml-3">
<h3 class="text-sm font-medium text-yellow-800">Browser Compatibility Notice</h3>
<div class="mt-2 text-sm text-yellow-700">
<p>This application requires the Web Serial API which is currently only supported in Chrome/Edge 89+ and Opera 76+.</p>
</div>
</div>
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<!-- Left Column - Connection & Upload -->
<div class="lg:col-span-1 space-y-6">
<!-- Connection Panel -->
<div class="bg-white rounded-lg shadow p-6">
<h2 class="text-xl font-semibold mb-4 flex items-center">
<i class="fas fa-plug mr-2 text-green-500"></i>
Device Connection
</h2>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Port</label>
<div class="flex">
<select id="portSelect" class="flex-grow border border-gray-300 rounded-l-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">
<option value="">Select a port</option>
</select>
<button id="refreshPorts" class="bg-gray-200 hover:bg-gray-300 px-3 py-2 rounded-r-md border border-l-0 border-gray-300">
<i class="fas fa-sync-alt"></i>
</button>
</div>
<p id="portHelp" class="mt-1 text-xs text-gray-500">Connect your device and click refresh</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Board Type</label>
<select id="boardSelect" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">
<optgroup label="ESP32 Boards">
<option value="esp32">ESP32 Dev Module</option>
<option value="esp32s2">ESP32-S2</option>
<option value="esp32c3">ESP32-C3</option>
<option value="esp32s3">ESP32-S3</option>
<option value="cyd_esp32">CYD ESP32-2432S028</option>
</optgroup>
<optgroup label="Arduino Boards">
<option value="uno">Arduino UNO</option>
<option value="nano">Arduino Nano</option>
<option value="mega">Arduino Mega 2560</option>
</optgroup>
</select>
</div>
<div id="boardOptions" class="hidden">
<label class="block text-sm font-medium text-gray-700 mb-1">Board Options</label>
<div id="specificOptions" class="space-y-2">
<!-- Options will be populated based on board selection -->
</div>
</div>
<div class="pt-2">
<button id="connectBtn" class="w-full bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded-md flex items-center justify-center disabled:opacity-50 disabled:cursor-not-allowed">
<i class="fas fa-link mr-2"></i>
Connect
</button>
</div>
<div id="connectionStatus" class="hidden mt-3 p-3 rounded-md bg-gray-100 text-gray-700 flex items-center">
<i class="fas fa-circle mr-2 text-gray-400"></i>
<span>Disconnected</span>
</div>
</div>
</div>
<!-- Upload Panel -->
<div class="bg-white rounded-lg shadow p-6">
<h2 class="text-xl font-semibold mb-4 flex items-center">
<i class="fas fa-upload mr-2 text-purple-500"></i>
Upload Sketch
</h2>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Sketch File</label>
<div class="flex items-center">
<input type="file" id="fileInput" accept=".ino,.cpp,.h" class="hidden">
<input type="text" id="fileName" placeholder="No file selected" readonly class="flex-grow border border-gray-300 rounded-l-md px-3 py-2 focus:outline-none">
<button id="browseBtn" class="bg-gray-200 hover:bg-gray-300 px-3 py-2 rounded-r-md border border-l-0 border-gray-300">
<i class="fas fa-folder-open"></i>
</button>
</div>
</div>
<div id="uploadProgress" class="hidden">
<div class="flex justify-between text-sm text-gray-600 mb-1">
<span>Uploading...</span>
<span id="progressPercent">0%</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-2.5">
<div id="progressBar" class="progress-bar bg-blue-600 h-2.5 rounded-full" style="width: 0%"></div>
</div>
</div>
<div class="pt-2">
<button id="uploadBtn" disabled class="w-full bg-purple-600 hover:bg-purple-700 text-white py-2 px-4 rounded-md flex items-center justify-center disabled:opacity-50 disabled:cursor-not-allowed">
<i class="fas fa-cloud-upload-alt mr-2"></i>
Upload to Device
</button>
</div>
</div>
</div>
</div>
<!-- Right Column - Editor & Serial Monitor -->
<div class="lg:col-span-2 space-y-6">
<!-- Code Editor Tabs -->
<div class="bg-white rounded-lg shadow">
<div class="border-b border-gray-200">
<nav class="-mb-px flex">
<button id="editorTab" class="tab-active py-4 px-6 text-sm font-medium flex items-center">
<i class="fas fa-code mr-2"></i>
Code Editor
</button>
<button id="serialTab" class="py-4 px-6 text-sm font-medium text-gray-500 hover:text-gray-700 flex items-center">
<i class="fas fa-terminal mr-2"></i>
Serial Monitor
</button>
</nav>
</div>
<!-- Editor Content -->
<div id="editorContent" class="p-4">
<div id="editor"></div>
<div class="mt-4 flex justify-between">
<div>
<button id="newFileBtn" class="bg-gray-200 hover:bg-gray-300 text-gray-700 py-1 px-3 rounded-md text-sm mr-2">
<i class="fas fa-file mr-1"></i> New
</button>
<button id="saveFileBtn" class="bg-gray-200 hover:bg-gray-300 text-gray-700 py-1 px-3 rounded-md text-sm">
<i class="fas fa-save mr-1"></i> Save
</button>
</div>
<div>
<button id="verifyBtn" class="bg-blue-100 hover:bg-blue-200 text-blue-700 py-1 px-3 rounded-md text-sm">
<i class="fas fa-check-circle mr-1"></i> Verify
</button>
</div>
</div>
</div>
<!-- Serial Monitor Content -->
<div id="serialContent" class="hidden p-4">
<div id="serialMonitor"></div>
<div class="mt-4 flex">
<input type="text" id="serialInput" placeholder="Enter command..." class="flex-grow border border-gray-300 rounded-l-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">
<button id="serialSendBtn" class="bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded-r-md">
Send
</button>
</div>
<div class="mt-3 flex justify-between items-center">
<div class="flex items-center">
<label class="inline-flex items-center">
<input type="checkbox" id="autoScroll" checked class="h-4 w-4 text-blue-600 rounded border-gray-300 focus:ring-blue-500">
<span class="ml-2 text-sm text-gray-600">Auto-scroll</span>
</label>
<label class="inline-flex items-center ml-4">
<input type="checkbox" id="showTimestamps" class="h-4 w-4 text-blue-600 rounded border-gray-300 focus:ring-blue-500">
<span class="ml-2 text-sm text-gray-600">Timestamps</span>
</label>
</div>
<div>
<button id="clearSerialBtn" class="text-sm text-gray-600 hover:text-gray-800 flex items-center">
<i class="fas fa-trash-alt mr-1"></i> Clear
</button>
</div>
</div>
</div>
</div>
<!-- Output Console -->
<div class="bg-white rounded-lg shadow p-6">
<h2 class="text-xl font-semibold mb-4 flex items-center">
<i class="fas fa-terminal mr-2 text-yellow-500"></i>
Compilation Output
</h2>
<div id="outputConsole" class="bg-gray-800 text-green-100 font-mono text-sm p-3 rounded-md h-40 overflow-y-auto">
<p>> Ready to compile and upload code to your device</p>
</div>
</div>
</div>
</div>
</div>
<script>
// Board configurations
const boardConfigurations = {
// ESP32 Boards
'esp32': {
name: 'ESP32 Dev Module',
baudRate: 115200,
flashMode: 'dio',
flashFreq: '80m',
uploadSpeed: 921600,
partitionScheme: 'default',
cpuFreq: '240',
flashSize: '4MB',
options: [
{ id: 'flashMode', label: 'Flash Mode', type: 'select', options: ['qio', 'qout', 'dio', 'dout'], default: 'dio' },
{ id: 'flashFreq', label: 'Flash Frequency', type: 'select', options: ['80m', '40m'], default: '80m' },
{ id: 'uploadSpeed', label: 'Upload Speed', type: 'select', options: ['115200', '230400', '460800', '921600'], default: '921600' }
]
},
'cyd_esp32': {
name: 'CYD ESP32-2432S028',
baudRate: 115200,
flashMode: 'dio',
flashFreq: '80m',
uploadSpeed: 460800,
partitionScheme: 'huge_app',
cpuFreq: '240',
flashSize: '16MB',
options: [
{ id: 'flashMode', label: 'Flash Mode', type: 'select', options: ['dio', 'dout'], default: 'dio' },
{ id: 'flashFreq', label: 'Flash Frequency', type: 'select', options: ['80m', '40m'], default: '80m' },
{ id: 'uploadSpeed', label: 'Upload Speed', type: 'select', options: ['115200', '230400', '460800'], default: '460800' },
{ id: 'touchScreen', label: 'Touch Screen', type: 'checkbox', default: true }
]
},
// Arduino Boards
'uno': {
name: 'Arduino UNO',
baudRate: 9600,
processor: 'atmega328p',
programmer: 'arduino',
options: [
{ id: 'programmer', label: 'Programmer', type: 'select', options: ['arduino', 'avrisp', 'usbtiny'], default: 'arduino' }
]
},
'nano': {
name: 'Arduino Nano',
baudRate: 9600,
processor: 'atmega328p',
programmer: 'arduino',
options: [
{ id: 'processor', label: 'Processor', type: 'select', options: ['atmega328p', 'atmega328'], default: 'atmega328p' },
{ id: 'programmer', label: 'Programmer', type: 'select', options: ['arduino', 'avrisp', 'usbtiny'], default: 'arduino' }
]
},
'mega': {
name: 'Arduino Mega 2560',
baudRate: 115200,
processor: 'atmega2560',
programmer: 'wiring',
options: [
{ id: 'programmer', label: 'Programmer', type: 'select', options: ['wiring', 'arduino', 'avrisp'], default: 'wiring' }
]
}
};
// Global variables for serial connection
let port = null;
let reader = null;
let writer = null;
let isReading = false;
let isConnected = false;
let activePorts = [];
document.addEventListener('DOMContentLoaded', function() {
// Check browser compatibility first
checkBrowserCompatibility();
// Tab switching
const editorTab = document.getElementById('editorTab');
const serialTab = document.getElementById('serialTab');
const editorContent = document.getElementById('editorContent');
const serialContent = document.getElementById('serialContent');
editorTab.addEventListener('click', () => {
editorTab.classList.add('tab-active');
serialTab.classList.remove('tab-active');
editorContent.classList.remove('hidden');
serialContent.classList.add('hidden');
});
serialTab.addEventListener('click', () => {
serialTab.classList.add('tab-active');
editorTab.classList.remove('tab-active');
serialContent.classList.remove('hidden');
editorContent.classList.add('hidden');
});
// Initialize Monaco Editor
require.config({ paths: { 'vs': 'https://cdn.jsdelivr.net/npm/monaco-editor@0.43.0/min/vs' }});
require(['vs/editor/editor.main'], function() {
window.editor = monaco.editor.create(document.getElementById('editor'), {
value: [
'void setup() {',
' Serial.begin(115200);',
' pinMode(LED_BUILTIN, OUTPUT);',
'}',
'',
'void loop() {',
' digitalWrite(LED_BUILTIN, HIGH);',
' delay(1000);',
' digitalWrite(LED_BUILTIN, LOW);',
' delay(1000);',
'}'
].join('\n'),
language: 'cpp',
theme: 'vs',
minimap: { enabled: false },
fontSize: 14,
lineNumbers: 'on',
roundedSelection: true,
scrollBeyondLastLine: false,
automaticLayout: true
});
});
// Board selection handler
const boardSelect = document.getElementById('boardSelect');
const boardOptions = document.getElementById('boardOptions');
const specificOptions = document.getElementById('specificOptions');
boardSelect.addEventListener('change', function() {
const boardType = this.value;
const config = boardConfigurations[boardType];
if (config && config.options && config.options.length > 0) {
boardOptions.classList.remove('hidden');
specificOptions.innerHTML = '';
config.options.forEach(option => {
const optionDiv = document.createElement('div');
optionDiv.className = 'flex items-center justify-between';
const label = document.createElement('label');
label.className = 'text-sm text-gray-600';
label.textContent = option.label;
let input;
if (option.type === 'select') {
input = document.createElement('select');
input.className = 'text-sm border border-gray-300 rounded px-2 py-1';
input.id = option.id;
option.options.forEach(opt => {
const optionEl = document.createElement('option');
optionEl.value = opt;
optionEl.textContent = opt;
if (opt === option.default) {
optionEl.selected = true;
}
input.appendChild(optionEl);
});
} else if (option.type === 'checkbox') {
const container = document.createElement('div');
container.className = 'flex items-center';
input = document.createElement('input');
input.type = 'checkbox';
input.className = 'h-4 w-4 text-blue-600 rounded border-gray-300 focus:ring-blue-500';
input.id = option.id;
input.checked = option.default;
container.appendChild(input);
input = container;
}
optionDiv.appendChild(label);
optionDiv.appendChild(input);
specificOptions.appendChild(optionDiv);
});
} else {
boardOptions.classList.add('hidden');
}
});
// File handling
const fileInput = document.getElementById('fileInput');
const fileName = document.getElementById('fileName');
const browseBtn = document.getElementById('browseBtn');
const newFileBtn = document.getElementById('newFileBtn');
const saveFileBtn = document.getElementById('saveFileBtn');
browseBtn.addEventListener('click', () => fileInput.click());
fileInput.addEventListener('change', function() {
if (this.files.length > 0) {
const file = this.files[0];
fileName.value = file.name;
const reader = new FileReader();
reader.onload = function(e) {
window.editor.setValue(e.target.result);
};
reader.readAsText(file);
document.getElementById('uploadBtn').disabled = false;
}
});
newFileBtn.addEventListener('click', () => {
fileName.value = 'new_sketch.ino';
window.editor.setValue([
'void setup() {',
' // Initialize your hardware here',
'}',
'',
'void loop() {',
' // Your main code here',
'}'
].join('\n'));
});
saveFileBtn.addEventListener('click', () => {
const content = window.editor.getValue();
const blob = new Blob([content], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = fileName.value || 'arduino_sketch.ino';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
});
// Serial monitor
const serialMonitor = document.getElementById('serialMonitor');
const serialInput = document.getElementById('serialInput');
const serialSendBtn = document.getElementById('serialSendBtn');
const clearSerialBtn = document.getElementById('clearSerialBtn');
const autoScroll = document.getElementById('autoScroll');
function addToSerialMonitor(text, type = 'output') {
const line = document.createElement('p');
line.className = 'serial-line';
if (type === 'input') {
line.innerHTML = `<span class="text-blue-400">>></span> ${text}`;
} else {
line.textContent = text;
}
serialMonitor.appendChild(line);
if (autoScroll.checked) {
serialMonitor.scrollTop = serialMonitor.scrollHeight;
}
}
async function sendSerialData(data) {
if (!isConnected || !writer) {
addToOutputConsole('Not connected to a device', 'error');
return;
}
try {
addToSerialMonitor(data, 'input');
// Add newline if not present
if (!data.endsWith('\n')) {
data += '\n';
}
await writer.write(new TextEncoder().encode(data));
} catch (error) {
addToOutputConsole(`Error sending data: ${error}`, 'error');
}
}
serialSendBtn.addEventListener('click', () => {
const command = serialInput.value.trim();
if (command) {
sendSerialData(command);
serialInput.value = '';
}
});
serialInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
serialSendBtn.click();
}
});
clearSerialBtn.addEventListener('click', () => {
serialMonitor.innerHTML = '';
});
// Serial port handling
const portSelect = document.getElementById('portSelect');
const refreshPorts = document.getElementById('refreshPorts');
const connectBtn = document.getElementById('connectBtn');
const connectionStatus = document.getElementById('connectionStatus');
const uploadBtn = document.getElementById('uploadBtn');
const portHelp = document.getElementById('portHelp');
const permissionModal = document.getElementById('permissionModal');
const requestPermissionBtn = document.getElementById('requestPermissionBtn');
const learnMoreBtn = document.getElementById('learnMoreBtn');
const browserWarning = document.getElementById('browserWarning');
// Check browser compatibility
function checkBrowserCompatibility() {
if (!('serial' in navigator)) {
browserWarning.classList.remove('hidden');
refreshPorts.disabled = true;
connectBtn.disabled = true;
portHelp.textContent = 'Web Serial API not supported in this browser. Use Chrome/Edge 89+ or Opera 76+.';
addToOutputConsole('Web Serial API not supported in this browser. Try Chrome/Edge 89+ or Opera 76+.', 'error');
return false;
}
return true;
}
// Show permission modal
function showPermissionModal() {
permissionModal.style.display = 'flex';
}
// Hide permission modal
function hidePermissionModal() {
permissionModal.style.display = 'none';
}
// Learn more button handler
learnMoreBtn.addEventListener('click', () => {
window.open('https://developer.mozilla.org/en-US/docs/Web/API/Web_Serial_API', '_blank');
});
// Request permission button handler
requestPermissionBtn.addEventListener('click', async () => {
try {
// Request permission to access serial ports
const port = await navigator.serial.requestPort({
filters: [
{ usbVendorId: 0x2341 }, // Arduino
{ usbVendorId: 0x2A03 }, // Arduino (alternative)
{ usbVendorId: 0x10C4 }, // ESP32
{ usbVendorId: 0x1A86 } // CH340 (common USB-to-serial)
]
});
// If we get here, permission was granted
hidePermissionModal();
await refreshPortList();
} catch (error) {
if (error.name === 'NotFoundError') {
addToOutputConsole('No device was selected', 'info');
portHelp.textContent = 'No device selected. Make sure your device is connected.';
} else if (error.name === 'SecurityError') {
addToOutputConsole('Permission denied for serial port access', 'error');
portHelp.textContent = 'Permission denied. Please allow serial port access.';
} else {
addToOutputConsole(`Error: ${error.message}`, 'error');
portHelp.textContent = `Error: ${error.message}`;
}
hidePermissionModal();
}
});
async function refreshPortList() {
if (!checkBrowserCompatibility()) return;
portSelect.innerHTML = '<option value="">Select a port</option>';
activePorts = [];
try {
// Get list of ports we already have permission for
const ports = await navigator.serial.getPorts();
if (ports.length === 0) {
portSelect.innerHTML = '<option value="">No ports found</option>';
portHelp.textContent = 'No serial ports found. Connect a device and click refresh.';
addToOutputConsole('No serial ports found. Connect a device and click refresh.');
// Show permission modal since we don't have any ports
showPermissionModal();
return;
}
// Store ports for later reference
activePorts = ports;
ports.forEach((port, index) => {
const option = document.createElement('option');
option.value = index; // Using index as value since port objects can't be serialized
const portInfo = port.getInfo();
const portName = portInfo.usbVendorId ?
`Serial Port (Vendor: 0x${portInfo.usbVendorId.toString(16)}, Product: 0x${portInfo.usbProductId.toString(16)})` :
'Serial Port';
const optionContent = document.createElement('div');
optionContent.className = 'port-option';
optionContent.innerHTML = `
<i class="fas fa-microchip port-icon"></i>
<span>${portName}</span>
`;
option.textContent = portName;
portSelect.appendChild(option);
});
portHelp.textContent = `Found ${ports.length} serial port(s)`;
addToOutputConsole(`Found ${ports.length} serial port(s)`);
} catch (error) {
if (error.name === 'SecurityError') {
addToOutputConsole('Permission denied for serial port access. Please grant permission.', 'error');
portHelp.textContent = 'Permission denied. Please allow serial port access.';
showPermissionModal();
} else {
addToOutputConsole(`Error accessing serial ports: ${error}`, 'error');
portSelect.innerHTML = '<option value="">Error accessing ports</option>';
portHelp.textContent = 'Error accessing ports. Please try again.';
}
}
}
refreshPorts.addEventListener('click', async () => {
refreshPorts.classList.add('blink');
addToOutputConsole('Searching for serial ports...');
portHelp.textContent = 'Searching for devices...';
try {
// First try to get ports we already have permission for
await refreshPortList();
} finally {
refreshPorts.classList.remove('blink');
}
});
async function connectToPort() {
if (!portSelect.value) {
addToOutputConsole('Please select a port first', 'error');
return;
}
const portIndex = parseInt(portSelect.value);
if (isNaN(portIndex) || portIndex < 0 || portIndex >= activePorts.length) {
addToOutputConsole('Invalid port selection', 'error');
return;
}
try {
port = activePorts[portIndex];
if (!port) {
throw new Error('Selected port not found');
}
// Get board configuration
const boardType = boardSelect.value;
const config = boardConfigurations[boardType];
// Open the port with the board's baud rate
await port.open({ baudRate: config.baudRate });
// Set up the reader and writer
writer = port.writable.getWriter();
reader = port.readable.getReader();
// Start reading from the port
isReading = true;
readSerialData();
// Update UI
isConnected = true;
connectBtn.innerHTML = '<i class="fas fa-unlink mr-2"></i> Disconnect';
connectBtn.classList.remove('bg-blue-600', 'hover:bg-blue-700');
connectBtn.classList.add('bg-red-600', 'hover:bg-red-700');
connectionStatus.classList.remove('hidden', 'bg-gray-100', 'text-gray-700');
connectionStatus.classList.add('bg-green-100', 'text-green-700');
connectionStatus.innerHTML = '<i class="fas fa-circle mr-2 text-green-500"></i><span>Connected to ' + portSelect.options[portSelect.selectedIndex].text + '</span>';
addToOutputConsole(`Connected to ${portSelect.options[portSelect.selectedIndex].text} at ${config.baudRate} baud`);
uploadBtn.disabled = false;
// Send a test message to check connection
setTimeout(() => {
if (isConnected) {
sendSerialData("AT"); // Common test command
}
}, 500);
} catch (error) {
addToOutputConsole(`Error connecting to port: ${error}`, 'error');
portHelp.textContent = `Connection failed: ${error.message}`;
if (port) {
try {
await port.close();
} catch (e) {
console.error('Error closing port:', e);
}
port = null;
}
// Reset connection state
isConnected = false;
connectBtn.innerHTML = '<i class="fas fa-link mr-2"></i> Connect';
connectBtn.classList.remove('bg-red-600', 'hover:bg-red-700');
connectBtn.classList.add('bg-blue-600', 'hover:bg-blue-700');
connectionStatus.classList.remove('bg-green-100', 'text-green-700');
connectionStatus.classList.add('bg-gray-100', 'text-gray-700');
connectionStatus.innerHTML = '<i class="fas fa-circle mr-2 text-gray-400"></i><span>Disconnected</span>';
uploadBtn.disabled = true;
}
}
async function disconnectFromPort() {
isReading = false;
if (reader) {
try {
await reader.cancel();
} catch (error) {
console.error('Error cancelling reader:', error);
}
reader = null;
}
if (writer) {
try {
await writer.releaseLock();
} catch (error) {
console.error('Error releasing writer:', error);
}
writer = null;
}
if (port) {
try {
await port.close();
} catch (error) {
console.error('Error closing port:', error);
}
port = null;
}
// Update UI
isConnected = false;
connectBtn.innerHTML = '<i class="fas fa-link mr-2"></i> Connect';
connectBtn.classList.remove('bg-red-600', 'hover:bg-red-700');
connectBtn.classList.add('bg-blue-600', 'hover:bg-blue-700');
connectionStatus
</html>