npm package: @oahl/adapter-arduino
Description: OAHL adapter for Arduino Uno — digital I/O, analog read, PWM, servo, I2C, SPI, EEPROM, and serial messaging over USB.
Latest version: 0.1.1

# @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

```bash
npm install @oahl/adapter-arduino
```

---

## Communication 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`

```json
{
  "adapters": [
    {
      "id": "arduino-adapter",
      "module": "@oahl/adapter-arduino"
    }
  ]
}
```

### Use directly in code

```ts
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.

```ts
await adapter.execute(deviceId, 'digital.write', { pin: 13, value: 1 });
```

#### `digital.read`
Read a pin's current value.

```ts
const { value } = await adapter.execute(deviceId, 'digital.read', { pin: 7 });
```

#### `digital.write_bulk`
Write multiple pins at once.

```ts
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).

```ts
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).

```ts
await adapter.execute(deviceId, 'pwm.write', { pin: 6, value: 128 }); // 50% duty cycle
```

#### `pwm.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°).

```ts
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.

```ts
const { addresses, hex } = await adapter.execute(deviceId, 'i2c.scan', {});
// { addresses: [60], hex: ['0x3c'] }
```

#### `i2c.write`
Write bytes to a device.

```ts
await adapter.execute(deviceId, 'i2c.write', {
  address: 0x3C, register: 0x00, bytes: [0xAE]
});
```

#### `i2c.read`
Read bytes from a device.

```ts
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.

```ts
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`
```ts
const { bytes } = await adapter.execute(deviceId, 'eeprom.read', { address: 0, length: 4 });
```

#### `eeprom.write`
```ts
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

```bash
npm install
npm run build
```

## Publish

```bash
npm publish --access public
```

---

## License

Apache-2.0