Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,6 @@ test-local: build-local

lint-local:
python3 tools/validation/check_repository.py
ruff check tools tests
ruff check tools tests packages

check-local: lint-local list-local test-local
8 changes: 6 additions & 2 deletions configs/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Configuration

Versioned experiment, scenario, agent, task, and simulator configuration belongs
in this domain. Configuration schemas and the MVP examples are introduced with
the components that can validate them; PR 01 does not add speculative examples.
in this domain. [`experiments/mvp.yaml`](experiments/mvp.yaml) is the canonical
schema-version 1 PointNav example validated by `rh_core`.

Episode initialization uses a complete 3D pose (`x/y/z` in metres and
`roll/pitch/yaw` in radians). PointNav goals are 3D positions without target
orientation semantics.
25 changes: 25 additions & 0 deletions configs/experiments/mvp.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
schema_version: 1
experiment:
name: go2_keyboard_pointnav
execution_mode: manual
episodes:
- episode_id: "0000"
scenario: warehouse_default
initial_pose:
frame_id: map
x: 1.0
y: 2.0
z: 0.4
roll: 0.0
pitch: 0.0
yaw: 0.0
task:
type: pointnav
goal:
frame_id: map
x: 8.0
y: 4.0
z: 0.4
success_radius_m: 0.5
timeout_s: 120.0
seed: 42
32 changes: 20 additions & 12 deletions docs/architecture-and-development-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ Simulator、Robot、Agent、Task、Evaluation 均通过 ROS contract、配置约
| Agent | 消费标准观测、执行策略、输出命令、agent reset、任务局部状态;MVP 处理键盘输入 | simulator reset、spawn、Episode 调度、评估和持久化 |
| Experiment container | control/evaluation plane 的部署边界 | 高频导航数据转发、simulator 或 policy 实现 |
| Experiment Orchestrator | Experiment/Episode 权威状态、readiness 等待、reset 顺序、task 分配、start/abort、终止协调、失败策略 | 机器人实时控制、指标公式细节 |
| Task Manager | 加载并校验 EpisodeSpec、构造 PointNav task、start/goal/timeout/success 条件 | 控制 robot、判定组件 readiness、保存结果 |
| Task Manager | 加载并校验 EpisodeSpec、构造 PointNav goal/timeout/success 条件 | 控制 robot、判定组件 readiness、保存结果 |
| Evaluator | 订阅 ground truth/episode/task,维护轨迹并计算 metrics、提出终止候选 | 发布 `cmd_vel`、改变 world、决定调度策略 |
| Result Recorder | 原子写入 config、metadata、episode spec、events、trajectory、metrics 和 summary | 计算控制或拥有生命周期状态 |
| Simulator Backend | Isaac app/extension、timeline、stage、physics、world、原生 ROS 2 Bridge 配置与 backend readiness | Agent、Task/Eval、跨 simulator 的机器人声明 |
Expand Down Expand Up @@ -218,8 +218,8 @@ Core contract 由 ROS interfaces、配置 schema 和行为测试共同定义;P

- `ComponentStatus.msg`:`builtin_interfaces/Time stamp`、`string component_id`、`uint8 state`、`uint32 error_code`、`string detail`、`bool restart_required`;常量 `STARTING/RESETTING/READY/ERROR`。
- `EpisodeState.msg`:`stamp`、`experiment_id`、`episode_id`、`uint64 sequence`、`uint8 state`、`uint8 termination_reason`、`string detail`;定义生命周期和终止原因常量。
- `PointNavTask.msg`:`experiment_id`、`episode_id`、`geometry_msgs/PoseStamped start`、`goal`、`float64 success_radius_m`、`float64 timeout_s`、`int64 seed`。frame 必须相同且 MVP 为 `map`。
- `ResetEnv.srv` request:`request_id`、`experiment_id`、`episode_id`、`start`、`seed`;response:`success`、`error_code`、`detail`。
- `PointNavTask.msg`:`experiment_id`、`episode_id`、`geometry_msgs/PointStamped goal`、`float64 success_radius_m`、`float64 timeout_s`、`int64 seed`。PointNav goal 是 `map` frame 中的 3D position,不表达目标朝向
- `ResetEnv.srv` request:`request_id`、`experiment_id`、`episode_id`、`geometry_msgs/PoseStamped initial_pose`、`seed`;initial pose 完整表达 3D position 和 orientation,四元数必须有限且归一化;response:`success`、`error_code`、`detail`。
- `ResetAgent.srv` request:`request_id`、`experiment_id`、`episode_id`;response 同上。
- `StartEpisode.srv` request:`experiment_id`、`episode_id`;response:`accepted`、`detail`。
- `AbortEpisode.srv` request:IDs、`reason`;response:`accepted`、`detail`。
Expand Down Expand Up @@ -256,7 +256,8 @@ Experiment
└─ ordered Episode instances
├─ immutable EpisodeSpec
│ ├─ Scenario/world reference
│ ├─ Task(type=pointnav, start, goal)
│ ├─ Initial pose(x, y, z, roll, pitch, yaw)
│ ├─ Task(type=pointnav, 3D goal)
│ ├─ timeout/success radius
│ └─ seed
├─ lifecycle state
Expand All @@ -280,16 +281,23 @@ experiment:
episodes:
- episode_id: "0000"
scenario: warehouse_default
initial_pose:
frame_id: map
x: 1.0
y: 2.0
z: 0.4
roll: 0.0
pitch: 0.0
yaw: 0.0
task:
type: pointnav
start: {frame_id: map, x: 1.0, y: 2.0, yaw: 0.0}
goal: {frame_id: map, x: 8.0, y: 4.0, yaw: 0.0}
goal: {frame_id: map, x: 8.0, y: 4.0, z: 0.4}
success_radius_m: 0.5
timeout_s: 120.0
seed: 42
```

配置加载后转换为 typed dataclass,并一次性完成不依赖环境的静态验证:唯一 ID、有限数值、正 timeout/radius 和 frame 一致。静态无效配置直接拒绝启动 Experiment;依赖已加载 world 的检查(例如 start/goal 是否处于有效区域)在对应 Episode 的 PREPARING 阶段执行,失败时以 `INVALID_TASK` 结束该局且不允许机器人运动。
配置加载后转换为 typed dataclass,并一次性完成不依赖环境的静态验证:唯一 ID、完整且有限的 initial pose、有限的 3D goal、正 timeout/radius 和 frame 一致。配置中的 position 单位为米,roll/pitch/yaw 单位为弧度;RPY 采用绕固定 X/Y/Z 轴的旋转,等价旋转矩阵为 `Rz(yaw) * Ry(pitch) * Rx(roll)`。二维数据源必须在适配层显式补齐缺失的 `z/roll/pitch`(通常置零),再转换成完整 pose。静态无效配置直接拒绝启动 Experiment;依赖已加载 world 的检查(例如 initial pose/goal 是否处于有效区域)在对应 Episode 的 PREPARING 阶段执行,失败时以 `INVALID_TASK` 结束该局且不允许机器人运动。

---

Expand Down Expand Up @@ -635,16 +643,16 @@ Env 只有在 Isaac、stage、Go2、physics、ROS bridge、clock、required topi

## Part XIII — PointNav MVP

PointNav EpisodeSpec 必须包含唯一 ID、`map` frame 中的 start/goal、positive success radius、positive timeout、seed 和 execution mode。yaw 用于初始姿态,MVP success 只判断平面位置距离,不要求目标朝向
EpisodeSpec 必须包含唯一 ID、`map` frame 中的完整 3D initial pose、PointNav 3D goal、positive success radius、positive timeout、seed 和 execution mode。initial pose 的 orientation 属于环境初始化条件,不属于 PointNav 目标语义;PointNav 不包含目标 orientation,MVP success 判断机器人 tracking point(默认 `base_link` 原点)到 goal 的 3D 欧氏距离。未来若需要目标朝向,应新增语义明确的 PoseNav task/model/interface,而不是重新解释 PointNav 字段

1. Task Manager 在运动前验证 schema 和有限数值。
2. Env reset 到 start 并确认 robot 静止;Agent reset;task 直接发布到 Env/Agent/Evaluator。
2. Env reset 到 initial pose 并确认 robot 静止;Agent reset;task 直接发布到 Env/Agent/Evaluator。
3. Episode 进入 READY,manual 模式等待 `/episode/start`。
4. RUNNING 后 Keyboard Agent 才能驱动;Evaluator 计算 goal distance。
5. 距离首次 `<= success_radius_m` 为 `SUCCESS`;simulation elapsed `>= timeout_s` 为 `TIMEOUT`。
6. 同时发生时优先级为 runtime safety error、user abort、success、timeout;最终 reason 只提交一次,其他候选作为事件保存。

MVP 不包含 SPL、语义目标、动态场景、复杂碰撞惩罚或 start/goal 自动采样。
MVP 不包含 SPL、语义目标、动态场景、复杂碰撞惩罚或 initial pose/goal 自动采样。

---

Expand Down Expand Up @@ -733,7 +741,7 @@ CPU CI 至少执行 `colcon build`、lint/type checks、unit/interface tests、m
**Out of Scope:** physics realism、Gazebo、Isaac compatibility layer。
**Files / Modules:** `tests/fixtures/mock_env/`、integration tests。
**ROS Interfaces:** Env status/reset、Episode state、`/clock`、`/robot/cmd_vel`、odom/TF。
**Tests:** readiness、idempotent reset、start pose、non-RUNNING zero gate、freeze/reset failure injection。
**Tests:** readiness、idempotent reset、完整 initial pose、non-RUNNING zero gate、freeze/reset failure injection。
**Acceptance Criteria:** headless 测试 < 30 s 且 deterministic;fixture 明确标注不可作为 simulator 产品实现。
**Dependencies:** PR 04。
**Risks:** mock 与真实 contract 偏离;接口测试共享同一 black-box suite。
Expand Down Expand Up @@ -772,7 +780,7 @@ CPU CI 至少执行 `colcon build`、lint/type checks、unit/interface tests、m
**Out of Scope:** evaluator aggregation、自动 goal sampling、其他 Task。
**Files / Modules:** `tasks/pointnav/`、configs、tests。
**ROS Interfaces:** publish `PointNavTask`,验证 direct subscribers 和 transient-local behavior。
**Tests:** start/goal/frame/radius/timeout、late subscriber、episode mismatch。
**Tests:** 3D goal/frame/radius/timeout、late subscriber、episode mismatch。
**Acceptance Criteria:** Env/Agent/Evaluator 可直接获得同一不可变 task;非法 goal 在运动前拒绝。
**Dependencies:** PR 03、PR 04。
**Risks:** Task 与 Eval 耦合;只共享 typed spec/termination predicate,不共享 recorder。
Expand Down
9 changes: 6 additions & 3 deletions packages/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ This domain contains shared RoboHarness platform and communication packages.
Packages must depend toward stable layers and must not statically depend on a
specific simulator, robot, agent, task, or evaluator implementation.

The first platform package is [`rh_interfaces`](rh_interfaces/README.md), which
owns the implementation-independent ROS wire contract. `rh_core`, `rh_ros`,
and `rh_experiment` are introduced by their roadmap PRs.
- [`rh_interfaces`](rh_interfaces/README.md) owns the implementation-independent
ROS wire contract.
- [`rh_core`](rh_core/README.md) owns ROS-independent configuration, domain
models, lifecycle rules, and termination policy.

`rh_ros` and `rh_experiment` are introduced by their roadmap PRs.
99 changes: 99 additions & 0 deletions packages/rh_core/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# `rh_core`

`rh_core` is the ROS-independent domain layer of RoboHarness. It defines what
an Experiment means, which static configuration is valid, which lifecycle
transitions are legal, and how competing termination candidates are resolved.
It does not communicate with ROS or execute an Experiment.

## Responsibilities

- Immutable typed models for versioned Experiment, Episode, and PointNav input.
- Strict YAML decoding with unknown-field and duplicate-key rejection.
- Static validation that does not require a loaded simulator world.
- Pure Experiment and Episode lifecycle transition guards.
- Deterministic Episode termination priority.
- Structured errors containing a machine-readable code, field path, and
human-readable message.

The package must not import `rclpy`, `rh_interfaces`, Isaac Sim, or any concrete
Simulator, Agent, Task, or Evaluator implementation. ROS message conversion
belongs to the future `rh_ros` package.

## Configuration schema version 1

The canonical example is
[`configs/experiments/mvp.yaml`](../../configs/experiments/mvp.yaml). A document
contains an Experiment name, execution mode, and a non-empty ordered list of
Episode specifications. Each MVP Episode references a scenario and contains an
immutable PointNav task plus a signed 64-bit reproducibility seed.

The loader validates:

- exact known fields and `schema_version: 1`;
- non-empty identifiers, scenario references, and Experiment names;
- unique Episode IDs;
- `manual` or `automatic` execution mode;
- PointNav as the only MVP task type;
- finite initial-pose position/orientation, goal, radius, and timeout values;
- positive radius and timeout;
- matching `map` initial-pose/goal frames; and
- an integer seed in the ROS `int64` range.

The Episode initial state is a full 3D robot pose: position is expressed in
metres and roll, pitch, and yaw in radians. RPY means fixed-axis rotations about
X, Y, and Z, with the equivalent rotation matrix
`Rz(yaw) * Ry(pitch) * Rx(roll)`. A source that only provides planar data must
explicitly adapt the absent `z`, roll, and pitch dimensions, normally to zero.
The ROS adapter converts this representation to a normalized `PoseStamped`
quaternion.

PointNav contains only a 3D goal position. It has no desired target orientation,
and success uses the 3D Euclidean distance from the configured robot tracking
point (MVP: the `base_link` origin) to the goal. PoseNav, if later required, will
be a separate task model and interface rather than an extension hidden inside
these PointNav fields.

Checks that require a loaded world, such as collision-free or reachable poses,
remain an Environment/Task concern during Episode `PREPARING`.

```python
from rh_core import ConfigError, load_experiment_config

try:
config = load_experiment_config("configs/experiments/mvp.yaml")
except ConfigError as error:
for issue in error.issues:
print(issue.as_dict())
```

## Lifecycle rules

Experiment progression is:

```text
CREATED -> STARTING -> RUNNING -> FINALIZING -> FINISHED
\---------- errors ----------> FAILED
```

Episode progression is:

```text
PREPARING -> READY -> RUNNING -> TERMINATING -> FINISHED
| | |
+----------+---------+---- early termination
```

Entering `TERMINATING` requires a non-`NONE` reason. The reason is committed
once, retained through `FINISHED`, and cannot be replaced. Every accepted
Episode transition returns a new immutable snapshot with `sequence + 1`.

Termination candidates use this priority:

```text
ENV_ERROR > AGENT_ERROR > FAILURE > INVALID_TASK
> ABORTED > SUCCESS > TIMEOUT
```

This refines the architectural rule “runtime safety error, user abort, success,
timeout” with deterministic ordering inside the failure tier. The Environment
wins ties because it is the final motion-safety boundary.
20 changes: 20 additions & 0 deletions packages/rh_core/package.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>rh_core</name>
<version>0.0.0</version>
<description>ROS-independent domain models and rules for RoboHarness.</description>
<maintainer email="2300012435@stu.pku.edu.cn">Staaaaaaaaar</maintainer>
<!-- Project licensing and release versioning are finalized by roadmap PR 18. -->
<license>NOASSERTION</license>

<buildtool_depend>ament_python</buildtool_depend>

<exec_depend>python3-yaml</exec_depend>

<test_depend>python3-pytest</test_depend>

<export>
<build_type>ament_python</build_type>
</export>
</package>
1 change: 1 addition & 0 deletions packages/rh_core/resource/rh_core
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

41 changes: 41 additions & 0 deletions packages/rh_core/rh_core/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""ROS-independent RoboHarness domain models and rules."""

from rh_core.config import load_experiment_config, parse_experiment_config
from rh_core.errors import ConfigError, ErrorCode, LifecycleError, ValidationIssue
from rh_core.lifecycle import EpisodeLifecycle, ExperimentLifecycle
from rh_core.models import (
EpisodeSpec,
EpisodeState,
ExecutionMode,
ExperimentConfig,
ExperimentSpec,
ExperimentState,
Point3D,
PointNavTaskSpec,
Pose3D,
TerminationReason,
)
from rh_core.termination import TERMINATION_PRIORITY, resolve_termination_reason

__all__ = [
"ConfigError",
"EpisodeLifecycle",
"EpisodeSpec",
"EpisodeState",
"ErrorCode",
"ExecutionMode",
"ExperimentConfig",
"ExperimentLifecycle",
"ExperimentSpec",
"ExperimentState",
"LifecycleError",
"Point3D",
"PointNavTaskSpec",
"Pose3D",
"TERMINATION_PRIORITY",
"TerminationReason",
"ValidationIssue",
"load_experiment_config",
"parse_experiment_config",
"resolve_termination_reason",
]
Loading
Loading