File size: 9,439 Bytes
d7dc28f
 
0364dc0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d7dc28f
 
 
 
 
0364dc0
d7dc28f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0364dc0
 
 
 
 
 
 
 
 
 
 
 
d7dc28f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0364dc0
d7dc28f
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
---
language:
- en
license: apache-2.0
pipeline_tag: robotics
tags:
- OpenRAL
- rskill
- ros2
- moveit
- franka_panda
- ur5e
- ur10e
- so100_follower
- openarm
- rizon4
- sawyer
- widowx
inference: false
---

# rskill-moveit-look-at

Aim a robot-mounted camera at a 3-D point (ADR-0044 Phase 3; renamed from
`openral/rskill-look-at` under [ADR-0054](../../docs/adr/0054-moveit-goal-builder-library.md)).

A `kind: ros_action` rSkill wrapping `moveit_msgs/action/MoveGroup` β€” like
[`rskill-moveit-joints`](../rskill-moveit-joints/), but the goal is a
`look_at` block instead of raw constraints, and `ros_integration.goal_builder:
look_at` selects the `LookAtRskill` adapter that lowers it into MoveGroup
pose constraints.

## What this skill does

Given a target point and a camera name, plans a collision-aware arm motion
that points the named camera's optical axis at the target β€” so a later
perception query (`locate_in_view`) or manipulation skill sees the object
framed. It is the "look" rung of the ADR-0044 *recall β†’ navigate β†’ look β†’
verify β†’ manipulate* ladder.

## How it works

`ros_integration.goal_builder: look_at` selects `LookAtRskill`
(`openral_rskill.look_at_rskill`), which lowers the `look_at` block at dispatch
time:

1. **Resolve the camera** named by `look_at.camera` (default `"wrist"`) from
   the host `RobotDescription.sensors`. No such sensor β†’ `ROSConfigError`
   listing the robot's available sensors β€” never a silent guess. A sensor
   whose `frame_id` is itself a robot link (franka's LIBERO eye-in-hand on
   `panda_hand`) is constrained directly; a sensor with `parent_frame` +
   `static_transform_xyz_rpy` (so101-style mount) constrains the parent link
   through the declared offset.
2. **Read the camera's current pose over TF2** (the only source of frames) in
   the goal frame.
3. **Place the camera goal** β€” in place (pure re-aim, the default) or at
   `look_at.standoff_m` from the target along the current line of approach.
4. **Orient it** with `compute_gaze_pose` (ROS optical convention: camera +Z
   hits `look_at.target_xyz`; roll about the optical axis left free at
   tolerance Ο€ for planner reachability) and submit MoveGroup
   `position_constraints` + `orientation_constraints`.

`plan_only: true`, deliberately: OpenRAL's actuation path is the per-waypoint
replay through `/openral/candidate_action` (`chunk_size: 1`, so the safety
supervisor's per-joint envelope check sees every aiming step, and the HAL
actuates). Letting `move_group` also execute on its own controllers would
bypass the kernel and double-drive the arm.

### Observation β†’ action contract

Input is the ADR-0026 `goal_params_json` `look_at` block; output is a joint
trajectory replayed one waypoint per `step()` as a 1-row `JOINT_POSITION`
`Action` chunk.

```json
{"look_at": {"target_xyz": [0.5, 0.0, 0.2], "camera": "wrist"}}
```

Planner settings (`request.group_name`, scaling, attempts) are inherited from
`default_goal_json`. Omit `standoff_m` to re-aim in place; set it to also move
the camera to that distance from the target.

### GPU-accelerated planning (cuMotion, ADR-0065)

On a host that clears the cuMotion GPU floor (`RobotCapabilities.supports_cumotion()`
β€” Ampere+, CUDA β‰₯ 13, ~8 GB VRAM), the runner sets
`MotionPlanRequest.pipeline_id = "isaac_ros_cumotion"` so MoveIt plans with
NVIDIA's CUDA-accelerated cuMotion pipeline; otherwise it falls back to OMPL.
Transparent β€” same skill, no manifest change β€” and it never bypasses the safety
kernel: the planned trajectory still replays through `/openral/candidate_action`
and is validated waypoint-by-waypoint. Install: see
[`docs/contributing/toolchain.md`](../../docs/contributing/toolchain.md) β†’
"GPU motion planning β€” cuMotion".

## How it was trained / Upstream provenance

Nothing is trained β€” this rSkill wraps the upstream MoveIt motion planner and
computes the gaze pose analytically.

| Field | Value |
| --- | --- |
| Upstream | [`moveit_msgs/action/MoveGroup`](https://github.com/moveit/moveit_msgs/blob/master/action/MoveGroup.action) (BSD-3-Clause) |
| Planner library | [MoveIt 2](https://moveit.picknik.ai/) (BSD-3-Clause) |
| Gaze geometry | `openral_world_state.geometry.compute_gaze_pose` (Apache-2.0, in-tree) |
| Collision check | FCL via MoveIt's `PlanningScene` (run during planning) |
| Wrapped artefact | rSkill manifest + README β€” no weights, no preprocessor JSONs |

## Supported robots / embodiments

| Robot | Embodiment tag | Status | Notes |
| --- | --- | --- | --- |
| Franka Panda | `franka_panda` | validated | default goal targets `panda_arm`; camera `wrist` = LIBERO eye-in-hand on `panda_hand` |
| Universal Robots UR5e | `ur5e` | experimental | needs `request.group_name` + `look_at.camera`/`frame_id` overrides |
| Universal Robots UR10e | `ur10e` | experimental | same as UR5e |
| SO-100 follower | `so100_follower` | experimental | needs a MoveIt config; `wrist` cam declares a static mount |
| OpenArm | `openarm` | experimental | bi-manual β€” choose `left_arm`/`right_arm` group |
| Flexiv Rizon4 | `rizon4` | experimental | upstream MoveIt config exists; manifest override needed |
| Rethink Sawyer | `sawyer` | experimental | upstream MoveIt config exists |
| Trossen WidowX | `widowx` | experimental | upstream MoveIt config exists |

A robot must declare the named camera (default `wrist`) in its `robot.yaml`
sensors, or the skill fails at configure with the available-sensor list.
Listed `embodiment_tags` only gate palette visibility; actual resolution needs
`move_group` up for that robot.

## Sensors required / Observation contract

This skill consumes no camera frames through OpenRAL's sensor pipeline β€” it
*aims* a camera, it doesn't read one. It needs the named camera's **frame** to
exist in TF (declared in the robot manifest + published by
`robot_state_publisher`), plus MoveIt's own subscriptions:

| Source | Topic / contract | Why it's needed |
| --- | --- | --- |
| Camera frame | TF `frame_id` of `look_at.camera` (default `wrist`) | Read the camera's current pose; target the gaze |
| Joint state | `/joint_states` | Plan from the live start state |
| Planning scene | `/planning_scene` | Collision check during planning |
| TF | `/tf`, `/tf_static` | Resolve camera + link frames |

## Manifest summary

| Field | Value |
| --- | --- |
| `name` | `OpenRAL/rskill-moveit-look-at` |
| `version` | `0.1.0` |
| `license` | `apache-2.0` |
| `kind` | `ros_action` |
| `role` | `s1` |
| `actions` | `[look]` |
| `chunk_size` | `1` (schema-enforced for `kind: ros_action`) |
| `latency_budget.per_chunk_ms` | `2000` (planning latency) |
| `ros_integration.package` | `moveit_msgs` |
| `ros_integration.interface_type` | `MoveGroup` |
| `ros_integration.interface_name` | `/move_action` |
| `ros_integration.goal_builder` | `look_at` (selects `LookAtRskill`) |
| `ros_integration.result_trajectory_field` | `planned_trajectory.joint_trajectory` |
| `commercial_use_allowed` | `true` (apache-2.0) |

Full schema:
[`openral_core.schemas.RSkillManifest`](../../python/core/src/openral_core/schemas.py).

## Quick start

```python
from openral_rskill.loader import rSkill
pkg = rSkill.from_yaml("rskills/rskill-moveit-look-at/rskill.yaml")
print(pkg.manifest.name, pkg.manifest.ros_integration.goal_builder)
```

End-to-end, with a real MoveIt launch up:

```bash
# 1. Bring up MoveIt for your robot (example: Panda)
ros2 launch moveit_resources_panda_moveit_config demo.launch.py

# 2. Dispatch a look-at goal (aim the wrist camera at a tabletop point):
ros2 action send_goal /openral/execute_rskill openral_msgs/action/ExecuteRskill \
    "{rskill_id: 'OpenRAL/rskill-moveit-look-at', deadline_s: 30.0, prompt: 'look at the mug',
      goal_params_json: '{\"look_at\": {\"target_xyz\": [0.5, 0.0, 0.2], \"camera\": \"wrist\"}}'}"
```

## Limitations / Roadmap

- **Reachability is the planner's call.** Roll about the optical axis is left
  free, but a target outside the arm's dexterous workspace simply fails to
  plan β€” there's no base-repositioning fallback here (that's the navigate rung
  of the ladder, ADR-0044 Phase 4).
- **Single-camera aim.** One camera per dispatch; multi-camera coverage is a
  reasoner-level concern.
- **No velocity / jerk bound at the supervisor.** Same posture as
  `rskill-moveit-joints`: the per-joint position envelope runs per
  waypoint; richer bounds are tracked separately.

## License

The rSkill package itself (this manifest + README) is **Apache-2.0**. The
wrapped MoveIt code (`moveit_msgs` IDL, `moveit2` planners) is **BSD-3-Clause**
and is installed via `ros-${ROS_DISTRO}-moveit`, outside this repository. Per
[ADR-0012](../../docs/adr/0012-open-core-licensing.md) both postures are
commercial-use-permissive.

## See also

- [ADR-0054 β€” MoveIt goal-builder library + rskill-moveit-* rename](../../docs/adr/0054-moveit-goal-builder-library.md)
- [ADR-0044 β€” look_at skill + grid-refined approach](../../docs/adr/0044-look-at-skill-grid-refined-approach.md)
- [`openral_rskill.look_at_rskill`](../../python/rskill/src/openral_rskill/look_at_rskill.py) β€” adapter source
- [`openral_world_state.geometry`](../../python/world_state/src/openral_world_state/geometry.py) β€” gaze math
- [`rskills/rskill-moveit-joints/`](../rskill-moveit-joints/) β€” sibling joint-space MoveIt wrapper
- [`rskills/rskill-moveit-eef-pose/`](../rskill-moveit-eef-pose/) β€” sibling Cartesian end-effector pose wrapper