〇、问题和解法
问题:家里地面上的杂物每天都要收拾好几回。狗的玩具球、揉成团的换洗衣物、拖鞋、快递包装袋,今天一样明天一样,没有固定清单,全靠弯腰捡。
解决:我拿科沃斯八界开放的 45 项原子能力重新组装,写了一个地面杂物自主清理 Skill,叫 main_tidy,已经在真机上跑通。跑起来是这样的:机器人自己从充电桩下来,导航走到客厅,原地环视一圈,把地面上的杂物全部登记在册,然后逐个转身对准、下探、夹取、投箱,掉了它自己发现、重新捡,收完自己回去充电,全程没人碰遥控器。
先看真机结果,识别、夹取、投放三个瞬间。
同一地面,清理前后的对比。
两周真机调试,填了九个坑(两个官方级的坑见第四节),其中三个确认是固件问题,已反馈官方。这篇文章写清楚四件事:这个 Skill 解决什么问题、我是怎么把 45 项原子能力拼成它的、每个坑我是怎么解的、你想复现要准备什么。所有数据、日志、代码来自真机,文末开源。
一、场景与需求:地面上出现什么收什么
官方 Demo 抓的是玩具,固定的类别、固定的筐,词表外的东西直接跳过。真实家庭的地面不这么配合:狗的玩具球、衣物团、拖鞋、快递袋,今天一个样,明天一个样。
所以第一条设计原则就定下来了:不预设物品清单。地面上出现什么收什么,训练时没见过的类别,照样该收。
需求一共四条。
- 全自动,从下桩到回充一条链跑完,无人干预
- 开放集,地面上出现什么收什么
- 安全,不碰家具、不伤宠物、收纳箱之外不放任何东西
- 失败可恢复,卡住能自己出来,掉落能自己重捡
还有一条绕不开的现实约束。家庭环境里机器人无人看守,任何一次卡死都意味着任务失败。后文一半的设计都是围着这条约束做的,先把失败之后怎么办想清楚,再谈成功率。
先说现状。当前版本(v41)在摆位约束下稳定运行:杂物位于正前方 0.45~0.6m 抓取带,收纳箱在右前方 0.6~1.3m、开口朝机器人。放宽到任意摆放、任意房间是下一阶段的事(见第六节)。先把约束收窄、把闭环做稳,我觉得是正路。
二、官方 Skill 的边界和我改了什么
2.1 官方 toy_storage 能做到哪
八界官方把"玩具收纳"封装成了标准 Skill(toy_storage)上架应用市场,流程是搜筐、搜玩具(过滤筐内已有)、臂观测、检测、抓取、投放,失败统一重试 3 次。跑是能跑通的,官方能力的底子确实扎实。
2.2 四个撑不起真实场景的边界
但跑了两轮我就开始皱眉,四个问题。
- 每轮全场重搜。每收一件东西都要主动搜索一遍全屋,一轮 60 秒起步,还靠底盘跑动扫描,收 5 件就是 5 分钟
- 只认"玩具"单标签。词表外的杂物,检测直接跳过
- 单帧检测,无记忆。这一帧框到的东西,底盘一动下一帧就不知道去哪了
- 报 SUCCESS 就是成功吗。抓取半路掉落,日志照样报成功,物品还在地上,流程却"完成"了(伏笔,5.5 揭晓,全场最坑的一个)
这四条都有真机实证,其中三条翻过车,第五节逐条展开。边界 1 的对策写在 2.3 改进表第一行。
2.3 我的改进,逐条对症
基于官方原子能力,main_tidy对这四个边界各做了一个针对性改动,另外补了两层官方没有的防御。
| # | 官方 toy_storage | 我的改法 | 解决什么 |
|---|---|---|---|
| 1 | 每轮全场重搜(60s+/轮) | 到位后原地批量登记,之后按记忆逐件取 | 搜索耗时 O(n)→O(1) |
| 2 | 单标签"玩具" | OVD 词表 + SHOE 无类别端点双端点接力 | 开放集目标 |
| 3 | 单帧检测无记忆 | 地图坐标+视线角+观察位姿三元组记忆 | 底盘位移后不丢目标 |
| 4 | 统一重试 3 次 | 错误码分类处置,各错各治 | 重试不再无脑 |
| 5 | 无假成功防御 | 掉落实锤(36897)+ 自动重捡 | “日志成功、鞋在地上” |
| 6 | 线性流程无状态 | 持物状态机,持物未投放绝不抓下一件 | 连锁翻车 |
官方 Skill 解决的是演示可行,我要解决的是真实场景里连续可靠。所有改动都在应用层,底层 45 项原子能力一个没动。能做到这一点,靠的就是八界把这些能力真正开放了出来。
三、开发环境:原子能力就在下位机里
3.1 到手前提与开发环境
前提一件事:机器人要先完成一次建图。我这套方案里的重定位、定点导航全部建在这张图上,没建图的话导航能力起不来。
我实际用的开发环境:
| 项 | 版本/值 | 说明 |
|---|---|---|
| 下位机系统 | Ubuntu 22.04(双板架构,下板开放) | SSH 进去就是完整 shell |
| 中间件 | ROS 2 Humble | 相机、雷达话题都在 |
| Python | 3.10 | 系统自带 |
| SDK | bajie-sdk==1.5.0 | pip 安装,WebSocket 连本机 |
| SSH 端口 | 2222 | 记一下,默认 22 不通 |
| 硬件 | 六自由度机械臂(0~85cm,1kg 二指夹爪,自带相机)、头部 RGB+对齐深度双目、双 RK3588 |
3.2 三步接入
第一步,网页控制台。机器人开机后在同一局域网访问控制台页面,能看到相机画面、地图、手动控制,日常调试和录检测框画面都在这里。
第二步,SSH 进下位机。
ssh-p2222Agi@<机器人IP>第三步,SDK 连接。SDK 在机器人本机上跑,连本机 WebSocket。
frombajie_sdkimportBajieRobot SDK=BajieRobot(ws_url="ws://127.0.0.1:9900/")ifnotSDK.Connect(timeout_sec=8.0):raiseSystemExit("SDK 连接失败")print(SDK.eco_robotInfo().battery.value)# 电量到这里,45 项原子能力就全是你的了。它们落在下位机里,就是一个个脚本文件,每一项都看得见、可以读。
控制台的 API 清单页能看到每个能力的调用入口和实时状态。
3.3 第一个程序:12 项运动自检
SDK 通了先别急着写业务,我写了个motion_test.py给整台机器做体检:底盘前后、原地转向、头部两档俯仰、臂复位等 12 项逐个 PASS/FAIL。这个习惯后来救了我两次,一是确认硬件状态正常再开任务,二是顺带发现了转向指令欠交付,25° 指令实际只转 14°,欠了四成多。所以 main_tidy 里所有转向都带最终校验加两次修正,这是自检测出来的。
这套’能力即脚本’的形态对开发者很友好。SDK 文档没写清的行为,直接读脚本源码就能确认,我后面好几个坑都是这么定位的。
四、整体方案:一条七步任务链
4.1 任务链
整个 Skill 是一条串行任务链。
| 步骤 | 动作 | 原子能力 |
|---|---|---|
| 1 脱桩 | 自主离开充电桩 | eco_leaveDock |
| 2 导航 | 定点导航到清理工作位 | eco_relocate+eco_navigateToPoint |
| 3 登记 | 原地批量登记全部地面目标 | eco_captureImages+eco_detect_objects+eco_computeObjectPose |
| 4 门槛 | 见到收纳箱才动手 | eco_detect_objects(容器词表) |
| 5 清理 | 复检、取景、夹取、投放,逐件执行 | eco_lookto+eco_pick+eco_place_in |
| 6 补扫 | 再登记一轮,收漏网目标 | 同步骤 3 |
| 7 回充 | 自主返回充电桩 | eco_startRecharge |
一条链跑起来是这样的。
[1/7] 脱桩 → [2/7] 重定位+定点导航 → [3/7] 两档俯仰×2帧登记 → [4/7] 见箱门槛 → [5/7] 逐件 复检→取景→精抓→投放 → [6/7] 补扫登记 → [7/7] 回充展开讲每一步实际发生什么。
步骤 3 的"登记"是全链地基:机器人原地不动,头部在两档俯仰各拍两帧,对每帧跑检测,把地面目标全部登记在册。每件东西记五样:类别、bbox、经eco_computeObjectPose解算的地图坐标(0.14°/px 像素方位角交叉校验,坏点弃用)、视线角 bearing、登记时的观察位姿。之后清理就靠这份档案干活。
步骤 4 的"门槛"是个保险:检测词表里挂了容器类标签,画面里见不到收纳箱就不开始抓,防止箱子没就位就空转。
步骤 5 的清理循环逐件执行,每个环节用的原子能力如下。
| 环节 | 干什么 | 原子能力 |
|---|---|---|
| 复检 | 按记忆找到这一件 | eco_captureImages+eco_detect_objects |
| 取景 | 对准并站进抓取带 | eco_lookto+eco_setRobotHead+ 必要时eco_moveChassis后退 |
| 精抓 | 臂观测+验身+出手 | eco_captureImages(ARM) +eco_match_obj_views+eco_pick |
| 投放 | 放进收纳箱 | eco_place_in |
全收完再登记一轮补扫,兜住漏网目标,最后回充。
这类场景应用要的是原子能力的组合,这条链每一环都是官方能力拼装,没有一行代码碰底层。整条链的设计原则是每一步都默认下一步会出错,两周调试下来,这个默认救了任务很多次。先看两个官方级的坑。
4.2 语义区域导航,SDK 和 UI 不互通
第一个坑在导航。UI 地图上明明标了"客厅",语义区域导航却稳定报错 131072。用eco_manageSemanticMap一查,SDK 侧的语义区域是 0 条。UI 上画的区域和 SDK 里的地图数据是两套东西。
绕行方案是放弃语义区域,改用工作点标定加定点导航。机器人推到理想清理位,跑一次标定脚本存下坐标,之后每次定点导航过去,稳定可用。
importjson,mathfrombajie_sdkimportPointNavigationRequest wp=json.load(open("/home/Agi/workpoint.json"))SDK.eco_navigateToPoint(PointNavigationRequest(x=float(wp["x"]),y=float(wp["y"]),yaw=math.radians(float(wp.get("yaw",0.0)))),timeout_sec=180.0)4.3 下桩死锁,cancel 都穿不透
第二个坑更凶。eco_leaveDock的表现是,物理下桩 10 秒完成(位移 0.6m),但任务句柄永久占死串行队列,后续任何运动能力全部报 8197(任务已在执行),eco_cancelAllMissions也清不掉,复现 5 次中 4 次,唯一恢复手段是整机重启。
固件级缺陷,应用层绕不过去,只能改任务编排。下桩和主任务拆成两段,第一段只负责下桩,完成后提示重启;重启后再跑第二段(导航、清理、回充),此时不在桩上,直接跳过下桩环节。
SDK.eco_leaveDock(timeout_sec=5.0)# 物理下桩约10s即完成# 固件缺陷:任务句柄占死队列,cancel 穿不透# → 本段到此为止,重启后再跑 full_mission 直接进入 导航→清理→回充代价是多一次重启,换来的是不再偶发卡死。两个坑都已反馈官方。
五、七个坑,各个都有解法
两周真机调试,大部分时间花在解决问题上。两个官方级的坑(语义导航不互通、下桩死锁)在第四节已经讲了绕法,剩下七个实现级的坑,从 v37 到 v41 三个版本逐步修掉。这一节逐个按问题、解决来写,完整代码都在第七节的仓库里。
5.1 OVD 对深色物体失明,解法是双端点接力
问题:同一画面,白色物体检测正常,深色小物一个都检不出来。OVD(开放词汇检测)对深色物体会失明。
解决:官方检测有两条推理通道,OVD 按词表认类别(我挂了 12 个中文标签),SHOE 端点做无类别检测,框出来的东西一律叫"鞋子"。做法是 OVD 为主,没有有效检出才跑 SHOE 兜底。单次检测 10.3 秒,两个端点各跑一遍耗时直接翻倍,接力就好,无脑并跑没必要。
defdetect_targets(robot,view):"""OVD 为主,无有效检出才跑 SHOE 兜底(深色小物仅SHOE可见)"""pool=[]try:ovd=list(robot.eco_detect_objects(DetectObjectsRequest(rgb_image=view.rgb_image.img,labels=CATALOG_LABELS,entry=OvdEndpoint.OVD)).items)pool=pool_filter(ovd)exceptExceptionase:print(f" [OVD异常]{str(e)[:40]}")ifpool:returnpool shoes=list(robot.eco_detect_objects(DetectObjectsRequest(rgb_image=view.rgb_image.img,labels=["鞋子"],entry=OvdEndpoint.SHOE)).items)returnpool_filter(shoes)这套做法依赖头部双视觉模组。RGB 负责纹理和颜色,对齐深度给出像素级距离,同视角互补,目标既能被认出也能被定位。
5.2 检测名字不可信,解法是外观验身
问题:SHOE 端点框出来的东西一律叫"鞋子",毛巾团会被标成 shoe_11。透明矿泉水瓶实测返回的标签是"未知物品",名字和实物对不上。按名字触发精抓(官方的eco_pick_with_arm_review拿目标名当 OVD 标签),必然拿到 0 候选。
解决:精抓阶段用eco_match_obj_views做外观验身。登记时存下目标的外观特征,精抓时做相似度匹配,比长相,不比名字。标签只是线索,外观才是身份,这个原则贯穿全链。彩蛋:匹配出的名字带"shoe"前缀就触发鞋子侧夹模式(官方同款逻辑),白色运动鞋实测 4/4。
# 精抓链自己拼:复位→瞄准→臂图检测→外观匹配→pickarm_view=robot.eco_captureImages(CameraType.ARM,timeout_sec=30.0)cands=pool_filter(detect_targets(robot,arm_view),floor=False)match=robot.eco_match_obj_views(ref_view,arm_view)# 外观嵌入相似度target=pick_candidate(cands,match,item)# 按匹配度选框robot.eco_pick(PickRequest(...))# 直接pick,绕开按名字的封装(节选,完整精抓链见仓库main_tidy.py的pick_with_retry。)
5.3 投完第一个找不到第二个,解法是三元组记忆
问题:place_in投放动作自带一次底盘位移,机器人挪了,旧框全漂,实测漂移 190px 以上。回到第二个目标按旧 bbox 复检,直接扑空。
解决:目标记忆从"一个框"升级成三元组:地图坐标、视线角 bearing、登记时的观察位姿。复检时先按里程计驶回观察位姿(恢复登记时的几何),再按当前朝向和 bearing 预测目标该出现在画面哪个像素位置,取最近者。bearing 对平移免疫,底盘挪过也认得。
deffind_target(pool,robot,t):"""按当前朝向与目标bearing预测像素x,取横向最近者(±140px门)"""yw=yaw_now(robot)b=t.get("bearing")ifywisNoneorbisNone:# 无bearing时退回bbox匹配returnnearest_item(pool,t["bbox"])cx_pred=320+norm_deg(yw-b)/PIX_DEG# 640宽画面,右=正偏best,bd=None,1e9foritinpool:d=abs((it.bbox[0]+it.bbox[2])/2-cx_pred)ifd<bd:best,bd=it,dreturnbestifbd<140elseNone配套还做了三级去重:地图距离 0.14m、bearing ±4°、同帧像素 120px,逐级合并重复目标。旧版只按 yaw ±12° 去重,同帧多个物体会被误合并成一个。
位姿解算也批量化了:旧版逐框调用eco_computeObjectPose,每次 7.8 秒,一帧 3 个目标就是 23 秒;新版一帧一次 RPC 算全部新框。
res=robot.eco_computeObjectPose([bbfor_,bbinkept],view,timeout_sec=8.0)5.4 机械臂带外不动,解法是 0.45~0.6m 抓取带
问题:目标明明在画面里,eco_pick却报 57346(无可达抓取点),机械臂纹丝不动。
解决:先做标定实验找到稳定抓取带。同一个物体摆在三个位置各抓三次:画面下沿 y≈304(约 0.42m)0/3,中部 y≈261(约 0.5m)3/3,上部 y≈214(约 0.7m)0/3。稳定抓取带 [0.45, 0.6]m,对应画面 y∈[250, 290]。带内才取景出手;带外先换低头档重拍(近物在低头档会落到画面中部,免挪动),还不行就底盘后退 0.15m 重新登记。只后退不前进,原因见 5.6。
defensure_good_framing(robot,view,item):"""带外先换低头档重拍;仍带外后退0.15m——底盘唯一允许的平移"""ifnotout_of_band(item):returnFalse,view,item set_head(robot,HEAD_DOWN)# 低头档:近物落画面中部nv=robot.eco_captureImages(CameraType.HEAD,timeout_sec=30.0)tgt=nearest_item(detect_targets(robot,nv),item.bbox)iftgtisnotNoneandnotout_of_band(tgt):returnTrue,nv,tgt# 免挪动,重拍解决robot.eco_moveChassis(-0.15,0.0,timeout_sec=60.0)# 只后退...(节选,完整实现见仓库main_tidy.py的ensure_good_framing。)
这一节还埋着两个官方接口的暗坑,一并记下。头部视图标定的地面过滤门(y1≥130 且框高≤150)误用到臂视图会把候选全杀光,臂相机俯视特写的框天然在画面上部,一套用全被杀,v38 回归实测"臂图候选恒空"就是这个。加上按名字精抓 0 候选的问题,整条精抓链只能自己拼:ARM_HOME 复位、eco_lookto瞄准、臂图检测、外观匹配、eco_pick。
5.5 掉落误报成功,解法是掉落实锤加状态机
问题:place 半路物体掉落,机械臂空着爪把整套放置动作做完,日志报 SUCCESS。eco_place_in不校验爪上负载,它只管"动作做完了",不管"东西进箱了"。
解决:三层防御。
第一层掉落实锤。place 再报 36897(未抓到物体)=爪是空的=东西半路掉了,确认掉落后原目标重新定位、重新入队,自动重捡。这里有个细节:旧版掉落后走"重新登记",结果新目标被去重逻辑当成旧目标吃掉,重捡从未真正生效,重排必须绕过去重。
第二层持物状态机。SEARCHING、CARRYING、PLACING 三个状态显式管理,铁律是持物未投放绝不抓下一件,否则掉落的第一件和正在抓的第二件会连锁翻车。补扫同理:持物终止就跳过补扫,否则会把爪里的东西当"新发现"再抓一次。
第三层两条禁令。持物期间禁止收臂,收臂的震动会把轻物甩出去(实测教训);连续三次投放失败保持持物直接停机,宁可停机等人,也不把东西放回地面。
res=place_in_box()ifres=="placed":t["status"]="done"elifres=="dropped":# place报36897 = 爪空 = 掉落实锤ift.get("drop_count",0)<=1andrelocate(t):work.append(t)# 原目标重新入队,自动重捡else:# "held":持物终止,人工介入t["held_flag"]=True5.6 误框会撞东西,解法是四条铁律
问题:贴边一个幻影框,洗地机在背景里被框成了"鞋",机器人转身抬臂,把我桌上的监控撞翻了。开放集检测在真实背景里一定会误框,误框躲不掉,能设计的是误框之后不造成伤害。
解决:四条铁律全部写进代码。
- 只认地面物:框顶 y1≥130 且框高≤150 才可能是地面小物,桌面高度的一票否决
- 居中修正上限 ±35°:要转身超过 35° 才能居中的检出,视为贴边幻影,放弃
- 收纳箱绝对免疫:名字命中容器词表直接排除,永不进抓取池
- 底盘只后退不前进:朝目标前进这个动作被整个删掉,因为它压过毛巾、撞过箱子(两次事故换来的铁律)。需要靠近,就用"驶回已知安全位姿"代替"朝未知目标前进"
defpool_filter(items,floor=True):"""抓取池:容器免疫 + 基本尺寸 + 地面物过滤(floor门仅头部视图)"""out=[itforitinitemsifnotany(cinit.nameforcinCONTAINER_LABELS)and(it.bbox[2]-it.bbox[0])>=20and(it.bbox[3]-it.bbox[1])>=20]iffloor:out=[itforitinoutifis_floor_item(it.bbox)]returnout这套在应用层再加保险的做法有官方定调背书。八界开放体系分 L1/L2/L3 三层,45 项高层原子能力已内置封装、无碰本体风险,只有直接操作 ROS 节点的 L3 层有风险。本文全程只用原子能力层,四条铁律是官方边界内再加的保险丝。
5.7 检测偏慢,解法是三项提速
问题:每次 OVD 检测 10.3 秒,拆开一看,其中 8 秒是yolo_label预处理服务超时,下位机日志里每次都有这行。
yolo_label preprocess unavailable, fallback to original labels: timed out有意思的是 8 个标签和 19 个标签耗时一样,瓶颈在预处理服务起不来,检测管线退回原始路径硬扛。这 8 秒是固件层的问题,已反馈官方,应用层改不了。
解决:让这 10.3 秒少发生几次。
- 批量位姿解算:逐框 7.8 秒改成一帧一次 RPC(5.3 已述)
- OVD-first 单端点推理:OVD 有检出就不跑 SHOE,省一整次 10.3 秒
- 俯仰缓存:同姿态重复的
set_head直接跳过,每次省 1.2 秒
应用层能优化的都优化了,剩下的天花板在固件。对开源机器人来说,把问题定位清楚并反馈官方,本身就是生态贡献的一部分。
5.8 插曲:8206 充电互锁,重启解决
某天机器人明明站在客厅地上,所有运动能力却全部报 8206"机器正在充电",isCharge标志却是 0,状态标志和互锁逻辑自相矛盾。
refresh后 isCharge: 0 battery: 100 moveChassis 拒绝: code=8206, msg=机器正在充电 setHead 拒绝: code=8206, msg=机器正在充电 再读 isCharge: 0 ← 标志与互锁自相矛盾,refresh无法清除SDK 侧无解,整机重启后全绿。
relocate: SUCCESS [0x00000000] setHead ok capture ok: (400, 640, 3) position: (0.15, -0.5, 0.038)运维经验一句话:遇到大面积 8206 且实体并未在桩上,先重启再排查,别跟状态标志较劲(与 4.3 的下桩死锁同类)。
六、实测:数据、盲区与边界
同一布景、同一 v41 版本,六种代表性物体逐个实测。
| 物体 | 结果 | 说明 |
|---|---|---|
| 黄色毛巾团 | ✅ 收纳入箱 | 全链演示:识别→夹取→投放(见图1) |
| 粉色薄布团 | ✅ 收纳入箱 | OVD 词表命中 |
| 深色圆环 | ✅ 收纳入箱 | SHOE 端点兜底命中 |
| 白色运动鞋 | ✅ 收纳入箱 | 侧夹模式,实测 4/4 |
| 透明矿泉水瓶 | ❌ 自动跳过 | 深度失效 41223 + RGB 低对比 |
| 平铺鞋垫 | ❌ 自动跳过 | 薄物抓取失败 |
6 个物体是单轮小样本,用来看能力边界,别当成统计意义上的成功率。
两个盲区值得单独看。平铺鞋垫太薄,夹爪闭合高度以下的物体抓不起来;透明瓶在 RGB 视角下和地砖几乎零对比度,深度图在其表面失效(41223),视觉和深度双重失效,物理原因决定,属于当前传感器方案的原理性边界。同一块毛巾,团起来稳稳收走,摊平了就抓不起来,薄物都在盲区里。
有盲区不影响能干什么的结论,但边界必须写进文档。main_tidy对这类物体识别后主动跳过,深度无效的目标不进抓取池,不卡死也不乱抓。
要复现的话,先跟机器人签一份"摆位合同",这是 v41 稳定运行的前提。
- 杂物在正前方 0.45~0.6m 抓取带内,件间距 20cm 以上
- 收纳箱在右前方 0.6~1.3m,开口朝机器人,落在头部视野里
- 背景尽量收干净,工作中的洗地机就是幻影鞋制造机
- 电量 30% 以上
当前版本 v41 的摆位约束示意如下,这是工程现状,终态在下面的迭代路线里。
迭代路线按优先级排三件事:抓取带约束放宽(任意位置加自主趋近)、透明物检测(HSV 标签色 seed 区域生长)、多房间任务编排。
七、开源与复现
本 Skill 已提交至八界官方 Skill 共享仓库,PR 编号 merge_requests/7(宿主仓库gitcode.com/ecovacs-bajie/ecovacs-bajie-csdn),评审中。全部代码在我的分支feat/yuanhao-floor-tidy上,仓库路径gitcode.com/gcw_rB4YDQD0/ecovacs-bajie-csdn,合并后源码进入官方仓库的yuanhao-floor-tidy/目录。
分支里两份文档建议先看:yuanhao-floor-tidy/README.md,含与官方 toy_storage 的对比、反应式架构说明和六物体实测表;yuanhao-floor-tidy/references/error-codes.md,36897、41223、57346、8197 及四个已反馈官方的坑的速查表。
合并后源码会固定在官方仓库的yuanhao-floor-tidy/目录,上面分支路径长期有效。完整结构如下。
yuanhao-floor-tidy/ ├── README.md # 功能、摆位约束、错误码表、能力边界 ├── SKILL.md # Agent 调用规范(意图→命令对照) ├── requirements.txt # bajie-sdk==1.5.0 ├── references/ │ └── error-codes.md # 错误码速查 + 已知固件问题 └── scripts/ ├── main_tidy.py # 原地清理核心(v41 稳定版) ├── full_mission.py # 全链:脱桩→导航→清理→回充(两段式) ├── scene_check.py # 30 秒布景预检(零运动) ├── motion_test.py # 12 项运动自检 ├── set_workpoint.py # 清理工作点标定 └── soft_reset.py # 任务队列软恢复复现只要两步,SSH 进八界下位机后依次执行。
python3 scripts/scene_check.py# 布景预检,输出"布景合格✓"python3 scripts/main_tidy.py# 原地清理想按你家客厅调整的话,这几个参数都在main_tidy.py顶部的配置区,改完即用。
| 参数 | 当前值 | 含义 |
|---|---|---|
| 抓取带 | [0.45, 0.6] m | 目标必须落在这个距离带才出手 |
| 检测词表 | 12 个中文标签 | 按你家常见杂物增删 |
| 复检匹配门 | ±140px | bearing 预测位置与检测框的匹配窗口 |
| 居中修正上限 | ±35° | 超出视为贴边幻影,放弃 |
| 地面物过滤 | y1≥130 且框高≤150 | 头部视图的地面物判据 |
| 像素方位角 | 0.14°/px | bearing 与像素换算系数 |
| 三级去重 | 0.14m / ±4° / 120px | 地图、视线角、像素逐级合并 |
| 后退步长 | 0.15m | 带外自纠的唯一底盘动作 |
| 电量门槛 | 30% | 低于可能被拒任务,先充电 |
scene_check.py单独说一句。它用零运动的方式预检布景,看抓取带内有没有目标、收纳箱可不可见,30 秒内给出能不能跑的结论。开拍前跑一次,省得正式跑三轮才发现箱子摆远了。motion_test.py就是 3.3 那个体检脚本。这两个工具都是被真机逼出来的,也送给后面要复现的人。
八、写在最后
两周体验期结束,机器人已寄回。回头看,这次开发最值钱的产出有三条经验。
- 原子能力的价值在组合,更在组合之后的防御。45 项能力单独看都顺手,串成 7 步任务链后,错误处理代码占了全项目的近一半。真实场景的可靠性要靠设计,demo 给不了
- 遇到固件级缺陷,绕行设计加反馈官方是应用开发者的正解。下桩死锁(dock_down)、yolo_label 超时、充电互锁卡死,三个固件缺陷,加上 place 不校验负载这处接口设计缺陷,全部反馈官方并附了复现路径
- 把边界写明白,后面的人拿到的才是能用的方案。盲区、摆位约束、小样本口径全部写明,别人不用重踩一遍坑
给官方的两条建议,一并放进这篇(问题细节前面都写了):语义区域的 SDK 层和 UI 层尽早互通,"地图上看得见、SDK 查不到"对新手非常劝退;yolo_label预处理服务值得加一个常驻守护,一次超时让每次检测多背 8 秒。
八界开源社区:www.ecovacs-tech.com
参赛落款:本文为八界开源共创大赛 B 赛道(创意场景应用)参赛作品。代码与数据全部来自真机实测,欢迎 Fork 与修改,转载请注明出处。
附录:错误码速查表
调试期间整理的完整错误码对照,已随 Skill 开源(references/error-codes.md)。
| 码 | 含义 | 触发场景 | 对策 |
|---|---|---|---|
| 36897 | 未抓到物体 | 没夹住 / place 爪空 | 刷新参考重试;掉落重排重收 |
| 41223 | 深度图无效 | 深色 / 贴边 / 透明 | 后退 0.15m 重拍 |
| 57346 | 无可达抓取点 | 带外 / 姿态受限 | 跳过(不前进) |
| 36864 | 机械臂执行失败 | 投放半路失败 | 重试 |
| 8197 | 任务已在执行 | 队列被占 | soft_reset 清队列;无效则重启 |
| 131072 | 语义区域导航失败 | SDK/UI 地图层不互通 | 定点导航绕行 |
| 8206 | 状态未刷新 / 互锁 | robotInfo 缓存态 | 先 refresh;充电互锁卡死则重启 |