feat(embodiments): quadcopter preset + optional gripper + payload_release#132
Merged
Merged
Conversation
…ease Adds first-class quadcopter as a fourth shipped embodiment, and the structural schema changes that drones (and future gripper-less robots) need without special-casing every call site. ### Schema changes - `gripper` moved from `required` to optional. Embodiments without a gripper (drones, future fixed-wing, AGVs) simply omit the block. - `constraints.max_gripper_velocity` moved from required to optional — only meaningful when a gripper is present. Cross-field validation now enforces "gripper present implies max_gripper_velocity present" so arm presets can't accidentally drop it. - New optional `payload_release` block with `component_idx` + `trigger_threshold`. Mutually exclusive with `gripper` in practice (an embodiment has one end-effector type). - `quadcopter` added to the embodiment enum. No reformatting of schema.json — only the lines that actually change. ### Runtime - `EmbodimentConfig.gripper` and `EmbodimentConfig.payload_release` are now `default_factory=dict` so loading is order-insensitive. - New `has_gripper` property — callers should use this instead of unconditionally accessing `gripper_idx`. - `gripper_idx` still raises KeyError when no gripper is present (loud failure, not a silent -1 sentinel). - `to_dict()` omits empty optional fields so round-trip output validates against the schema's `additionalProperties: false` constraint. - `SafetyLimits.from_embodiment_config` handles gripper-less embodiments by broadcasting `max_ee_velocity` across all action dims. The existing arm path is unchanged — regression test added. ### Quadcopter preset - 5-DOF action: body rates (roll/pitch/yaw, rad/s) + thrust [0,1] + payload release [0,1]. Action index 4 is the payload trigger. - 10-DOF state: position (3) + orientation quat xyzw (4) + linear velocity (3). Matches PX4/MAVROS Odometry message shape, the canonical drone state. A 4-DOF "orientation only" state is unusable for real drone control (no damping, no drift correction). - 50 Hz control loop (matches PX4 outer-loop rate), 20-action chunks. - `max_ee_velocity: 5.0` (flight speed, ~11 mph) — fits within the existing schema cap of 10 m/s. The schema cap is intentionally NOT raised; if you need faster, override at the runtime layer or split per-embodiment in a future schema version. - Single canonical location at `src/reflex/embodiments/presets/` (not duplicated in `configs/embodiments/`). ### Tests - 200 passing across embodiments + safety + guard tests. - New `TestGripperOptional` class with 8 cases: quadcopter omits gripper, validates clean, gripper_idx raises, arm presets unchanged, payload release idx bounds check, gripper-implies-velocity cross-check, SafetyLimits builds for both drone and arm. - Split parameterised `test_max_ee_velocity_capped` into arm (≤ 2 m/s) and drone (≤ 6 m/s) variants. Supersedes #125 and #126. Co-Authored-By: Divyansh Rawat <186957976+DsThakurRawat@users.noreply.github.com> Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced May 15, 2026
rylinjames
added a commit
that referenced
this pull request
May 15, 2026
Adds docs/adding_a_robot.md walking new contributors through adding a fifth embodiment to Reflex. Two worked examples — MyArm-6 (6-DOF arm + gripper) and SkyScout (quadcopter delivery drone) — both validated against the live src/reflex/embodiments/schema.json before commit. The example JSONs use only real schema fields (mean_action / std_action / mean_state / std_state, resolution + color_space, etc.) so anyone copy-pasting can run validate_embodiment_config on them and get a clean pass after Step 3. Drops fabrications from the original attempt (#128): - Field names like 'labels', 'mean'/'std' (instead of mean_action), camera 'width'/'height'/'encoding' (instead of resolution + color_space), 'workspace_bbox' — none of which are in schema.json - The 'configs/embodiments/' duplicate-file step (that location was a dev fallback; #132 made src/reflex/embodiments/presets/ canonical) Reflects what's actually shipped after recent foundation merges: - Optional gripper + payload_release (from #132) - New quadcopter preset with 10-DOF state matching MAVROS Odometry - ROS2 bridge --state-msg-type flag (from #133) for drone deployments - 'Common patterns by vertical' section reflects the FastCrest customer research vault's P0 verticals (warehouse AMR, farm, drone, smart-camera) Supersedes #128. Co-authored-by: Divyansh Rawat <186957976+DsThakurRawat@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds first-class quadcopter as a fourth shipped embodiment plus the structural schema changes that gripper-less robots need without special-casing every call site.
gripper+constraints.max_gripper_velocitymove from required to optional. New optionalpayload_releaseblock.quadcopteradded to embodiment enum. No reformatting noise.EmbodimentConfig.has_gripperproperty.to_dict()omits empty optional fields.SafetyLimits.from_embodiment_confighandles gripper-less embodiments by broadcastingmax_ee_velocity.TestGripperOptionalclass with 8 cases. Splitmax_ee_velocity_cappedinto arm (≤ 2 m/s) and drone (≤ 6 m/s) variants. 200 passing across embodiments + safety + guard.Adapted from #125 and #126
@DsThakurRawat opened both PRs aiming at the same goal (quadcopter support). Credit for the conceptual direction goes to them (preserved via
Co-Authored-Byon the commit).This PR differs by:
serve-drone-support: doneinGOALS.yaml. That's a roadmap decision for the maintainer to flip, not something a contributor PR should self-assert.—, one-property-per-line, lost trailing newline). This PR only edits the lines that actually change semantics.quadcopter.jsonin BOTHconfigs/embodiments/ANDsrc/reflex/embodiments/presets/. The package reads frompresets/; the other was a dev fallback. Drift risk eliminated.payload_releaseas a clean new concept, not overloaded onto thegripperfield (which feat(serve): first-class quadcopter drone embodiment support #126 did). They're semantically distinct end-effectors.SafetyLimits.from_embodiment_config) updated so it doesn't crash on a gripper-less embodiment — with regression tests for both the new drone path and the unchanged arm path.Closes / supersedes
Test plan
pytest tests/test_embodiments.py— 59/59 passpytest -k "embodiment or safety or guard or preset"— 200 pass, 3 unrelated skips