Initial Commit
parent
6a29623470
commit
970748e974
@ -0,0 +1,8 @@
|
|||||||
|
# Web Serial Console
|
||||||
|
|
||||||
|
Serial console access in the browser! This project uses and [experimental API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Serial_API) available as of 08/06/2024 only on Chrome 89, Edge 89, or Opera 75.
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
|
||||||
|
## Use
|
@ -0,0 +1,16 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Web Serial API Example</title>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Web Serial Connect</h1>
|
||||||
|
<button id="connect">Connect to Serial Device</button>
|
||||||
|
<pre id="output"></pre>
|
||||||
|
|
||||||
|
<script src="script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,8 @@
|
|||||||
|
import time
|
||||||
|
|
||||||
|
i = 0
|
||||||
|
|
||||||
|
while True:
|
||||||
|
print("Iteration: {}".format(i))
|
||||||
|
i+=1
|
||||||
|
time.sleep(1)
|
@ -0,0 +1,37 @@
|
|||||||
|
document.getElementById('connect').addEventListener('click', async () => {
|
||||||
|
// Feature detection
|
||||||
|
if ('serial' in navigator) {
|
||||||
|
try {
|
||||||
|
// Request a port and open a connection
|
||||||
|
const port = await navigator.serial.requestPort();
|
||||||
|
await port.open({ baudRate: 115200 });
|
||||||
|
|
||||||
|
// Create a text decoder to decode the bytes from the serial device
|
||||||
|
const decoder = new TextDecoderStream();
|
||||||
|
const inputDone = port.readable.pipeTo(decoder.writable);
|
||||||
|
const inputStream = decoder.readable;
|
||||||
|
|
||||||
|
// Read data from the serial device
|
||||||
|
const reader = inputStream.getReader();
|
||||||
|
const outputElement = document.getElementById('output');
|
||||||
|
outputElement.textContent = '';
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const { value, done } = await reader.read();
|
||||||
|
if (done) {
|
||||||
|
// Allow the serial port to be closed later.
|
||||||
|
reader.releaseLock();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Print the output to the webpage
|
||||||
|
outputElement.textContent += value;
|
||||||
|
// Scroll to the bottom as new data comes in
|
||||||
|
outputElement.scrollTop = outputElement.scrollHeight;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('There was an error:', error);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('Web Serial API not supported in this browser.');
|
||||||
|
}
|
||||||
|
});
|
@ -0,0 +1,35 @@
|
|||||||
|
document.getElementById('connect').addEventListener('click', async () => {
|
||||||
|
// Feature detection
|
||||||
|
if ('serial' in navigator) {
|
||||||
|
try {
|
||||||
|
// Request a port and open a connection
|
||||||
|
const port = await navigator.serial.requestPort();
|
||||||
|
await port.open({ baudRate: 115200 });
|
||||||
|
|
||||||
|
// Create a text decoder to decode the bytes from the serial device
|
||||||
|
const decoder = new TextDecoderStream();
|
||||||
|
const inputDone = port.readable.pipeTo(decoder.writable);
|
||||||
|
const inputStream = decoder.readable;
|
||||||
|
|
||||||
|
// Read data from the serial device
|
||||||
|
const reader = inputStream.getReader();
|
||||||
|
const outputElement = document.getElementById('output');
|
||||||
|
outputElement.textContent = '';
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const { value, done } = await reader.read();
|
||||||
|
if (done) {
|
||||||
|
// Allow the serial port to be closed later.
|
||||||
|
reader.releaseLock();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Print the output to the webpage
|
||||||
|
outputElement.textContent += value;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('There was an error:', error);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('Web Serial API not supported in this browser.');
|
||||||
|
}
|
||||||
|
});
|
@ -0,0 +1,22 @@
|
|||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#output {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 33vh; /* Top third of the screen */
|
||||||
|
background-color: #333; /* Slightly darker background */
|
||||||
|
color: #fff;
|
||||||
|
overflow-y: scroll;
|
||||||
|
padding: 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-bottom: 1px solid #555;
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Web Serial Console</title>
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
</head>
|
||||||
|
<body class="h-full m-0 p-0 bg-neutral-100">
|
||||||
|
<div class="flex flex-col h-screen" id="container">
|
||||||
|
<div class="p-5" id="top-half">
|
||||||
|
<h1 class="text-3xl font-bold mb-4">Web Serial Console</h1>
|
||||||
|
<div class="flex flex-col md:flex-row justify-between items-start md:items-center mb-4 space-y-4 md:space-y-0">
|
||||||
|
<div class="flex flex-col md:flex-row items-start md:items-center space-y-4 md:space-y-0 md:space-x-4">
|
||||||
|
<div class="flex flex-col md:flex-row items-start md:items-center space-y-4 md:space-y-0 md:space-x-4">
|
||||||
|
<label for="serial-select" class="text-sm font-medium leading-6 text-gray-900">Port:</label>
|
||||||
|
<select id="serial-select" name="serial-select" class="font-medium bg-white block w-full md:w-auto rounded-sm border-0 py-1.5 pl-3 pr-10 text-gray-900 ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-red-500 sm:text-sm sm:leading-6">
|
||||||
|
<option value="">Select a device... </option>
|
||||||
|
</select>
|
||||||
|
<button id="add-port-button" class="ml-2 p-1 bg-gray-200 rounded-sm hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-opacity-50">
|
||||||
|
<!-- <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="4">
|
||||||
|
<path stroke-linecap="square" stroke-linejoin="round" d="M12 4v16m8-8H4" />
|
||||||
|
</svg> -->
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-500" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="size-6">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button id="refresh-ports" class="ml-2 p-1 bg-gray-200 rounded-sm hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-opacity-50">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-600" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="7" >
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<label for="baud" class="text-sm font-medium leading-6 text-gray-900">Baud:</label>
|
||||||
|
<select id="baud" name="baud" class="font-medium bg-white block w-full md:w-auto rounded-sm border-0 py-1.5 pl-3 pr-10 text-gray-900 ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-red-500 sm:text-sm sm:leading-6">
|
||||||
|
<option value="4800">4800</option>
|
||||||
|
<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</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<button id="connect-button" class="px-4 py-1 bg-red-500 text-white rounded-sm hover:bg-red-600 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-opacity-50">Connect</button>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col md:flex-row items-start md:items-center space-y-4 md:space-y-0 md:space-x-4">
|
||||||
|
<label class="flex items-center space-x-2">
|
||||||
|
<input type="checkbox" id="autoscroll-checkbox" checked class="form-checkbox">
|
||||||
|
<span class="text-sm font-medium leading-6 text-gray-900">Autoscroll</span>
|
||||||
|
</label>
|
||||||
|
<button id="clear-button" class="px-4 py-1 bg-red-500 text-white rounded-sm hover:bg-red-600 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-opacity-50">Clear</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="font-mono rounded-sm bg-white shadow-xl h-[calc(50vh-70px)] overflow-y-auto border border-gray-300 p-2.5 bg-gray-200 mx-5 mb-5" id="scrollable-element"></div>
|
||||||
|
</div>
|
||||||
|
<script src="script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,72 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Scrollable Element with Autoscroll</title>
|
||||||
|
<style>
|
||||||
|
body, html {
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
#container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
#top-half {
|
||||||
|
flex: 1;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
#scrollable-element {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
padding: 10px;
|
||||||
|
background-color: #e2e8f0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="container">
|
||||||
|
<div id="top-half">
|
||||||
|
<h1>Scrollable Element Demo</h1>
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" id="autoscroll-checkbox" checked> Autoscroll
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div id="scrollable-element"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const scrollableElement = document.getElementById('scrollable-element');
|
||||||
|
const autoscrollCheckbox = document.getElementById('autoscroll-checkbox');
|
||||||
|
let autoscroll = true;
|
||||||
|
|
||||||
|
autoscrollCheckbox.addEventListener('change', (e) => {
|
||||||
|
autoscroll = e.target.checked;
|
||||||
|
if (autoscroll) {
|
||||||
|
scrollToBottom();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function addText() {
|
||||||
|
const newText = document.createElement('p');
|
||||||
|
newText.textContent = `New text added at ${new Date().toLocaleTimeString()}`;
|
||||||
|
scrollableElement.appendChild(newText);
|
||||||
|
|
||||||
|
if (autoscroll) {
|
||||||
|
scrollToBottom();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function scrollToBottom() {
|
||||||
|
scrollableElement.scrollTop = scrollableElement.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simulate adding text every 2 seconds
|
||||||
|
setInterval(addText, 2000);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,68 @@
|
|||||||
|
|
||||||
|
const scrollableElement = document.getElementById('scrollable-element');
|
||||||
|
const autoscrollCheckbox = document.getElementById('autoscroll-checkbox');
|
||||||
|
const clearButton = document.getElementById('clear-button');
|
||||||
|
const refreshPorts = document.getElementById('refresh-ports');
|
||||||
|
const select = document.getElementById('serial-select');
|
||||||
|
let autoscroll = true;
|
||||||
|
|
||||||
|
|
||||||
|
refreshPorts.addEventListener('click', async () => {
|
||||||
|
while (select.children.length > 1) {
|
||||||
|
select.removeChild(select.lastChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
ports = await navigator.serial.getPorts();
|
||||||
|
console.log(ports)
|
||||||
|
ports.forEach(port => {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = port;
|
||||||
|
const { usbVendorId, usbProductId } = port.getInfo();
|
||||||
|
option.text = `Device ${usbVendorId}:${usbProductId}`;
|
||||||
|
select.appendChild(option);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
autoscrollCheckbox.addEventListener('change', (e) => {
|
||||||
|
autoscroll = e.target.checked;
|
||||||
|
if (autoscroll) {
|
||||||
|
scrollToBottom();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
clearButton.addEventListener('click', () => {
|
||||||
|
while (scrollableElement.firstChild) {
|
||||||
|
scrollableElement.removeChild(scrollableElement.firstChild);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function addText() {
|
||||||
|
const newText = document.createElement('p');
|
||||||
|
newText.textContent = `New text added at ${new Date().toLocaleTimeString()}`;
|
||||||
|
scrollableElement.appendChild(newText);
|
||||||
|
|
||||||
|
if (autoscroll) {
|
||||||
|
scrollToBottom();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function scrollToBottom() {
|
||||||
|
scrollableElement.scrollTop = scrollableElement.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function updateSerialSelect(ports) {
|
||||||
|
const select = document.getElementById('serial-select');
|
||||||
|
// Clear existing options
|
||||||
|
select.innerHTML = '<option value="">Select a device...</option>';
|
||||||
|
|
||||||
|
ports.forEach(port => {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = port;
|
||||||
|
option.text = `Serial device`;
|
||||||
|
select.appendChild(option);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simulate adding text every 2 seconds
|
||||||
|
setInterval(addText, 2000);
|
Reference in New Issue