Implement WebSerial I/O

This commit is contained in:
Kai Vogelgesang 2022-12-25 03:33:43 +01:00
parent 444ca860fa
commit d85bf03011
Signed by: kai
GPG Key ID: 3FC8578CC818A9EB
3 changed files with 133 additions and 60 deletions

View File

@ -0,0 +1,130 @@
<script lang="ts">
import { onMount } from "svelte";
export let port: SerialPort;
export let fps: number = 50;
const FRAME_TIME = 1000 / fps; // milliseconds
let lines = [];
let linesResolve: (value: string | PromiseLike<string>) => void = null;
function readLine(): Promise<string> {
if (lines.length > 0) {
return Promise.resolve(lines.shift());
} else {
// lines is empty, register callbacks
return new Promise((resolve) => {
linesResolve = resolve;
});
}
}
const handleLine = (line: string) => {
if (linesResolve !== null) {
linesResolve(line);
linesResolve = null;
} else {
lines.push(line);
}
};
let running = true;
async function readLoop() {
console.log("runner started");
await port.open({ baudRate: 500_000 });
let reader = port.readable.getReader();
let data = "";
try {
while (running) {
const { value, done } = await reader.read();
if (done) break;
const chunk = new TextDecoder().decode(value);
data += chunk;
const lines = data.split("\r\n");
data = lines.pop();
for (let line of lines) {
handleLine(line);
}
}
} finally {
reader.releaseLock();
}
await port.close();
console.log("runner stopped");
}
let readLoopHandle: Promise<void> = null;
async function sync() {
while (running) {
const line = await readLine();
if (line === "Sync.") {
return;
}
}
}
let payload = new Uint8Array(512);
const sleep = (ms: number) =>
new Promise((resolve) => setTimeout(resolve, ms));
let lastLoopTime: number = null;
async function writeLoop() {
await sync();
while (running) {
const loopStart = performance.now();
// write out payload
let writer = port.writable.getWriter();
try {
await writer.write(payload);
} finally {
writer.releaseLock();
}
// read response
const response = await readLine();
if (response !== "Ack.") {
console.log(`received bad response: "${response}"`);
lastLoopTime = null;
await sync();
continue;
}
const loopTime = performance.now() - loopStart;
if (loopTime < FRAME_TIME) {
await sleep(FRAME_TIME - loopTime);
} else {
console.warn(`loop took too long (+${(loopTime - FRAME_TIME).toFixed(2)} ms)`);
}
lastLoopTime = loopTime;
}
}
let writeLoopHandle: Promise<void> = null;
onMount(() => {
console.log("mount");
readLoopHandle = readLoop();
writeLoopHandle = writeLoop();
return () => {
console.log("unmount");
running = false;
if (linesResolve !== null) {
linesResolve("");
}
};
});
</script>
<p>
{#if lastLoopTime !== null}
{lastLoopTime.toFixed(2).padStart(5)}
{:else}
syncing
{/if}
</p>

View File

@ -1,5 +1,5 @@
<script lang="ts">
import SerialReader from "./SerialReader.svelte";
import SerialConnection from "./SerialConnection.svelte";
let port: SerialPort = null;
@ -14,9 +14,9 @@
</script>
{#if navigator.serial}
<p>serial baby</p>
<p>Serial available &#x1f680;</p>
{#if port !== null}
<SerialReader {port}/>
<SerialConnection {port}/>
<p><button on:click={disconnect}>disconnect</button></p>
{:else}
<p><button on:click={connect}>connect</button></p>

View File

@ -1,57 +0,0 @@
<script lang="ts">
import { onMount } from "svelte";
export let port: SerialPort;
let lines = [];
const handleLine = (line: string) => {
lines.push(line);
if (lines.length > 10) lines.shift();
lines = lines;
}
let running = true;
async function runner() {
console.log("runner started");
await port.open({baudRate: 500_000});
let reader = port.readable.getReader();
let data = "";
try {
while (running) {
const { value, done } = await reader.read();
if (done) break;
const chunk = new TextDecoder().decode(value);
data += chunk;
const lines = data.split("\n");
data = lines.pop();
for (let line of lines) {
handleLine(line);
}
}
} finally {
reader.releaseLock();
}
await port.close();
console.log("runner stopped");
}
let handle: Promise<void> = null;
onMount(() => {
console.log("mount");
handle = runner();
return () => {
console.log("unmount");
running = false;
};
});
</script>
<div>
{#each lines as line}
<div>{line}</div>
{/each}
</div>