Adds Transmit functionality and fixes disconnect error in the event of powerloss (#11)

closes #7
close #8

Co-authored-by: Drew Bednar <drew@androiddrew.com>
Reviewed-on: #11
pull/16/head
Drew Bednar 8 months ago
parent 489a1a8a08
commit 8f070a4e45

@ -0,0 +1,14 @@
import time
from machine import UART
# Initialize UART1 (or any other UART you want to use). These pins are for UART1 on ESP-32 S3 Devkit
uart1 = UART(1, baudrate=115200, tx=17, rx=18)
counter = 0
while True:
if uart1.any():
print(uart1.read())
print("I'm still here. Count {}".format(counter))
counter += 1
time.sleep(1)

1165
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -1,3 +1,12 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@tailwind utilities;
select:focus {
outline: none;
box-shadow: none;
}
input:focus {
outline: none;
box-shadow: none;
}

@ -6,12 +6,6 @@
<title>Web Serial Console</title>
<link rel="icon" type="image/x-icon" href="./favicon.ico">
<link rel="stylesheet" type="text/css" href="./main.css">
<style>
select:focus {
outline: none;
box-shadow: none;
}
</style>
</head>
<body class="min-h-screen m-0 p-0 bg-gradient-to-b from-neutral-100 to-neutral-50 dark:bg-slate-900">
<div class="flex flex-col h-3/4" id="container">
@ -60,7 +54,52 @@
</div>
</div>
</div>
<div class="font-mono rounded-md bg-white shadow-xl h-[calc(75vh-70px)] overflow-y-auto border border-gray-300 p-2.5 bg-gray-200 mx-5 mb-5" id="scrollable-element"></div>
<div class="font-mono rounded-md bg-white shadow-xl h-[calc(75vh-70px)] overflow-y-auto border border-gray-300 p-2.5 bg-gray-200 mx-5 mb-6 " id="scrollable-element"></div>
<!-- Transmit OLD-->
<!-- <div class="flex items-center space-x-2 px-5 mb-5">
<input type="text" id="transmit-input" class="font-mono flex-grow font-medium bg-white rounded-md py-1.5 px-3 border-0 border-gray-300 text-gray-900 ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-emerald-500 sm:text-sm sm:leading-6" placeholder="Enter text to send...">
<button id="transmit-button" class="relative inline-flex items-center gap-x-1.5 px-4 py-1.5 bg-blue-500 text-white rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-opacity-50">
<svg class="-ml-0.5 h-5 w-5 text-white" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd" d="M2 3.75A.75.75 0 012.75 3h11.5a.75.75 0 010 1.5H2.75A.75.75 0 012 3.75zM2 7.5a.75.75 0 01.75-.75h6.365a.75.75 0 010 1.5H2.75A.75.75 0 012 7.5zM14 7a.75.75 0 01.55.24l3.25 3.5a.75.75 0 11-1.1 1.02l-1.95-2.1v6.59a.75.75 0 01-1.5 0V9.66l-1.95 2.1a.75.75 0 11-1.1-1.02l3.25-3.5A.75.75 0 0114 7zM2 11.25a.75.75 0 01.75-.75H7A.75.75 0 017 12H2.75a.75.75 0 01-.75-.75z" clip-rule="evenodd" />
</svg>
Send</button>
</div> -->
<!-- End Transmit OLD-->
<!-- Experiment -->
<div class="flex items-center space-x-2 px-5 mb-5">
<div contenteditable="true" class="shadow-xl flex-grow rounded-md font-mono py-1.5 px-3 ring-1 ring-inset ring-gray-300 text-gray-900 outline-none focus:ring-2 focus:ring-emerald-500">
<p data-placeholder="Enter text to send..." class="empty:before:content-[attr(data-placeholder)] empty:before:text-gray-500"></p>
</div>
<button id="transmit-button" class="shadow-xl relative inline-flex items-center gap-x-1.5 px-4 py-1.5 bg-blue-500 text-white rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-opacity-50">
<svg class="-ml-0.5 h-5 w-5 text-white" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd" d="M2 3.75A.75.75 0 012.75 3h11.5a.75.75 0 010 1.5H2.75A.75.75 0 012 3.75zM2 7.5a.75.75 0 01.75-.75h6.365a.75.75 0 010 1.5H2.75A.75.75 0 012 7.5zM14 7a.75.75 0 01.55.24l3.25 3.5a.75.75 0 11-1.1 1.02l-1.95-2.1v6.59a.75.75 0 01-1.5 0V9.66l-1.95 2.1a.75.75 0 11-1.1-1.02l3.25-3.5A.75.75 0 0114 7zM2 11.25a.75.75 0 01.75-.75H7A.75.75 0 017 12H2.75a.75.75 0 01-.75-.75z" clip-rule="evenodd" />
</svg>
Send
</button>
</div>
<!-- <div class="flex items-center space-x-2 px-5 mb-5">
<div aria-label="Enter text to send..." class="flex-grow mt-1 max-h-96 w-full overflow-y-auto break-words">
<div contenteditable="true" translate="no" enterkeyhint="enter" tabindex="0"
class="font-mono flex-grow font-medium bg-white rounded-md py-1.5 px-3 text-gray-900 ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-emerald-500 sm:text-sm sm:leading-6 break-words max-w-[60ch]">
<p data-placeholder="Enter text to send..."
class="m-0 text-gray-900 focus:outline-none empty:before:content-[attr(data-placeholder)] empty:before:text-gray-500">
</p>
</div>
</div>
<button id="transmit-button" class="relative inline-flex items-center gap-x-1.5 px-4 py-1.5 bg-blue-500 text-white rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-opacity-50">
<svg class="-ml-0.5 h-5 w-5 text-white" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd" d="M2 3.75A.75.75 0 012.75 3h11.5a.75.75 0 010 1.5H2.75A.75.75 0 012 3.75zM2 7.5a.75.75 0 01.75-.75h6.365a.75.75 0 010 1.5H2.75A.75.75 0 012 7.5zM14 7a.75.75 0 01.55.24l3.25 3.5a.75.75 0 11-1.1 1.02l-1.95-2.1v6.59a.75.75 0 01-1.5 0V9.66l-1.95 2.1a.75.75 0 11-1.1-1.02l3.25-3.5A.75.75 0 0114 7zM2 11.25a.75.75 0 01.75-.75H7A.75.75 0 017 12H2.75a.75.75 0 01-.75-.75z" clip-rule="evenodd" />
</svg>
Send
</button>
</div> -->
<!-- End experiment -->
<!-- <div aria-label="Enter text to send..." class="mt-1 max-h-96 w-full overflow-y-auto break-words">
<div contenteditable="true" translate="no" enterkeyhint="enter" tabindex="0" class="break-words max-w-[60ch]">
<p data-placeholder="Enter text to send..." class="text-gray-900 ring-1 ring-gray-300 font-mono ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-emerald-500 is-empty is-editor-empty before:!text-text-500 before:whitespace-nowrap"><br class="ProseMirror-trailingBreak"></p>
</div>
</div> -->
</div>
<footer>
<div class="mx-auto max-w-7xl overflow-hidden py-12 px-4 sm:px-6 lg:px-8">

@ -588,6 +588,10 @@ video {
}
}
.relative {
position: relative;
}
.m-0 {
margin: 0px;
}
@ -602,6 +606,10 @@ video {
margin-right: auto;
}
.-ml-0\.5 {
margin-left: -0.125rem;
}
.mb-2 {
margin-bottom: 0.5rem;
}
@ -626,6 +634,10 @@ video {
margin-top: 2rem;
}
.mt-1 {
margin-top: 0.25rem;
}
.block {
display: block;
}
@ -634,6 +646,10 @@ video {
display: flex;
}
.inline-flex {
display: inline-flex;
}
.size-6 {
width: 1.5rem;
height: 1.5rem;
@ -651,6 +667,10 @@ video {
height: calc(75vh - 70px);
}
.max-h-96 {
max-height: 24rem;
}
.min-h-screen {
min-height: 100vh;
}
@ -667,6 +687,14 @@ video {
max-width: 80rem;
}
.max-w-\[60ch\] {
max-width: 60ch;
}
.flex-grow {
flex-grow: 1;
}
.flex-col {
flex-direction: column;
}
@ -687,6 +715,11 @@ video {
justify-content: space-between;
}
.gap-x-1\.5 {
-moz-column-gap: 0.375rem;
column-gap: 0.375rem;
}
.space-x-2 > :not([hidden]) ~ :not([hidden]) {
--tw-space-x-reverse: 0;
margin-right: calc(0.5rem * var(--tw-space-x-reverse));
@ -713,6 +746,10 @@ video {
overflow-y: auto;
}
.break-words {
overflow-wrap: break-word;
}
.rounded-md {
border-radius: 0.375rem;
}
@ -735,6 +772,11 @@ video {
background-color: rgb(245 158 11 / var(--tw-bg-opacity));
}
.bg-blue-500 {
--tw-bg-opacity: 1;
background-color: rgb(59 130 246 / var(--tw-bg-opacity));
}
.bg-emerald-500 {
--tw-bg-opacity: 1;
background-color: rgb(16 185 129 / var(--tw-bg-opacity));
@ -755,6 +797,16 @@ video {
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
}
.bg-blue-100 {
--tw-bg-opacity: 1;
background-color: rgb(219 234 254 / var(--tw-bg-opacity));
}
.bg-blue-50 {
--tw-bg-opacity: 1;
background-color: rgb(239 246 255 / var(--tw-bg-opacity));
}
.bg-gradient-to-b {
background-image: linear-gradient(to bottom, var(--tw-gradient-stops));
}
@ -785,11 +837,21 @@ video {
padding: 1.25rem;
}
.px-3 {
padding-left: 0.75rem;
padding-right: 0.75rem;
}
.px-4 {
padding-left: 1rem;
padding-right: 1rem;
}
.px-5 {
padding-left: 1.25rem;
padding-right: 1.25rem;
}
.py-1 {
padding-top: 0.25rem;
padding-bottom: 0.25rem;
@ -883,6 +945,11 @@ video {
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
}
.outline-none {
outline: 2px solid transparent;
outline-offset: 2px;
}
.outline {
outline-style: solid;
}
@ -902,11 +969,42 @@ video {
--tw-ring-color: rgb(209 213 219 / var(--tw-ring-opacity));
}
select:focus {
outline: none;
box-shadow: none;
}
input:focus {
outline: none;
box-shadow: none;
}
.before\:whitespace-nowrap::before {
content: var(--tw-content);
white-space: nowrap;
}
.empty\:before\:text-gray-500:empty::before {
content: var(--tw-content);
--tw-text-opacity: 1;
color: rgb(107 114 128 / var(--tw-text-opacity));
}
.empty\:before\:content-\[attr\(data-placeholder\)\]:empty::before {
--tw-content: attr(data-placeholder);
content: var(--tw-content);
}
.hover\:bg-amber-600:hover {
--tw-bg-opacity: 1;
background-color: rgb(217 119 6 / var(--tw-bg-opacity));
}
.hover\:bg-blue-600:hover {
--tw-bg-opacity: 1;
background-color: rgb(37 99 235 / var(--tw-bg-opacity));
}
.hover\:bg-emerald-600:hover {
--tw-bg-opacity: 1;
background-color: rgb(5 150 105 / var(--tw-bg-opacity));
@ -938,6 +1036,11 @@ video {
--tw-ring-color: rgb(245 158 11 / var(--tw-ring-opacity));
}
.focus\:ring-blue-500:focus {
--tw-ring-opacity: 1;
--tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity));
}
.focus\:ring-emerald-500:focus {
--tw-ring-opacity: 1;
--tw-ring-color: rgb(16 185 129 / var(--tw-ring-opacity));

@ -8,12 +8,18 @@ const connectButton = document.getElementById('connect-button')
const refreshPorts = document.getElementById('refresh-ports');
const scrollableElement = document.getElementById('scrollable-element');
const select = document.getElementById('serial-select');
const transmitInput = document.querySelector('div[contenteditable="true"]');
const transmitButton = document.getElementById('transmit-button');
const uPyKeyboardInterrupt = new Uint8Array([13, 3, 3])
let isPortConnected = false;
let controller;
let autoscroll = true;
let serialPorts = [];
let encoder;
let reader;
let writer;
// Event Listeners
addPort.addEventListener('click', async () => {
@ -42,6 +48,7 @@ connectButton.addEventListener('click', async () => {
await connectToSerialPort(selectedPort, baudRate);
} else {
isPortConnected = false;
onPortDisconnect();
}
});
@ -51,7 +58,35 @@ refreshPorts.addEventListener('click', async () => {
updateSerialSelect(ports)
});
transmitButton.addEventListener('click', async (event) => {
transmitContents(transmitInput.innerText)
transmitInput.innerHTML = ''
});
transmitInput.addEventListener('keydown', (event) => {
if (event.key === 'c' && event.ctrlKey) {
event.preventDefault();
console.log("Sending interrupt ", uPyKeyboardInterrupt)
writer.write(uPyKeyboardInterrupt);
}
});
transmitInput.addEventListener('keyup', (event) => {
if (event.key === 'Enter' && !event.shiftKey) {
transmitContents(transmitInput.innerText);
transmitInput.innerHTML = ''
}
});
// Functions
function transmitContents(input) {
encoded_string = encoder.encode(input + '\r')
console.log("Binary Contents: ", encoded_string)
writer.write(encoded_string)
}
function onPortConnect() {
connectButton.classList.remove('bg-emerald-500', 'hover:bg-emerald-600', 'focus:ring-emerald-500');
connectButton.classList.add('bg-red-500', 'hover:bg-red-600', 'focus:ring-red-500');
@ -108,16 +143,24 @@ async function connectToSerialPort(port, baud) {
let buffer = ''
// The TextDecoderStream interface of the Encoding API converts a stream of text in a binary encoding,
// such as UTF-8 etc., to a stream of strings
// const textEncoder = new TextEncoderStream();
// const writableStreamClosed = textEncoder.readable.pipeTo(port.writable);
// writer = textEncoder.writable.getWriter();
encoder = new TextEncoder()
writer = port.writable.getWriter();
const textDecoder = new TextDecoderStream();
const readableStreamClosed = port.readable.pipeTo(textDecoder.writable);
reader = textDecoder.readable.getReader();
controller = new AbortController();
const signal = controller.signal;
try {
isPortConnected = true;
while (isPortConnected) {
const { value, done } = await reader.read({ signal });
if (done) {
if (done || !port.readable) {
break;
}
@ -134,11 +177,20 @@ async function connectToSerialPort(port, baud) {
} catch (error) {
console.error('Error reading data from serial port:', error);
} finally {
// Port cleanup
controller.abort();
writer.releaseLock();
reader.releaseLock();
// try {
// writer.close();
// await writableStreamClosed;
// } catch (error) {
// console.error("Error encountered in writeableSteamClosed:", error)
// }
try {
await textDecoder.readable.cancel();
reader.cancel();
} catch (error) {
console.error("Error encountered in readableStreamClosed:", error);
}
@ -148,6 +200,11 @@ async function connectToSerialPort(port, baud) {
} catch (error) {
}
try {
await readableStreamClosed;
} catch (error) {
}
try {
await port.close();
} catch (error) {