Skill: senserobotclawbot/senserobot (version 1.1.2)

--- name: SenseRobot 元萝卜机器人 description: 用户可以让 OpenClaw 控制元萝卜下棋机器人进行围棋取落子、机械臂移动、表情控制、语音播报、棋盘管理时使用此技能。触发词包括"元萝卜"、"下棋机器人"、"机械臂控制"。 homepage: www.SenseRobotChess.com metadata: {"clawdbot":{"emoji":"🤖","requires":{"bins":["curl"],"env":[]},"primaryEnv":""}} --- # 元萝卜下棋机器人 SKILL 你连接了一台"元萝卜下棋机器人"，它具有实体围棋棋盘、棋子、左右两个棋盒，棋盘和棋盒在同一个平面上。机器人有一个 SCARA 机械臂，可以通过指令操作机械臂，从棋盒取子，在棋盘上指定位置落子。 ## !! 最高优先级行为规则 !! **你必须严格遵守以下规则，不得违反：** 1. **取子失败必须重试。** 从棋盒取子时，如果取子不成功，请使用缓存中的下一个棋子位置进行重试。 2. **使用本 SKILL 提供的指令控制机器人。** 通过使用文档中定义的 HTTP API 组合，让机器人完成复杂任务。 3. **必须先吸子成功再落子。** 只有当 `skill-move-tcp` 执行 `action=1` 返回成功（`"result": "success"`）后，才允许执行 `action=2` 落子，否则会出现 `"result": "empty_put"`（未吸子却落子）。 ## 🔒 连接与安全说明 ### HTTP API - **基础地址**: `http://192.168.199.10:60010` ## 必需环境 执行任何操作前，必须确认： - 机器人已开机且网络可达（`192.168.199.10`） ## 📐 坐标系统 ### 棋盘坐标 (x, y) 注意，下面的“左右”和“上下”的定义，都是以使用者的视角来看的，对机器人自己来说则刚好相反。 | 位置 | 坐标 | |------|------| | 右上角 | (0, 0) | | 左下角 | (12, 12) | ### 棋盒坐标 (x, y) | 棋盒 | 右上角 | 左下角 | |------|--------|--------| | 右棋盒 | (-3.8, 0) | (-1.8, 7) | | 左棋盒 | (14.2, 0) | (15.8, 7) | ## 工作流 ### 流程一：取子并落子（最常见场景） ```bash # 1. 查找棋盒棋子 curl --location 'http://192.168.199.10:60010/skill-detect-box' # 2. 解析出目标棋子坐标后，移动并取子 (action=1) curl --location 'http://192.168.199.10:60010/skill-move-tcp?x=-3.1&y=1.5&action=1' # 3. 只有吸子成功后，才移动到目标位置并落子 (action=2) curl --location 'http://192.168.199.10:60010/skill-move-tcp?x=6&y=6&action=2' ``` ### 流程二：智能取子（CV检测 + 缓存重试） 1. 调用 `skill-detect-box` 获取所有棋子坐标缓存。 2. 遍历缓存中的坐标尝试取子。 3. 如果所有缓存坐标都尝试失败： - 语音提示用户"取子失败，请整理棋盒或放入新棋子"。 - 结束本次取子流程（不循环等待）。 ```bash # 1. 查找棋盒棋子 curl --location 'http://192.168.199.10:60010/skill-detect-box' # 2. 尝试取第一个 curl --location 'http://192.168.199.10:60010/skill-move-tcp?x=x1&y=y1&action=1' # 3. 失败则尝试取第二个 curl --location 'http://192.168.199.10:60010/skill-move-tcp?x=x2&y=y2&action=1' # 4. 全部失败，提示用户并重试 curl --location 'http://192.168.199.10:60010/skill-tts-chinese?content=取子失败，请整理棋盒或放入新棋子' # ... 结束 ``` ### 流程三：表情控制 ```bash # 显示特定表情 curl --location 'http://192.168.199.10:60010/skill-show-emotion?code=008' ``` 表情编号 code： | 编号 | 表情 | |------|------| | 002 | 快乐 | | 003 | 哭 | | 004 | 默认 | | 008 | 兴趣 | | 011 | 摇头轻快 | | 012 | 摇头否认 | | 013 | 消失 | | 014 | 出现 | ### 流程四：语音播报 ```bash curl --location 'http://192.168.199.10:60010/skill-tts-chinese?content=你好' ``` ### 流程五：清理棋盘 把棋盘上的子收回棋盒。由于清理过程可能需要几分钟，此接口为异步调用，需要通过轮询该接口并判断返回的 `result` 字段来获取清理进度（`running`、`done` 或 `error`）。 ```bash # 轮询调用，直到 result 变为 done 或 error curl --location 'http://192.168.199.10:60010/skill-clean-board' ``` ### 流程六：查找棋盒棋子 ```bash # 返回包含棋子坐标数组的 JSON 对象，其中 color 1=黑, 2=白 curl --location 'http://192.168.199.10:60010/skill-detect-box' ``` ### 流程七：查找棋盘棋子 ```bash # 返回包含棋盘上棋子坐标数组的 JSON 对象，其中 color 1=黑, 2=白 curl --location 'http://192.168.199.10:60010/skill-detect-board' ``` ### 流程八：拍照 ```bash # id: 0=前置, 1=右边, 2=左边 curl --location 'http://192.168.199.10:60010/skill-take-photo?id=0' ``` ### 流程九：录音 ```bash # 开始录音 curl --location 'http://192.168.199.10:60010/skill-record?code=0' # 结束录音 curl --location 'http://192.168.199.10:60010/skill-record?code=1' ``` ### 流程十：显示图片 ```bash curl --location 'http://192.168.199.10:60010/skill-show-image' --form 'image=@"/path/to/image.png"' ``` ## 控制指令速查表 ### HTTP API 指令 | 操作 | 方法 | URL | |------|------|-----| | 复位机械臂 | GET | `http://192.168.199.10:60010/skill-move-home` | | 取落子控制 | GET | `http://192.168.199.10:60010/skill-move-tcp?x=1&y=2&action=1` | | 语音播报 | GET | `http://192.168.199.10:60010/skill-tts-chinese?content=中文` | | 看棋盘状态 | GET | `http://192.168.199.10:60010/skill-look-board` | | 清理棋盘 | GET | `http://192.168.199.10:60010/skill-clean-board` | | 查找棋盒棋子 | GET | `http://192.168.199.10:60010/skill-detect-box` | | 查找棋盘棋子 | GET | `http://192.168.199.10:60010/skill-detect-board` | | 显示表情 | GET | `http://192.168.199.10:60010/skill-show-emotion?code=008` | | 拍照 | GET | `http://192.168.199.10:60010/skill-take-photo?id=0` | | 录音 | GET | `http://192.168.199.10:60010/skill-record?code=0` | | 停止录音 | GET | `http://192.168.199.10:60010/skill-record?code=1` | | 显示图片 | POST | `http://192.168.199.10:60010/skill-show-image` | ## 错误处理 ### 连接异常 | 错误 | 处理方法 | |------|----------| | HTTP API 无响应 | 确认机器人服务是否正常运行 | ## 关键注意事项 - 棋盘坐标范围：x ∈ [0, 12]，y ∈ [0, 12] - 取子失败时自动重试，使用缓存中的下一个位置重试

path: scripts/main.py
content: #!/usr/bin/env python3
"""元萝卜下棋机器人控制客户端"""

import argparse
import json
import random
import subprocess
import sys
import urllib.request
import urllib.parse

ROBOT_IP = "192.168.199.10"
API_BASE = f"http://{ROBOT_IP}:60010"


class RobotClient:
    """元萝卜机器人控制客户端"""

    def __init__(self, ip=ROBOT_IP):
        self.ip = ip
        self.api_base = f"http://{ip}:60010"
        
    # ── HTTP API ──

    def _api_get(self, path, params=None, binary=False):
        url = f"{self.api_base}{path}"
        if params:
            url += "?" + urllib.parse.urlencode(params, quote_via=urllib.parse.quote)
        print(f"📡 GET {url}")
        req = urllib.request.Request(url)
        with urllib.request.urlopen(req, timeout=30) as resp:
            data = resp.read()
            if binary:
                print(f"✅ 响应: [Binary Data] {len(data)} bytes")
                return data
            body = data.decode("utf-8")
            print(f"✅ 响应: {body[:500]}")
            return body

    def look_board(self):
        """查看当前棋盘状态"""
        return self._api_get("/skill-look-board")

    def move_home(self):
        """复位机械臂"""
        return self._api_get("/skill-move-home")

    def move_tcp(self, x, y, action):
        """取落子控制 (action: 0=移动 1=取子 2=落子)"""
        return self._api_get("/skill-move-tcp", {"x": x, "y": y, "action": action})

    def detect_box(self):
        """查找棋盒棋子的位置
        returns: list of pieces, e.g. [{"color":1,"x":-3.1,"y":1.5}] (color 1=黑, 2=白)
        """
        data = self._api_get("/skill-detect-box")
        try:
            res = json.loads(data)
            if res.get("ok") and res.get("result") == "success":
                return res.get("pieces", [])
        except json.JSONDecodeError:
            pass
        return []

    def detect_board(self):
        """查找棋盘棋子的位置
        returns: list of pieces, e.g. [{"color":1,"x":3,"y":4}] (color 1=黑, 2=白)
        """
        data = self._api_get("/skill-detect-board")
        try:
            res = json.loads(data)
            if res.get("ok") and res.get("result") == "success":
                return res.get("pieces", [])
        except json.JSONDecodeError:
            pass
        return []

    def clean_board(self):
        """清理棋盘 (异步调用，返回任务状态)
        返回包含 result (running/done/error) 和 detail 的 JSON 对象
        """
        data = self._api_get("/skill-clean-board")
        try:
            return json.loads(data)
        except json.JSONDecodeError:
            return {"result": "error", "detail": "Invalid JSON response"}

    def tts(self, content):
        """语音播报"""
        return self._api_get("/skill-tts-chinese", {"content": content})

    def show_emotion(self, code):
        """显示表情"""
        return self._api_get("/skill-show-emotion", {"code": code})

    def show_image(self, image_path):
        """显示图片"""
        # Using curl for multipart/form-data upload as it's simpler and more robust
        # than manual multipart construction in standard library without requests
        url = f"{self.api_base}/skill-show-image"
        print(f"📡 POST {url} image={image_path}")
        
        try:
            # Use curl to upload the file
            cmd = ['curl', '--location', url, '--form', f'image=@"{image_path}"']
            result = subprocess.run(cmd, capture_output=True, text=True, check=True)
            print(f"✅ 响应: {result.stdout[:500]}")
            return result.stdout
        except subprocess.CalledProcessError as e:
            print(f"❌ Curl failed: {e.stderr}")
            return ""

    def take_photo(self, camera_id):
        """拍照 (0=前置 1=右边 2=左边)"""
        return self._api_get("/skill-take-photo", {"id": camera_id}, binary=True)

    def record(self, code):
        """录音 (0=开始 1=结束)"""
        return self._api_get("/skill-record", {"code": code}, binary=(code == 1))


    # ── 复合操作 ──

    def pick_with_retry(self):
        """从棋盒智能取子（CV检测 + 缓存重试）"""
        pieces = self.detect_box()
        if not pieces:
            print("⚠️ 未检测到棋子")
            pieces = []

        cached_pieces = list(pieces)
        while cached_pieces:
            piece = cached_pieces.pop(0)  # 无论成功失败，都从缓存中移除该位置
            base_x = piece.get("x", 0)
            base_y = piece.get("y", 0)

            x = round(base_x, 3)
            y = round(base_y, 3)

            result = self.move_tcp(x, y, 1)
            try:
                res_json = json.loads(result)
                is_success = res_json.get("ok") and res_json.get("result") == "success"
            except json.JSONDecodeError:
                is_success = "0" in result or "ret:0" in result

            if is_success:
                print("✅ 取子成功")
                return True
            print("⚠️ 取子失败，使用缓存的下个位置重试...")

        print("❌ 取子失败，请整理棋盒或放入新棋子")
        self.tts("取子失败，请整理棋盒或放入新棋子")
        return False

    def place_stone(self, x, y):
        """完整落子流程：移动 → 落子"""
        print(f"🎯 落子到 ({x}, {y})")
        result = self.move_tcp(x, y, 2)
        try:
            res_json = json.loads(result)
            is_success = res_json.get("ok") and res_json.get("result") == "success"
        except json.JSONDecodeError:
            is_success = "0" in result or "ret:0" in result

        if is_success:
            print("✅ 落子完成")
            return True
        print(f"❌ 落子失败: {result.strip()}")
        return False


# ── CLI ──

def cmd_look(args):
    RobotClient().look_board()

def cmd_place(args):
    RobotClient().place_stone(args.x, args.y)

def cmd_clean(args):
    import time
    print("开始清理棋盘任务...")
    while True:
        res = RobotClient().clean_board()
        status = res.get("result", "error")
        detail = res.get("detail", "")
        
        if status == "running":
            print(f"⏳ {detail} (等待 10 秒后查询...)")
            time.sleep(10)
        elif status == "done":
            print(f"✅ 清理完成: {detail}")
            break
        else:
            print(f"❌ 清理异常: {detail}")
            break

def cmd_home(args):
    RobotClient().move_home()

def cmd_tts(args):
    RobotClient().tts(args.content)

def cmd_pick(args):
    RobotClient().pick_with_retry()

def cmd_expression(args):
    RobotClient().show_emotion(args.code)

def cmd_photo(args):
    data = RobotClient().take_photo(args.id)
    filename = f"photo_{args.id}.jpg"
    with open(filename, "wb") as f:
        f.write(data)
    print(f"✅ 照片已保存: {filename}")

def cmd_record(args):
    if args.action == "start":
        RobotClient().record(0)
    elif args.action == "stop":
        data = RobotClient().record(1)
        filename = "record.pcm"
        with open(filename, "wb") as f:
            f.write(data)
        print(f"✅ 录音已保存: {filename}")


def cmd_detect(args):
    client = RobotClient()
    if args.board:
        print(json.dumps(client.detect_board(), indent=2))
    else:
        print(json.dumps(client.detect_box(), indent=2))

def main():
    parser = argparse.ArgumentParser(description="元萝卜下棋机器人控制")
    sub = parser.add_subparsers(dest="command", required=True)

    sub.add_parser("look", help="查看棋盘状态")
    sub.add_parser("clean", help="清理棋盘")
    sub.add_parser("home", help="复位机械臂")

    p = sub.add_parser("place", help="落子")
    p.add_argument("x", type=float, help="横坐标")
    p.add_argument("y", type=float, help="纵坐标")

    p = sub.add_parser("tts", help="语音播报")
    p.add_argument("content", help="播报内容")

    p = sub.add_parser("expression", help="表情控制")
    p.add_argument("code", help="表情编号 (002=快乐 003=哭 004=默认 008=兴趣)")

    sub.add_parser("pick", help="从棋盒取子(智能取子)")

    p = sub.add_parser("photo", help="拍照")
    p.add_argument("id", type=int, choices=[0, 1, 2], help="摄像头ID (0=前置 1=右 2=左)")

    p = sub.add_parser("record", help="录音")
    p.add_argument("action", choices=["start", "stop"], help="start=开始 stop=结束并保存")

    p = sub.add_parser("detect", help="查找棋盒棋子")
    p.add_argument("--board", action="store_true", help="查找棋盘上的棋子而不是棋盒")
    
    p = sub.add_parser("show_image", help="显示图片")
    p.add_argument("path", help="图片路径 (仅支持PNG)")

    args = parser.parse_args()
    cmds = {
        "look": cmd_look, "place": cmd_place, "clean": cmd_clean,
        "home": cmd_home, "tts": cmd_tts, 
        "pick": cmd_pick,
        "expression": cmd_expression, "photo": cmd_photo, "record": cmd_record,
        "detect": cmd_detect,
        "show_image": lambda a: RobotClient().show_image(a.path)
    }
    cmds[args.command](args)


if __name__ == "__main__":
    main()


path: references/API_REFERENCE.md
content: # 元萝卜下棋机器人 API 参考文档

## 连接信息

- **IP 地址**: `192.168.199.10`
- **HTTP API 端口**: `60010`

---

## 📐 坐标系统

### 1. 棋盘坐标 (x, y)

| 位置 | 坐标 |
| :--- | :--- |
| **右上角** | `(0, 0)` |
| **左下角** | `(12, 12)` |

### 2. 棋盒坐标 (x, y)

| 棋盒 | 右上角 | 左下角 |
| :--- | :--- | :--- |
| **右棋盒** | `(-3.8, 0)` | `(-1.8, 7)` |
| **左棋盒** | `(14.2, 0)` | `(15.8, 7)` |

## 📡 指令控制方式

该机器人系统运行 Linux，内部搭建了 HTTP Server。你可以通过 `curl` 访问该元萝卜机器人，调用对应的控制指令。

- **基础地址**: `http://192.168.199.10:60010`

---

## 🛠️ 具体功能指令

### 1. 机械臂复位

将机械臂从棋盘上移开。

- **指令**:
  ```bash
  curl --location 'http://192.168.199.10:60010/skill-move-home'
  ```
- **参数**: 无
- **返回值**:
  ```json
  {
      "ok": true,
      "cmd": "skill-move-home",
      "result": "success|fail"
  }
  ```

### 2. 机械臂控制

操作机械臂到指定的 `x` 和 `y` 坐标位置去执行某一个 `action` 的动作。

- **指令**:
  ```bash
  curl --location 'http://192.168.199.10:60010/skill-move-tcp?x={x}&y={y}&action={action}'
  ```
- **参数**:
    - `x`: 横坐标
    - `y`: 纵坐标
    - `action`: 机械臂具体动作（`0`: 移动, `1`: 取子, `2`: 落子）
- **返回值**:
  ```json
  {
      "ok": true,
      "cmd": "skill-move-tcp",
      "result": "success|empty_put|catch_fail|full_catch|out_of_range|collision|fail_xxx",
      "x": 6,
      "y": 4,
      "action": 2
  }
  ```

### 3. 语音播报

控制机器人将 `content` 转为语音进行播放。

- **指令**:
  ```bash
  curl --location 'http://192.168.199.10:60010/skill-tts-chinese?content={content}'
  ```
- **参数**:
    - `content`: 仅限中文内容
- **返回值**:
  ```json
  {
      "ok": true,
      "cmd": "skill-tts-chinese",
      "result": "success|fail_empty_content"
  }
  ```

### 4. 看当前棋盘状态

进行棋盘分类和定位。**注意：在同一个棋盘下有且仅需要做一次。**

- **指令**:
  ```bash
  curl --location 'http://192.168.199.10:60010/skill-look-board'
  ```
- **参数**: 无
- **返回值**:
  ```json
  {
      "ok": true,
      "cmd": "skill-look-board",
      "result": "GO_BOARD_9" // GO_BOARD_19, GO_BOARD_13, GO_BOARD_9, CHINESE_CHECKERS, INTERNATIONAL_CHECKERS_8, INTERNATIONAL_CHECKERS_10, CHINESE_CHESS, INTERNATIONAL_CHESS, NIEDAO_BOARD_19, NIEDAO_BOARD_15, GAME_NONE, GAME_NUM
  }
  ```

### 5. 清理棋盘

把棋盘上的子收回棋盒。由于清理过程可能需要几分钟，此接口为异步调用，客户端需要通过轮询该接口来获取清理进度。

- **指令**:
  ```bash
  curl --location 'http://192.168.199.10:60010/skill-clean-board'
  ```
- **参数**: 无
- **返回值**:
  ```json
  {
      "ok": true,
      "cmd": "skill-clean-board",
      "started_at": "2024-03-19 12:00:00",
      "result": "running|done|error",
      "detail": "清理棋盘任务已开始，预计耗时约 3-5 分钟"
  }
  ```

### 6. 查找棋盒棋子的位置

使用 CV 能力从棋盒中准确检测棋子的位置。

- **指令**:
  ```bash
  curl --location 'http://192.168.199.10:60010/skill-detect-box'
  ```
- **参数**: 无
- **返回值**:
  ```json
  {
      "ok": true,
      "cmd": "skill-detect-box",
      "result": "success|fail_no_piece",
      "pieces": [
          {
              "color": 1,
              "x": -3.130963087081909,
              "y": 1.56145179271698
          }
      ]
  }
  ```
  *说明：`pieces` 数组中的 `color` 表示棋子颜色（1黑，2白），`x` 和 `y` 表示在棋盘坐标系下的具体坐标。如果未检测到棋子，`ok` 为 `false`，`result` 为 `"fail_no_piece"`，`pieces` 为空数组 `[]`。*

### 7. 查找棋盘棋子的位置

使用 CV 能力从棋盘中准确检测棋子的位置。

- **指令**:
  ```bash
  curl --location 'http://192.168.199.10:60010/skill-detect-board'
  ```
- **参数**: 无
- **返回值**:
  ```json
  {
      "ok": true,
      "cmd": "skill-detect-board",
      "result": "success|fail_no_piece",
      "pieces": [
          {
              "color": 1,
              "x": 3,
              "y": 4
          }
      ]
  }
  ```
  *说明：`pieces` 数组中的 `color` 表示棋子颜色（1黑，2白），`x` 和 `y` 表示在棋盘坐标系下的具体坐标。如果未检测到棋子，`ok` 为 `false`，`result` 为 `"fail_no_piece"`，`pieces` 为空数组 `[]`。*

### 8. 显示表情

控制机器人的屏幕显示一个表情。

- **指令**:
  ```bash
  curl --location 'http://192.168.199.10:60010/skill-show-emotion?code={code}'
  ```
- **参数**:
    - `code`: 表情数字编号
      | 编号 | 表情 | 编号 | 表情 |
      | :--- | :--- | :--- | :--- |
      | `002` | 快乐 | `011` | 摇头轻快 |
      | `003` | 哭 | `012` | 摇头否认 |
      | `004` | 默认 | `013` | 消失 |
      | `008` | 兴趣 | `014` | 出现 |
- **返回值**:
  ```json
  {
      "ok": true,
      "cmd": "skill-show-emotion",
      "result": "success|fail_empty_code"
  }
  ```

### 9. 拍照

控制机器人的任意一个摄像头拍照。

- **指令**:
  ```bash
  curl --location 'http://192.168.199.10:60010/skill-take-photo?id={id}'
  ```
- **参数**:
    - `id`: 摄像头编号
      | 编号 | 描述 |
      | :--- | :--- |
      | `0` | 前置人脸摄像头 |
      | `1` | 右边棋盘摄像头 |
      | `2` | 左边棋盘摄像头 |
- **返回值**: JPEG图片二进制数据

### 10. 录音

控制机器人的麦克风录音。

- **指令**:
  ```bash
  curl --location 'http://192.168.199.10:60010/skill-record?code={code}'
  ```
- **参数**:
    - `code`: 录音指令
      | 编号 | 描述 |
      | :--- | :--- |
      | `0` | 开始录音 |
      | `1` | 结束录音 |
- **返回值**:
    - 开始录音 (`code=0`): `skill-record success` (纯文本)
    - 结束录音 (`code=1`): PCM音频二进制数据(采样率:16000; 声道:单通道)

### 11. 显示图片

控制机器人的屏幕显示一张图片。

- **指令**:
  ```bash
  curl --location 'http://192.168.199.10:60010/skill-show-image' --form 'image=@"/C:/Users/Pictures/xxx.png"'
  ```
- **参数**:
    - `image`: 图片地址。注意：只支持png格式的图片
- **返回值**: `skill-show-image success`

---

## 🚨 错误处理与策略

### 落子失败（未吸子）

- **现象**: `skill-move-tcp` 执行 `action=2` 返回 `"result": "empty_put"`
- **原因**: 未吸子却尝试落子
- **处理**: 必须先确保 `action=1` 吸子成功（`"result": "success"`）后，才能执行 `action=2` 落子

### 取子失败重试策略

1. 调用 `skill-detect-box` 获取棋盒中棋子的坐标缓存
2. 遍历缓存中的坐标尝试取子。不管取子成功还是失败，都需要从缓存中移除该位置。若取子失败，就用缓存的下个位置重试
3. 如果所有缓存坐标都尝试失败，语音提示用户，并结束本次取子流程


path: references/CHEATSHEET.md
content: # 元萝卜下棋机器人 速查表

## 连接

```
API: http://192.168.199.10:60010
```

## HTTP API 速查

| 操作 | URL |
|------|-----|
| 看棋盘 | `curl 'http://192.168.199.10:60010/skill-look-board'` |
| 复位机械臂 | `curl 'http://192.168.199.10:60010/skill-move-home'` |
| 取落子 | `curl 'http://192.168.199.10:60010/skill-move-tcp?x=X&y=Y&action=N'` |
| 查找棋盒棋子 | `curl 'http://192.168.199.10:60010/skill-detect-box'` |
| 查找棋盘棋子 | `curl 'http://192.168.199.10:60010/skill-detect-board'` |
| 清理棋盘 | `curl 'http://192.168.199.10:60010/skill-clean-board'` (需轮询判断 result: running/done) |
| 语音播报 | `curl 'http://192.168.199.10:60010/skill-tts-chinese?content=TEXT'` |
| 显示表情 | `curl 'http://192.168.199.10:60010/skill-show-emotion?code=008'` |
| 拍照 | `curl 'http://192.168.199.10:60010/skill-take-photo?id=0'` |
| 录音 | `curl 'http://192.168.199.10:60010/skill-record?code=0'` |
| 显示图片 | `curl 'http://192.168.199.10:60010/skill-show-image' -F 'image=@/path'` |

## 动作与颜色参数
- **action**: 0=移动 1=取子 2=落子
- **color**: 1=黑 2=白

## 表情编号 code

code: 002=快乐 003=哭 004=默认 008=兴趣 011=摇头轻快 012=摇头否认 013=消失 014=出现

## 坐标

- 棋盘: (0,0)右上 → (12,12)左下
- 右棋盒: (-3.8,0) → (-1.8,7)
- 左棋盒: (14.2,0) → (15.8,7)

## 标准操作流程

```bash
# 1. 查找棋子
curl 'http://192.168.199.10:60010/skill-detect-box'
# 2. 根据返回坐标取子
curl 'http://192.168.199.10:60010/skill-move-tcp?x=X&y=Y&action=1'
# 3. 只有取子成功（"result": "success"）后，才落子到 (6,6)
curl 'http://192.168.199.10:60010/skill-move-tcp?x=6&y=6&action=2'
```


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)