# Node.js SDK

The Node.js SDK is for TypeScript or JavaScript device programs. The package uses `@device-agent/device-sdk` and `BaseDevice` to handle MQTT connection, command subscription, command responses, state reports, and event reports. Add real device behavior in `src/device.ts`.

## Use Cases

- TypeScript/JavaScript is used for device-side, gateway, or edge services.
- The device needs an existing Node.js service, HTTP API, database, or business system.
- Claude Code, Cursor, or Codex will continue the device-side implementation.

## Package Contents

| File | Purpose |
| --- | --- |
| `src/index.ts` | Startup entry point that loads `.env` and creates the device instance |
| `src/device.ts` | Device logic entry point that extends `BaseDevice` |
| `device-spec.json` | Current DeviceSpec, used for command validation and state fields |
| `packages/device-sdk` | Local copy of the device-side SDK with `BaseDevice`, voice, and vision clients |
| `AGENTS.md` / `CLAUDE.md` | Context for local AI coding tools |

For a real device, you mainly work in `src/device.ts`: handle commands, call hardware or business services, update state, and report events.

## Access Steps

1. Download the Node.js SDK package.
2. Copy `.env.example` to `.env` and update MQTT broker or credentials if needed.
3. Install dependencies and start the program.
4. Replace the default behavior in `src/device.ts` with real device logic.
5. Return to the Device Agent workspace, confirm the device is online, and test a control command.

```bash
cp .env.example .env
npm install
npm run start
```

## Implement Device Logic

The device class in `src/device.ts` extends `BaseDevice`. After connection, it publishes a state snapshot. Commands enter `handleCommand()`. Call hardware interfaces or business services here:

```ts
protected override async handleCommand(command: DeviceCommandMessage) {
  if (command.cmd === "set_temperature") {
    const target = Number(command.params?.target_temperature);

    await thermostatClient.setTargetTemperature(target);
    this.patchState({ target_temperature: target });
    await this.publishStateSnapshot();

    return { code: 0, msg: "ok", data: { target_temperature: target } };
  }

  return { code: 404, msg: `Unknown command: ${command.cmd}` };
}
```

`BaseDevice` handles MQTT connection, command subscription, response publishing, and state reporting. Keep command names, parameter names, and state fields aligned with `device-spec.json`.

After state changes, call `publishStateSnapshot()`. To report an event, use an event already defined in the DeviceSpec:

```ts
await this.sendEvent("temperature_alarm", {
  current_temperature: 32.5,
  level: "warning",
});
```

## Voice Access Code

`VoiceClient` in the Node.js SDK is for device-side voice conversations. The device connects to `/ws/voice`, sends 16 kHz mono Int16LE PCM audio, and listens for ASR text, agent replies, and TTS audio events.

```ts
import { VoiceClient } from "@device-agent/device-sdk";

const voice = new VoiceClient({
  wsUrl: "ws://127.0.0.1:3001/ws/voice",
  deviceId: "device-001",
  productId: "agent-001",
});

voice.on("agentReply", (text) => console.log(text));

await voice.connect();
voice.startListening("manual");
voice.sendAudio(pcmChunk);
voice.stopListening();
```

`packages/device-sdk/examples/voice-chat.ts` is the complete example. For a real device, connect microphone capture and speaker playback to `sendAudio()` and TTS events.

## Vision Recognition Code

The Node.js toolkit derives `/api/vision/frames` and `/api/chat` from `VOICE_CHAT_HOST`. When the DeviceSpec contains one of these commands, `src/device.ts` runs the photo recognition flow:

- `capture_and_recognize`
- `take_photo_vision`
- `vision_recognize`
- `photo_identify`

The default flow checks `imageDataUrl` and `imageBase64` in command parameters, then `VISION_FALLBACK_IMAGE_DATA_URL` in `.env`. For a real device, override `captureLocalVisionImage()` to read one image from a camera, screenshot, or image file.

```ts
protected override async captureLocalVisionImage() {
  return {
    mimeType: "image/jpeg",
    imageBase64: await readCameraFrameAsBase64(),
    source: "sdk-camera",
  };
}
```

The device uploads the image, calls `/api/chat` with `visionRefs`, and returns the recognition result as the command response. This is command-triggered single-frame recognition, not continuous video streaming.

## Continue with a Local AI Coding Tool

Node.js packages include `AGENTS.md`, `CLAUDE.md`, and device implementation guidance. Open the package folder in Claude Code, Cursor, or Codex and ask it to continue `src/device.ts` based on `device-spec.json`.

## Verify Access

After the program starts, return to the Device Agent workspace and confirm:

1. The device appears in the device list and is online.
2. Current state shows fields reported by the Node.js program.
3. A conversation command executes the logic in `handleCommand()`.
4. If `sendEvent()` is called, the event appears in recent events.
