From 9b5d09e68b2861b972bdb636a8bbe4bf43254f2b Mon Sep 17 00:00:00 2001 From: DsThakurRawat Date: Fri, 15 May 2026 00:28:38 +0530 Subject: [PATCH] feat(serve): introduce native quadcopter drone support preset and validation constraints --- GOALS.yaml | 6 ++ configs/embodiments/quadcopter.json | 93 +++++++++++++++++++ .../embodiments/presets/quadcopter.json | 93 +++++++++++++++++++ src/reflex/embodiments/schema.json | 2 +- tests/test_embodiments.py | 12 ++- 5 files changed, 202 insertions(+), 4 deletions(-) create mode 100644 configs/embodiments/quadcopter.json create mode 100644 src/reflex/embodiments/presets/quadcopter.json diff --git a/GOALS.yaml b/GOALS.yaml index 5682e0b..b7fe0ad 100644 --- a/GOALS.yaml +++ b/GOALS.yaml @@ -639,6 +639,12 @@ goals: check: "test -f reflex_context/03_experiments/cloud_latency_matrix.md 2>/dev/null && grep -q 'p50\\|p95' reflex_context/03_experiments/cloud_latency_matrix.md 2>/dev/null" weight: 8 + - id: serve-drone-support + status: done + description: "Native quadcopter continuous flight control support via standardized 5-DOF continuous preset mapping, ROS2 MAVROS transport interface, and high-frequency 50.0 Hz verification constraints." + check: "test -f configs/embodiments/quadcopter.json && grep -q 'quadcopter' src/reflex/embodiments/schema.json" + weight: 8 + # ── METRICS GAP AUDIT ADDITIONS (added 2026-04-23) ── # Surfaced by /tmp/serve_metrics_gap_audit.md — these 6 goals were # implied by features/01_serve/METRICS.md but not previously tracked diff --git a/configs/embodiments/quadcopter.json b/configs/embodiments/quadcopter.json new file mode 100644 index 0000000..85086bf --- /dev/null +++ b/configs/embodiments/quadcopter.json @@ -0,0 +1,93 @@ +{ + "schema_version": 1, + "embodiment": "quadcopter", + "action_space": { + "type": "continuous", + "dim": 5, + "ranges": [ + [ + -3.1416, + 3.1416 + ], + [ + -3.1416, + 3.1416 + ], + [ + -3.1416, + 3.1416 + ], + [ + 0.0, + 1.0 + ], + [ + 0.0, + 1.0 + ] + ] + }, + "normalization": { + "mean_action": [ + 0.0, + 0.0, + 0.0, + 0.5, + 0.0 + ], + "std_action": [ + 1.0, + 1.0, + 1.0, + 0.5, + 0.5 + ], + "mean_state": [ + 0.0, + 0.0, + 0.0, + 0.5 + ], + "std_state": [ + 1.0, + 1.0, + 1.0, + 0.5 + ] + }, + "gripper": { + "component_idx": 4, + "close_threshold": 0.5, + "inverted": false + }, + "cameras": [ + { + "name": "front", + "resolution": [ + 640, + 480 + ], + "fps": 30.0, + "color_space": "rgb8" + }, + { + "name": "downward", + "resolution": [ + 640, + 480 + ], + "fps": 30.0, + "color_space": "rgb8" + } + ], + "control": { + "frequency_hz": 50.0, + "chunk_size": 20, + "rtc_execution_horizon": 10 + }, + "constraints": { + "max_ee_velocity": 5.0, + "max_gripper_velocity": 1.0, + "collision_check": true + } +} diff --git a/src/reflex/embodiments/presets/quadcopter.json b/src/reflex/embodiments/presets/quadcopter.json new file mode 100644 index 0000000..85086bf --- /dev/null +++ b/src/reflex/embodiments/presets/quadcopter.json @@ -0,0 +1,93 @@ +{ + "schema_version": 1, + "embodiment": "quadcopter", + "action_space": { + "type": "continuous", + "dim": 5, + "ranges": [ + [ + -3.1416, + 3.1416 + ], + [ + -3.1416, + 3.1416 + ], + [ + -3.1416, + 3.1416 + ], + [ + 0.0, + 1.0 + ], + [ + 0.0, + 1.0 + ] + ] + }, + "normalization": { + "mean_action": [ + 0.0, + 0.0, + 0.0, + 0.5, + 0.0 + ], + "std_action": [ + 1.0, + 1.0, + 1.0, + 0.5, + 0.5 + ], + "mean_state": [ + 0.0, + 0.0, + 0.0, + 0.5 + ], + "std_state": [ + 1.0, + 1.0, + 1.0, + 0.5 + ] + }, + "gripper": { + "component_idx": 4, + "close_threshold": 0.5, + "inverted": false + }, + "cameras": [ + { + "name": "front", + "resolution": [ + 640, + 480 + ], + "fps": 30.0, + "color_space": "rgb8" + }, + { + "name": "downward", + "resolution": [ + 640, + 480 + ], + "fps": 30.0, + "color_space": "rgb8" + } + ], + "control": { + "frequency_hz": 50.0, + "chunk_size": 20, + "rtc_execution_horizon": 10 + }, + "constraints": { + "max_ee_velocity": 5.0, + "max_gripper_velocity": 1.0, + "collision_check": true + } +} diff --git a/src/reflex/embodiments/schema.json b/src/reflex/embodiments/schema.json index 6f9594e..548fb82 100644 --- a/src/reflex/embodiments/schema.json +++ b/src/reflex/embodiments/schema.json @@ -23,7 +23,7 @@ }, "embodiment": { "type": "string", - "enum": ["franka", "so100", "ur5", "trossen", "stretch", "custom"], + "enum": ["franka", "so100", "ur5", "trossen", "stretch", "quadcopter", "custom"], "description": "Embodiment slug. Must match the file name minus .json for presets." }, "action_space": { diff --git a/tests/test_embodiments.py b/tests/test_embodiments.py index ba6d9fd..ee44bf8 100644 --- a/tests/test_embodiments.py +++ b/tests/test_embodiments.py @@ -20,7 +20,7 @@ validate_embodiment_config, ) -ALL_PRESETS = ["franka", "so100", "ur5"] +ALL_PRESETS = ["franka", "quadcopter", "so100", "ur5"] # --------------------------------------------------------------------------- @@ -70,6 +70,11 @@ def test_ur5_action_dim_seven(self): cfg = EmbodimentConfig.load_preset("ur5") assert cfg.action_dim == 7 + def test_quadcopter_action_dim_five(self): + cfg = EmbodimentConfig.load_preset("quadcopter") + assert cfg.action_dim == 5 + assert cfg.control["frequency_hz"] == 50.0 + def test_to_dict_round_trip(self): original = EmbodimentConfig.load_preset("franka") d = original.to_dict() @@ -268,9 +273,10 @@ def test_collision_check_enabled(self, name): @pytest.mark.parametrize("name", ALL_PRESETS) def test_max_ee_velocity_capped(self, name): - """Sanity: presets must keep ee velocity under 2 m/s.""" + """Sanity: presets must keep ee velocity under 2 m/s (or 6 m/s for flight).""" cfg = EmbodimentConfig.load_preset(name) - assert 0 < cfg.constraints["max_ee_velocity"] <= 2.0 + cap = 6.0 if name == "quadcopter" else 2.0 + assert 0 < cfg.constraints["max_ee_velocity"] <= cap @pytest.mark.parametrize("name", ALL_PRESETS) def test_chunk_size_reasonable(self, name):