{"skill":{"slug":"joyin-robot-control","displayName":"JoyIn Robot Control","summary":"Control JoyIn AI robots (W-1 Walle / M-1 Mini) — movement, follow, photo, video, live stream, TTS, agent config, and device status via OpenAPI.","description":"---\nname: joyin-robot-control\ndescription: Control JoyIn AI robots (W-1 Walle / M-1 Mini) — movement, follow, photo, video, live stream, TTS, agent config, and device status via OpenAPI.\nmetadata: {\"openclaw\": {\"requires\": {\"bins\": [\"python3\"], \"env\": [\"JOYIN_API_BASE\", \"JOYIN_AUTH_KEY\", \"JOYIN_DEVICE_SN\"]}, \"primaryEnv\": \"JOYIN_AUTH_KEY\"}}\n---\n\n# JoyIn Robot Control\n\nControl JoyIn AI robots through the official OpenAPI. Supports **W-1 (Walle)** and **M-1 (Mini)** robots.\n\n## Setup\n\n### OpenClaw Configuration\n\nAdd the following to `~/.openclaw/openclaw.json`:\n\n```json5\n{\n  \"skills\": {\n    \"entries\": {\n      \"joyin-robot-control\": {\n        \"enabled\": true,\n        \"apiKey\": \"YOUR_AUTH_KEY\",\n        \"env\": {\n          \"JOYIN_API_BASE\": \"https://api-open-test.joyin-ai.com\",\n          \"JOYIN_AUTH_KEY\": \"YOUR_AUTH_KEY\",\n          \"JOYIN_DEVICE_SN\": \"YOUR_DEVICE_SN\",\n          \"JOYIN_DEVICE_TYPE_ID\": \"3\"\n        }\n      }\n    }\n  }\n}\n```\n\nSee `{baseDir}/openclaw.config.example.json5` for a full template.\n\n### Environment Variables\n\n| Variable | Required | Description |\n|----------|----------|-------------|\n| `JOYIN_API_BASE` | Yes | API base URL (test: `https://api-open-test.joyin-ai.com`) |\n| `JOYIN_AUTH_KEY` | Yes | Authorization key (contact JoyIn staff to obtain) |\n| `JOYIN_DEVICE_SN` | Yes | Target device serial number |\n| `JOYIN_DEVICE_TYPE_ID` | No | `3` for Walle (default), `2` for Mini |\n\nOpenClaw injects these via `skills.entries.*.env` at agent run time. They are scoped to the run and do not leak into the global shell environment.\n\n## Tool\n\n```\npython3 {baseDir}/scripts/robot_cmd.py <command> [options]\n```\n\n## Agent Workflow Rules (IMPORTANT)\n\n**ALWAYS run `preflight` before sending any robot command.** This checks whether the device is online, its battery level, and current work mode.\n\n### Decision flow:\n\n```\n1. Run: python3 {baseDir}/scripts/robot_cmd.py preflight\n2. Check the result:\n   - \"ready\": false  → Tell the user why (offline / OTA / low battery). Do NOT send commands.\n   - \"ready\": true, with \"note\" about current mode → Inform the user, then proceed.\n   - \"ready\": true, idle → Proceed with the command.\n3. Execute the requested command.\n4. Report the result to the user.\n```\n\n### State-aware behavior:\n\n| current_status | Meaning | What to do |\n|----------------|---------|------------|\n| `offline` | Device not connected | Tell user. Do not send commands. |\n| `idle` | Ready | Proceed normally. |\n| `follow` | Following a person | Warn before sending conflicting commands (e.g. remote_control). Use `stop` first. |\n| `remote_control` | Joystick active | Already in RC mode. Can send move commands directly. |\n| `patrol` | Patrolling | Warn before interrupting. Use `stop` first. |\n| `go_charge` | Returning to charger | Warn before interrupting. |\n| `guard` | Monitoring | Warn before interrupting. |\n| `ota` | Firmware updating | Do NOT send any commands. Wait. |\n| `build_map` | Building SLAM map | Do NOT interrupt. |\n| `active_action` | Performing action | Conflicts with follow. Use `stop` first if needed. |\n\n### Battery rules:\n\n- **< 10%** and not charging → Suggest `charge` command before any movement.\n- **< 5%** → Refuse movement commands, only allow `charge` and `status`.\n\n## Quick Examples\n\n```bash\n# ALWAYS run preflight check first\npython3 {baseDir}/scripts/robot_cmd.py preflight\n\n# Move robot forward\npython3 {baseDir}/scripts/robot_cmd.py move --direction forward\n\n# Stop all movement\npython3 {baseDir}/scripts/robot_cmd.py stop\n\n# Make robot say something\npython3 {baseDir}/scripts/robot_cmd.py tts --text \"你好，我是你的机器人助手\"\n\n# Check device status (battery, online, charging)\npython3 {baseDir}/scripts/robot_cmd.py status\n\n# Get live video stream URL\npython3 {baseDir}/scripts/robot_cmd.py live_pull_url\n```\n\n---\n\n## Command Reference\n\n### 1. Movement — Remote Control\n\n**Enter / Exit remote control mode:**\n\n```bash\npython3 {baseDir}/scripts/robot_cmd.py rc_enter\npython3 {baseDir}/scripts/robot_cmd.py rc_exit\n```\n\n**Chassis movement** (8 directions, send continuously at ~100ms interval, robot stops when commands stop):\n\n```bash\npython3 {baseDir}/scripts/robot_cmd.py move --direction forward\npython3 {baseDir}/scripts/robot_cmd.py move --direction backward\npython3 {baseDir}/scripts/robot_cmd.py move --direction left\npython3 {baseDir}/scripts/robot_cmd.py move --direction right\npython3 {baseDir}/scripts/robot_cmd.py move --direction left_up\npython3 {baseDir}/scripts/robot_cmd.py move --direction right_up\npython3 {baseDir}/scripts/robot_cmd.py move --direction left_down\npython3 {baseDir}/scripts/robot_cmd.py move --direction right_down\n\n# Stop chassis movement\npython3 {baseDir}/scripts/robot_cmd.py move_stop\n```\n\n**Head control (Walle only)** — up/down/left/right, send continuously at ~100ms:\n\n```bash\npython3 {baseDir}/scripts/robot_cmd.py head --direction up\npython3 {baseDir}/scripts/robot_cmd.py head --direction down\npython3 {baseDir}/scripts/robot_cmd.py head --direction left\npython3 {baseDir}/scripts/robot_cmd.py head --direction right\npython3 {baseDir}/scripts/robot_cmd.py head --direction stop\n```\n\n**Arm control (Walle only)** — left_arm or right_arm:\n\n```bash\npython3 {baseDir}/scripts/robot_cmd.py arm --side left --direction up\npython3 {baseDir}/scripts/robot_cmd.py arm --side left --direction down\npython3 {baseDir}/scripts/robot_cmd.py arm --side left --direction stop\npython3 {baseDir}/scripts/robot_cmd.py arm --side right --direction up\npython3 {baseDir}/scripts/robot_cmd.py arm --side right --direction stop\n```\n\n**Reset position (Walle only):**\n\n```bash\npython3 {baseDir}/scripts/robot_cmd.py reset --target all     # reset all\npython3 {baseDir}/scripts/robot_cmd.py reset --target head    # reset head only\npython3 {baseDir}/scripts/robot_cmd.py reset --target arm     # reset arms only\n```\n\n### 2. Fixed Actions (Mini only)\n\n```bash\npython3 {baseDir}/scripts/robot_cmd.py car_on          # 上车\npython3 {baseDir}/scripts/robot_cmd.py car_off         # 下车\npython3 {baseDir}/scripts/robot_cmd.py standup          # 站立\npython3 {baseDir}/scripts/robot_cmd.py boot_off         # 收脚\npython3 {baseDir}/scripts/robot_cmd.py boot_on          # 卡脚\npython3 {baseDir}/scripts/robot_cmd.py hello            # 打招呼\npython3 {baseDir}/scripts/robot_cmd.py hello_off        # 收手\npython3 {baseDir}/scripts/robot_cmd.py head_up          # 抬头\npython3 {baseDir}/scripts/robot_cmd.py head_down        # 回头 (低头)\npython3 {baseDir}/scripts/robot_cmd.py charge           # 回充电桩\npython3 {baseDir}/scripts/robot_cmd.py charge_stop      # 停止回充\n```\n\n### 3. Emergency Stop\n\n```bash\npython3 {baseDir}/scripts/robot_cmd.py stop             # 急停，停止所有移动和功能\n```\n\n### 4. Voice — TTS\n\n```bash\n# Play text on device speaker\npython3 {baseDir}/scripts/robot_cmd.py tts --text \"你好世界\"\n\n# Play text then close microphone (keep_silent=1)\npython3 {baseDir}/scripts/robot_cmd.py tts --text \"请安静\" --keep-silent\n```\n\n### 5. Device Status & Preflight\n\n```bash\n# Pre-flight check (ALWAYS run this before any command)\npython3 {baseDir}/scripts/robot_cmd.py preflight\n# Returns: ready (bool), current_status, battery, is_charging, issues[], suggestion\n\n# Get raw device status\npython3 {baseDir}/scripts/robot_cmd.py status\n```\n\n**Preflight response example (ready):**\n```json\n{\n  \"ready\": true,\n  \"current_status\": \"idle\",\n  \"current_status_name\": \"空闲\",\n  \"battery\": 85,\n  \"is_charging\": false,\n  \"note\": \"Device is online and idle. Ready to accept commands.\"\n}\n```\n\n**Preflight response example (not ready):**\n```json\n{\n  \"ready\": false,\n  \"current_status\": \"offline\",\n  \"battery\": -1,\n  \"issues\": [\"Device is OFFLINE — cannot accept commands\"],\n  \"suggestion\": \"Device is OFFLINE — cannot accept commands\"\n}\n```\n\n### 6. Live Video\n\n```bash\n# Start push stream from device camera\npython3 {baseDir}/scripts/robot_cmd.py live_push --status 1\n\n# Stop push stream\npython3 {baseDir}/scripts/robot_cmd.py live_push --status 0\n\n# Get pull stream URL (HLS/RTMP)\npython3 {baseDir}/scripts/robot_cmd.py live_pull_url\n\n# Get push URL\npython3 {baseDir}/scripts/robot_cmd.py live_push_url\n```\n\n### 7. ASR Result\n\n```bash\n# Get the robot's latest speech recognition result\npython3 {baseDir}/scripts/robot_cmd.py asr_result\n```\n\n### 8. WiFi Configuration\n\n```bash\n# Configure robot WiFi (base64-encoded SSID and password)\npython3 {baseDir}/scripts/robot_cmd.py wifi --ssid \"MyWiFi\" --password \"12345678\"\n```\n\n### 9. LLM Configuration (register your own model)\n\n```bash\n# Register a custom LLM\npython3 {baseDir}/scripts/robot_cmd.py llm_register --name \"My GPT\" --base-url \"https://api.openai.com/v1\" --api-key \"sk-xxx\" --model \"gpt-4\"\n\n# List registered LLMs\npython3 {baseDir}/scripts/robot_cmd.py llm_list\n\n# Update a registered LLM\npython3 {baseDir}/scripts/robot_cmd.py llm_update --id 123 --model \"gpt-4o\"\n```\n\n### 10. Agent Configuration\n\n```bash\n# Create an agent with a registered LLM\npython3 {baseDir}/scripts/robot_cmd.py agent_create --name \"My Agent\" --llm-id 123\n\n# List agents\npython3 {baseDir}/scripts/robot_cmd.py agent_list\n\n# Update agent\npython3 {baseDir}/scripts/robot_cmd.py agent_update --id 123 --name \"New Name\"\n\n# Bind agent to a device\npython3 {baseDir}/scripts/robot_cmd.py agent_bind --agent-id 123\n\n# Query device's bound agent\npython3 {baseDir}/scripts/robot_cmd.py agent_query\n\n# Reset device to default JoyIn agent\npython3 {baseDir}/scripts/robot_cmd.py agent_reset\n```\n\n---\n\n## Device Types\n\n| Type ID | Model | Codename | Key Capabilities |\n|---------|-------|----------|-----------------|\n| 3 | W-1 (Walle) | walle | Chassis + head + arm control, 8-direction joystick, position reset |\n| 2 | M-1 (Mini) | mini | Chassis control, fixed body actions (standup/car/hello/head), charging |\n\n## API Protocol\n\n- **Base URL**: `https://api-open-test.joyin-ai.com` (test)\n- **Auth**: All requests carry 3 headers — `Authorization`, `Device-Sn`, `Device-Type-Id`\n- **Robot commands**: `POST /v1/device/interaction/cmd` with body `{\"cmd_type\":\"...\",\"status\":\"...\",\"name\":\"...\",\"data\":{...}}`\n- **Response format**: `{\"code\": 200, \"msg\": \"success\", \"data\": {...}}`\n\n## Important Notes\n\n- The device must be **online** for commands to work.\n- Joystick commands (move/head/arm) need to be sent **continuously at ~100ms intervals**. The robot stops automatically when commands stop arriving.\n- `keep_silent=1` on TTS means the microphone will be closed after playback.\n- Some APIs (LLM config, Agent config) are marked as **\"开发中\"** and may not be fully available.\n","tags":{"latest":"1.1.0"},"stats":{"comments":0,"downloads":618,"installsAllTime":23,"installsCurrent":0,"stars":0,"versions":2},"createdAt":1773507496307,"updatedAt":1778491910241},"latestVersion":{"version":"1.1.0","createdAt":1773508498976,"changelog":"Add preflight check command and state-aware agent workflow rules.","license":"MIT-0"},"metadata":{"setup":[{"key":"JOYIN_API_BASE","required":true},{"key":"JOYIN_AUTH_KEY","required":true},{"key":"JOYIN_DEVICE_SN","required":true}],"os":null,"systems":null},"owner":{"handle":"yanfang724","userId":"s171dqhxtcszrg75j8na0sf1ex884pfc","displayName":"yanfang724","image":"https://avatars.githubusercontent.com/u/2231008?v=4"},"moderation":{"isSuspicious":false,"isMalwareBlocked":false,"verdict":"clean","reasonCodes":["review.llm_review"],"summary":"Review: review.llm_review","engineVersion":"v2.4.24","updatedAt":1780089888639}}