94 lines
2.5 KiB
TypeScript
94 lines
2.5 KiB
TypeScript
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<WorkerMessage>) => {
|
|
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<void> {
|
|
if (this.#closed) throw new Error("Input is closed")
|
|
|
|
if (this.#lastValue === targetValue) return
|
|
|
|
return new Promise((resolve, reject) => {
|
|
let timeoutId: ReturnType<typeof setTimeout> | 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()
|
|
}
|
|
}
|