Use ES6 modules

pull/14/head
Drew Bednar
parent 200fc7629b
commit 4e41910dc1

@ -1,5 +1,5 @@
serve:
npx vite
.PHONY: serve
watch:

@ -0,0 +1,10 @@
import * as globals from './globals.js';
import { setupEventListeners } from './eventListeners.js';
import { updateSerialSelect } from './uiHelpers.js';
document.addEventListener('DOMContentLoaded', async () => {
const ports = await navigator.serial.getPorts();
globals.setSerialPorts(ports);
updateSerialSelect(ports);
setupEventListeners();
});

@ -0,0 +1,67 @@
import * as globals from './globals.js';
import { connectToSerialPort, transmitContents } from './serialCommunication.js';
import { scrollToBottom, updateSerialSelect, onPortDisconnect } from './uiHelpers.js';
const uPyKeyboardInterrupt = new Uint8Array([13, 3, 3]);
export function setupEventListeners() {
globals.addPort.addEventListener('click', async () => {
const port = await navigator.serial.requestPort();
globals.serialPorts.push(port);
updateSerialSelect(globals.serialPorts);
});
globals.autoscrollCheckbox.addEventListener('change', (e) => {
globals.setAutoscroll(e.target.checked);
if (globals.autoscroll) {
scrollToBottom();
}
});
globals.clearButton.addEventListener('click', () => {
while (globals.scrollableElement.firstChild) {
globals.scrollableElement.removeChild(globals.scrollableElement.firstChild);
}
});
globals.connectButton.addEventListener('click', async () => {
console.log(globals.select)
const selectedPort = globals.serialPorts[globals.select.selectedIndex];
console.log("selected port: ", selectedPort)
const baudRate = Math.round(globals.baud.value);
console.log("Baud Rate: ", baudRate)
if (!globals.isPortConnected) {
await connectToSerialPort(selectedPort, baudRate);
} else {
globals.setPortConnected(false);
onPortDisconnect();
}
});
globals.refreshPorts.addEventListener('click', async () => {
const ports = await navigator.serial.getPorts();
console.log(ports)
globals.setSerialPorts(ports)
updateSerialSelect(globals.serialPorts)
});
globals.transmitButton.addEventListener('click', async (event) => {
transmitContents(globals.transmitInput.innerText)
globals.transmitInput.innerHTML = ''
});
globals.transmitInput.addEventListener('keydown', (event) => {
if (event.key === 'c' && event.ctrlKey) {
event.preventDefault();
console.log("Sending interrupt ", uPyKeyboardInterrupt)
globals.writer.write(uPyKeyboardInterrupt);
}
});
globals.transmitInput.addEventListener('keyup', (event) => {
if (event.key === 'Enter' && !event.shiftKey) {
transmitContents(globals.transmitInput.innerText);
globals.transmitInput.innerHTML = ''
}
});
}

@ -0,0 +1,66 @@
export const addDeviceMessage = `Add a device...         `;
export const addPort = document.getElementById('add-port');
export const autoscrollCheckbox = document.getElementById('autoscroll-checkbox');
export const baud = document.getElementById('baud');
export const clearButton = document.getElementById('clear-button');
export const connectButton = document.getElementById('connect-button');
export const refreshPorts = document.getElementById('refresh-ports');
export const scrollableElement = document.getElementById('scrollable-element');
export const select = document.getElementById('serial-select');
export const transmitInput = document.querySelector('div[contenteditable="true"]');
export const transmitButton = document.getElementById('transmit-button');
export const uPyKeyboardInterrupt = new Uint8Array([13, 3, 3]);
export let isPortConnected = false;
export let autoscroll = true;
export let serialPorts = [];
export let encoder;
export let reader;
export let writer;
export function setPortConnected(isConnected) {
isPortConnected = isConnected;
}
export function setAutoscroll(isSet) {
autoscroll = isSet;
console.log('Set autoscroll: ', autoscroll)
}
export function setSerialPorts(ports) {
serialPorts = ports
console.log('Set ports: ', serialPorts)
}
export function getEncoder() {
return encoder;
}
export function setEncoder(newEncoder) {
encoder = newEncoder;
}
export function getReader() {
return reader;
}
export function setReader(newReader) {
reader = newReader;
}
export function getWriter() {
return writer;
}
export function setWriter(newWriter) {
writer = newWriter;
}
// expose some globals for debuggin in webconsole
if (typeof window !== 'undefined') {
window.wsc = {
isPortConnected,
autoscroll,
serialPorts,
};
}

@ -76,6 +76,6 @@
</p>
</div>
</footer>
<script type="module" src="./main.js"></script>
<script type="module" src="./app.js"></script>
</body>
</html>

@ -1,230 +0,0 @@
// Globals
const addDeviceMessage = `Add a device...&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`
const addPort = document.getElementById('add-port')
const autoscrollCheckbox = document.getElementById('autoscroll-checkbox');
const baud = document.getElementById('baud');
const clearButton = document.getElementById('clear-button');
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 () => {
const port = await navigator.serial.requestPort();
serialPorts.push(port);
updateSerialSelect(serialPorts);
});
autoscrollCheckbox.addEventListener('change', (e) => {
autoscroll = e.target.checked;
if (autoscroll) {
scrollToBottom();
}
});
clearButton.addEventListener('click', () => {
while (scrollableElement.firstChild) {
scrollableElement.removeChild(scrollableElement.firstChild);
}
});
connectButton.addEventListener('click', async () => {
const selectedPort = serialPorts[select.selectedIndex]
const baudRate = Math.round(baud.value)
if (!isPortConnected) {
await connectToSerialPort(selectedPort, baudRate);
} else {
isPortConnected = false;
onPortDisconnect();
}
});
refreshPorts.addEventListener('click', async () => {
ports = await navigator.serial.getPorts();
serialPorts = ports
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');
connectButton.textContent = 'Disconnect';
}
function onPortDisconnect() {
connectButton.classList.remove('bg-red-500', 'hover:bg-red-600', 'focus:ring-red-500');
connectButton.classList.add('bg-emerald-500', 'hover:bg-emerald-600', 'focus:ring-emerald-500');
connectButton.textContent = 'Connect';
}
function buildPortOption(port) {
const option = document.createElement('option');
option.value = port;
try {
const info = port.getInfo();
if (info && 'usbVendorId' in info && 'usbProductId' in info) {
const { usbVendorId, usbProductId } = info;
option.text = `Device ${usbVendorId}:${usbProductId}`;
} else {
console.error('getInfo() did not return expected properties:', info);
option.text = 'Unknown Device';
}
} catch (error) {
console.error('Error retrieving port information:', error);
option.text = 'Unknown Device';
}
return option;
}
function addText(text) {
const newText = document.createElement('p');
newText.textContent = `${new Date().toLocaleTimeString()} ${text}`;
scrollableElement.appendChild(newText);
if (autoscroll) {
scrollToBottom();
}
}
function scrollToBottom() {
scrollableElement.scrollTop = scrollableElement.scrollHeight;
}
// Async Functions
async function connectToSerialPort(port, baud) {
await port.open({ baudRate: baud });
onPortConnect()
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 || !port.readable) {
break;
}
buffer += value;
while (buffer.includes('\n')) {
const newlineIndex = buffer.indexOf('\n');
const line = buffer.slice(0, newlineIndex);
buffer = buffer.slice(newlineIndex + 1);
addText(line);
}
}
} 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 {
reader.cancel();
} catch (error) {
console.error("Error encountered in readableStreamClosed:", error);
}
try {
await readableStreamClosed;
} catch (error) {
}
try {
await readableStreamClosed;
} catch (error) {
}
try {
await port.close();
} catch (error) {
console.error("Error encountered closing port:", error);
}
onPortDisconnect()
}
}
async function updateSerialSelect(ports) {
if (ports.length < 1) {
const option = document.createElement('option');
option.text = addDeviceMessage;
select.innerHTML = ''
select.appendChild(option)
return;
}
select.innerHTML = ''
ports.forEach(port => {
const option = buildPortOption(port)
select.appendChild(option);
});
}

@ -0,0 +1,73 @@
import * as globals from './globals.js';
import { onPortConnect, onPortDisconnect, addText } from './uiHelpers.js';
export async function connectToSerialPort(port, baud) {
console.log("Connecting to port: ", port);
await port.open({ baudRate: baud });
onPortConnect()
let buffer = ''
globals.setEncoder(new TextEncoder());
globals.setWriter(port.writable.getWriter());
const textDecoder = new TextDecoderStream();
const readableStreamClosed = port.readable.pipeTo(textDecoder.writable);
globals.setReader(textDecoder.readable.getReader());
const controller = new AbortController();
const signal = controller.signal;
try {
globals.setPortConnected(true);
while (globals.isPortConnected) {
const { value, done } = await globals.reader.read({ signal });
if (done || !port.readable) {
break;
}
buffer += value;
while (buffer.includes('\n')) {
const newlineIndex = buffer.indexOf('\n');
const line = buffer.slice(0, newlineIndex);
buffer = buffer.slice(newlineIndex + 1);
addText(line);
}
}
} catch (error) {
console.error('Error reading data from serial port:', error);
} finally {
// Port cleanup
controller.abort();
globals.writer.releaseLock();
globals.reader.releaseLock();
try {
globals.reader.cancel();
} catch (error) {
}
try {
await readableStreamClosed;
} catch (error) {
}
try {
await readableStreamClosed;
} catch (error) {
}
try {
await port.close();
} catch (error) {
console.error("Error encountered closing port:", error);
}
onPortDisconnect()
}
}
export function transmitContents(input) {
const encoded_string = globals.encoder.encode(input + '\r')
console.log("Binary Contents: ", encoded_string)
globals.writer.write(encoded_string)
}

@ -0,0 +1,63 @@
import * as globals from './globals.js';
export function onPortConnect() {
globals.connectButton.classList.remove('bg-emerald-500', 'hover:bg-emerald-600', 'focus:ring-emerald-500');
globals.connectButton.classList.add('bg-red-500', 'hover:bg-red-600', 'focus:ring-red-500');
globals.connectButton.textContent = 'Disconnect';
}
export function onPortDisconnect() {
globals.connectButton.classList.remove('bg-red-500', 'hover:bg-red-600', 'focus:ring-red-500');
globals.connectButton.classList.add('bg-emerald-500', 'hover:bg-emerald-600', 'focus:ring-emerald-500');
globals.connectButton.textContent = 'Connect';
}
export function buildPortOption(port) {
const option = document.createElement('option');
option.value = port;
try {
const info = port.getInfo();
if (info && 'usbVendorId' in info && 'usbProductId' in info) {
const { usbVendorId, usbProductId } = info;
option.text = `Device ${usbVendorId}:${usbProductId}`;
} else {
console.error('getInfo() did not return expected properties:', info);
option.text = 'Unknown Device';
}
} catch (error) {
console.error('Error retrieving port information:', error);
option.text = 'Unknown Device';
}
return option;
}
export function addText(text) {
const newText = document.createElement('p');
newText.textContent = `${new Date().toLocaleTimeString()} ${text}`;
globals.scrollableElement.appendChild(newText);
if (globals.autoscroll) {
scrollToBottom();
}
}
export function scrollToBottom() {
globals.scrollableElement.scrollTop = globals.scrollableElement.scrollHeight;
}
export async function updateSerialSelect(ports) {
if (ports.length < 1) {
const option = document.createElement('option');
option.text = addDeviceMessage;
globals.select.innerHTML = ''
globals.select.appendChild(option)
return;
}
globals.select.innerHTML = ''
ports.forEach(port => {
const option = buildPortOption(port)
globals.select.appendChild(option);
});
}