From f4c24976c6599c3f02998da846d9785ebae2bc82 Mon Sep 17 00:00:00 2001 From: Fischer Moseley <42497969+fischermoseley@users.noreply.github.com> Date: Thu, 18 Apr 2024 11:21:51 -0700 Subject: [PATCH] web: implement synchronous IO with web and service workers --- doc/assets/main.py | 12 ++++ doc/javascripts/serial.js | 43 ------------ doc/javascripts/service-worker.js | 23 ++++++ doc/javascripts/upload.js | 42 ----------- doc/javascripts/web-worker.js | 40 +++++++++++ doc/javascripts/webTerminal.js | 112 ++++++++++++++++++------------ doc/web_terminal.md | 23 ++---- 7 files changed, 147 insertions(+), 148 deletions(-) create mode 100644 doc/assets/main.py delete mode 100644 doc/javascripts/serial.js create mode 100644 doc/javascripts/service-worker.js delete mode 100644 doc/javascripts/upload.js create mode 100644 doc/javascripts/web-worker.js diff --git a/doc/assets/main.py b/doc/assets/main.py new file mode 100644 index 0000000..0d33a20 --- /dev/null +++ b/doc/assets/main.py @@ -0,0 +1,12 @@ +from js import workerReadSerial, workerWriteSerial + +def capture(foo): + print(f"(Python): capture() has been called with args: {foo}") + workerWriteSerial("W00000001\r\n") + workerWriteSerial("W00010001\r\n") + workerWriteSerial("R0000\r\n") + foo = workerReadSerial() + print(f"(Python): capture() has completed and returned value: ", {foo}) + return foo + +print("(Python) Load Complete!") \ No newline at end of file diff --git a/doc/javascripts/serial.js b/doc/javascripts/serial.js deleted file mode 100644 index c82e657..0000000 --- a/doc/javascripts/serial.js +++ /dev/null @@ -1,43 +0,0 @@ -let port; - -async function getPort() { - if (port) return port; - try { - [port] = await navigator.serial.getPorts(); - await port.open({ baudRate: 115200 }); - console.log('Connected to serial device:', port); - return port; - } catch (error) { - console.error('Error connecting to serial device:', error); - } -} - -async function write(data) { - const port = await getPort(); - const writer = port.writable.getWriter(); - await writer.write(new TextEncoder().encode(data)); - await writer.releaseLock(); -} - -async function read() { - const port = await getPort(); - const reader = port.readable.getReader(); - const {value, done} = await reader.read(); - reader.releaseLock(); - - if (!done) { - return new TextDecoder().decode(value); - } -} - -self.addEventListener('message', async function(event) { - await write(event.data); -}); - -async function readWrapper() { - if (port) { - self.postMessage(await read()); // this is a hack but i'm curious - } -} - -setInterval(readWrapper, 500); \ No newline at end of file diff --git a/doc/javascripts/service-worker.js b/doc/javascripts/service-worker.js new file mode 100644 index 0000000..fb9b624 --- /dev/null +++ b/doc/javascripts/service-worker.js @@ -0,0 +1,23 @@ +addEventListener('install', () => { +self.skipWaiting(); +}); +addEventListener('activate', () => { +self.clients.claim(); +}); + +let resolver; +console.log("(Service Worker): Loaded!"); + +addEventListener('message', event => { +resolver(new Response(event.data,{status:200})); +console.log("(Service Worker): fufilling fetch, sending: ", event.data); +}); + +addEventListener('fetch', e => { +const u = new URL(e.request.url); +console.log("(Service Worker): Got fetch to: ", u); +if (u.pathname === '/read_serial/' || u.pathname === '/write_serial/') { + e.respondWith(new Promise(r => resolver = r)); +} +}); + diff --git a/doc/javascripts/upload.js b/doc/javascripts/upload.js deleted file mode 100644 index 97ea5e6..0000000 --- a/doc/javascripts/upload.js +++ /dev/null @@ -1,42 +0,0 @@ -let data; -let fileUploaded = false; - -// Map the fake upload button to the real (hidden) upload button -document.addEventListener("DOMContentLoaded", function() { - const uploadButton = document.getElementById("uploadButton"); - const fileInput = document.getElementById("fileInput"); - const filePathDisplay = document.getElementById("filePath"); - - uploadButton.addEventListener("click", function() { - fileInput.click(); - }); - - fileInput.addEventListener("change", function() { - const filePath = fileInput.value; // Get the file path from the file input - filePathDisplay.textContent = "Loaded " + filePath; // Display the file path - }); -}); - -// Update fileUploaded flag when a file gets uploaded -document.getElementById('fileInput').addEventListener('change', function(event) { - const file = event.target.files[0]; - const reader = new FileReader(); - reader.onload = function(e) { - data = e.target.result; - fileUploaded = true; - }; - reader.readAsText(file); -}); - -// Continuously poll to see if the file has been uploaded -function runAfterUpload() { - if (!fileUploaded) { - setTimeout(runAfterUpload, 100); // Check again after 100ms - return; - } - - // Additional JavaScript code to run after file upload - // WebTerminal(data); -} - -runAfterUpload(); \ No newline at end of file diff --git a/doc/javascripts/web-worker.js b/doc/javascripts/web-worker.js new file mode 100644 index 0000000..4b98e2f --- /dev/null +++ b/doc/javascripts/web-worker.js @@ -0,0 +1,40 @@ +let pyodide; + +importScripts("https://cdn.jsdelivr.net/pyodide/v0.25.1/full/pyodide.js"); + +(async function() { + pyodide = await loadPyodide({ indexURL : "https://cdn.jsdelivr.net/pyodide/v0.25.1/full/" }); + const response = await fetch('../assets/main.py'); + pyodide.runPython(await response.text()); + console.log("(Web Worker) Python loaded!"); +})() + +addEventListener('message', function(e) { + console.log("(Web Worker): Message received from main thread: ", e.data); + const output = pyodide.globals.get("capture")(e.data); +}); + +function blockingRequestToURL(url) { + const request = new XMLHttpRequest(); + + // `false` makes the request synchronous + request.open('GET', url, false); + request.send(null); + console.log('status', request.status); + return request.responseText; +} + +function workerReadSerial() { + console.log("(Web Worker): workerReadSerial called"); + postMessage({awaitingRead: true}); + data = blockingRequestToURL('/read_serial/'); + postMessage({awaitingRead: false}); + return data; +} + +function workerWriteSerial(data) { + console.log("(Web Worker): workerWriteSerial called with: ", data); + postMessage({awaitingWrite: true, writeData: data}); + blockingRequestToURL('/write_serial/'); + postMessage({awaitingWrite: false, writeData: undefined}); +} \ No newline at end of file diff --git a/doc/javascripts/webTerminal.js b/doc/javascripts/webTerminal.js index 84201f0..71bae9b 100644 --- a/doc/javascripts/webTerminal.js +++ b/doc/javascripts/webTerminal.js @@ -1,53 +1,75 @@ -document.getElementById('connectButton').addEventListener('click', selectPort); -document.getElementById('runButton').addEventListener('click', webTerminal); +// serial +let port; -async function selectPort(){ - await navigator.serial.requestPort(); +async function connect() { + try { + const serialPort = await navigator.serial.requestPort(); + await serialPort.open({ baudRate: 115200 }); + port = serialPort; + console.log('Connected to serial device:', port); + } catch (error) { + console.error('Error connecting to serial device:', error); + } } -// Start Web Worker for Serial API -globalThis.receiveBuffer = []; - -const serialWorker = new Worker("../javascripts/serial.js"); -function sendToSerialWorker(data){ - console.log("Main -> Worker: ", data); - serialWorker.postMessage(data); +async function write(data) { + console.log("(Main) write() called with: ", data); + const writer = port.writable.getWriter(); + await writer.write(new TextEncoder().encode(data)); + await writer.releaseLock(); } -serialWorker.onmessage = (e) => { - console.log("Worker -> Main: ", e.data); - receiveBuffer.push(e.data); +async function read() { + console.log("(Main) read() called"); + const reader = port.readable.getReader(); + const {value, done} = await reader.read(); + reader.releaseLock(); + + if (!done) { + return new TextDecoder().decode(value); + } +} + +// workers +let serviceWorker; + +navigator.serviceWorker.register('../javascripts/service-worker.js').then(function(registration) { + serviceWorker = registration.active; + if (!serviceWorker) { + location.reload(); + } +}); + +// main loop +let awaitingRead = false; +let awaitingWrite = false; +let writeData; + +const worker = new Worker('../javascripts/web-worker.js'); +worker.onmessage = function(e) { + console.log("(Main) received message from web worker: ", e.data); + if ("awaitingRead" in e.data) {awaitingRead = e.data.awaitingRead}; + if ("awaitingWrite" in e.data) {awaitingWrite = e.data.awaitingWrite}; + if ("writeData" in e.data) {writeData = e.data.writeData}; }; -// Main function for the Web Terminal -async function webTerminal(){ - let pyodide = await loadPyodide(); - - // Load Manta.yaml into pyodide's file system - // pyodide.FS.writeFile("/manta.yaml", data, { encoding: "utf8" }); - - // Load micropip, setuptools, manta - await pyodide.loadPackage("micropip"); - await pyodide.loadPackage("setuptools"); - const micropip = pyodide.pyimport("micropip"); - await micropip.install('../assets/manta-1.0.0-py3-none-any.whl'); - - pyodide.runPythonAsync(` - import asyncio - from js import sendToSerialWorker - from js import receiveBuffer - from manta import Manta - - #m = Manta("/manta.yaml") - - print("Sending read request") - sendToSerialWorker("R0000\\r\\n") - - for _ in range(10): - print(len(receiveBuffer)) - print(receiveBuffer) - await asyncio.sleep(0.1) - - print("Python Complete") - `); +async function capture() { + console.log("(Main): Sending message to Web Worker: ", ""); + worker.postMessage(""); } + +async function checkForAwaitingInput() { + if (awaitingRead) { + let out = await read(); + console.log("(Main): Sending message to Service Worker: ", out); + serviceWorker.postMessage(out); + } + + if (awaitingWrite) { + let out = await write(writeData); + console.log("(Main): Sending message to Service Worker: ", out); + serviceWorker.postMessage(out); + } +} + +setInterval(checkForAwaitingInput, 100); diff --git a/doc/web_terminal.md b/doc/web_terminal.md index 161f461..7c8afb5 100644 --- a/doc/web_terminal.md +++ b/doc/web_terminal.md @@ -1,20 +1,7 @@ -## Open Serial Port - - -## Load Manta Configuration File - - -
- -## Run Main Thread - - - - - - - - - + + + + + \ No newline at end of file