
目录
- 1.八界提供了哪些硬件和开发能力
- 2.先打通数据采集:让机器人真正"看见"家庭环境
-
- [2.1 安装 SDK 与连接机器人](#2.1 安装 SDK 与连接机器人)
- [2.2 采集头部和腕部 RGB-D](#2.2 采集头部和腕部 RGB-D)
- [2.3 原子技能自测示例](#2.3 原子技能自测示例)
- 3.为什么"定点投喂+禁区守护"更适合移动机器人
-
- [3.1 定点投喂流程](#3.1 定点投喂流程)
- [3.2 家庭禁区守护流程](#3.2 家庭禁区守护流程)
- 4.伴宠的软件框架
-
- [4.1 任务调度层](#4.1 任务调度层)
- [4.2 感知与地图层](#4.2 感知与地图层)
- [4.3 安全状态机](#4.3 安全状态机)
- [4.4 执行与记录层](#4.4 执行与记录层)
- 5.需要组合哪些八界接口
- 6.运行流程
-
- [6.1 定点投喂](#6.1 定点投喂)
- [6.2 禁区守护](#6.2 禁区守护)
- 7.扩展
-
- [7.1 多宠物与分区投喂](#7.1 多宠物与分区投喂)
- [7.2 婴儿房边界与成年人协同](#7.2 婴儿房边界与成年人协同)
- [7.3 厨房与阳台巡查](#7.3 厨房与阳台巡查)
- [7.4 接入 OpenClaw / Hermes](#7.4 接入 OpenClaw / Hermes)
- 8.总结
家里有宠物之后,喂食和区域管理往往是两件需要反复处理的小事:到时间要把食物放到固定餐位,宠物靠近厨房、婴儿房或阳台等区域时,又需要及时提醒和引导。如果只依靠一个固定喂食器,它只能照顾设备所在的位置;如果只安装摄像头,又只能看见问题,无法移动到现场。
八界是一台由移动底盘 、六自由度机械臂 、二指夹爪 和双 RGB-D 相机组成的移动操作机器人。它可以在不同房间之间移动,也可以从远近两个视角观察环境,并在固定、受控的条件下操作轻质物品。基于这些特点,我把"伴宠"重新定义为两个互相关联的家庭任务:
- 宠物定点投喂:在设定时间取用预分装食物,移动到固定餐位,完成放置并记录结果;
- 家庭禁区守护:巡查婴儿房、厨房等预设边界,发现宠物持续进入后,通过语音和预设移动路线进行非接触劝离,同时通知主人。
这两个任务共享同一套地图、视觉、状态检查和事件记录,因此比单独做一个提醒工具更能发挥移动机器人的优势。
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 定点投喂
-
上位机显示宠物、餐次、取料架和餐位,为宠物设置餐次、取料点、固定餐位以及婴儿房、厨房等家庭禁区。

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

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

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

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

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

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

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

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

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

7.扩展
7.1 多宠物与分区投喂
不同宠物可以配置不同餐位、不同预分装餐盒和不同时间窗口。机器人在取料架读取餐盒标记,到达餐位后再确认位置。对于需要隔离进食的家庭,可以把两个餐位设置在不同房间,并通过任务顺序避免宠物互相抢食。
7.2 婴儿房边界与成年人协同
婴儿房属于高敏感区域。机器人只在门外观察和提示,不进入婴儿近身范围,也不使用机械臂。发现宠物越界后先劝离,再通知成年人;发现婴儿、成人或障碍物处于路径中时,直接停止自动移动。
7.3 厨房与阳台巡查
厨房可以关注操作台、垃圾桶和食品储存区,阳台则关注门口和宠物停留时间。机器人通过多个固定观察点减少单一摄像头的盲区,并将异常画面和时间记录交给主人确认。
7.4 接入 OpenClaw / Hermes
完成工具接入后,主人可以直接说:
每天晚上六点把团子的预分装晚餐送到客厅餐位;如果它进入婴儿房或厨房,就先语音提醒并引导离开,多次无效再通知我。
智能体负责把自然语言拆成餐次、点位、禁区和策略参数,确定性的状态机负责安全条件、任务去重、执行和记录。这样既能保留自然交互,也不会让模型临时决定机械臂和底盘动作。
8.总结
"定点投喂+家庭禁区守护"比单纯记录是否喂过更能体现八界的产品形态:移动底盘把感知和操作带到不同房间,头部与腕部相机负责全景和局部复核,机械臂承担固定对象的递送与放置,语音和地图则让机器人参与家庭空间管理。
关键不是让机器人追着宠物跑,而是把动作限制在预设点位、固定物品和安全距离内。定点投喂从预分装餐盒开始,禁区管理从观察、提示和温和引导开始;每一步都留下图像、任务状态和事件记录。
当这些基础环节形成稳定闭环之后,伴宠还可以继续扩展到多宠物分区投喂、宠物用品递送、厨房巡查和家庭事件提醒。对移动操作机器人来说,这样的应用既发挥了硬件优势,也更接近真实家庭能够长期使用的服务。
#八界创造者计划 #科沃斯八界 #具身智能 #家庭机器人 #宠物科技
一起加入八界开源生态吧!八界开源社区:www.ecovacs-tech.com