上层 Agent ↔ 机械臂后端服务 的 HTTP 接口契约(OpenAPI3.1)
python
# arm-agent-workflow API 契约 v0.1 ------ 第三周 D1 产物
# OpenAPI 3.1(JSON Schema 2020-12 方言)
openapi: 3.1.0
info:
title: arm-agent-workflow API
version: 0.1.0
description: |
机械臂上层 Agent 契约 v0.1。
统一错误响应四字段:code / message / retryable / details;
每个响应带 X-Request-ID;除 /healthz、/ 与 /static/* 外均需 X-API-Key。
servers:
- url: http://localhost:5000
security:
- ApiKeyAuth: []
tags:
- name: arm
description: 机械臂动作与状态
- name: voice
description: 语音/文本指令
- name: system
description: 健康检查
paths:
/healthz:
get:
tags: [system]
operationId: healthz
summary: 公开健康检查(无需 API Key)
security: []
responses:
"200":
description: ok
content:
application/json:
schema:
$ref: "#/components/schemas/Healthz"
/v1/arm/status:
get:
tags: [arm]
operationId: getArmStatus
summary: 查询机械臂状态
responses:
"200":
description: 当前状态
content:
application/json:
schema:
$ref: "#/components/schemas/ArmStatus"
"401":
$ref: "#/components/responses/Unauthorized"
/v1/arm/actions:
post:
tags: [arm]
operationId: armAction
summary: 下发机械臂动作
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ActionRequest"
examples:
gripper_close:
summary: 关闭夹爪
value:
action: gripper_close
move_to_pose:
summary: 移动到 (0.15, 0, 0.20) m
value:
action: move_to_pose
x: 0.15
y: 0.0
z: 0.20
speed: 0.3
estop:
summary: 急停
value:
action: estop
responses:
"200":
description: 执行结果
content:
application/json:
schema:
$ref: "#/components/schemas/ArmActionResult"
examples:
ok:
summary: 成功
value:
success: true
message: ok
action: gripper_close
busy:
summary: 机械臂忙(可重试)
value:
success: false
code: ARM_BUSY
message: 机械臂忙,请稍后再试
retryable: true
details: {}
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"500":
$ref: "#/components/responses/InternalError"
/v1/voice:
post:
tags: [voice]
operationId: voiceCommand
summary: 语音/文本指令 → 场景闭环(快路/抓放/基础动作)
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/VoiceRequest"
examples:
grasp:
summary: 抓放闭环
value:
text: 把红色方块放到左侧箱
nod:
summary: 基础动作
value:
text: 点头
home:
summary: 快路回原点
value:
text: 回原点
responses:
"200":
description: 场景执行报告
content:
application/json:
schema:
$ref: "#/components/schemas/VoiceReport"
"401":
$ref: "#/components/responses/Unauthorized"
components:
securitySchemes:
ApiKeyAuth:
type: apiKey
in: header
name: X-API-Key
schemas:
Error:
type: object
required: [code, message, retryable, details]
properties:
code:
type: string
example: INVALID_TASK
message:
type: string
example: 请求体必须是 JSON 对象
retryable:
type: boolean
example: false
details:
type: object
ActionRequest:
type: object
required: [action]
properties:
action:
type: string
enum: [gripper_open, gripper_close, move_to_pose, home, enable, estop]
example: gripper_close
x: { type: number, description: 目标 x(米,move_to_pose 用) }
y: { type: number, description: 目标 y(米) }
z: { type: number, description: 目标 z(米) }
speed: { type: number, default: 0.3, minimum: 0.05, maximum: 1.0 }
on: { type: boolean, description: enable 动作使用 }
ArmActionResult:
type: object
required: [success, message]
properties:
success: { type: boolean }
message: { type: string }
action: { type: string }
code: { type: string, description: 失败时的业务码(如 ARM_BUSY / STEP_FAILED) }
retryable: { type: boolean }
details: { type: object }
ArmStatus:
type: object
properties:
state: { type: string, enum: [idle, moving, error, estopped] }
position:
type: array
minItems: 3
maxItems: 3
items: { type: number }
euler:
type: array
minItems: 3
maxItems: 3
items: { type: number }
gripper_open: { type: boolean }
joints:
type: array
items: { type: number }
arm_enabled: { type: boolean }
motion_status: { type: integer, enum: [0, 1, 2] }
error_message: { type: string }
motor_faults:
type: array
items: { type: integer }
VoiceRequest:
type: object
required: [text]
properties:
text:
type: string
minLength: 1
example: 把红色方块放到左侧箱
VoiceReport:
type: object
properties:
result: { type: string, example: SUCCESS }
mode: { type: string, example: joint_sim_demo }
task: { type: object }
steps:
type: array
items: { type: string }
message: { type: string }
Healthz:
type: object
properties:
status: { type: string, example: ok }
backend: { type: string, example: ros2 }
arm_state: { type: string, example: idle }
responses:
BadRequest:
description: 请求体非法
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
Unauthorized:
description: 缺失或无效 API Key
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
InternalError:
description: 系统内部错误(不泄露堆栈)
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
整体分层:
- 外部输入层:语音 / 文本指令(
/v1/voice) - 底层动作层:原始机械臂指令(
/v1/arm/*) - 观测层:状态查询
/v1/arm/status - 运维层:健康检查
/healthz
整体数据流:
用户自然语言文本 → POST /v1/voice → Agent业务层解析任务 → 内部调用 /v1/arm/actions下发底层动作 → 驱动机械臂硬件 → 返回执行报告VoiceReport
1、安全机制
security:
- ApiKeyAuth: []
- 除了
/healthz,所有接口请求头必须携带X‑API‑Key,401 鉴权失败走统一 Error 结构。 - 每个 HTTP 响应头携带
X‑Request‑ID,用于链路追踪、排查日志。 - 统一错误体固定 4 字段:
code/message/retryable/details,所有 400/401/500 复用这套 schema。
2、接口逐个拆解链路
① /healthz GET 健康探针
用途:监控、k8s 存活探针、客户端预检测服务是否活着 不需要 API‑Key。 返回:
{"status":"ok","backend":"ros2","arm_state":"idle"}
arm_state直接透传机械臂当前状态,客户端可以在调用动作前先探活。
② /v1/arm/status GET【观测接口】
只读,拿机械臂全量实时状态 返回
ArmStatus对象:
state:状态枚举idle/moving/error/estopped空闲 / 运动中 / 报错 / 急停position:笛卡尔坐标 x,y,z 米euler:姿态欧拉角gripper_open:夹爪开闭布尔joints:各关节角度数组arm_enabled:驱动器是否使能motor_faults:电机故障码
使用场景:
- Agent 在下发动作前先拉取状态:如果
state=moving就不要下发新动作,返回ARM_BUSY(retryable=true) - UI 前端实时展示机械臂位姿面板
③ /v1/arm/actions POST【底层原始动作接口】
裸动作接口,不做业务逻辑,只下发硬件原语
ActionRequest支持动作枚举:gripper_open/gripper_close/move_to_pose/home/enable/estop
| action | 参数说明 |
|---|---|
move_to_pose |
必须附带 x,y,z;speed 速度 0.05‑1.0 m/s |
enable |
附带on:true/false使能 / 失能电机 |
estop |
急停,立刻截断运动 |
返回 ArmActionResult
{
"success":true,
"message":"ok",
"action":"gripper_close",
"code":"ARM_BUSY",
"retryable":true,
"details":{}
}
重点:
retryable业务标记:
retryable=true:比如机械臂忙,客户端可以隔一段时间重试retryable=false:参数非法、硬件故障,不要重试
调用链路: Agent / 客户端 → POST/v1/arm/actions→ service 层转发 ROS2 驱动 → 机械臂执行 → 返回结果。
④ /v1/voice POST【高层业务闭环接口,核心入口】
{"text":"把红色方块放到左侧箱"}
这是面向自然语言的高层接口,不直接控制硬件。 内部处理链路:
-
接收自然语言文本
text -
内部 Agent 解析语义,拆解成一连串底层动作序列 plaintext
1. move_to_pose靠近方块 2. gripper_close抓取 3. move_to_pose移动到箱子 4. gripper_open放下 -
内部循环调用本服务的
/v1/arm/actions接口执行每一步原语 -
收集每一步执行结果,组装成
VoiceReport返回给调用方
VoiceReport返回结构:
{
"result":"SUCCESS",
"mode":"joint_sim_demo",
"task":{},
"steps":["移动到目标","闭合夹爪","移动到投放点","打开夹爪"],
"message":"任务完成"
}
steps:把拆解的每一步暴露出来,方便前端展示进度、日志调试result:SUCCESS / FAILED
⚠️ 边界区分(非常关键)
/v1/arm/actions:原语层,你告诉我做什么动作,我直接发给硬件;不理解语义。/v1/voice:任务层,你说人话,服务内部自己拆成原语序列调用 arm 接口。
完整端到端业务时序(举例:指令 "把红色方块放到左侧箱")
客户端
↓ POST /v1/voice {"text":"把红色方块放到左侧箱"}
arm‑agent‑workflow服务
├─语义解析:拆解为一组动作序列
├─内部调用GET /v1/arm/status,确认机械臂state=idle
├─内部 POST /v1/arm/actions move_to_pose(靠近方块)
├─内部 POST /v1/arm/actions gripper_close
├─内部 POST /v1/arm/actions move_to_pose(箱子位置)
├─内部 POST /v1/arm/actions gripper_open
└─收集全部步骤,组装 VoiceReport 返回
客户端 ← VoiceReport(steps数组看到每一步发生了什么)
错误流转逻辑
- 鉴权失败 → 401 返回统一 Error schema
retryable=false - 参数格式错误 →400 BadRequest
- 机械臂正在运动,调用
/v1/arm/actions→返回success:false code=ARM_BUSY retryable=true,上层可以重试 - 硬件故障 →
retryable=false,上层不能重试,需要人工介入。
架构分层总结(对应你项目 arm‑agent‑workflow)
| 层级 | API | 职责 |
|---|---|---|
| 任务层 | /v1/voice |
自然语言解析、任务编排、多步骤串联 |
| 动作原语层 | /v1/arm/actions |
单条硬件动作下发 |
| 状态观测层 | /v1/arm/status |
读取位姿、故障、夹爪状态 |
| 探针层 | /healthz |
服务存活检测 |
demo_contract_validate.py
①openapi-spec-validator 整体校验 ②每个示例载荷用 jsonschema(2020-12) 校验
python
#!/usr/bin/env python3
"""demo_contract_validate.py ------ 校验 contracts/openapi.yaml(第三周 D1)。
1) OpenAPI 3.1 规范整体校验(openapi-spec-validator)
2) 每个示例载荷(requestBody/responses examples)用 jsonschema(2020-12) 校验
用法:python3 scripts/demo_contract_validate.py
"""
from __future__ import annotations
import pathlib
import sys
import yaml
from jsonschema import Draft202012Validator
from openapi_spec_validator import validate_spec
ROOT = pathlib.Path(__file__).resolve().parent.parent
SPEC_PATH = ROOT / "contracts" / "openapi.yaml"
def _resolve(schema: dict, schemas: dict) -> dict:
"""扁平 $ref 解析:#/components/schemas/<Name>"""
if isinstance(schema, dict) and "$ref" in schema:
ref = schema["$ref"]
if ref.startswith("#/components/schemas/"):
name = ref.split("/")[-1]
if name in schemas:
return schemas[name]
raise KeyError(f"未找到 schema: {ref}")
return schema
def _collect_examples(spec: dict):
"""收集 (位置, schema, 示例载荷)。"""
schemas = spec.get("components", {}).get("schemas", {})
examples: list[tuple[str, dict, dict]] = []
def walk_media(where: str, media: dict):
schema = _resolve(media.get("schema", {}), schemas)
for name, ex in (media.get("examples") or {}).items():
if isinstance(ex, dict) and "value" in ex:
examples.append((f"{where}.{name}", schema, ex["value"]))
if "example" in media:
examples.append((f"{where}.example", schema, media["example"]))
for path, methods in spec.get("paths", {}).items():
if not isinstance(methods, dict):
continue
for method, op in methods.items():
if not isinstance(op, dict):
continue
rb = op.get("requestBody")
if isinstance(rb, dict):
for mime, media in (rb.get("content") or {}).items():
walk_media(f"{method.upper()} {path} request", media)
for status, resp in (op.get("responses") or {}).items():
if not isinstance(resp, dict):
continue
for mime, media in (resp.get("content") or {}).items():
walk_media(f"{method.upper()} {path} {status}", media)
return examples
def main() -> int:
spec = yaml.safe_load(SPEC_PATH.read_text(encoding="utf-8"))
validate_spec(spec)
print(f"✅ 规范校验通过(openapi={spec.get('openapi')})")
examples = _collect_examples(spec)
fails = 0
for where, schema, value in examples:
errors = sorted(Draft202012Validator(schema).iter_errors(value), key=lambda e: list(e.path))
if errors:
fails += 1
print(f"❌ {where}: {errors[0].message}(path={list(errors[0].path)})")
else:
print(f"✅ {where}: 通过")
print(f"\n示例载荷 {len(examples)} 个,失败 {fails} 个")
return 1 if fails else 0
if __name__ == "__main__":
sys.exit(main())
作用 :专门校验你写的 openapi.yaml API 契约文件,干两件大事
- 检查 YAML 本身是不是合法 OpenAPI3.1 文档(语法、关键字不能写错)
- 把 YAML 里面写的所有
examples示例 JSON,拿对应的 Schema 模型校验:示例数据是否符合接口定义
业务背景:你在 openapi 里面写了很多 request/response 的例子,人眼看容易错;这个脚本自动化跑一遍,防止契约文档和实际样例对不上,后端写代码就不会被文档坑。
文件位置:scripts/demo_contract_validate.py,读取 contracts/openapi.yaml
整体执行流程(从上到下)
plaintext
main()入口
↓
1.读取yaml文件 → 解析成python字典spec
↓
2.openapi_spec_validator.validate_spec(spec) 【第一层校验:OpenAPI文档规范】
↓
3._collect_examples(spec) 遍历整个openapi文档,收集全部示例
↓
4.循环每一组 (位置, schema模板, 示例数据)
↓
5.Draft202012Validator 用Schema校验示例载荷
↓
6.打印成功/失败,统计失败数量,返回退出码
逐个函数通俗解释
1. 常量部分
python
运行
ROOT = pathlib.Path(__file__).resolve().parent.parent
SPEC_PATH = ROOT / "contracts" / "openapi.yaml"
拿到脚本上级目录,定位契约文件,兼容 WSL/linux,不用写死绝对路径。
2. _resolve(schema, schemas) 解析$ref引用
OpenAPI 大量使用
$ref: "#/components/schemas/ArmStatus"复用模型。
- 输入:遇到带
$ref的字典 - 逻辑:把引用地址截取,从
components/schemas字典取出真实 schema 返回 - 通俗讲:解引用,把别名替换成真实模型定义
局限:只处理顶层
#/components/schemas,不处理嵌套深层 ref,够用,简单实现,不是完整 OpenAPI 解析器。
如果找不到引用的 schema →抛 KeyError,直接脚本报错。
3. _collect_examples(spec) 核心收集函数
输出列表:[(位置字符串, schema模型, 示例数据)]
plaintext
examples: list[tuple[str, dict, dict]] = []
where:字符串标记这个示例来自哪里,例如POST /v1/arm/actions request.gripper_close,报错的时候方便定位 yaml 哪一块出问题schema:对应的 json schema 模型(ActionRequest / ArmActionResult 等)value:example 里面写的样例 json
内部嵌套函数 walk_media(): 专门处理content -> application/json下面的内容:
- 调用
_resolve解开$ref拿到真实 schema - 读取
examples:{}(多个命名示例,比如 gripper_close、move_to_pose)取里面value - 同时兼容老写法
example:(单个示例,不带大 examples)
然后双重循环遍历整个 openapi 的 paths:
- 遍历每一个接口路径
/healthz/v1/arm/actions - 遍历方法 get/post
- 处理请求体
requestBody,收集请求的 examples - 处理每个响应 200/400/401,收集返回值 examples
跑完之后,我们手上就拿到契约文档全部写出来的样例,不管是入参还是出参。
4. main () 主函数
python
运行
spec = yaml.safe_load(SPEC_PATH.read_text(encoding="utf-8"))
validate_spec(spec)
yaml.safe_load:把 yaml 文本转 python 字典;yaml 格式写错这里直接炸。validate_spec(spec):第三方库校验OpenAPI 规范- 检查
openapi:3.1.0版本关键字对不对 - paths、components 语法是否符合 OpenAPI 标准
- 如果 yaml 写错字段,这里直接抛异常终止脚本。 ✅打印:
✅ 规范校验通过
- 检查
python
运行
examples = _collect_examples(spec)
拿到全部样例列表。
循环每一条样例:
python
运行
errors = sorted(Draft202012Validator(schema).iter_errors(value), key=lambda e: list(e.path))
Draft202012Validator 就是 JSON‑Schema 2020‑12 校验器,和你 openapi 版本匹配。 iter_errors(value):拿 schema 规则,校验示例数据,产出全部错误,不会直接抛异常。
- 没有错误:打印
✅ xxx:通过 - 有错误:打印
❌,打印错误信息 + json 路径 path,告诉你样例哪里违反 schema。
最后统计:一共多少样例,失败几个。
返回退出码:
- 0:全部 ok;
- 1:有失败;
sys.exit(main())shell 可以拿到返回码,可以接入 CI 流水线,只要返回 1 CI 直接失败。
机械臂后端的契约ROS2
srv/(服务请求/响应):
MoveToPose.srv --- 末端位姿 x/y/z/roll/pitch/yaw + cartesian_path
机械臂笛卡尔空间运动请求,底层驱动层(ROS2 / 硬件 SDK)的入参出参
python
# Request: end-effector target pose
float64 x
float64 y
float64 z
float64 roll
float64 pitch
float64 yaw
# Use Cartesian path planning (linear motion)
bool cartesian_path false
# Max velocity scaling factor (0.0~1.0)
float64 velocity_scaling 1.0
# Max acceleration scaling factor (0.0~1.0)
float64 acceleration_scaling 1.0
---
# Response
bool success
string message
字段释义
-
x, y, z(float64) 末端执行器目标笛卡尔坐标,单位米。就是机械臂工具中心点要到达的空间位置。
-
roll, pitch, yaw(float64) 欧拉角,单位弧度,代表末端姿态:工具怎么 "拧、俯仰、偏转"。
xyz 决定在哪;rpy 决定工具是什么朝向。
👉 对应你 OpenAPIArmStatus里面的position:[x,y,z]、euler:[roll,pitch,yaw]。
bool cartesian_path = false
true:笛卡尔直线规划,末端在空间严格走直线;受奇异点、关节限位约束,有可能规划失败。false:关节空间规划,只关心起点终点,中间路径由关节插值,路径不一定是直线,更容易成功。
关键区别: cartesian_path=true:末端轨迹是直线; cartesian_path=false:关节平滑转动,空间轨迹是曲线。
velocity_scaling =1.0速度缩放系数,范围0.0 ~1.01.0 = 允许硬件最大速度;0.3 就是最大速度的 30%。
对应上层 API 的
speed参数,上层speed最终映射到这个字段。
acceleration_scaling =1.0加速度缩放系数,0.0~1.0控制启动、刹车快慢;1.0 硬件最大加速度,数值越小启动停止越柔和。
MoveToJoint.srv --- 6 个关节角
python
# Request: 6 joint angles in radians
float64[6] joint_angles
# Max velocity scaling factor (0.0~1.0)
float64 velocity_scaling 1.0
# Max acceleration scaling factor (0.0~1.0)
float64 acceleration_scaling 1.0
---
# Response
bool success
string message
这是底层驱动:关节角度直接运动接口,和上面笛卡尔位姿接口是两套独立下发入口。
笛卡尔:给末端 xyz+rpy;本接口:直接给 6 个关节角度(弧度)。
# Request: 6 joint angles in radians
float64[6] joint_angles
# Max velocity scaling factor (0.0~1.0)
float64 velocity_scaling 1.0
# Max acceleration scaling factor (0.0~1.0)
float64 acceleration_scaling 1.0
字段说明
- joint_angles: float646 6 轴机械臂,
[j0,j1,j2,j3,j4,j5],单位弧度。 直接指定每一个关节要转到什么角度,不需要运动学逆解。
- 优点:不会奇异点报错,不做逆解计算;
- 缺点:你要自己算好每组关节角,不知道末端会走到哪个空间位置。
典型用途:回原点 home、摆固定姿态、演示姿态序列。
-
velocity_scaling 1.0 速度缩放系数
0.0‑1.0,控制所有关节最大转动速度。0.3→ 所有关节只跑硬件最大速度的 30%。 -
acceleration_scaling 1.0 加速度缩放系数,控制关节启动、刹车的剧烈程度。
对比上一个笛卡尔接口:
- 笛卡尔:输入末端位姿,内部做逆解算出关节角;可能奇异点、工作空间越界规划失败。
- 关节角度接口:直接给关节目标,不做逆解;但要你保证角度在关节限位内。
GripperControl.srv --- open / close / half_open
Request 请求字段
action字符串枚举,3 种状态open:完全打开夹爪close:闭合夹爪(夹紧物体)half_open:半开,中间位置,用于预定位
不同硬件实现:
- 简单二值夹爪:
half_open可能不支持,驱动直接返回失败。 - 自适应 / 位置可控夹爪:可以走到半开位置。
Response 返回
success: true:夹爪指令接收下发完成,不等于物理已经到位。success: false:执行被拒绝。 典型原因:急停状态、驱动器故障、硬件不支持half_open。message:描述文本,例如gripper hardware fault/half_open not supported。
⚠️重点: 下发夹爪指令返回成功,只是命令发给硬件;上层需要读取
/v1/arm/status中gr
GripperSrv.srv --- 夹爪详细控制(角度/力矩/模式/清零)
python
# Detailed gripper control
float64 gripper_angle # Target gripper position (meters, 0.0~0.04)
float64 gripper_effort # Max effort/torque
uint8 gripper_code # 0=position mode, 1=effort mode
bool set_zero # If true, reset gripper zero position
---
int64 code # 0=success, non-zero=error code
bool status
Request 请求字段
gripper_anglefloat64,单位米,范围 0.0 ~ 0.04m
夹爪开口宽度。
0.0:完全闭合;0.04:最大张开 4 厘米。
注意单位是米,不是角度!字段命名叫
gripper_angle容易迷惑,实际是开口行程距离。
gripper_effortfloat64 最大夹持力 / 力矩上限。
- 位置模式:限制最大输出力,碰到物体不会硬顶;
- 力控模式:这就是目标夹持力。
gripper_codeuint8 模式选择
0--- position mode 位置模式 :夹爪走到gripper_angle指定开口;到达位置就停止;gripper_effort作为保护最大力。1--- effort mode 力控模式 :维持gripper_effort恒定夹持力;夹爪会自适应物体厚度,不会卡死。适合抓取易碎物品。
set_zerobooltrue= 重置夹爪机械零点。 上电夹爪机械偏移、更换夹爪爪片时使用;日常抓取任务填false。
⚠️校准零点动作不要频繁调用。
Response 返回
code int64:业务错误码,0成功;非 0 代表硬件错误。status bool:指令下发状态,true= 命令已接收下发。
同样:
status=true、code=0只是下发成功,不等于夹爪已经到达目标位置。上层依旧要读状态接口的夹爪实际状态。
GoZero.srv --- 回零
python
# Move arm to home (zero) position
bool use_mit_mode
---
int64 code
bool status
Request 请求
use_mit_modebool 是否使用 MIT 模式回零。false:普通回零,直接执行预设的原点关节角数组,关节运动到机械零点;速度受全局速度缩放限制。true:MIT 模式回零(柔顺 / 零重力模式回原点),电机进入力矩柔顺模式,缓慢归位;遇到碰撞会柔顺退让,适合开机初始化。
硬件差异:部分机械臂固件不支持
use_mit_mode=true,此时传入 true 会返回错误码。
Response 返回
code int64:0= 请求接收成功;非 0 代表错误码status bool:true= 回零指令已经下发驱动器
⚠️重点:
code=0 && status=true只代表指令下发成功,机械臂还在运动中 。 上层业务必须轮询状态接口/v1/arm/status,等待state:idle,才算 home 动作真正完成。
失败常见原因:
- 电机未使能;
- 处于急停 estopped 状态;
- 固件不支持 mit 柔顺回零模式。
Enable.srv --- 使能
python
# Enable/disable the arm
bool enable_request
---
bool enable_response
Request 请求
enable_request: booltrue:使能电机,电机上电抱闸释放,机械臂允许接收运动指令;开机之后必须先 enable,才能做 move、home、夹爪动作。false:失能电机,电机抱闸锁住,手可以手动掰动机械臂;此时所有运动指令全部拒绝执行。
业务场景:
- 系统启动流程:上电 → enable_request=true 使能机械臂
- 调试模式:enable_request=false,人手拖动机械臂示教
- 发生故障时,可失能保护
Response 返回
enable_response: booltrue:使能 / 失能执行成功false:执行失败,无法切换使能状态
⚠️注意:
enable_response=true代表状态切换指令执行完毕 ,不是异步运动,不需要轮询等待。 使能完成之后,上层调用/v1/arm/status,读取arm_enabled字段确认硬件真实使能状态。
失败常见原因:
- 硬件报错、电机故障,无法使能;
- 处于急停 estopped 状态,不允许使能。
msg/(话题消息):
ArmStatus.msg --- arm_enabled / motion_status / motor_faults / gripper_position
python
# ArmStatus.msg - Arm status feedback (adapted from Piper's PiperStatusMsg)
std_msgs/Header header
# Overall arm state
bool arm_enabled # Whether the arm is enabled and accepting commands
uint8 motion_status # 0=idle, 1=moving, 2=error
string error_message # Human-readable error description
# Per-motor mode and fault
uint8[6] motor_modes # Control mode per motor
uint8[6] motor_faults # Fault code per motor (0 = no fault)
# Joint limit flags: true means the joint has hit its position limit
bool[6] joint_at_limit
# Gripper state
float64 gripper_position # Gripper position
uint8 gripper_fault # Gripper motor fault code
1.std_msgs/Header header ROS 标准消息头,携带时间戳、frame_id。
作用:标记这份状态数据是哪个时刻采集的,做时间同步;转 HTTP 输出时可以把时间戳放到响应头
X‑Timestamp。
2.bool arm_enabled 电机总使能状态。
true:电机使能,可以接收运动指令;false:失能,不能执行运动,可手动掰动机械臂。 👉 对应 OpenAPIArmStatus.arm_enabled
3.uint8 motion_status 整机运动状态
0→ idle 空闲,可以下发新动作1→ moving 运动中,此时不要下发新动作,返回 ARM_BUSY (retryable=true)2→ error 整机故障,需要人工处理 👉 映射 OpenAPIArmStatus.state枚举idle / moving / error
4.string error_message
整机可读错误文本,例如motor 2 over current。 👉 对应 OpenAPI ArmStatus.error_message
5.uint8[6] motor_modes 6 个电机各自控制模式
数组长度固定 6,每个电机独立模式:力矩模式、位置模式等。上层业务一般只做日志展示,业务逻辑很少使用。
6.uint8[6] motor_faults 每个电机故障码
0 = 无故障;非 0 代表对应电机报错。 👉 直接映射 OpenAPI ArmStatus.motor_faults
7.bool[6] joint_at_limit
6 个关节限位标记。true代表该关节撞到硬件限位。
业务用途:状态上报给前端;如果下发运动指令后出现该标记,可以生成业务错误码
JOINT_HIT_LIMIT。
8.float64 gripper_position
夹爪实际开口距离(单位米,0.0~0.04),硬件真实反馈值,不是下发的目标值。
上层据此计算布尔值
gripper_open:例如大于 0.02 判定为打开。
9.uint8 gripper_fault 夹爪电机故障码,0 代表无故障。
💡补充:这条 ROS 消息没有携带笛卡尔 xyz/euler、joints 关节角度 。 真实 Piper 机器人:这个 ArmStatus 只反馈故障、使能、运动状态; 末端位姿、关节角度来自另外一个话题
joint_states。 所以你的 OpenAPIArmStatus结构是合并两份 ROS 话题的数据:
- ArmStatus.msg:arm_enabled、motion_status、error_message、motor_faults、joint_at_limit、夹爪状态
- JointState 话题:
joints数组、笛卡尔position、euler姿态
EndPoseEuler.msg --- 末端位姿(欧拉角)
python
# EndPoseEuler.msg - End-effector pose feedback in Euler angles
std_msgs/Header header
float64 x
float64 y
float64 z
float64 roll
float64 pitch
float64 yaw
-
std_msgs/Header headerROS 标准头,包含时间戳stamp、frame_id(基座坐标系),用来标记这组位姿的采集时刻。 -
x / y / z末端 TCP 空间坐标,单位米,相对于机械臂基座坐标系。 -
roll / pitch / yaw末端姿态欧拉角,单位弧度。
- roll:绕 X 轴翻滚
- pitch:绕 Y 轴俯仰
- yaw:绕 Z 轴偏航
这是硬件实时反馈出来的真实位姿,不是下发的目标位姿。
ArmPose.msg / PosCmd.msg --- 位姿 + 夹爪复合指令
python
# PosCmd.msg - Combined end-effector pose + gripper command
float64 x
float64 y
float64 z
float64 roll
float64 pitch
float64 yaw
float64 gripper # Gripper value (0.0=close, 1.0=fully open)
uint8 mode1 # 0=joint-space planning, 1=cartesian path
uint8 mode2 # Reserved for future use
-
x, y, z 末端目标笛卡尔坐标,单位米。
-
roll, pitch, yaw 目标末端欧拉角,单位弧度。
-
gripper float64 夹爪归一化目标值:
0.0= 完全闭合1.0= 完全张开
⚠️注意:这里是归一化 0‑1;之前精细夹爪接口是物理米
0.0~0.04,两者单位不一样,需要做换算。gripper_val * 0.04→ 转换成物理开口米数。
- mode1 uint8 规划模式
0:关节空间规划(不保证末端直线)1:笛卡尔直线规划,末端走空间直线
等价于之前笛卡尔请求的
cartesian_path布尔字段。
- mode2 uint8 预留字段,现在填 0 即可,留给后续扩展。