import type { InputEvent, InputOptions } from "./types" type WorkerMessage = | { type: "ready"; initialValue: 0 | 1 } | { type: "event"; value: 0 | 1; timestamp: bigint } | { type: "error"; message: string } export class Input { #worker: Worker #callbacks = new Set<(event: InputEvent) => void>() #closed = false #lastValue: 0 | 1 = 0 constructor(chipPath: string, offset: number, options: InputOptions = {}) { const pull = options.pull ?? "up" const debounce = options.debounce ?? 0 const edge = options.edge ?? "both" this.#worker = new Worker(new URL("./input-worker.ts", import.meta.url).href) this.#worker.onmessage = (msg: MessageEvent) => { if (this.#closed) return const data = msg.data if (data.type === "ready") { this.#lastValue = data.initialValue } else if (data.type === "event") { this.#lastValue = data.value for (const callback of this.#callbacks) { callback({ value: data.value, timestamp: data.timestamp }) } } else if (data.type === "error") { console.error(`GPIO Input Error (pin ${offset}):`, data.message) } } this.#worker.postMessage({ chipPath, offset, pull, debounce, edge }) } get value(): 0 | 1 { if (this.#closed) throw new Error("Input is closed") return this.#lastValue } onChange(callback: (event: InputEvent) => void): () => void { if (this.#closed) throw new Error("Input is closed") this.#callbacks.add(callback) return () => this.#callbacks.delete(callback) } async waitForValue(targetValue: 0 | 1, timeout?: number): Promise { if (this.#closed) throw new Error("Input is closed") if (this.#lastValue === targetValue) return return new Promise((resolve, reject) => { let timeoutId: ReturnType | undefined const cleanup = () => { if (timeoutId) clearTimeout(timeoutId) unsubscribe() } const unsubscribe = this.onChange((event) => { if (event.value === targetValue) { cleanup() resolve() } }) if (!timeout) return timeoutId = setTimeout(() => { cleanup() reject(new Error(`Timeout waiting for value ${targetValue}`)) }, timeout) }) } close() { if (this.#closed) return this.#closed = true this.#callbacks.clear() this.#worker.onmessage = null this.#worker.terminate() } [Symbol.dispose]() { this.close() } }