From b841b2641c5a8a08ce41221c50966ed12cd782c2 Mon Sep 17 00:00:00 2001 From: R2m1liA <15258427350@163.com> Date: Wed, 24 Dec 2025 15:01:38 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0SerialProvider?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - SerialProvider用于为串口连接提供context --- src/lib/serial/index.ts | 2 + src/lib/serial/serial.providers.svelte | 103 +++++++++++++++++++++++ src/lib/serial/serial.service.ts | 108 +++++++++++++++++++++++++ src/lib/serial/serial.store.ts | 13 +++ src/lib/serial/serial.types.ts | 24 ++++++ src/routes/+layout.svelte | 6 +- 6 files changed, 255 insertions(+), 1 deletion(-) create mode 100644 src/lib/serial/index.ts create mode 100644 src/lib/serial/serial.providers.svelte create mode 100644 src/lib/serial/serial.service.ts create mode 100644 src/lib/serial/serial.store.ts create mode 100644 src/lib/serial/serial.types.ts diff --git a/src/lib/serial/index.ts b/src/lib/serial/index.ts new file mode 100644 index 0000000..dba1c5e --- /dev/null +++ b/src/lib/serial/index.ts @@ -0,0 +1,2 @@ +export * from './serial.service'; +export * from './serial.types'; diff --git a/src/lib/serial/serial.providers.svelte b/src/lib/serial/serial.providers.svelte new file mode 100644 index 0000000..c630296 --- /dev/null +++ b/src/lib/serial/serial.providers.svelte @@ -0,0 +1,103 @@ + + +{@render children?.()} diff --git a/src/lib/serial/serial.service.ts b/src/lib/serial/serial.service.ts new file mode 100644 index 0000000..5f77530 --- /dev/null +++ b/src/lib/serial/serial.service.ts @@ -0,0 +1,108 @@ +// src/lib/serial/serial.service.ts +// Web Serial API implementation +// Requires: @types/w3c-web-serial + +/** + * 内部IO资源 + */ +let reader: ReadableStreamDefaultReader | null = null; +let writer: WritableStreamDefaultWriter | null = null; + +let isOpen = false; + +/** + * 判断当前环境是否支持Web Serial API + */ +export function isWebSerialSupported(): boolean { + return typeof navigator !== 'undefined' && 'serial' in navigator; +} + +/** + * 请求用户选择串口设备 + */ +export async function requestPort( + filters?: SerialPortRequestOptions['filters'] +): Promise { + if (!isWebSerialSupported()) { + throw new Error('Web Serial API is not supported in this environment.'); + } + + const port = await navigator.serial.requestPort( + filters ? { filters } : undefined + ); + + return port ?? null; +} + +/** + * 打开串口 + */ +export async function openPort( + port: SerialPort, + options: SerialOptions +): Promise { + if (isOpen) { + // 串口已经打开,直接返回 + return; + } + + await port.open(options); + isOpen = true; + + // 初始化reader和writer + reader = port.readable?.getReader() ?? null; + writer = port.writable?.getWriter() ?? null; +} + +/** + * 关闭串口 + */ +export async function closePort(port: SerialPort) { + if (!isOpen) { + // 串口未打开,直接返回 + return; + } + + await reader?.cancel(); + reader?.releaseLock(); + await writer?.close(); + writer?.releaseLock(); + + reader = null; + writer = null; + isOpen = false; + + await port.close(); +} + +/** + * 向串口写入数据 + */ +export async function write(data: Uint8Array): Promise { + if (!writer) { + throw new Error('Serial port is not writable.'); + } + await writer.write(data); +} + +/** + * 开始读取串口数据循环 + */ +export async function startReadLoop( + onData: (chunk: Uint8Array) => void, + onError?: (err: unknown) => void +): Promise { + if (!reader) { + throw new Error('Serial port is not readable.'); + } + + try { + while (true) { + const { value, done } = await reader.read(); + if (done) break; + if (value) onData(value); + } + } catch (err) { + onError?.(err); + } +} diff --git a/src/lib/serial/serial.store.ts b/src/lib/serial/serial.store.ts new file mode 100644 index 0000000..23a1cbb --- /dev/null +++ b/src/lib/serial/serial.store.ts @@ -0,0 +1,13 @@ +import { writable } from 'svelte/store'; +import { createContext } from 'svelte'; +import type { SerialState, SerialContext } from './serial.types'; + +export const serialState = writable({ + port: null, + portName: null, + status: 'idle', + error: undefined, +}); + +export const [getSerialContext, setSerialContext] = + createContext(); diff --git a/src/lib/serial/serial.types.ts b/src/lib/serial/serial.types.ts new file mode 100644 index 0000000..393dace --- /dev/null +++ b/src/lib/serial/serial.types.ts @@ -0,0 +1,24 @@ +import type { Readable } from 'svelte/store'; + +export type SerialStatus = + | 'idle' + | 'requesting' + | 'connecting' + | 'connected' + | 'disconnecting' + | 'error'; + +export type SerialState = { + port: SerialPort | null; + portName: string | null; + status: SerialStatus; + error?: string; +}; + +export type SerialContext = { + state: Readable; + requestPort: () => Promise; + openPort: (options: SerialOptions) => Promise; + reopenPort: (options: SerialOptions) => Promise; + closePort: () => Promise; +}; diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index e801ec7..b200ce1 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,9 +1,13 @@ -{@render children()} + + + {@render children()} +