@oahl/adapter-arduino
v0.1.1
Published
OAHL adapter for Arduino Uno — digital I/O, analog read, PWM, servo, I2C, SPI, EEPROM, and serial messaging over USB.
Maintainers
Readme
@oahl/adapter-arduino
OAHL adapter for Arduino Uno. Exposes digital I/O, analog reading, PWM output, servo control, I2C, SPI, EEPROM, and raw serial messaging to AI agents — over USB via Serial or Firmata protocol.
Prerequisites
- Node.js >= 18
- Arduino Uno connected via USB
- Either:
- A custom sketch uploaded that handles serial commands (for Serial mode)
- StandardFirmata uploaded (for Firmata mode — Tools → Manage Libraries → search "Firmata" → upload StandardFirmata example)
Installation
npm install @oahl/adapter-arduinoCommunication Modes
Every capability has a mode argument: "serial" or "firmata".
| Mode | How it works | Requires |
|---|---|---|
| firmata | Structured Firmata protocol via johnny-five | StandardFirmata sketch on the board |
| serial | Raw string commands over serial | Custom sketch that parses the command format below |
Firmata is the default and recommended for most use cases — no custom sketch needed.
Serial Command Format (for custom sketches)
When using mode: "serial", commands follow this pattern:
| Command | Meaning |
|---|---|
| DW:<pin>:<0\|1> | Digital write |
| DR:<pin> | Digital read |
| AR:<pin> | Analog read |
| PW:<pin>:<0-255> | PWM write |
| SV:<pin>:<angle> | Servo write |
| PM:<pin>:<mode> | Set pin mode |
| I2C:SCAN | Scan I2C bus |
| I2C:W:<addr>:<reg>:<bytes> | I2C write |
| I2C:R:<addr>:<reg>:<len> | I2C read |
| SPI:<cs>:<bytes> | SPI transfer |
| EE:R:<addr>:<len> | EEPROM read |
| EE:W:<addr>:<bytes> | EEPROM write |
Usage
Register in oahl-config.json
{
"adapters": [
{
"id": "arduino-adapter",
"module": "@oahl/adapter-arduino"
}
]
}Use directly in code
import ArduinoAdapter from '@oahl/adapter-arduino';
const adapter = new ArduinoAdapter({ baudRate: 9600 });
await adapter.initialize();
const devices = await adapter.getDevices();
// [{ id: '/dev/ttyACM0', type: 'arduino-uno', name: 'Arduino Uno @ /dev/ttyACM0' }]
const deviceId = devices[0].id;
// Blink the built-in LED (pin 13) 3 times
await adapter.execute(deviceId, 'digital.blink', { pin: 13, times: 3, onMs: 500 });
// Read a potentiometer on A0
const reading = await adapter.execute(deviceId, 'analog.read', { pin: 0 });
console.log(reading.voltage); // e.g. 2.47
// Set a servo to 90 degrees
await adapter.execute(deviceId, 'servo.write', { pin: 9, angle: 90 });Capabilities
Connection
serial.connect
Open the serial port. Called automatically by all other capabilities, but useful to pre-warm the connection.
| Arg | Type | Default | Description |
|---|---|---|---|
| baudRate | integer | 9600 | Serial baud rate |
| waitMs | integer | 2000 | Wait after open (for Arduino reset) |
serial.disconnect
Close the serial port.
firmata.connect
Connect via Firmata protocol. Requires StandardFirmata on the board.
Serial Messaging
serial.send
Send a raw string to the Arduino.
| Arg | Type | Default | Description |
|---|---|---|---|
| message | string | — | String to send |
| awaitResponse | boolean | false | Wait for a response line |
| responseTimeoutMs | integer | 3000 | — |
serial.read
Read buffered lines from the Arduino's serial output.
| Arg | Type | Default | Description |
|---|---|---|---|
| lines | integer | 10 | Max lines to return |
| waitMs | integer | 200 | Wait time before reading |
| flush | boolean | false | Clear buffer after read |
serial.flush
Clear the receive buffer.
serial.send_command
Send a command and collect response lines.
| Arg | Type | Description |
|---|---|---|
| command | string | Command string |
| responseLines | integer | Lines to collect (default 1) |
| responseTimeoutMs | integer | Timeout (default 3000ms) |
Digital I/O
digital.write
Set a pin HIGH or LOW.
await adapter.execute(deviceId, 'digital.write', { pin: 13, value: 1 });digital.read
Read a pin's current value.
const { value } = await adapter.execute(deviceId, 'digital.read', { pin: 7 });digital.write_bulk
Write multiple pins at once.
await adapter.execute(deviceId, 'digital.write_bulk', {
pins: [{ pin: 8, value: 1 }, { pin: 9, value: 0 }]
});digital.read_all
Read all 14 digital pins simultaneously.
digital.pulse
Pulse a pin HIGH for a duration then back LOW.
| Arg | Type | Default |
|---|---|---|
| pin | integer | — |
| durationMs | integer | 100 |
digital.blink
Blink a pin N times.
| Arg | Type | Default |
|---|---|---|
| pin | integer | — |
| times | integer | 3 |
| onMs | integer | 500 |
| offMs | integer | 500 |
Analog Read
analog.read
Read an analog pin. Returns raw (0-1023) and voltage (0-5V).
const { raw, voltage } = await adapter.execute(deviceId, 'analog.read', { pin: 0, samples: 5 });| Arg | Type | Default | Description |
|---|---|---|---|
| pin | integer | — | Analog pin 0-5 (A0-A5) |
| samples | integer | 1 | Readings to average |
| intervalMs | integer | 50 | Between samples |
analog.read_all
Read all 6 analog pins at once.
analog.read_stream
Sample a pin continuously for a duration and return all readings with timestamps.
| Arg | Type | Default |
|---|---|---|
| pin | integer | — |
| durationMs | integer | 1000 |
| intervalMs | integer | 100 |
PWM Output
PWM-capable pins on the Uno: 3, 5, 6, 9, 10, 11
pwm.write
Write a duty cycle value (0–255).
await adapter.execute(deviceId, 'pwm.write', { pin: 6, value: 128 }); // 50% duty cyclepwm.fade
Fade from one value to another over a duration.
| Arg | Type | Default |
|---|---|---|
| pin | integer | — |
| from | integer | 0 |
| to | integer | 255 |
| durationMs | integer | 1000 |
| steps | integer | 50 |
servo.write
Set a servo angle (0–180°).
await adapter.execute(deviceId, 'servo.write', { pin: 9, angle: 90 });servo.sweep
Sweep a servo back and forth between two angles.
| Arg | Type | Default |
|---|---|---|
| pin | integer | — |
| from | integer | 0 |
| to | integer | 180 |
| sweeps | integer | 1 |
| stepMs | integer | 15 |
I2C
I2C uses pins A4 (SDA) and A5 (SCL) on the Uno.
i2c.scan
Scan bus and return all responding addresses.
const { addresses, hex } = await adapter.execute(deviceId, 'i2c.scan', {});
// { addresses: [60], hex: ['0x3c'] }i2c.write
Write bytes to a device.
await adapter.execute(deviceId, 'i2c.write', {
address: 0x3C, register: 0x00, bytes: [0xAE]
});i2c.read
Read bytes from a device.
const { bytes } = await adapter.execute(deviceId, 'i2c.read', {
address: 0x68, register: 0x3B, length: 6
});SPI
SPI pins on Uno: 10 (SS), 11 (MOSI), 12 (MISO), 13 (SCK)
spi.transfer
Send bytes and receive simultaneously. Requires a sketch that handles SPI:<cs>:<bytes> commands.
const { received } = await adapter.execute(deviceId, 'spi.transfer', {
bytes: [0x9F], csPin: 10
});EEPROM
The Uno has 1024 bytes of EEPROM (addresses 0–1023).
eeprom.read
const { bytes } = await adapter.execute(deviceId, 'eeprom.read', { address: 0, length: 4 });eeprom.write
await adapter.execute(deviceId, 'eeprom.write', { address: 0, bytes: [72, 101, 108, 108] });eeprom.clear
Writes 0xFF to all 1024 bytes.
Pin Management
pin.mode
Set a pin mode: INPUT, OUTPUT, INPUT_PULLUP, ANALOG, PWM.
pin.info
Return capabilities for a specific pin.
pin.list
Return all pins with their labels and capability flags.
Board
board.reset
Toggle DTR to trigger a hardware reset.
board.info
Return port, baud rate, connection mode, Firmata version, and pin counts.
board.ping
Send a message and wait for the echo. Useful to confirm the sketch is running.
Build
npm install
npm run buildPublish
npm publish --access publicLicense
Apache-2.0
