@kog/hardware
The imperative hardware layer. The hardware hooks are thin reactive wrappers over these APIs; use this layer directly for full control or outside components.
All pin parameters accept PinLike = number | Pin. Plain numbers are native SoC GPIOs; Pin objects allow future controllers (GPIO expanders) without API changes.
gpio
gpio.configure(pin, { mode, pull?, driveStrength?, openDrain? }): void
gpio.read(pin): boolean
gpio.write(pin, level: boolean): void
gpio.watch(pin, { edge: 'rising'|'falling'|'both', debounceMs? }, cb): () => void
Watches are interrupt-driven with native debounce. The returned function unsubscribes.
adc
adc.read(pin, { attenuation?, samples? }): number // calibrated mV
adc.stream(pin, { hz }, cb: (mv: number) => void): () => void
pwm
const ch = pwm.channel(pin, { freqHz, resolutionBits? });
ch.setDuty(duty: number): void // 0..1
ch.stop(): void
Channels sharing a frequency+resolution share hardware timers automatically; exhaustion throws with the allocation state in the message.
i2c
const bus = await i2c.open({ sda, scl, hz? });
await bus.readReg(addr, reg, len): Uint8Array
await bus.writeReg(addr, reg, data): void
await bus.read(addr, len): Uint8Array
await bus.write(addr, data): void
await bus.scan(): number[]
bus.close(): void
spi
const dev = await spi.open({ mosi, miso, sclk, cs?, hz, mode? });
await dev.transfer(tx: Uint8Array): Uint8Array
dev.close(): void
All bus transactions execute on a native worker task and return Promises — they can never block rendering or input.
Errors
Hardware errors throw (or reject) with structured, actionable messages: the owning peripheral for reserved pins (GPIO 21 is reserved by the display (RGB DE)), free-resource lists on exhaustion, and platform constraints (input-only pins, strapping-pin warnings, ADC2-with-Wi-Fi) called out by name.