伴宠:基于八界的宠物定点投喂与家庭禁区守护

目录

  家里有宠物之后,喂食和区域管理往往是两件需要反复处理的小事:到时间要把食物放到固定餐位,宠物靠近厨房、婴儿房或阳台等区域时,又需要及时提醒和引导。如果只依靠一个固定喂食器,它只能照顾设备所在的位置;如果只安装摄像头,又只能看见问题,无法移动到现场。

  八界是一台由移动底盘 、六自由度机械臂 、二指夹爪 和双 RGB-D 相机组成的移动操作机器人。它可以在不同房间之间移动,也可以从远近两个视角观察环境,并在固定、受控的条件下操作轻质物品。基于这些特点,我把"伴宠"重新定义为两个互相关联的家庭任务:

  1. 宠物定点投喂:在设定时间取用预分装食物,移动到固定餐位,完成放置并记录结果;
  2. 家庭禁区守护:巡查婴儿房、厨房等预设边界,发现宠物持续进入后,通过语音和预设移动路线进行非接触劝离,同时通知主人。

这两个任务共享同一套地图、视觉、状态检查和事件记录,因此比单独做一个提醒工具更能发挥移动机器人的优势。

1.八界提供了哪些硬件和开发能力

  八界的硬件形态可以概括为"能移动的机械臂平台"。移动底盘负责跨房间移动和切换观察点;机械臂负责拿取、递送和放置轻质物品;头部相机提供较大的环境视野;腕部相机用于靠近目标后的精细复核;双 RK3588 融合 SoC 为端侧计算和应用部署提供基础。

  软件侧提供 ROS 2、C++17 和 Python 3.10 SDK,并开放 45 项原子能力,覆盖建图导航、视觉感知、目标搜索、机械臂控制、抓取放置、语音交互和机器人状态管理。对于伴宠场景,最重要的不是把 45 项接口全部调用一遍,而是从中选择一条稳定的任务链:

text 复制代码
状态检查 → 定时触发 → 导航 → 双相机观察
         → 抓取/放置或区域判断 → 语音交互 → 结果记录
  • 八界系统信息:
    机器人端采用 ARM64 架构,开发电脑可以使用 Ubuntu x86_64 环境进行连接和调试。

实际使用时,我会先在机身屏幕完成自检,再从电脑连接 SDK。这样可以先排除相机、本体状态或机械臂模式问题,避免把所有异常都归因到代码上。

2.先打通数据采集:让机器人真正"看见"家庭环境

  无论是投喂还是禁区守护,都要先解决同一个问题:怎样稳定地取得机器人看到的画面,并把数据保存下来供后续判断和复盘。

2.1 安装 SDK 与连接机器人

  本次使用 Python SDK 2.1.0。Ubuntu 开发电脑可以创建 Python 3.10 虚拟环境并安装仓库中的 x86_64 wheel:

bash 复制代码
cd ecovacs_bajie_sdk-main/python
python3 -m venv .venv
source .venv/bin/activate
python3 -m pip install ./bajie_sdk-2.1.0-cp310-cp310-linux_x86_64.whl

  开发电脑和机器人处于可互通网络后,从机器人屏幕读取实际 IP,并设置 WebSocket 地址:

bash 复制代码
export bajie_sdk_WS_URL='ws://<机器人IP>:9900/'

python3 -m bajie_sdk run eco_robotInfo \
  --json '{}' \
  -o evidence/robot_info.json

  连接失败时,应依次检查 IP 格式、ping、9900 端口和本机路由。WebSocket 地址必须是类似 ws://10.10.10.12:9900/ 的纯地址,不能混入网页链接格式。

2.2 采集头部和腕部 RGB-D

  相机接口为 eco_captureImages。HEAD 用于获取房间、门口、餐位等较大范围画面;ARM 用于靠近投喂容器或门口观察点后的局部复核。

bash 复制代码
# 头部相机
python3 -m bajie_sdk run eco_captureImages \
  --json '{"camera_type":"HEAD","timeout_sec":30.0}' \
  -o evidence/run_001/head.json

# 腕部相机
python3 -m bajie_sdk run eco_captureImages \
  --json '{"camera_type":"ARM","timeout_sec":30.0}' \
  -o evidence/run_001/arm.json

  CLI 会保存结构化 JSON,并将较大的 RGB、深度数据写成相邻图片文件。JSON 中使用 @(文件名) 引用图片,所以归档时要把 JSON 和关联文件作为一组保留。

下图是通过 SDK 调用相机并保存结果:

如果需要保存便于算法处理的原始数组,可以在 Python 中直接读取返回对象:

python 复制代码
from pathlib import Path
import cv2
import numpy as np
from bajie_sdk import BajieRobot, CameraType

output = Path("evidence/pet_home")
output.mkdir(parents=True, exist_ok=True)

robot = BajieRobot(ws_url="ws://<机器人IP>:9900/")
if not robot.Connect(timeout_sec=8.0):
    raise RuntimeError("机器人连接失败")

head = robot.eco_captureImages(CameraType.HEAD, timeout_sec=30.0)
arm = robot.eco_captureImages(CameraType.ARM, timeout_sec=30.0)

np.save(output / "head_rgb.npy", head.rgb_image.img)
np.save(output / "head_depth.npy", head.depth_image.img)
np.save(output / "arm_rgb.npy", arm.rgb_image.img)
np.save(output / "arm_depth.npy", arm.depth_image.img)

cv2.imwrite(
    str(output / "head_rgb.png"),
    cv2.cvtColor(head.rgb_image.img, cv2.COLOR_RGB2BGR),
)
robot.Disconnect()

下图是头部相机保存的 RGB 样本,分辨率为 640×400。

下图是同一组 HEAD 深度数据的伪彩预览,原始计算使用 uint16 npy,预览图主要用于快速检查空间轮廓。

下图是ARM 深度预览,腕部视角更适合靠近固定餐盒、餐位或边界观察点后检查局部空间。

  本次保存的两路 RGB 均为 (400, 640, 3)、uint8,深度均为 (400, 640)、uint16。HEAD 与 ARM 请求时间戳相差约 1.326 秒,因此这组数据属于顺序采集。实际做连续巡查时,还需要为图像、机器人位姿、区域事件和任务 ID 建立统一时间线。

2.3 原子技能自测示例

  • atomic_capabilities_demo.py
python 复制代码
#!/usr/bin/env python3
"""八界机器人低风险原子能力演示与证据采集。

默认:
1. 机器人状态查询;
2. 头部 RGB-D 相机采集;
3. 腕部 RGB-D 相机采集;
4. 基于头部 RGB 图像的开放词目标检测;
5. 机器人语音播报。

头部俯仰属于真实运动,默认禁用。只有同时传入
``--enable-head-motion --confirm-clearance`` 才会执行。
"""

from __future__ import annotations

import argparse
import json
import os
import sys
import time
import traceback
from dataclasses import fields, is_dataclass
from datetime import datetime
from enum import Enum
from pathlib import Path
from typing import Any, Callable, Optional

import cv2
import numpy as np


DEFAULT_WS_URL = "ws://10.10.10.12:9900/"


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(
        description="低风险演示八界状态、RGB-D 相机、OVD 检测和语音原子能力。"
    )
    parser.add_argument(
        "--ws-url",
        default=os.environ.get("bajie_sdk_WS_URL", DEFAULT_WS_URL),
        help=(
            "机器人 WebSocket 地址;默认读取 bajie_sdk_WS_URL,"
            f"否则使用 {DEFAULT_WS_URL}"
        ),
    )
    parser.add_argument(
        "--output-dir",
        type=Path,
        help="证据输出目录;默认 evidence/atomic_demo_时间戳。",
    )
    parser.add_argument(
        "--labels",
        nargs="+",
        default=["玩偶", "日历"],
        help="OVD 检测标签,例如:--labels 玩偶 日历。",
    )
    parser.add_argument(
        "--speech-text",
        default="八界原子能力演示开始,我将采集图像并识别桌面物品。",
        help="机器人播报文本。",
    )
    parser.add_argument("--connect-timeout", type=float, default=12.0)
    parser.add_argument("--capture-timeout", type=float, default=30.0)
    parser.add_argument("--detect-timeout", type=float, default=15.0)
    parser.add_argument("--skip-arm", action="store_true", help="跳过腕部相机。")
    parser.add_argument("--skip-detection", action="store_true", help="跳过目标检测。")
    parser.add_argument("--skip-speech", action="store_true", help="跳过语音播报。")
    parser.add_argument(
        "--enable-head-motion",
        action="store_true",
        help="允许执行头部俯仰;还必须同时传入 --confirm-clearance。",
    )
    parser.add_argument(
        "--confirm-clearance",
        action="store_true",
        help="确认机器人周围无人员、线缆和障碍物。",
    )
    parser.add_argument(
        "--head-angle",
        type=float,
        default=0.83,
        help="头部俯仰弧度,SDK 文档范围 [0, 1.3];默认 0.83(水平)。",
    )
    return parser.parse_args()


def jsonable(value: Any) -> Any:
    """将 SDK/dataclass/NumPy 对象转为紧凑、可读的 JSON 数据。"""
    if value is None or isinstance(value, (str, int, float, bool)):
        return value
    if isinstance(value, Enum):
        return value.value
    if isinstance(value, np.generic):
        return value.item()
    if isinstance(value, np.ndarray):
        summary: dict[str, Any] = {
            "kind": "ndarray",
            "shape": list(value.shape),
            "dtype": str(value.dtype),
        }
        if value.size:
            finite = value[np.isfinite(value)] if np.issubdtype(value.dtype, np.number) else []
            if len(finite):
                summary["min"] = float(np.min(finite))
                summary["max"] = float(np.max(finite))
        return summary
    if is_dataclass(value):
        return {field.name: jsonable(getattr(value, field.name)) for field in fields(value)}
    if isinstance(value, dict):
        return {str(key): jsonable(item) for key, item in value.items()}
    if isinstance(value, (list, tuple, set)):
        return [jsonable(item) for item in value]
    if hasattr(value, "__dict__"):
        return {
            key: jsonable(item)
            for key, item in vars(value).items()
            if not key.startswith("_")
        }
    return repr(value)


def write_json(path: Path, value: Any) -> None:
    path.write_text(
        json.dumps(jsonable(value), ensure_ascii=False, indent=2),
        encoding="utf-8",
    )


def save_rgb(path: Path, image: np.ndarray) -> None:
    if image.ndim == 3 and image.shape[2] == 3:
        encoded = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
    elif image.ndim == 3 and image.shape[2] == 4:
        encoded = cv2.cvtColor(image, cv2.COLOR_RGBA2BGRA)
    else:
        encoded = image
    if not cv2.imwrite(str(path), encoded):
        raise RuntimeError(f"PNG 保存失败:{path}")


def save_depth_preview(path: Path, depth: np.ndarray) -> None:
    finite_mask = np.isfinite(depth)
    valid = depth[finite_mask & (depth > 0)]
    if valid.size == 0:
        preview = np.zeros(depth.shape[:2], dtype=np.uint8)
    else:
        low, high = np.percentile(valid, [2, 98])
        if high <= low:
            high = low + 1.0
        clipped = np.nan_to_num(depth, nan=low, posinf=high, neginf=low)
        preview = np.clip((clipped - low) * 255.0 / (high - low), 0, 255).astype(np.uint8)
    colored = cv2.applyColorMap(preview, cv2.COLORMAP_TURBO)
    if not cv2.imwrite(str(path), colored):
        raise RuntimeError(f"深度预览保存失败:{path}")


def save_view(output_dir: Path, prefix: str, view: Any) -> dict[str, str]:
    rgb = getattr(getattr(view, "rgb_image", None), "img", None)
    depth = getattr(getattr(view, "depth_image", None), "img", None)
    artifacts: dict[str, str] = {}

    metadata_path = output_dir / f"{prefix}_metadata.json"
    write_json(metadata_path, view)
    artifacts["metadata"] = metadata_path.name

    if isinstance(rgb, np.ndarray):
        rgb_npy = output_dir / f"{prefix}_rgb.npy"
        rgb_png = output_dir / f"{prefix}_rgb.png"
        np.save(rgb_npy, rgb)
        save_rgb(rgb_png, rgb)
        artifacts.update(rgb_npy=rgb_npy.name, rgb_png=rgb_png.name)

    if isinstance(depth, np.ndarray):
        depth_npy = output_dir / f"{prefix}_depth.npy"
        depth_preview = output_dir / f"{prefix}_depth_preview.png"
        np.save(depth_npy, depth)
        save_depth_preview(depth_preview, depth)
        artifacts.update(depth_npy=depth_npy.name, depth_preview=depth_preview.name)

    return artifacts


def bbox_values(item: Any) -> Optional[list[float]]:
    bbox = getattr(item, "bbox", None)
    if bbox is None:
        return None
    if isinstance(bbox, np.ndarray):
        bbox = bbox.tolist()
    if isinstance(bbox, (list, tuple)) and len(bbox) >= 4:
        return [float(value) for value in bbox[:4]]
    names = ("x1", "y1", "x2", "y2")
    if all(hasattr(bbox, name) for name in names):
        return [float(getattr(bbox, name)) for name in names]
    return None


def draw_detections(path: Path, rgb: np.ndarray, items: list[Any]) -> None:
    canvas = cv2.cvtColor(rgb, cv2.COLOR_RGB2BGR) if rgb.ndim == 3 else rgb.copy()
    for item in items:
        bbox = bbox_values(item)
        if bbox is None:
            continue
        x1, y1, x2, y2 = [int(round(value)) for value in bbox]
        name = getattr(item, "name", None) or getattr(item, "label", None) or "object"
        score = getattr(item, "score", None)
        label = str(name) if score is None else f"{name} {float(score):.2f}"
        cv2.rectangle(canvas, (x1, y1), (x2, y2), (0, 255, 0), 3)
        # OpenCV 默认字体不可靠地支持中文;JSON 中会保留完整中文标签。
        if label.isascii():
            cv2.putText(
                canvas,
                label,
                (x1, max(20, y1 - 8)),
                cv2.FONT_HERSHEY_SIMPLEX,
                0.7,
                (0, 255, 0),
                2,
            )
    if not cv2.imwrite(str(path), canvas):
        raise RuntimeError(f"检测标注图保存失败:{path}")


class DemoRunner:
    def __init__(self, output_dir: Path) -> None:
        self.output_dir = output_dir
        self.records: list[dict[str, Any]] = []

    def persist_summary(self) -> None:
        write_json(
            self.output_dir / "run_summary.json",
            {
                "generated_at": datetime.now().astimezone().isoformat(),
                "steps": self.records,
            },
        )

    def run(self, name: str, operation: Callable[[], Any]) -> Any:
        print(f"\n{'=' * 12} {name} {'=' * 12}", flush=True)
        started = time.monotonic()
        record: dict[str, Any] = {"name": name, "status": "running"}
        self.records.append(record)
        self.persist_summary()
        try:
            result = operation()
            record.update(status="success", elapsed_sec=round(time.monotonic() - started, 3))
            print(f"[成功] {name},耗时 {record['elapsed_sec']} 秒", flush=True)
            return result
        except Exception as exc:  # 保留其他能力的演示机会
            record.update(
                status="failed",
                elapsed_sec=round(time.monotonic() - started, 3),
                error_type=type(exc).__name__,
                error=str(exc),
                traceback=traceback.format_exc(),
            )
            print(f"[失败] {name}: {type(exc).__name__}: {exc}", file=sys.stderr, flush=True)
            return None
        finally:
            self.persist_summary()

    def skip(self, name: str, reason: str) -> None:
        print(f"\n[跳过] {name}: {reason}", flush=True)
        self.records.append({"name": name, "status": "skipped", "reason": reason})
        self.persist_summary()


def main() -> int:
    args = parse_args()
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    output_dir = args.output_dir or Path("evidence") / f"atomic_demo_{timestamp}"
    output_dir = output_dir.expanduser().resolve()
    output_dir.mkdir(parents=True, exist_ok=False)

    runner = DemoRunner(output_dir)
    robot: Any = None
    head_view: Any = None

    print("八界低风险原子能力演示")
    print(f"WebSocket: {args.ws_url}")
    print(f"证据目录: {output_dir}")
    print(f"检测标签: {', '.join(args.labels)}")
    print("默认不执行底盘、机械臂、夹爪和升降运动。")

    try:
        try:
            from bajie_sdk import BajieRobot, CameraType, DetectObjectsRequest, OvdEndpoint
        except Exception as exc:
            runner.run("导入 bajie_sdk", lambda: (_ for _ in ()).throw(exc))
            return 2

        def connect() -> Any:
            instance = BajieRobot(ws_url=args.ws_url)
            if not instance.Connect(timeout_sec=args.connect_timeout):
                raise ConnectionError(f"无法连接 {args.ws_url}")
            return instance

        robot = runner.run("连接机器人", connect)
        if robot is None:
            return 2

        def capture_status() -> Any:
            try:
                robot.eco_refreshRobotInfo(timeout_sec=5.0)
            except Exception as exc:
                print(f"提示:刷新状态失败,继续读取现有快照:{exc}")
            info = robot.eco_robotInfo()
            write_json(output_dir / "01_robot_info.json", info)
            print(json.dumps(jsonable(info), ensure_ascii=False, indent=2))
            return info

        runner.run("原子能力1:机器人状态查询", capture_status)

        def capture_head() -> Any:
            view = robot.eco_captureImages(CameraType.HEAD, timeout_sec=args.capture_timeout)
            artifacts = save_view(output_dir, "02_head", view)
            print(json.dumps(artifacts, ensure_ascii=False, indent=2))
            return view

        head_view = runner.run("原子能力2:头部 RGB-D 相机", capture_head)

        if args.skip_arm:
            runner.skip("原子能力3:腕部 RGB-D 相机", "命令行指定 --skip-arm")
        else:
            def capture_arm() -> Any:
                view = robot.eco_captureImages(CameraType.ARM, timeout_sec=args.capture_timeout)
                artifacts = save_view(output_dir, "03_arm", view)
                print(json.dumps(artifacts, ensure_ascii=False, indent=2))
                return view

            runner.run("原子能力3:腕部 RGB-D 相机", capture_arm)

        if args.skip_detection:
            runner.skip("原子能力4:开放词目标检测", "命令行指定 --skip-detection")
        elif head_view is None:
            runner.skip("原子能力4:开放词目标检测", "头部相机采集失败")
        else:
            def detect_objects() -> Any:
                rgb = getattr(getattr(head_view, "rgb_image", None), "img", None)
                if not isinstance(rgb, np.ndarray):
                    raise RuntimeError("头部视图中没有可用的 RGB ndarray")
                request = DetectObjectsRequest(
                    rgb_image=rgb,
                    labels=args.labels,
                    entry=OvdEndpoint.OVD,
                )
                response = robot.eco_detect_objects(request, timeout=args.detect_timeout)
                write_json(output_dir / "04_detections.json", response)
                items = list(getattr(response, "items", None) or [])
                draw_detections(output_dir / "04_detections_annotated.png", rgb, items)
                print(f"检测目标数量: {len(items)}")
                for index, item in enumerate(items):
                    name = getattr(item, "name", None) or getattr(item, "label", None)
                    print(
                        f"  {index}: name={name!r}, "
                        f"score={getattr(item, 'score', None)}, bbox={bbox_values(item)}"
                    )
                return response

            runner.run("原子能力4:开放词目标检测", detect_objects)

        if args.skip_speech:
            runner.skip("原子能力5:语音播报", "命令行指定 --skip-speech")
        else:
            def speak() -> Any:
                task_id = robot.eco_robotSpeech(args.speech_text)
                write_json(output_dir / "05_speech.json", {"text": args.speech_text, "task_id": task_id})
                print(f"语音任务 ID: {task_id}")
                return task_id

            runner.run("原子能力5:语音播报", speak)

        if args.enable_head_motion and args.confirm_clearance:
            def move_head() -> Any:
                if not 0.0 <= args.head_angle <= 1.3:
                    raise ValueError("--head-angle 必须处于 [0, 1.3] 弧度")
                status = robot.eco_setRobotHead(args.head_angle, timeout_sec=30.0)
                write_json(output_dir / "06_head_motion.json", status)
                print(json.dumps(jsonable(status), ensure_ascii=False, indent=2))
                return status

            runner.run("可选原子能力:头部俯仰", move_head)
        else:
            reason = "默认禁用真实运动"
            if args.enable_head_motion and not args.confirm_clearance:
                reason = "缺少 --confirm-clearance,安全门未通过"
            runner.skip("可选原子能力:头部俯仰", reason)

    finally:
        if robot is not None:
            try:
                robot.Disconnect()
                print("\n机器人连接已断开。")
            except Exception as exc:
                print(f"断开连接时出现提示:{exc}", file=sys.stderr)
        runner.persist_summary()

    failed = [record for record in runner.records if record["status"] == "failed"]
    succeeded = [record for record in runner.records if record["status"] == "success"]
    print(f"\n演示结束:成功 {len(succeeded)} 项,失败 {len(failed)} 项。")
    print(f"证据目录:{output_dir}")
    return 1 if failed else 0


if __name__ == "__main__":
    raise SystemExit(main())

输出:

bash 复制代码
(.venv) zzw@zzw:~/project/ecovacs_bajie_sdk-main/python$ python atomic_capabilities_demo.py \
  --labels 玩偶 日历
八界低风险原子能力演示
WebSocket: ws://172.20.10.2:9900/
证据目录: /home/zzw/project/ecovacs_bajie_sdk-main/python/evidence/atomic_demo_20260913_173803
检测标签: 玩偶, 日历
默认不执行底盘、机械臂、夹爪和升降运动。

============ 连接机器人 ============
[bajie_sdk] WebSocket 已连接(172.20.10.2:9900)
Connect: 等待 robot_info 推送上报(最多 8s)...
Connect: robot_info 接收完毕
[成功] 连接机器人,耗时 0.099 秒

============ 原子能力1:机器人状态查询 ============
{
  "workState": {
    "task_id": "",
    "name": "",
    "cmd": "idle"
  },
  "battery": {
    "value": 66,
    "isCharge": 0,
    "mode": 3
  },
  "alarm": [],
  "pos": {
    "room": "房间0",
    "x": 2.130887508392334,
    "y": 0.5645290613174438,
    "yaw": -0.5976022481918335
  },
  "mapinfo": {
    "mid": "ABCDEFGHIJKL",
    "mname": "地图",
    "totalWidth": 1200,
    "totalHeight": 1200,
    "originX": -30,
    "originY": -30,
    "pixel": 50,
    "value": {
      "kind": "ndarray",
      "shape": [
        1200,
        1200
      ],
      "dtype": "uint8",
      "min": 0.0,
      "max": 254.0
    },
    "length": 1440000
  },
  "furniture": {
    "info": [
      {
        "id": "bbda75f7-dd01-43f8-92eb-b94550b2823b",
        "model_level": 0,
        "mssid": "",
        "model_name": "充电桩",
        "name": "充电桩",
        "content": [
          [
            0.0,
            0.0,
            0.0
          ]
        ],
        "direction": [
          [
            0.0,
            0.0,
            0.0
          ]
        ],
        "app_is_custom": 2
      },
      {
        "id": "0",
        "model_level": 1,
        "mssid": "",
        "model_name": "房间",
        "name": "房间0",
        "content": [
          [
            -1.0,
            -3.35,
            0.0
          ],
          [
            -0.75,
            -3.35,
            0.0
          ],
          [
            -0.7,
            -3.4,
            0.0
          ],
          [
            -0.7,
            -3.55,
            0.0
          ],
          [
            0.1,
            -3.55,
            0.0
          ],
          [
            0.1,
            -3.5,
            0.0
          ],
          [
            0.15,
            -3.45,
            0.0
          ],
          [
            0.15,
            -3.4,
            0.0
          ],
          [
            0.2,
            -3.35,
            0.0
          ],
          [
            0.4,
            -3.35,
            0.0
          ],
          [
            0.45,
            -3.4,
            0.0
          ],
          [
            2.4,
            -3.4,
            0.0
          ],
          [
            2.45,
            -3.45,
            0.0
          ],
          [
            4.05,
            -3.45,
            0.0
          ],
          [
            4.1,
            -3.5,
            0.0
          ],
          [
            5.7,
            -3.5,
            0.0
          ],
          [
            5.75,
            -3.55,
            0.0
          ],
          [
            7.55,
            -3.55,
            0.0
          ],
          [
            7.6,
            -3.6,
            0.0
          ],
          [
            7.75,
            -3.6,
            0.0
          ],
          [
            7.8,
            -3.55,
            0.0
          ],
          [
            7.9,
            -3.55,
            0.0
          ],
          [
            7.9,
            -3.4,
            0.0
          ],
          [
            7.65,
            -3.4,
            0.0
          ],
          [
            7.6,
            -3.35,
            0.0
          ],
          [
            7.6,
            -2.75,
            0.0
          ],
          [
            7.65,
            -2.7,
            0.0
          ],
          [
            7.9,
            -2.7,
            0.0
          ],
          [
            7.95,
            -2.75,
            0.0
          ],
          [
            8.1,
            -2.75,
            0.0
          ],
          [
            8.15,
            -2.7,
            0.0
          ],
          [
            8.15,
            -2.5,
            0.0
          ],
          [
            8.2,
            -2.45,
            0.0
          ],
          [
            8.3,
            -2.45,
            0.0
          ],
          [
            8.35,
            -2.5,
            0.0
          ],
          [
            8.45,
            -2.5,
            0.0
          ],
          [
            8.45,
            -1.85,
            0.0
          ],
          [
            8.5,
            -1.8,
            0.0
          ],
          [
            8.5,
            -1.2,
            0.0
          ],
          [
            8.45,
            -1.2,
            0.0
          ],
          [
            8.4,
            -1.15,
            0.0
          ],
          [
            8.4,
            -1.05,
            0.0
          ],
          [
            8.45,
            -1.0,
            0.0
          ],
          [
            8.5,
            -1.0,
            0.0
          ],
          [
            8.5,
            -0.55,
            0.0
          ],
          [
            8.55,
            -0.5,
            0.0
          ],
          [
            8.55,
            1.45,
            0.0
          ],
          [
            8.45,
            1.45,
            0.0
          ],
          [
            8.45,
            1.4,
            0.0
          ],
          [
            8.4,
            1.35,
            0.0
          ],
          [
            8.3,
            1.35,
            0.0
          ],
          [
            8.25,
            1.4,
            0.0
          ],
          [
            8.25,
            1.45,
            0.0
          ],
          [
            8.15,
            1.45,
            0.0
          ],
          [
            8.1,
            1.4,
            0.0
          ],
          [
            8.0,
            1.4,
            0.0
          ],
          [
            7.95,
            1.35,
            0.0
          ],
          [
            7.6,
            1.35,
            0.0
          ],
          [
            7.55,
            1.4,
            0.0
          ],
          [
            7.35,
            1.4,
            0.0
          ],
          [
            7.25,
            1.5,
            0.0
          ],
          [
            7.25,
            1.55,
            0.0
          ],
          [
            6.0,
            1.55,
            0.0
          ],
          [
            5.95,
            1.5,
            0.0
          ],
          [
            5.7,
            1.5,
            0.0
          ],
          [
            5.65,
            1.55,
            0.0
          ],
          [
            5.65,
            1.65,
            0.0
          ],
          [
            5.7,
            1.7,
            0.0
          ],
          [
            5.9,
            1.7,
            0.0
          ],
          [
            5.95,
            1.75,
            0.0
          ],
          [
            6.2,
            1.75,
            0.0
          ],
          [
            6.25,
            1.8,
            0.0
          ],
          [
            6.3,
            1.8,
            0.0
          ],
          [
            6.3,
            1.85,
            0.0
          ],
          [
            6.35,
            1.9,
            0.0
          ],
          [
            6.5,
            1.9,
            0.0
          ],
          [
            6.55,
            1.95,
            0.0
          ],
          [
            6.65,
            1.95,
            0.0
          ],
          [
            6.7,
            2.0,
            0.0
          ],
          [
            6.75,
            2.0,
            0.0
          ],
          [
            6.8,
            2.05,
            0.0
          ],
          [
            6.9,
            2.05,
            0.0
          ],
          [
            6.95,
            2.1,
            0.0
          ],
          [
            7.05,
            2.1,
            0.0
          ],
          [
            7.1,
            2.15,
            0.0
          ],
          [
            7.15,
            2.15,
            0.0
          ],
          [
            7.2,
            2.2,
            0.0
          ],
          [
            7.3,
            2.2,
            0.0
          ],
          [
            7.35,
            2.25,
            0.0
          ],
          [
            7.45,
            2.25,
            0.0
          ],
          [
            7.5,
            2.3,
            0.0
          ],
          [
            7.6,
            2.3,
            0.0
          ],
          [
            7.65,
            2.35,
            0.0
          ],
          [
            7.75,
            2.35,
            0.0
          ],
          [
            7.85,
            2.45,
            0.0
          ],
          [
            7.95,
            2.45,
            0.0
          ],
          [
            8.0,
            2.5,
            0.0
          ],
          [
            8.1,
            2.5,
            0.0
          ],
          [
            8.15,
            2.55,
            0.0
          ],
          [
            8.2,
            2.55,
            0.0
          ],
          [
            8.25,
            2.6,
            0.0
          ],
          [
            8.35,
            2.6,
            0.0
          ],
          [
            8.4,
            2.65,
            0.0
          ],
          [
            8.5,
            2.65,
            0.0
          ],
          [
            8.55,
            2.7,
            0.0
          ],
          [
            8.6,
            2.7,
            0.0
          ],
          [
            8.6,
            2.8,
            0.0
          ],
          [
            8.55,
            2.85,
            0.0
          ],
          [
            8.55,
            2.95,
            0.0
          ],
          [
            8.4,
            2.95,
            0.0
          ],
          [
            8.35,
            2.9,
            0.0
          ],
          [
            8.3,
            2.9,
            0.0
          ],
          [
            8.25,
            2.85,
            0.0
          ],
          [
            8.1,
            2.85,
            0.0
          ],
          [
            8.05,
            2.8,
            0.0
          ],
          [
            8.0,
            2.8,
            0.0
          ],
          [
            7.95,
            2.75,
            0.0
          ],
          [
            7.75,
            2.75,
            0.0
          ],
          [
            7.75,
            2.7,
            0.0
          ],
          [
            7.7,
            2.65,
            0.0
          ],
          [
            7.65,
            2.65,
            0.0
          ],
          [
            7.6,
            2.6,
            0.0
          ],
          [
            7.4,
            2.6,
            0.0
          ],
          [
            7.35,
            2.55,
            0.0
          ],
          [
            7.3,
            2.55,
            0.0
          ],
          [
            7.25,
            2.5,
            0.0
          ],
          [
            7.2,
            2.5,
            0.0
          ],
          [
            7.15,
            2.45,
            0.0
          ],
          [
            7.1,
            2.45,
            0.0
          ],
          [
            7.05,
            2.4,
            0.0
          ],
          [
            7.0,
            2.4,
            0.0
          ],
          [
            6.95,
            2.35,
            0.0
          ],
          [
            6.85,
            2.35,
            0.0
          ],
          [
            6.8,
            2.3,
            0.0
          ],
          [
            6.75,
            2.3,
            0.0
          ],
          [
            6.7,
            2.25,
            0.0
          ],
          [
            6.55,
            2.25,
            0.0
          ],
          [
            6.5,
            2.2,
            0.0
          ],
          [
            6.4,
            2.2,
            0.0
          ],
          [
            6.35,
            2.15,
            0.0
          ],
          [
            6.25,
            2.15,
            0.0
          ],
          [
            6.25,
            2.1,
            0.0
          ],
          [
            6.15,
            2.0,
            0.0
          ],
          [
            6.05,
            2.0,
            0.0
          ],
          [
            6.0,
            2.05,
            0.0
          ],
          [
            5.9,
            2.05,
            0.0
          ],
          [
            5.9,
            2.0,
            0.0
          ],
          [
            5.85,
            1.95,
            0.0
          ],
          [
            5.6,
            1.95,
            0.0
          ],
          [
            5.45,
            1.8,
            0.0
          ],
          [
            4.95,
            1.8,
            0.0
          ],
          [
            4.95,
            1.75,
            0.0
          ],
          [
            4.9,
            1.7,
            0.0
          ],
          [
            4.8,
            1.7,
            0.0
          ],
          [
            4.75,
            1.75,
            0.0
          ],
          [
            4.75,
            2.0,
            0.0
          ],
          [
            4.65,
            2.0,
            0.0
          ],
          [
            4.6,
            1.95,
            0.0
          ],
          [
            4.55,
            1.95,
            0.0
          ],
          [
            4.5,
            1.9,
            0.0
          ],
          [
            4.45,
            1.9,
            0.0
          ],
          [
            4.4,
            1.85,
            0.0
          ],
          [
            3.95,
            1.85,
            0.0
          ],
          [
            3.9,
            1.9,
            0.0
          ],
          [
            3.9,
            2.0,
            0.0
          ],
          [
            3.95,
            2.05,
            0.0
          ],
          [
            3.95,
            2.25,
            0.0
          ],
          [
            4.0,
            2.3,
            0.0
          ],
          [
            4.05,
            2.3,
            0.0
          ],
          [
            4.05,
            2.35,
            0.0
          ],
          [
            4.1,
            2.4,
            0.0
          ],
          [
            4.1,
            2.5,
            0.0
          ],
          [
            4.15,
            2.55,
            0.0
          ],
          [
            4.25,
            2.55,
            0.0
          ],
          [
            4.35,
            2.65,
            0.0
          ],
          [
            4.35,
            2.75,
            0.0
          ],
          [
            4.25,
            2.75,
            0.0
          ],
          [
            4.15,
            2.65,
            0.0
          ],
          [
            4.15,
            2.6,
            0.0
          ],
          [
            4.1,
            2.55,
            0.0
          ],
          [
            4.0,
            2.55,
            0.0
          ],
          [
            4.0,
            2.5,
            0.0
          ],
          [
            3.95,
            2.45,
            0.0
          ],
          [
            3.95,
            2.35,
            0.0
          ],
          [
            3.9,
            2.3,
            0.0
          ],
          [
            3.85,
            2.3,
            0.0
          ],
          [
            3.75,
            2.2,
            0.0
          ],
          [
            3.6,
            2.2,
            0.0
          ],
          [
            3.55,
            2.25,
            0.0
          ],
          [
            3.55,
            2.45,
            0.0
          ],
          [
            3.6,
            2.5,
            0.0
          ],
          [
            3.6,
            2.55,
            0.0
          ],
          [
            3.65,
            2.6,
            0.0
          ],
          [
            3.65,
            2.75,
            0.0
          ],
          [
            3.4,
            2.75,
            0.0
          ],
          [
            3.3,
            2.65,
            0.0
          ],
          [
            3.2,
            2.65,
            0.0
          ],
          [
            3.15,
            2.7,
            0.0
          ],
          [
            3.05,
            2.7,
            0.0
          ],
          [
            3.05,
            2.4,
            0.0
          ],
          [
            3.0,
            2.35,
            0.0
          ],
          [
            2.95,
            2.35,
            0.0
          ],
          [
            2.95,
            2.25,
            0.0
          ],
          [
            3.0,
            2.25,
            0.0
          ],
          [
            3.05,
            2.2,
            0.0
          ],
          [
            3.05,
            2.1,
            0.0
          ],
          [
            3.0,
            2.05,
            0.0
          ],
          [
            3.0,
            1.95,
            0.0
          ],
          [
            2.95,
            1.9,
            0.0
          ],
          [
            2.75,
            1.9,
            0.0
          ],
          [
            2.7,
            1.95,
            0.0
          ],
          [
            2.7,
            2.3,
            0.0
          ],
          [
            2.55,
            2.3,
            0.0
          ],
          [
            2.45,
            2.4,
            0.0
          ],
          [
            2.45,
            2.45,
            0.0
          ],
          [
            2.35,
            2.45,
            0.0
          ],
          [
            2.3,
            2.5,
            0.0
          ],
          [
            2.3,
            3.5,
            0.0
          ],
          [
            2.25,
            3.55,
            0.0
          ],
          [
            2.15,
            3.55,
            0.0
          ],
          [
            2.15,
            2.8,
            0.0
          ],
          [
            2.1,
            2.75,
            0.0
          ],
          [
            1.85,
            2.75,
            0.0
          ],
          [
            1.85,
            2.5,
            0.0
          ],
          [
            1.8,
            2.45,
            0.0
          ],
          [
            1.65,
            2.45,
            0.0
          ],
          [
            1.65,
            2.35,
            0.0
          ],
          [
            1.7,
            2.3,
            0.0
          ],
          [
            1.7,
            2.25,
            0.0
          ],
          [
            1.75,
            2.2,
            0.0
          ],
          [
            1.75,
            2.0,
            0.0
          ],
          [
            1.7,
            1.95,
            0.0
          ],
          [
            1.6,
            1.95,
            0.0
          ],
          [
            1.6,
            0.7,
            0.0
          ],
          [
            1.55,
            0.65,
            0.0
          ],
          [
            1.55,
            0.35,
            0.0
          ],
          [
            1.5,
            0.3,
            0.0
          ],
          [
            1.0,
            0.3,
            0.0
          ],
          [
            0.95,
            0.35,
            0.0
          ],
          [
            -0.9,
            0.35,
            0.0
          ],
          [
            -0.9,
            0.2,
            0.0
          ],
          [
            -0.85,
            0.15,
            0.0
          ],
          [
            -0.85,
            -0.15,
            0.0
          ],
          [
            -0.9,
            -0.2,
            0.0
          ],
          [
            -0.95,
            -0.2,
            0.0
          ],
          [
            -0.95,
            -0.35,
            0.0
          ],
          [
            -0.75,
            -0.35,
            0.0
          ],
          [
            -0.7,
            -0.3,
            0.0
          ],
          [
            -0.55,
            -0.3,
            0.0
          ],
          [
            -0.5,
            -0.35,
            0.0
          ],
          [
            -0.5,
            -0.45,
            0.0
          ],
          [
            -0.45,
            -0.5,
            0.0
          ],
          [
            -0.45,
            -0.7,
            0.0
          ],
          [
            -0.6,
            -0.85,
            0.0
          ],
          [
            -0.7,
            -0.85,
            0.0
          ],
          [
            -0.75,
            -0.8,
            0.0
          ],
          [
            -0.75,
            -0.75,
            0.0
          ],
          [
            -0.95,
            -0.75,
            0.0
          ],
          [
            -0.95,
            -0.8,
            0.0
          ],
          [
            -1.0,
            -0.85,
            0.0
          ]
        ],
        "direction": [],
        "app_is_custom": 0
      },
      {
        "id": "2sfjzwsRcFkR",
        "model_level": 2,
        "mssid": "0",
        "model_name": "桌子",
        "name": "房间0的桌子0",
        "content": [
          [
            5.2750001,
            1.67500007,
            0.76344258
          ],
          [
            5.07500029,
            1.47500002,
            0.76344258
          ],
          [
            1.67500007,
            1.72500002,
            0.76344258
          ],
          [
            1.625,
            1.77499998,
            0.76344258
          ],
          [
            1.875,
            2.125,
            0.76344258
          ],
          [
            4.7750001,
            2.67499995,
            0.76344258
          ],
          [
            5.2750001,
            2.42499995,
            0.76344258
          ]
        ],
        "direction": [
          [
            5.07500029,
            1.47500002,
            0.76344258
          ],
          [
            1.67500007,
            1.72500002,
            0.76344258
          ]
        ],
        "app_is_custom": 0
      },
      {
        "id": "xN4fWSGDtZw7",
        "model_level": 2,
        "mssid": "0",
        "model_name": "柜子",
        "name": "房间0的柜子1",
        "content": [
          [
            0.82499999,
            0.375,
            0.40619957
          ],
          [
            0.77500004,
            0.32500002,
            0.40619957
          ],
          [
            -0.77500004,
            0.52500004,
            0.40619957
          ],
          [
            -0.82499999,
            0.625,
            0.40619957
          ],
          [
            -0.82499999,
            0.77500004,
            0.40619957
          ],
          [
            0.82499999,
            0.47499999000000004,
            0.40619957
          ]
        ],
        "direction": [
          [
            0.77500004,
            0.32500002,
            0.40619957
          ],
          [
            -0.77500004,
            0.52500004,
            0.40619957
          ]
        ],
        "app_is_custom": 0
      },
      {
        "id": "GBeGbSJNXmjx",
        "model_level": 2,
        "mssid": "0",
        "model_name": "桌子",
        "name": "房间0的桌子3",
        "content": [
          [
            8.324999810000001,
            0.32500002,
            0.73971701
          ],
          [
            8.02500057,
            0.32500002,
            0.73971701
          ],
          [
            7.5250001,
            0.92500001,
            0.73971701
          ],
          [
            7.5250001,
            1.17500007,
            0.73971701
          ],
          [
            7.57500029,
            1.22500002,
            0.73971701
          ],
          [
            8.425000189999999,
            1.42500007,
            0.73971701
          ],
          [
            8.574999810000001,
            1.27499998,
            0.73971701
          ]
        ],
        "direction": [
          [
            8.02500057,
            0.32500002,
            0.73971701
          ],
          [
            7.5250001,
            0.92500001,
            0.73971701
          ]
        ],
        "app_is_custom": 0
      },
      {
        "id": "TJ65CFCnaJ7F",
        "model_level": 2,
        "mssid": "0",
        "model_name": "桌子",
        "name": "房间0的桌子6",
        "content": [
          [
            -0.42500001,
            -2.375,
            0.7518877399999999
          ],
          [
            -0.47499999000000004,
            -2.42499995,
            0.7518877399999999
          ],
          [
            -0.625,
            -2.42499995,
            0.7518877399999999
          ],
          [
            -0.97500002,
            -1.77499998,
            0.7518877399999999
          ],
          [
            -0.92500001,
            -1.07500005,
            0.7518877399999999
          ],
          [
            -0.47499999000000004,
            -1.125,
            0.7518877399999999
          ],
          [
            -0.375,
            -1.17500007,
            0.7518877399999999
          ]
        ],
        "direction": [
          [
            -0.375,
            -1.17500007,
            0.7518877399999999
          ],
          [
            -0.42500001,
            -2.375,
            0.7518877399999999
          ]
        ],
        "app_is_custom": 0
      },
      {
        "id": "z7nyeS2TnXDS",
        "model_level": 2,
        "mssid": "0",
        "model_name": "桌子",
        "name": "房间0的桌子8",
        "content": [
          [
            8.425000189999999,
            -2.57500005,
            0.76160175
          ],
          [
            7.9749999,
            -2.57500005,
            0.76160175
          ],
          [
            7.9250001899999996,
            -2.5250001,
            0.76160175
          ],
          [
            7.9749999,
            -1.375,
            0.76160175
          ],
          [
            8.02500057,
            -1.3250000499999999,
            0.76160175
          ],
          [
            8.27500057,
            -1.3250000499999999,
            0.76160175
          ],
          [
            8.47500038,
            -1.42500007,
            0.76160175
          ]
        ],
        "direction": [
          [
            7.9250001899999996,
            -2.5250001,
            0.76160175
          ],
          [
            7.9749999,
            -1.375,
            0.76160175
          ]
        ],
        "app_is_custom": 0
      }
    ]
  }
}
[成功] 原子能力1:机器人状态查询,耗时 0.076 秒

============ 原子能力2:头部 RGB-D 相机 ============
[bajie_sdk] 握手成功(task_id=image_query_c6583cbf),阻塞模式,剩余超时 30.0s,等待任务完成
{
  "metadata": "02_head_metadata.json",
  "rgb_npy": "02_head_rgb.npy",
  "rgb_png": "02_head_rgb.png",
  "depth_npy": "02_head_depth.npy",
  "depth_preview": "02_head_depth_preview.png"
}
[成功] 原子能力2:头部 RGB-D 相机,耗时 1.696 秒

============ 原子能力3:腕部 RGB-D 相机 ============
[bajie_sdk] 握手成功(task_id=image_query_23514c68),阻塞模式,剩余超时 30.0s,等待任务完成
{
  "metadata": "03_arm_metadata.json",
  "rgb_npy": "03_arm_rgb.npy",
  "rgb_png": "03_arm_rgb.png",
  "depth_npy": "03_arm_depth.npy",
  "depth_preview": "03_arm_depth_preview.png"
}
[成功] 原子能力3:腕部 RGB-D 相机,耗时 1.133 秒

============ 原子能力4:开放词目标检测 ============
检测目标数量: 0
[成功] 原子能力4:开放词目标检测,耗时 0.551 秒

============ 原子能力5:语音播报 ============
语音任务 ID: speech_b3ecfcff
[成功] 原子能力5:语音播报,耗时 0.001 秒

[跳过] 可选原子能力:头部俯仰: 默认禁用真实运动
[bajie_sdk] WebSocket 连接已关闭

机器人连接已断开。

演示结束:成功 6 项,失败 0 项。
证据目录:/home/zzw/project/ecovacs_bajie_sdk-main/python/evidence/atomic_demo_20260913_173803

3.为什么"定点投喂+禁区守护"更适合移动机器人

  普通定时喂食器能够按时出粮,但它固定在一个位置,通常不知道当前靠近的是哪只宠物,也无法巡查厨房或婴儿房。固定摄像头可以观察,却不能改变视角,更不能把预分装食物送到另一个房间。

  移动机器人的优势在于可以把多件事串在一起:先去储物点,再去餐位,投喂结束后退到观察位置;非用餐时,它又能按照固定路线查看家庭禁区。这样一台设备可以服务多个房间、多个固定点位,而不是每个位置都单独安装一套设备。

家庭需求 固定喂食器/摄像头 八界移动机器人方案
多个餐位 每个位置单独部署设备 按地图前往不同固定餐位
投喂前确认 通常只知道设备是否出粮 可先观察餐盒、餐位和周围空间
婴儿房与厨房禁区 需要增加多路固定摄像头 按路线切换多个边界观察点
发现宠物越界 只能推送画面或告警 可以语音提示并移动到预设引导点
轻质用品递送 不具备跨房间操作能力 机械臂与底盘组合完成取送放

  真实家庭的作息也不是永远固定:餐位可能因为打扫临时调整,厨房门可能敞开,宠物也可能在投喂时提前守在食盆旁。移动机器人方案并不是简单替代喂食器,而是在执行前加入环境检查,在执行后留下结果画面,并把投喂和区域巡查放进同一套任务记录中。对多房间、多宠物或需要婴儿房边界管理的家庭,这种组合更有价值。

伴宠的两个核心流程如下:

3.1 定点投喂流程

  为了降低散粮倾倒带来的误差,投喂物采用主人提前定量、带防洒沿和固定抓取把手的轻质预分装餐盒。宠物可以在餐盒放稳后直接进食;机器人不负责计算营养剂量或倾倒散粮,只负责把指定餐盒从固定取料架送到固定餐位。

text 复制代码
到达计划时间
  → 查询电量、告警和任务状态
  → 导航到固定取料架
  → HEAD 确认餐盒与周围空间
  → ARM 近距离复核并抓取预分装餐盒
  → 导航到固定宠物餐位
  → 确认餐位无人、无宠物贴近
  → 将餐盒放入固定托盘
  → 机械臂收回,机器人后退到观察点
  → 再次拍照,保存投喂记录

  这里强调"预分装餐盒"和"固定托盘",是为了把操作对象、抓取姿态和放置位置都限制在可重复范围内。相比让机械臂直接倾倒散粮,这种方式更容易验证,也更容易发现是否拿错、是否放稳。

3.2 家庭禁区守护流程

  禁区可以是婴儿房门口、厨房入口、阳台门或猫粮储存区。主人先在地图中设置区域边界和两个位置:一个观察点,一个劝离引导点。

text 复制代码
按计划巡查或收到区域事件
  → 导航到边界外观察点
  → HEAD 获取门口全景
  → 连续多帧确认宠物是否持续越界
  → 检查路径中是否有人、婴儿或其他宠物
  → 播放主人预设的温和提示音
  → 必要时移动到预设引导点
  → 宠物离开后停止移动并记录事件
  → 多次无效则通知主人,不继续追逐

  这一过程称为禁区劝离,它实现了驱赶目标,但不依赖高速追逐、机械臂接触或突然逼近。移动底盘负责改变站位和形成温和引导,语音负责提示,机械臂在整个过程中保持收拢。

4.伴宠的软件框架

伴宠可以分为六个模块:

text 复制代码
定时器 / 自然语言入口
          │
          ▼
任务编排器 ── 安全状态机 ── 事件与投喂记录
    │              │               │
    ▼              ▼               ▼
地图与导航      双相机与检测      SQLite / 上位机
    │              │
    └──────► 机械臂与语音执行器

4.1 任务调度层

  主人为每只宠物设置餐次、取料点、餐位和允许执行的时间窗口。定时触发后,程序生成唯一任务 ID;同一宠物、同一餐次已经完成时不会再次执行。禁区巡查也有独立的冷却时间,避免宠物在门口反复移动时连续播报。

4.2 感知与地图层

  语义地图保存"取料架""宠物餐位""婴儿房门口""厨房入口"等位置。HEAD 相机先观察整体环境,ARM 相机只在靠近固定操作点后启用。宠物和餐盒检测结果需要结合连续帧和区域关系,而不是只凭单个检测框决定是否行动。

4.3 安全状态机

  每一步都有明确前提:电量不足不离开当前安全点;路径有人或宠物时暂停;抓取对象不明确时不动机械臂;餐位被占用时等待;宠物贴近底盘或机械臂工作区时立即收回机械臂并停止本轮。

4.4 执行与记录层

  投喂任务记录餐盒 ID、取料点、餐位、任务开始和完成时间,以及放置前后的图像。禁区任务记录区域、首次发现时间、连续确认次数、提示次数和最终结果。上位机把两类任务放在同一条时间线上,方便主人回看。

  上位机不只显示一个"完成"按钮,而是把当前任务拆成可检查的步骤:是否已到取料架、目标餐盒是否明确、餐位是否空闲、机械臂是否已经收回、宠物是否离开禁区。任何一步未满足条件,都保留原因并停止继续动作。这样主人回看记录时,能区分"任务按计划完成""等待环境恢复"和"需要人工处理",而不是只看到一条笼统的失败提示。

5.需要组合哪些八界接口

环节 八界接口 用途
状态检查 eco_robotInfo / eco_refreshRobotInfo 电量、位置、告警和工作状态
地图与到点 eco_navigateToPoint / eco_navigateToSemanticArea 前往取料架、餐位或禁区观察点
小范围调整 eco_moveChassis 在确认安全后调整到预设站位
图像采集 eco_captureImages(HEAD/ARM) 获取全景和局部 RGB-D
目标检测 eco_detect_objects / eco_vlm_detect_objects 识别宠物、餐盒和固定容器候选
对准目标 eco_lookto 调整视角和机械臂观察方向
抓取复核 eco_match_obj_views / eco_pick_with_arm_review 从 HEAD 候选切换到 ARM 复核后抓取
固定点放置 eco_place_with_view / eco_place_in 将预分装餐盒放入固定托盘
语音提示 eco_robotSpeech 播放主人设置的提示语
任务控制 eco_missionControl / eco_cancelAllMissions 暂停、恢复或取消当前任务

  现有 SDK 的玩具递送和收纳示例已经给出了"拍照---检测---抓取---导航---放置"的组合方式。伴宠可以复用这套编排思路,但操作对象要改成规格统一、重量已知的预分装餐盒,放置区域也要固定化。

6.运行流程

下面按照一次完整任务的顺序展示伴宠的交互和动作流程。

  • 八界连接固定取料点、宠物餐位与家庭禁区。预分装餐盒集中放在固定取料点,宠物在另一侧固定餐位进食,取料架、机器人通道和宠物餐位保持相对固定,便于重复执行。

6.1 定点投喂

  1. 上位机显示宠物、餐次、取料架和餐位,为宠物设置餐次、取料点、固定餐位以及婴儿房、厨房等家庭禁区。

  2. 八界到达固定取料站位,先确认餐盒、取料架和周围操作空间。

  3. HEAD 视角覆盖整个取料架,用于确认目标餐盒和周围空地。

  4. 底盘保持静止,夹爪从固定把手位置抓取规格统一的预分装餐盒。

  5. 机械臂保持稳定运输姿态,移动底盘将餐盒送往固定宠物餐位。

  1. 机械臂把预分装餐盒放入尺寸匹配的固定防滑托盘,宠物停留在安全距离外。

  2. 机械臂收回后,八界后退到观察点,复核餐位并保存任务记录。

6.2 禁区守护

  1. 上位机显示婴儿房和厨房禁区;
  2. 宠物出现在婴儿房门口,机器人在边界外观察;
  3. 连续观察确认后播放温和提示音;
  4. 机器人移动到预设引导点,始终与宠物保持距离;
  5. 宠物离开边界后,机器人停止并保存事件记录;
  6. 若宠物没有离开,系统通知主人并结束自动动作。
  • 八界停在婴儿房边界外的观察点,确认宠物进入预设禁区。

  • 八界通过预设站位和温和语音提示引导宠物离开,全程不追逐、不接触宠物。

  • 投喂任务和禁区事件进入同一条时间线,记录各步骤状态和对应画面,方便主人回看。

7.扩展

7.1 多宠物与分区投喂

  不同宠物可以配置不同餐位、不同预分装餐盒和不同时间窗口。机器人在取料架读取餐盒标记,到达餐位后再确认位置。对于需要隔离进食的家庭,可以把两个餐位设置在不同房间,并通过任务顺序避免宠物互相抢食。

7.2 婴儿房边界与成年人协同

  婴儿房属于高敏感区域。机器人只在门外观察和提示,不进入婴儿近身范围,也不使用机械臂。发现宠物越界后先劝离,再通知成年人;发现婴儿、成人或障碍物处于路径中时,直接停止自动移动。

7.3 厨房与阳台巡查

  厨房可以关注操作台、垃圾桶和食品储存区,阳台则关注门口和宠物停留时间。机器人通过多个固定观察点减少单一摄像头的盲区,并将异常画面和时间记录交给主人确认。

7.4 接入 OpenClaw / Hermes

  完成工具接入后,主人可以直接说:

每天晚上六点把团子的预分装晚餐送到客厅餐位;如果它进入婴儿房或厨房,就先语音提醒并引导离开,多次无效再通知我。

  智能体负责把自然语言拆成餐次、点位、禁区和策略参数,确定性的状态机负责安全条件、任务去重、执行和记录。这样既能保留自然交互,也不会让模型临时决定机械臂和底盘动作。

8.总结

  "定点投喂+家庭禁区守护"比单纯记录是否喂过更能体现八界的产品形态:移动底盘把感知和操作带到不同房间,头部与腕部相机负责全景和局部复核,机械臂承担固定对象的递送与放置,语音和地图则让机器人参与家庭空间管理。

  关键不是让机器人追着宠物跑,而是把动作限制在预设点位、固定物品和安全距离内。定点投喂从预分装餐盒开始,禁区管理从观察、提示和温和引导开始;每一步都留下图像、任务状态和事件记录。

  当这些基础环节形成稳定闭环之后,伴宠还可以继续扩展到多宠物分区投喂、宠物用品递送、厨房巡查和家庭事件提醒。对移动操作机器人来说,这样的应用既发挥了硬件优势,也更接近真实家庭能够长期使用的服务。

#八界创造者计划 #科沃斯八界 #具身智能 #家庭机器人 #宠物科技

一起加入八界开源生态吧!八界开源社区:www.ecovacs-tech.com