From abe477a2eefcff6c9e1f91f8d409996c99c37296 Mon Sep 17 00:00:00 2001
From: SJ7F <235936019+SJ7F@users.noreply.github.com>
Date: Wed, 29 Oct 2025 19:44:32 -0400
Subject: [PATCH 1/5] Add SerialConfig encoding parameter and menu
---
src/components/ConnectModal.tsx | 14 +++++++++++++-
src/hooks/useDataConnection.ts | 4 +++-
2 files changed, 16 insertions(+), 2 deletions(-)
diff --git a/src/components/ConnectModal.tsx b/src/components/ConnectModal.tsx
index 65ab0ee..ffadb11 100644
--- a/src/components/ConnectModal.tsx
+++ b/src/components/ConnectModal.tsx
@@ -153,8 +153,9 @@ export function ConnectModal({
+
-
+
+
+
+
+
+
diff --git a/src/hooks/useDataConnection.ts b/src/hooks/useDataConnection.ts
index 3526c69..4980770 100644
--- a/src/hooks/useDataConnection.ts
+++ b/src/hooks/useDataConnection.ts
@@ -8,6 +8,7 @@ export interface SerialConfig {
stopBits: 1 | 2
parity: 'none' | 'even' | 'odd'
flowControl: 'none' | 'hardware'
+ encoding: 'ascii' | 'cobs-f32'
}
export type ConnectionType = 'serial' | 'generator'
@@ -35,7 +36,8 @@ const DEFAULT_SERIAL_CONFIG: SerialConfig = {
dataBits: 8,
stopBits: 1,
parity: 'none',
- flowControl: 'none'
+ flowControl: 'none',
+ encoding: 'ascii'
}
export function useDataConnection(onLine: (line: string) => void): UseDataConnection {
From f94120d53adc0831debaa38595f6c5ccd8fc1fe0 Mon Sep 17 00:00:00 2001
From: SJ7F <235936019+SJ7F@users.noreply.github.com>
Date: Wed, 29 Oct 2025 20:19:25 -0400
Subject: [PATCH 2/5] Refactor serial.connect to take SerialConfig instead of
only baudRate
---
src/hooks/useDataConnection.ts | 4 +---
src/hooks/useSerial.ts | 6 ++++--
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/hooks/useDataConnection.ts b/src/hooks/useDataConnection.ts
index 4980770..32a1804 100644
--- a/src/hooks/useDataConnection.ts
+++ b/src/hooks/useDataConnection.ts
@@ -67,12 +67,10 @@ export function useDataConnection(onLine: (line: string) => void): UseDataConnec
setError(null)
try {
- // Convert our config to the format useSerial expects
// Note: Web Serial API has limited configuration options
- await serial.connect(config.baudRate)
+ await serial.connect(config);
setConnectionType('serial')
// The actual port configuration would need to be done at the port.open() level
- // For now, we'll just use baudRate as useSerial currently does
} catch (err) {
const message = err instanceof Error ? err.message : 'Failed to connect to serial port'
setError(message)
diff --git a/src/hooks/useSerial.ts b/src/hooks/useSerial.ts
index cc56291..5ebc94f 100644
--- a/src/hooks/useSerial.ts
+++ b/src/hooks/useSerial.ts
@@ -1,5 +1,7 @@
import { useCallback, useRef, useState } from 'react'
+import type { SerialConfig } from './useDataConnection'
+
export type BaudRate = 300 | 600 | 1200 | 2400 | 4800 | 9600 | 19200 | 38400 | 57600 | 115200 | 230400 | 460800 | 921600
export interface SerialState {
@@ -79,7 +81,7 @@ export function useSerial(): UseSerial {
}
}, []) // No dependencies - use refs for everything
- const connect = useCallback(async (baudRate: number) => {
+ const connect = useCallback(async (config: SerialConfig) => {
if (!state.isSupported) {
setState((s) => ({ ...s, error: 'Web Serial not supported in this browser.' }))
return
@@ -108,7 +110,7 @@ export function useSerial(): UseSerial {
}
}
- await port.open({ baudRate })
+ await port.open({ baudRate: config.baudRate })
const textDecoder = new TextDecoderStream()
const readableClosed = port.readable.pipeTo(textDecoder.writable)
From 82df32f519e1266dcfb0681255f6c8f1a55e9ff3 Mon Sep 17 00:00:00 2001
From: SJ7F <235936019+SJ7F@users.noreply.github.com>
Date: Wed, 29 Oct 2025 21:13:40 -0400
Subject: [PATCH 3/5] Implement COBS decoding and float32 parsing
TODO: Fix hack (where the Array of numbers is converted back to a string) on line 194 of useSerial.ts
---
src/hooks/useSerial.ts | 144 ++++++++++++++++++++++++++++-----------
src/utils/cobsDecoder.ts | 32 +++++++++
2 files changed, 138 insertions(+), 38 deletions(-)
create mode 100644 src/utils/cobsDecoder.ts
diff --git a/src/hooks/useSerial.ts b/src/hooks/useSerial.ts
index 5ebc94f..a8d2f74 100644
--- a/src/hooks/useSerial.ts
+++ b/src/hooks/useSerial.ts
@@ -2,6 +2,9 @@ import { useCallback, useRef, useState } from 'react'
import type { SerialConfig } from './useDataConnection'
+import { decodeCOBS, COBSDecoderError } from '../utils/cobsDecoder.ts'
+
+
export type BaudRate = 300 | 600 | 1200 | 2400 | 4800 | 9600 | 19200 | 38400 | 57600 | 115200 | 230400 | 460800 | 921600
export interface SerialState {
@@ -112,52 +115,117 @@ export function useSerial(): UseSerial {
await port.open({ baudRate: config.baudRate })
- const textDecoder = new TextDecoderStream()
- const readableClosed = port.readable.pipeTo(textDecoder.writable)
- const reader = textDecoder.readable.getReader()
- readerRef.current = reader
-
- const writer = port.writable.getWriter()
- writerRef.current = writer
- portRef.current = port
-
- setState((s) => ({ ...s, port, isConnected: true }))
+ if (config.encoding === 'ascii') {
+ const textDecoder = new TextDecoderStream()
+ const readableClosed = port.readable.pipeTo(textDecoder.writable)
+ const reader = textDecoder.readable.getReader()
+ readerRef.current = reader
- const abort = new AbortController()
- abortControllerRef.current = abort
+ const writer = port.writable.getWriter()
+ writerRef.current = writer
+ portRef.current = port
- let buffer = ''
- ;(async () => {
- try {
- while (true) {
- const { value, done } = await reader.read()
- if (done) break
- if (value) {
- buffer += value
- let index
- while ((index = buffer.indexOf('\n')) >= 0) {
- const line = buffer.slice(0, index).replace(/\r$/, '')
- buffer = buffer.slice(index + 1)
- lineHandlerRef.current?.(line)
+ setState((s) => ({ ...s, port, isConnected: true }))
+
+ const abort = new AbortController()
+ abortControllerRef.current = abort
+
+ let buffer = ''
+ ;(async () => {
+ try {
+ while (true) {
+ const { value, done } = await reader.read()
+ if (done) break
+ if (value) {
+ buffer += value
+ let index
+ while ((index = buffer.indexOf('\n')) >= 0) {
+ const line = buffer.slice(0, index).replace(/\r$/, '')
+ buffer = buffer.slice(index + 1)
+ lineHandlerRef.current?.(line)
+ }
}
}
- }
- } catch {
- // ignore if aborted
- } finally {
- try {
- reader.releaseLock()
} catch {
- // ignore
+ // ignore if aborted
+ } finally {
+ try {
+ reader.releaseLock()
+ } catch {
+ // ignore
+ }
+ try {
+ await readableClosed.catch(() => {})
+ } catch {
+ // ignore
+ }
+ setState((s) => ({ ...s, readerLocked: false }))
}
+ })()
+ } else if (config.encoding === 'cobs-f32') {
+ const reader = port.readable.getReader()
+ readerRef.current = reader
+
+ const writer = port.writable.getWriter()
+ writerRef.current = writer
+ portRef.current = port
+
+ setState((s) => ({ ...s, port, isConnected: true }))
+
+ const abort = new AbortController()
+ abortControllerRef.current = abort
+
+ let buffer = [];
+ (async () => {
try {
- await readableClosed.catch(() => {})
- } catch {
- // ignore
+ while (true) {
+ const { value, done } = await reader.read()
+ if (done) break
+ if (value) {
+ for (const byte of value) {
+ if (byte === 0x00) {
+ // End of COBS packet
+ if (buffer.length > 0) {
+ try {
+ const decoded = decodeCOBS(buffer)
+ const converted = new Float32Array(decoded.buffer, 0, Math.floor(decoded.byteLength/4));
+
+ // disgusting hack because I don't feel like refactoring stuff right now
+ const line = converted.map((x: any)=>x.toString()).join(' ');
+ lineHandlerRef.current?.(line);
+
+ } catch (err) { // catch COBS decoding errors
+ if (err instanceof COBSDecoderError) {
+ console.log(err);
+ } else {
+ throw err;
+ }
+ }
+
+ buffer = [] // reset for next packet
+ }
+ } else {
+ buffer.push(byte)
+ }
+ }
+ }
+ }
+ } catch (e) {
+ if (e instanceof Error && e.name === "AbortError") {
+ // ignore if aborted
+ } else {
+ throw e;
+ }
+ } finally {
+ try {
+ reader.releaseLock()
+ } catch {
+ // ignore
+ }
+ setState((s) => ({ ...s, readerLocked: false }))
}
- setState((s) => ({ ...s, readerLocked: false }))
- }
- })()
+ })()
+ }
} catch (err) {
const message = err instanceof Error ? err.message : 'Failed to connect.'
setState((s) => ({ ...s, error: message }))
diff --git a/src/utils/cobsDecoder.ts b/src/utils/cobsDecoder.ts
new file mode 100644
index 0000000..de19d0c
--- /dev/null
+++ b/src/utils/cobsDecoder.ts
@@ -0,0 +1,32 @@
+export class COBSDecoderError extends Error {
+ constructor(message?: string) {
+ super(message);
+ this.name = "COBSDecoderError";
+ }
+}
+
+
+export function decodeCOBS(data: Array) {
+ const output: Array = []
+ let i = 0
+
+ while (i < data.length) {
+ const code = data[i]
+ if (code === 0 || i + code > data.length + 1) {
+ throw new COBSDecoderError("Invalid COBS data")
+ }
+
+ const nextBlockEnd = i + code
+ for (let j = i + 1; j < nextBlockEnd && j < data.length; j++) {
+ output.push(data[j])
+ }
+
+ if (code < 0xFF && nextBlockEnd < data.length) {
+ output.push(0x00)
+ }
+
+ i = nextBlockEnd
+ }
+
+ return Uint8Array.from(output)
+}
\ No newline at end of file
From 7e1fe30058d179f1514d5c7e2e5c7c58092a435f Mon Sep 17 00:00:00 2001
From: SJ7F <235936019+SJ7F@users.noreply.github.com>
Date: Wed, 29 Oct 2025 21:36:20 -0400
Subject: [PATCH 4/5] Update roadmap to reflect COBS support
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 866d3b9..868e467 100644
--- a/README.md
+++ b/README.md
@@ -237,7 +237,7 @@ Firefox and Safari do not support Web Serial at this time. On desktop, use Chrom
**Potential Future Enhancements:**
- Session save/load (plot configurations and data)
-- Binary protocol support (CBOR/SLIP/COBS)
+- More binary protocol support (CBOR/SLIP) (already supports COBS)
- Advanced data processing filters (moving average, FFT)
- Cursor/crosshair tools for precise measurements
- Keyboard navigation and accessibility improvements
From 6f890ce49a1e2a304314177f7adb72e3bd742c4d Mon Sep 17 00:00:00 2001
From: SJ7F <235936019+SJ7F@users.noreply.github.com>
Date: Wed, 29 Oct 2025 21:43:37 -0400
Subject: [PATCH 5/5] Fix lint issue
For some reason VSCode complains about my fix
---
src/hooks/useSerial.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/hooks/useSerial.ts b/src/hooks/useSerial.ts
index a8d2f74..c17f346 100644
--- a/src/hooks/useSerial.ts
+++ b/src/hooks/useSerial.ts
@@ -191,7 +191,7 @@ export function useSerial(): UseSerial {
const converted = new Float32Array(decoded.buffer, 0, Math.floor(decoded.byteLength/4));
// disgusting hack because I don't feel like refactoring stuff right now
- const line = converted.map((x: any)=>x.toString()).join(' ');
+ const line = converted.map((x: number)=>x.toString()).join(' ');
lineHandlerRef.current?.(line);
} catch (err) { // catch COBS decoding errors