# Node.js SDK

Node.js SDK 适合 TypeScript 或 JavaScript 设备程序。代码包基于 `@device-agent/device-sdk` 和 `BaseDevice`，已处理 MQTT 连接、命令订阅、命令响应、状态上报和事件上报；真实设备逻辑在 `src/device.ts` 中补充。

## 适用场景

- 使用 TypeScript/JavaScript 开发设备端、网关或边缘服务。
- 需要接入已有 Node.js 服务、HTTP API、数据库或业务系统。
- 希望用 Claude Code、Cursor 或 Codex 继续完善设备端逻辑。

## 代码包内容

| 内容 | 作用 |
| --- | --- |
| `src/index.ts` | 启动入口，读取 `.env` 并创建设备实例 |
| `src/device.ts` | 设备逻辑入口，继承 `BaseDevice` |
| `device-spec.json` | 当前设备规格，作为命令校验和状态字段依据 |
| `packages/device-sdk` | 本地复制的设备端 SDK，提供 `BaseDevice`、语音和视觉客户端 |
| `AGENTS.md` / `CLAUDE.md` | 本地 AI 编程工具可以读取的实现上下文 |

真实接入时，修改 `src/device.ts`：处理命令、调用硬件或业务服务、更新状态，并在需要时上报事件。

## 接入步骤

1. 下载 Node.js SDK 代码包。
2. 复制 `.env.example` 为 `.env`，按需替换 MQTT 地址和认证信息。
3. 安装依赖并启动程序。
4. 在 `src/device.ts` 中把默认逻辑替换为真实设备逻辑。
5. 回到设备智能体工作区，确认设备上线并测试控制命令。

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

## 实现设备逻辑

`src/device.ts` 中的设备类继承 `BaseDevice`。设备连接成功后会发布状态快照；命令会进入 `handleCommand()`。在这里调用硬件接口或业务服务：

```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` 会负责 MQTT 连接、命令订阅、响应发布和状态上报。命令名、参数名和状态字段需要与 `device-spec.json` 对齐。

状态变化后调用 `publishStateSnapshot()`，设备智能体就能看到最新数据。需要上报事件时，只上报设备规格中已经定义的事件：

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

## 语音接入代码

Node.js SDK 中的 `VoiceClient` 用于设备端语音对话。设备连接 `/ws/voice`，发送 16 kHz 单声道 Int16LE PCM 音频，并监听识别文本、智能体回复和 TTS 音频事件。

```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` 是完整示例。真实设备需要把麦克风采集和扬声器播放接到 `sendAudio()` 和 TTS 事件上。

## 视觉识别代码

Node.js 代码包会根据 `VOICE_CHAT_HOST` 派生 `/api/vision/frames` 和 `/api/chat`。当设备规格中存在以下命令时，`src/device.ts` 会走拍照识别流程：

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

默认流程会先读取命令里的 `imageDataUrl`、`imageBase64`，再读取 `.env` 中的 `VISION_FALLBACK_IMAGE_DATA_URL`。真实设备可以覆写 `captureLocalVisionImage()`，从摄像头、截图或图像文件读取一张图片。

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

设备端会上传这张图片，调用 `/api/chat` 并携带 `visionRefs`，然后把识别结果作为命令响应返回。这适合命令触发的单帧识别，不是连续视频流。

## 使用本地 AI 编程工具继续开发

Node.js 代码包会包含 `AGENTS.md`、`CLAUDE.md` 和设备实现说明。可以在代码包目录中打开 Claude Code、Cursor 或 Codex，让它根据 `device-spec.json` 继续实现 `src/device.ts`。

## 验证接入

启动程序后，回到设备智能体工作区确认：

1. 设备列表中出现这台设备，并且状态为在线。
2. 当前数据能看到 Node.js 程序上报的字段。
3. 通过对话下发命令后，`handleCommand()` 中的逻辑被执行。
4. 如果调用了 `sendEvent()`，最近上报事件中能看到对应记录。
