From 2a8962b9703a0a30356b28eda11f5438ed1678a1 Mon Sep 17 00:00:00 2001 From: wangxinhao Date: Mon, 20 Jul 2026 06:34:21 +0000 Subject: [PATCH 1/2] add Xinhao Wang to contributors --- CONTRIBUTORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 5b7a6cd..1956223 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -9,3 +9,4 @@ Cai Dongqi Zhi Helu Tam SikYuen Cheng Yin +Xinhao Wang From d4379ea8c7953110b3a31e410910b357758befaa Mon Sep 17 00:00:00 2001 From: wangxinhao Date: Tue, 21 Jul 2026 07:01:18 +0000 Subject: [PATCH 2/2] Add WebSocket-based deployment support, LIBERO/CALVIN/RoboTwin evaluators, launch scripts, tests, requirements, and documentation. --- .gitignore | 6 + .../condaenv.7heggirf.requirements.txt | 9 + MiniCPM-RobotManip/deployment/README.md | 177 + MiniCPM-RobotManip/deployment/__init__.py | 1 + .../deployment/model_server/__init__.py | 1 + .../deployment/model_server/policy_wrapper.py | 217 + .../deployment/model_server/protocol.py | 145 + .../deployment/model_server/server_policy.py | 114 + .../deployment/model_server/tools/__init__.py | 1 + .../model_server/tools/msgpack_numpy.py | 60 + .../tools/websocket_policy_client.py | 100 + .../tools/websocket_policy_server.py | 323 + MiniCPM-RobotManip/evaluation/README.md | 88 + .../evaluation/THIRD_PARTY_NOTICES.md | 161 + MiniCPM-RobotManip/evaluation/__init__.py | 1 + .../evaluation/calvin/README.md | 128 + .../evaluation/calvin/__init__.py | 3 + .../evaluation/calvin/eval_calvin.py | 511 + .../evaluation/calvin/eval_calvin.sh | 13 + .../evaluation/calvin/eval_sequences.json | 19002 ++++++++++++++++ .../evaluation/calvin/launcher.py | 276 + .../evaluation/calvin/requirements-client.txt | 12 + .../evaluation/calvin/run_policy_server.sh | 59 + .../evaluation/common/__init__.py | 1 + .../evaluation/common/adaptive_ensemble.py | 51 + .../evaluation/common/launcher_utils.py | 172 + .../evaluation/common/probe_server.py | 90 + .../evaluation/libero/README.md | 109 + .../evaluation/libero/__init__.py | 1 + .../auto_eval_scripts/auto_eval_libero.sh | 409 + .../auto_eval_scripts/eval_libero_parallel.sh | 13 + .../evaluation/libero/eval_libero.py | 319 + .../evaluation/libero/eval_libero.sh | 70 + .../libero/model2libero_interface.py | 236 + .../evaluation/libero/parallel_worker.py | 235 + .../evaluation/libero/requirements-client.txt | 9 + .../evaluation/libero/run_policy_server.sh | 49 + .../evaluation/robotwin/README.md | 167 + .../evaluation/robotwin/__init__.py | 5 + .../evaluation/robotwin/deploy_policy.yml | 15 + .../evaluation/robotwin/eval.sh | 101 + .../evaluation/robotwin/launcher.py | 799 + .../robotwin/model2robotwin_interface.py | 201 + .../evaluation/robotwin/probe_server.py | 11 + .../robotwin/requirements-client.txt | 9 + .../evaluation/robotwin/run_policy_server.sh | 85 + .../evaluation/robotwin/start_eval.sh | 13 + .../evaluation/tests/__init__.py | 1 + .../evaluation/tests/test_calvin_contract.py | 223 + .../evaluation/tests/test_launchers.py | 797 + .../evaluation/tests/test_policy_clients.py | 325 + README.md | 16 + README_zh.md | 16 + 53 files changed, 25956 insertions(+) create mode 100644 MiniCPM-RobotManip/condaenv.7heggirf.requirements.txt create mode 100644 MiniCPM-RobotManip/deployment/README.md create mode 100644 MiniCPM-RobotManip/deployment/__init__.py create mode 100644 MiniCPM-RobotManip/deployment/model_server/__init__.py create mode 100644 MiniCPM-RobotManip/deployment/model_server/policy_wrapper.py create mode 100644 MiniCPM-RobotManip/deployment/model_server/protocol.py create mode 100644 MiniCPM-RobotManip/deployment/model_server/server_policy.py create mode 100644 MiniCPM-RobotManip/deployment/model_server/tools/__init__.py create mode 100644 MiniCPM-RobotManip/deployment/model_server/tools/msgpack_numpy.py create mode 100644 MiniCPM-RobotManip/deployment/model_server/tools/websocket_policy_client.py create mode 100644 MiniCPM-RobotManip/deployment/model_server/tools/websocket_policy_server.py create mode 100644 MiniCPM-RobotManip/evaluation/README.md create mode 100644 MiniCPM-RobotManip/evaluation/THIRD_PARTY_NOTICES.md create mode 100644 MiniCPM-RobotManip/evaluation/__init__.py create mode 100644 MiniCPM-RobotManip/evaluation/calvin/README.md create mode 100644 MiniCPM-RobotManip/evaluation/calvin/__init__.py create mode 100644 MiniCPM-RobotManip/evaluation/calvin/eval_calvin.py create mode 100755 MiniCPM-RobotManip/evaluation/calvin/eval_calvin.sh create mode 100644 MiniCPM-RobotManip/evaluation/calvin/eval_sequences.json create mode 100644 MiniCPM-RobotManip/evaluation/calvin/launcher.py create mode 100644 MiniCPM-RobotManip/evaluation/calvin/requirements-client.txt create mode 100755 MiniCPM-RobotManip/evaluation/calvin/run_policy_server.sh create mode 100644 MiniCPM-RobotManip/evaluation/common/__init__.py create mode 100644 MiniCPM-RobotManip/evaluation/common/adaptive_ensemble.py create mode 100644 MiniCPM-RobotManip/evaluation/common/launcher_utils.py create mode 100644 MiniCPM-RobotManip/evaluation/common/probe_server.py create mode 100644 MiniCPM-RobotManip/evaluation/libero/README.md create mode 100644 MiniCPM-RobotManip/evaluation/libero/__init__.py create mode 100755 MiniCPM-RobotManip/evaluation/libero/auto_eval_scripts/auto_eval_libero.sh create mode 100755 MiniCPM-RobotManip/evaluation/libero/auto_eval_scripts/eval_libero_parallel.sh create mode 100644 MiniCPM-RobotManip/evaluation/libero/eval_libero.py create mode 100755 MiniCPM-RobotManip/evaluation/libero/eval_libero.sh create mode 100644 MiniCPM-RobotManip/evaluation/libero/model2libero_interface.py create mode 100644 MiniCPM-RobotManip/evaluation/libero/parallel_worker.py create mode 100644 MiniCPM-RobotManip/evaluation/libero/requirements-client.txt create mode 100755 MiniCPM-RobotManip/evaluation/libero/run_policy_server.sh create mode 100644 MiniCPM-RobotManip/evaluation/robotwin/README.md create mode 100644 MiniCPM-RobotManip/evaluation/robotwin/__init__.py create mode 100644 MiniCPM-RobotManip/evaluation/robotwin/deploy_policy.yml create mode 100755 MiniCPM-RobotManip/evaluation/robotwin/eval.sh create mode 100644 MiniCPM-RobotManip/evaluation/robotwin/launcher.py create mode 100644 MiniCPM-RobotManip/evaluation/robotwin/model2robotwin_interface.py create mode 100644 MiniCPM-RobotManip/evaluation/robotwin/probe_server.py create mode 100644 MiniCPM-RobotManip/evaluation/robotwin/requirements-client.txt create mode 100755 MiniCPM-RobotManip/evaluation/robotwin/run_policy_server.sh create mode 100755 MiniCPM-RobotManip/evaluation/robotwin/start_eval.sh create mode 100644 MiniCPM-RobotManip/evaluation/tests/__init__.py create mode 100644 MiniCPM-RobotManip/evaluation/tests/test_calvin_contract.py create mode 100644 MiniCPM-RobotManip/evaluation/tests/test_launchers.py create mode 100644 MiniCPM-RobotManip/evaluation/tests/test_policy_clients.py diff --git a/.gitignore b/.gitignore index 4f90b4d..8e8ed99 100644 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,9 @@ MiniCPM-RobotTrack/realworld/output/* *.log *.tmp *.part* + +.cursor/ + +# Local tests +MiniCPM-RobotManip/tests/ +MiniCPM-RobotManip/command \ No newline at end of file diff --git a/MiniCPM-RobotManip/condaenv.7heggirf.requirements.txt b/MiniCPM-RobotManip/condaenv.7heggirf.requirements.txt new file mode 100644 index 0000000..d492850 --- /dev/null +++ b/MiniCPM-RobotManip/condaenv.7heggirf.requirements.txt @@ -0,0 +1,9 @@ +numpy==1.26.4 +pillow==12.2.0 +torch==2.6.0 +scipy>=1.12,<2 +transformers==5.7.0 +diffusers==0.38.0 +safetensors>=0.4.3 +websockets==16.0 +msgpack==1.1.2 \ No newline at end of file diff --git a/MiniCPM-RobotManip/deployment/README.md b/MiniCPM-RobotManip/deployment/README.md new file mode 100644 index 0000000..b777c71 --- /dev/null +++ b/MiniCPM-RobotManip/deployment/README.md @@ -0,0 +1,177 @@ +# MiniCPM-RobotManip WebSocket deployment + +This server implements the WebSocket + MessagePack/NumPy contract used by +starVLA evaluators. The first release targets LIBERO, CALVIN, and RoboTwin. + +## Start the server + +Run from `MiniCPM-RobotManip` so that both `vla_infer.py` and the `deployment` +package are importable: + +```bash +conda activate MiniCPM-RobotManip +cd MiniCPM-RobotManip +python -m deployment.model_server.server_policy \ + --checkpoint openbmb/MiniCPM-RobotManip \ + --device cuda \ + --host 127.0.0.1 \ + --port 10093 \ + --default-embodiment-id 0 +``` + +Existing starVLA evaluators don't send `embodiment_id`. Select the ID required +by the target robot with `--default-embodiment-id`; the published checkpoint +doesn't provide a reliable public ID-to-robot mapping. A request-level +`embodiment_id` overrides the server default. + +The default host is local-only. Bind to `0.0.0.0` only on a trusted network or +behind an authenticated proxy. + +## Inference contract + +An existing starVLA client sends a flat MessagePack payload: + +```python +{ + "examples": [ + { + "image": [camera_0, camera_1], # ordered uint8 HWC RGB arrays + "lang": "Pick up the red block.", + "state": state, # optional 80-D state + } + ], + "unnorm_key": None, # accepted and ignored + "do_sample": False, # accepted and ignored + "use_ddim": True, # accepted and ignored + "num_ddim_steps": 10, # accepted and ignored + "embodiment_id": 0, # optional + "seed": 123, # optional +} +``` + +`examples` must contain exactly one current time step. Its `image` list may +contain multiple synchronized camera views, in training-time order. It must +not contain historical frames. `text` is accepted as an alias for `lang`. +Missing state is replaced by the model's existing 80-D zero-state behavior. +Every view is validated as RGB `uint8 HWC` and then resized by +`MiniCPMVLAInference` to 448×448. + +The response matches current starVLA clients: + +```python +{ + "status": "ok", + "ok": True, + "type": "inference_result", + "request_id": "default", + "data": { + "actions": actions, # np.float32, shape (1, 30, 80) + }, +} +``` + +MiniCPM-RobotManip actions are execution-ready. The server does not normalize, +un-normalize, clip, truncate, reorder, or otherwise transform action values. +It only adds the batch dimension required by starVLA. Consequently +`unnorm_key` is a compatibility-only no-op, and handshake metadata reports +`action_normalization="none"` and `available_unnorm_keys=[]`. + +The server also accepts the versioned envelope: + +```python +{ + "type": "infer", + "request_id": "request-1", + "payload": { + "examples": [...], + }, +} +``` + +## Target evaluators + +- **LIBERO:** one frame with views ordered as + `[agentview, eye_in_hand]`; the evaluator consumes the first 7 action + dimensions. +- **CALVIN:** one frame with views ordered as + `[rgb_static, rgb_gripper]`; the evaluator consumes the first 7 dimensions. +- **RoboTwin:** one frame with views ordered as + `[head, left_wrist, right_wrist]`; the client omits its incompatible 14-D + state and consumes the first 14 dimensions with the required joint reorder. + +The migrated evaluators live under `MiniCPM-RobotManip/evaluation`: + +```bash +# LIBERO multi-GPU +LIBERO_HOME=/path/to/LIBERO GPU_LIST="0 1" EMBODIMENT_ID=0 \ +bash evaluation/libero/auto_eval_scripts/auto_eval_libero.sh \ + --checkpoint openbmb/MiniCPM-RobotManip + +# CALVIN serial +MINICPM_PYTHON=/path/to/minicpm/python \ +CALVIN_PYTHON=/path/to/calvin/python \ +CALVIN_ROOT=/path/to/CALVIN CALVIN_DATASET_PATH=/path/to/task_D_D \ +EMBODIMENT_ID=0 \ +bash evaluation/calvin/eval_calvin.sh + +# RoboTwin multi-GPU +ROBOTWIN_PATH=/path/to/RoboTwin \ +bash evaluation/robotwin/start_eval.sh \ + --mode demo_clean --run-name minicpm \ + --checkpoint openbmb/MiniCPM-RobotManip \ + --default-embodiment-id 0 all +``` + +All migrated clients resize to 448×448 and send no normalization, DDIM, or +evaluator-side checkpoint fields. The server process alone loads the model +checkpoint. See `evaluation//README.md` for external simulator +setup, single-worker commands, and camera/action contracts. + +LIBERO OpenPI, BEHAVIOR's `normalized_actions` response, and VLN-CE's text +generation protocol are different wire contracts and are not supported by +this server. + +## Reserved streaming extension + +Stateless `infer` and `predict_action` always mean one complete current frame. +They will remain unchanged when streaming is added. + +Protocol version 1 reserves four envelope message types: + +- `session.open`: create a model-context session and return `session_id`. +- `stream.infer`: send `session_id`, monotonic `sequence_id`, and one current + frame. +- `session.reset`: clear the context for `session_id`. +- `session.close`: release `session_id`. + +The current model adapter implements only `FramePolicy`, so handshake +capabilities report `streaming=false` and `sessions=false`. Reserved calls +return `capability_not_supported` without closing the connection. + +`WebsocketPolicyServer.register_handler()` can replace each reserved route. +Future native-cache support can implement `StreamingPolicy` plus a separate +session manager and register those handlers without changing MessagePack +encoding, the receive loop, error responses, or stateless inference. +Multi-view images are never interpreted as temporal history. + +## Smoke test + +```python +import numpy as np + +from deployment.model_server.tools.websocket_policy_client import WebsocketClientPolicy + +image = np.zeros((448, 448, 3), dtype=np.uint8) +with WebsocketClientPolicy("127.0.0.1", 10093) as client: + print(client.get_server_metadata()) + response = client.predict_action({ + "examples": [{"image": [image], "lang": "Move forward."}], + }) + print(response["data"]["actions"].shape) +``` + +Run protocol and adapter tests without loading the checkpoint: + +```bash +python -m unittest discover -s tests -p 'test_*.py' -v +``` diff --git a/MiniCPM-RobotManip/deployment/__init__.py b/MiniCPM-RobotManip/deployment/__init__.py new file mode 100644 index 0000000..b444224 --- /dev/null +++ b/MiniCPM-RobotManip/deployment/__init__.py @@ -0,0 +1 @@ +"""MiniCPM-RobotManip deployment utilities.""" diff --git a/MiniCPM-RobotManip/deployment/model_server/__init__.py b/MiniCPM-RobotManip/deployment/model_server/__init__.py new file mode 100644 index 0000000..fc603d2 --- /dev/null +++ b/MiniCPM-RobotManip/deployment/model_server/__init__.py @@ -0,0 +1 @@ +"""starVLA-compatible MiniCPM policy server.""" diff --git a/MiniCPM-RobotManip/deployment/model_server/policy_wrapper.py b/MiniCPM-RobotManip/deployment/model_server/policy_wrapper.py new file mode 100644 index 0000000..b16fa47 --- /dev/null +++ b/MiniCPM-RobotManip/deployment/model_server/policy_wrapper.py @@ -0,0 +1,217 @@ +# Copyright 2026 The OpenBMB Team. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Adapt MiniCPM-RobotManip inference to the starVLA policy contract.""" + +from __future__ import annotations + +import logging +from typing import Any + +import numpy as np + + +_IGNORED_STARVLA_KWARGS = frozenset( + { + "do_sample", + "use_ddim", + "num_ddim_steps", + "cfg_scale", + } +) + + +def _model_dimension(runner: Any, name: str, default: int) -> int: + model = getattr(runner, "model", None) + config = getattr(model, "config", None) + value = getattr(config, name, None) + if value is None: + value = getattr(getattr(model, "action_head", None), name, default) + return int(value) + + +def _to_numpy(action: Any) -> np.ndarray: + if hasattr(action, "detach"): + action = action.detach() + if hasattr(action, "cpu"): + action = action.cpu() + if hasattr(action, "numpy"): + action = action.numpy() + return np.asarray(action) + + +class MiniCPMPolicyWrapper: + """Expose a single-frame, multi-view MiniCPM runner as a starVLA policy.""" + + def __init__(self, runner: Any, default_embodiment_id: int = 0) -> None: + self._runner = runner + self.action_chunk_size = _model_dimension(runner, "action_horizon", 30) + self.action_dim = _model_dimension(runner, "action_dim", 80) + self.state_dim = _model_dimension(runner, "state_dim", 80) + self.max_num_embodiments = _model_dimension(runner, "max_num_embodiments", 32) + self.default_embodiment_id = self._validate_embodiment_id(default_embodiment_id) + + @property + def metadata(self) -> dict[str, Any]: + """Return model-invariant fields sent during the WebSocket handshake.""" + return { + "action_chunk_size": self.action_chunk_size, + "action_dim": self.action_dim, + "state_dim": self.state_dim, + "max_num_embodiments": self.max_num_embodiments, + "default_embodiment_id": self.default_embodiment_id, + "training_obs_image_size": [448, 448], + "max_batch_size": 1, + "single_frame": True, + "multi_view": True, + "action_normalization": "none", + "actions_ready_for_execution": True, + "available_unnorm_keys": [], + "default_unnorm_key": None, + } + + def _validate_embodiment_id(self, value: Any) -> int: + if isinstance(value, bool) or not isinstance(value, (int, np.integer)): + raise TypeError("embodiment_id must be an integer") + embodiment_id = int(value) + if not 0 <= embodiment_id < self.max_num_embodiments: + raise ValueError( + f"embodiment_id must be in [0, {self.max_num_embodiments - 1}], " + f"got {embodiment_id}" + ) + return embodiment_id + + @staticmethod + def _validate_seed(value: Any) -> int | None: + if value is None: + return None + if isinstance(value, bool) or not isinstance(value, (int, np.integer)): + raise TypeError("seed must be an integer or None") + return int(value) + + @staticmethod + def _extract_text(example: dict[str, Any]) -> str: + lang = example.get("lang") + text = example.get("text") + if lang is not None and text is not None and lang != text: + raise ValueError("example.lang and example.text disagree") + prompt = lang if lang is not None else text + if not isinstance(prompt, str) or not prompt.strip(): + raise ValueError("example.lang (or example.text) must be a non-empty string") + return prompt + + @staticmethod + def _extract_images(example: dict[str, Any]) -> list[np.ndarray]: + images = example.get("image") + if isinstance(images, np.ndarray): + images = [images] + elif isinstance(images, (list, tuple)): + images = list(images) + else: + raise TypeError("example.image must be an ndarray or a non-empty image sequence") + + if not images: + raise ValueError("example.image must contain at least one camera view") + + validated = [] + for index, image in enumerate(images): + if not isinstance(image, np.ndarray): + raise TypeError(f"example.image[{index}] must be a NumPy array") + if image.dtype != np.uint8: + raise ValueError( + f"example.image[{index}] must have dtype uint8, got {image.dtype}" + ) + if image.ndim != 3 or image.shape[-1] != 3: + raise ValueError( + f"example.image[{index}] must have shape HxWx3, got {image.shape}" + ) + validated.append(image) + return validated + + def _extract_state(self, example: dict[str, Any]) -> np.ndarray | None: + state = example.get("state") + if state is None: + return None + state_array = np.asarray(state, dtype=np.float32) + valid_shapes = { + (self.state_dim,), + (1, self.state_dim), + (1, 1, self.state_dim), + } + if state_array.shape not in valid_shapes: + raise ValueError( + f"state must have shape ({self.state_dim},), " + f"(1, {self.state_dim}), or (1, 1, {self.state_dim}); " + f"got {state_array.shape}" + ) + if not np.isfinite(state_array).all(): + raise ValueError("state must contain only finite values") + return state_array + + def predict_action( + self, + examples: list[dict[str, Any]], + unnorm_key: str | None = None, + embodiment_id: int | None = None, + seed: int | None = None, + **kwargs: Any, + ) -> dict[str, np.ndarray]: + """Run one stateless frame and return execution-ready actions unchanged.""" + del unnorm_key # Compatibility-only; MiniCPM actions need no unnormalization. + + unknown = set(kwargs) - _IGNORED_STARVLA_KWARGS + if unknown: + names = ", ".join(sorted(unknown)) + raise TypeError(f"Unsupported inference arguments: {names}") + ignored = sorted(name for name in kwargs if kwargs[name] is not None) + if ignored: + logging.debug("Ignoring starVLA-only inference arguments: %s", ignored) + + if not isinstance(examples, list) or len(examples) != 1: + raise ValueError("examples must be a list containing exactly one frame") + example = examples[0] + if not isinstance(example, dict): + raise TypeError("examples[0] must be a dict") + + images = self._extract_images(example) + text = self._extract_text(example) + state = self._extract_state(example) + selected_embodiment = self._validate_embodiment_id( + self.default_embodiment_id if embodiment_id is None else embodiment_id + ) + selected_seed = self._validate_seed(seed) + + action = self._runner.predict( + images=images, + text=text, + state=state, + embodiment_id=selected_embodiment, + seed=selected_seed, + ) + action_array = _to_numpy(action) + expected_shape = (self.action_chunk_size, self.action_dim) + if action_array.shape != expected_shape: + raise ValueError( + f"MiniCPM returned action shape {action_array.shape}; " + f"expected {expected_shape}" + ) + if not np.issubdtype(action_array.dtype, np.floating): + raise TypeError(f"MiniCPM returned non-floating actions: {action_array.dtype}") + if not np.isfinite(action_array).all(): + raise ValueError("MiniCPM returned non-finite actions") + + # vla_infer already returns float32 execution-ready actions. astype(copy=False) + # only enforces the wire dtype; values, dimensions, and ordering are untouched. + action_array = action_array.astype(np.float32, copy=False) + return {"actions": action_array[None, ...]} diff --git a/MiniCPM-RobotManip/deployment/model_server/protocol.py b/MiniCPM-RobotManip/deployment/model_server/protocol.py new file mode 100644 index 0000000..f5bbf1c --- /dev/null +++ b/MiniCPM-RobotManip/deployment/model_server/protocol.py @@ -0,0 +1,145 @@ +# Copyright 2026 The OpenBMB Team. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Wire-level contracts for stateless and future streaming policy calls.""" + +from __future__ import annotations + +from typing import Any, Protocol + + +PROTOCOL_VERSION = 1 + +PING = "ping" +INFER = "infer" +PREDICT_ACTION = "predict_action" + +SESSION_OPEN = "session.open" +STREAM_INFER = "stream.infer" +SESSION_RESET = "session.reset" +SESSION_CLOSE = "session.close" +RESERVED_STREAM_MESSAGE_TYPES = ( + SESSION_OPEN, + STREAM_INFER, + SESSION_RESET, + SESSION_CLOSE, +) + + +class ProtocolError(ValueError): + """Raised when a decoded MessagePack frame violates the wire contract.""" + + +class FramePolicy(Protocol): + """Current stateless model boundary.""" + + @property + def metadata(self) -> dict[str, Any]: ... + + def predict_action(self, **payload: Any) -> dict[str, Any]: ... + + +class StreamingPolicy(Protocol): + """Optional future model boundary; MiniCPM's current adapter does not implement it.""" + + def open_session(self, **payload: Any) -> dict[str, Any]: ... + + def predict_stream_frame(self, **payload: Any) -> dict[str, Any]: ... + + def reset_session(self, **payload: Any) -> dict[str, Any]: ... + + def close_session(self, **payload: Any) -> dict[str, Any]: ... + + +def build_server_metadata( + policy_metadata: dict[str, Any], + *, + checkpoint: str, +) -> dict[str, Any]: + """Build the first frame sent to every connected starVLA client.""" + metadata = { + "protocol_version": PROTOCOL_VERSION, + "server": "minicpm_robot_manip", + "env": "minicpm_robot_manip", + "ckpt_path": checkpoint, + "capabilities": { + "infer": True, + "single_frame": True, + "multi_view": True, + "streaming": False, + "sessions": False, + "reserved_message_types": list(RESERVED_STREAM_MESSAGE_TYPES), + }, + } + metadata.update(policy_metadata) + return metadata + + +def decode_request(message: Any) -> tuple[str, Any, dict[str, Any]]: + """Normalize starVLA flat payloads and versioned request envelopes.""" + if not isinstance(message, dict): + raise ProtocolError("Request must decode to a dict") + + request_id = message.get("request_id", "default") + message_type = message.get("type", INFER) + if not isinstance(message_type, str) or not message_type: + raise ProtocolError("Request type must be a non-empty string") + + if "payload" in message: + payload = message["payload"] + else: + payload = { + key: value + for key, value in message.items() + if key not in {"type", "request_id"} + } + if not isinstance(payload, dict): + raise ProtocolError("Request payload must be a dict") + return message_type, request_id, payload + + +def success_response( + *, + request_id: Any, + message_type: str, + data: dict[str, Any] | None = None, +) -> dict[str, Any]: + response: dict[str, Any] = { + "status": "ok", + "ok": True, + "type": message_type, + "request_id": request_id, + } + if data is not None: + response["data"] = data + return response + + +def error_response( + *, + request_id: Any = "default", + message: str, + code: str, + message_type: str = "error", +) -> dict[str, Any]: + return { + "status": "error", + "ok": False, + "type": message_type, + "request_id": request_id, + "error": { + "code": code, + "message": message, + }, + } diff --git a/MiniCPM-RobotManip/deployment/model_server/server_policy.py b/MiniCPM-RobotManip/deployment/model_server/server_policy.py new file mode 100644 index 0000000..c191acc --- /dev/null +++ b/MiniCPM-RobotManip/deployment/model_server/server_policy.py @@ -0,0 +1,114 @@ +# Copyright 2026 The OpenBMB Team. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Launch a starVLA-compatible MiniCPM-RobotManip WebSocket server.""" + +from __future__ import annotations + +import argparse +import logging +from typing import Any + +from deployment.model_server.policy_wrapper import MiniCPMPolicyWrapper +from deployment.model_server.protocol import build_server_metadata +from deployment.model_server.tools.websocket_policy_server import WebsocketPolicyServer + + +def _load_runner(checkpoint: str, device: str | None) -> Any: + # Keep model imports and checkpoint loading out of module import so --help and + # protocol-only tests don't require a GPU or download model files. + from vla_infer import MiniCPMVLAInference + + return MiniCPMVLAInference( + checkpoint_path=checkpoint, + device=device, + ) + + +def main(args: argparse.Namespace) -> None: + logging.info("Loading MiniCPM-RobotManip from %s", args.checkpoint) + runner = _load_runner(args.checkpoint, args.device) + policy = MiniCPMPolicyWrapper( + runner=runner, + default_embodiment_id=args.default_embodiment_id, + ) + metadata = build_server_metadata( + policy.metadata, + checkpoint=args.checkpoint, + ) + logging.warning( + "Serving execution-ready MiniCPM actions without normalization or " + "un-normalization. Verify camera order and embodiment_id for the target robot." + ) + logging.info("Server metadata: %s", metadata) + + server = WebsocketPolicyServer( + policy=policy, + host=args.host, + port=args.port, + idle_timeout=args.idle_timeout, + metadata=metadata, + max_message_bytes=args.max_message_bytes, + ) + server.serve_forever() + + +def build_argparser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "--checkpoint", + default="openbmb/MiniCPM-RobotManip", + help="Hugging Face model ID or local checkpoint directory", + ) + parser.add_argument( + "--device", + default=None, + help="PyTorch device; default is CUDA when available, otherwise CPU", + ) + parser.add_argument("--host", default="127.0.0.1") + parser.add_argument("--port", type=int, default=10093) + parser.add_argument( + "--default-embodiment-id", + type=int, + default=0, + help="Used when a starVLA evaluator doesn't send embodiment_id", + ) + parser.add_argument( + "--idle-timeout", + type=int, + default=-1, + help="Server idle timeout in seconds; -1 disables automatic shutdown", + ) + parser.add_argument( + "--max-message-bytes", + type=int, + default=16 * 1024 * 1024, + help="Maximum incoming WebSocket frame size", + ) + parser.add_argument( + "--log-level", + choices=("DEBUG", "INFO", "WARNING", "ERROR"), + default="INFO", + ) + return parser + + +if __name__ == "__main__": + parsed_args = build_argparser().parse_args() + logging.basicConfig( + level=getattr(logging, parsed_args.log_level), + format="%(asctime)s %(levelname)s %(name)s: %(message)s", + force=True, + ) + main(parsed_args) diff --git a/MiniCPM-RobotManip/deployment/model_server/tools/__init__.py b/MiniCPM-RobotManip/deployment/model_server/tools/__init__.py new file mode 100644 index 0000000..2c8c7d4 --- /dev/null +++ b/MiniCPM-RobotManip/deployment/model_server/tools/__init__.py @@ -0,0 +1 @@ +"""Transport helpers for the MiniCPM policy server.""" diff --git a/MiniCPM-RobotManip/deployment/model_server/tools/msgpack_numpy.py b/MiniCPM-RobotManip/deployment/model_server/tools/msgpack_numpy.py new file mode 100644 index 0000000..5dda1a3 --- /dev/null +++ b/MiniCPM-RobotManip/deployment/model_server/tools/msgpack_numpy.py @@ -0,0 +1,60 @@ +# Copyright 2025 starVLA community. All rights reserved. +# Licensed under the MIT License. +# Modifications Copyright 2026 The OpenBMB Team. + +"""MessagePack helpers compatible with starVLA's NumPy wire encoding. + +The encoding is adapted through starVLA from the BSD-licensed +``msgpack-numpy`` project. See ``evaluation/THIRD_PARTY_NOTICES.md``. +""" + +from __future__ import annotations + +import functools +from typing import Any + +import msgpack +import numpy as np + + +def pack_array(obj: Any) -> Any: + if isinstance(obj, (np.ndarray, np.generic)) and obj.dtype.kind in ("V", "O", "c"): + raise ValueError(f"Unsupported dtype: {obj.dtype}") + + if isinstance(obj, np.ndarray): + return { + b"__ndarray__": True, + b"data": obj.tobytes(), + b"dtype": obj.dtype.str, + b"shape": obj.shape, + } + + if isinstance(obj, np.generic): + return { + b"__npgeneric__": True, + b"data": obj.item(), + b"dtype": obj.dtype.str, + } + + return obj + + +def unpack_array(obj: dict[Any, Any]) -> Any: + if b"__ndarray__" in obj: + return np.ndarray( + buffer=obj[b"data"], + dtype=np.dtype(obj[b"dtype"]), + shape=obj[b"shape"], + ) + + if b"__npgeneric__" in obj: + return np.dtype(obj[b"dtype"]).type(obj[b"data"]) + + return obj + + +Packer = functools.partial(msgpack.Packer, default=pack_array) +packb = functools.partial(msgpack.packb, default=pack_array) + +Unpacker = functools.partial(msgpack.Unpacker, object_hook=unpack_array) +unpackb = functools.partial(msgpack.unpackb, object_hook=unpack_array) diff --git a/MiniCPM-RobotManip/deployment/model_server/tools/websocket_policy_client.py b/MiniCPM-RobotManip/deployment/model_server/tools/websocket_policy_client.py new file mode 100644 index 0000000..b574be7 --- /dev/null +++ b/MiniCPM-RobotManip/deployment/model_server/tools/websocket_policy_client.py @@ -0,0 +1,100 @@ +# Copyright 2025 starVLA community. All rights reserved. +# Licensed under the MIT License. +# Modifications Copyright 2026 The OpenBMB Team. + +"""Small synchronous client for the MiniCPM/starVLA wire protocol.""" + +from __future__ import annotations + +import inspect +from typing import Any + +import websockets.sync.client + +from deployment.model_server.tools import msgpack_numpy + + +class WebsocketClientPolicy: + def __init__( + self, + host: str = "127.0.0.1", + port: int | None = 10093, + *, + open_timeout: float = 30, + response_timeout: float | None = 30, + max_message_bytes: int = 16 * 1024 * 1024, + ) -> None: + uri = f"ws://{host}" + if port is not None: + uri += f":{port}" + self._packer = msgpack_numpy.Packer() + connect_kwargs = { + "compression": None, + "max_size": max_message_bytes, + "open_timeout": open_timeout, + "ping_interval": None, + } + # websockets 15 added the proxy argument. Evaluator environments may use + # websockets 13 on Python 3.8, so disable proxies only when supported. + if "proxy" in inspect.signature( + websockets.sync.client.connect + ).parameters: + connect_kwargs["proxy"] = None + self._ws = websockets.sync.client.connect(uri, **connect_kwargs) + self._response_timeout = response_timeout + metadata_frame = self._ws.recv(timeout=open_timeout) + if isinstance(metadata_frame, str): + self._ws.close() + raise RuntimeError("Server metadata must be a binary MessagePack frame") + metadata = msgpack_numpy.unpackb(metadata_frame) + if not isinstance(metadata, dict): + self._ws.close() + raise RuntimeError("Server metadata must decode to a dict") + self._server_metadata = metadata + + def __enter__(self) -> "WebsocketClientPolicy": + return self + + def __exit__(self, *exc_info: Any) -> None: + self.close() + + def get_server_metadata(self) -> dict[str, Any]: + return dict(self._server_metadata) + + def _send_receive(self, message: dict[str, Any]) -> dict[str, Any]: + self._ws.send(self._packer.pack(message)) + frame = self._ws.recv(timeout=self._response_timeout) + if isinstance(frame, str): + raise RuntimeError(f"Server returned an unexpected text frame: {frame}") + response = msgpack_numpy.unpackb(frame) + if not isinstance(response, dict): + raise RuntimeError("Server response must decode to a dict") + return response + + def predict_action(self, query_info: dict[str, Any]) -> dict[str, Any]: + """Send the flat payload used by existing starVLA evaluators.""" + return self._send_receive(query_info) + + def request( + self, + message_type: str, + payload: dict[str, Any] | None = None, + *, + request_id: Any = "default", + ) -> dict[str, Any]: + return self._send_receive( + { + "type": message_type, + "request_id": request_id, + "payload": payload or {}, + } + ) + + def ping(self, *, request_id: Any = "default") -> dict[str, Any]: + return self.request("ping", request_id=request_id) + + def close(self) -> None: + try: + self._ws.close() + except Exception: + pass diff --git a/MiniCPM-RobotManip/deployment/model_server/tools/websocket_policy_server.py b/MiniCPM-RobotManip/deployment/model_server/tools/websocket_policy_server.py new file mode 100644 index 0000000..988c291 --- /dev/null +++ b/MiniCPM-RobotManip/deployment/model_server/tools/websocket_policy_server.py @@ -0,0 +1,323 @@ +# Copyright 2025 starVLA community. All rights reserved. +# Licensed under the MIT License. +# Modifications Copyright 2026 The OpenBMB Team. + +"""Async MessagePack WebSocket transport for MiniCPM policies.""" + +from __future__ import annotations + +import asyncio +import logging +import time +from collections.abc import Awaitable, Callable, Mapping +from typing import Any + +import websockets.asyncio.server +from websockets.exceptions import ConnectionClosed + +from deployment.model_server import protocol +from deployment.model_server.tools import msgpack_numpy + + +RouteHandler = Callable[ + [str, Any, dict[str, Any]], + Awaitable[dict[str, Any]], +] + +_CORE_MESSAGE_TYPES = frozenset( + { + protocol.PING, + protocol.INFER, + protocol.PREDICT_ACTION, + } +) + + +class WebsocketPolicyServer: + """Serve a frame policy with starVLA-compatible transport semantics.""" + + def __init__( + self, + policy: protocol.FramePolicy, + host: str = "127.0.0.1", + port: int = 10093, + idle_timeout: int = -1, + metadata: dict[str, Any] | None = None, + max_message_bytes: int = 16 * 1024 * 1024, + extra_handlers: Mapping[str, RouteHandler] | None = None, + ) -> None: + if not 0 <= port <= 65535: + raise ValueError(f"port must be in [0, 65535], got {port}") + if max_message_bytes <= 0: + raise ValueError("max_message_bytes must be positive") + + self._policy = policy + self._host = host + self._port = port + self._metadata = dict(metadata or {}) + self._idle_timeout = idle_timeout + self._max_message_bytes = max_message_bytes + self._last_active = time.monotonic() + self._active_inferences = 0 + self._inference_lock = asyncio.Lock() + self._server: Any = None + self._watchdog_task: asyncio.Task[None] | None = None + self._bound_port: int | None = None + + self._handlers: dict[str, RouteHandler] = { + protocol.PING: self._handle_ping, + protocol.INFER: self._handle_infer, + protocol.PREDICT_ACTION: self._handle_infer, + } + for message_type in protocol.RESERVED_STREAM_MESSAGE_TYPES: + self._handlers[message_type] = self._handle_unsupported_capability + for message_type, handler in (extra_handlers or {}).items(): + self.register_handler(message_type, handler) + + logging.getLogger("websockets.server").setLevel(logging.INFO) + + @property + def bound_port(self) -> int | None: + """Actual listening port, including an OS-assigned port when configured as 0.""" + return self._bound_port + + def register_handler(self, message_type: str, handler: RouteHandler) -> None: + """Register or replace a non-core route, including reserved stream routes.""" + if not isinstance(message_type, str) or not message_type: + raise ValueError("message_type must be a non-empty string") + if message_type in _CORE_MESSAGE_TYPES: + raise ValueError(f"Cannot replace core message type {message_type!r}") + if not callable(handler): + raise TypeError("handler must be callable") + self._handlers[message_type] = handler + + async def start(self) -> None: + """Start listening without blocking; primarily useful for integration tests.""" + if self._server is not None: + raise RuntimeError("WebSocket server is already running") + self._last_active = time.monotonic() + self._server = await websockets.asyncio.server.serve( + self._handler, + self._host, + self._port, + compression=None, + max_size=self._max_message_bytes, + ) + sockets = getattr(self._server, "sockets", None) + if sockets: + self._bound_port = int(sockets[0].getsockname()[1]) + else: + self._bound_port = self._port + if self._idle_timeout > 0: + self._watchdog_task = asyncio.create_task(self._idle_watchdog()) + logging.info( + "WebSocket policy server listening on %s:%s", + self._host, + self._bound_port, + ) + + async def run(self) -> None: + await self.start() + assert self._server is not None + try: + await self._server.wait_closed() + finally: + await self.close() + + def serve_forever(self) -> None: + asyncio.run(self.run()) + + async def close(self) -> None: + watchdog = self._watchdog_task + self._watchdog_task = None + if watchdog is not None and watchdog is not asyncio.current_task(): + watchdog.cancel() + await asyncio.gather(watchdog, return_exceptions=True) + + server = self._server + self._server = None + if server is not None: + server.close() + await server.wait_closed() + self._bound_port = None + + async def _idle_watchdog(self) -> None: + while True: + await asyncio.sleep(min(5, max(0.1, self._idle_timeout))) + if self._active_inferences: + continue + if time.monotonic() - self._last_active <= self._idle_timeout: + continue + logging.info( + "Idle timeout (%ss) reached; closing WebSocket server", + self._idle_timeout, + ) + server = self._server + if server is not None: + server.close() + await server.wait_closed() + return + + async def _handler( + self, + websocket: websockets.asyncio.server.ServerConnection, + ) -> None: + logging.info("Connection from %s opened", websocket.remote_address) + packer = msgpack_numpy.Packer() + await websocket.send(packer.pack(self._metadata)) + + while True: + try: + frame = await websocket.recv() + except ConnectionClosed: + logging.info("Connection from %s closed", websocket.remote_address) + return + + self._last_active = time.monotonic() + if isinstance(frame, str): + response = protocol.error_response( + message="Requests must be binary MessagePack frames", + code="invalid_frame", + ) + else: + try: + message = msgpack_numpy.unpackb(frame) + except Exception as exc: + logging.warning("Failed to decode MessagePack request: %s", exc) + response = protocol.error_response( + message="Malformed MessagePack request", + code="invalid_message", + ) + else: + response = await self._route_message(message) + + try: + encoded = packer.pack(response) + except Exception: + logging.exception("Failed to encode policy response") + request_id = ( + response.get("request_id", "default") + if isinstance(response, dict) + else "default" + ) + message_type = ( + response.get("type", "error") + if isinstance(response, dict) + else "error" + ) + encoded = packer.pack( + protocol.error_response( + request_id=request_id, + message="Response serialization failed", + code="serialization_error", + message_type=message_type, + ) + ) + try: + await websocket.send(encoded) + except ConnectionClosed: + logging.info("Connection from %s closed", websocket.remote_address) + return + + async def _route_message(self, message: Any) -> dict[str, Any]: + request_id = ( + message.get("request_id", "default") + if isinstance(message, dict) + else "default" + ) + requested_type = ( + message.get("type", protocol.INFER) + if isinstance(message, dict) + else "error" + ) + try: + message_type, request_id, payload = protocol.decode_request(message) + except protocol.ProtocolError as exc: + return protocol.error_response( + request_id=request_id, + message=str(exc), + code="invalid_request", + message_type=( + requested_type if isinstance(requested_type, str) else "error" + ), + ) + + handler = self._handlers.get(message_type) + if handler is None: + return protocol.error_response( + request_id=request_id, + message=f"Unsupported message type {message_type!r}", + code="unknown_message_type", + message_type="unknown", + ) + + try: + return await handler(message_type, request_id, payload) + except Exception as exc: + logging.exception( + "Policy route failed (type=%s, request_id=%s)", + message_type, + request_id, + ) + response_type = ( + "inference_result" + if message_type in {protocol.INFER, protocol.PREDICT_ACTION} + else message_type + ) + return protocol.error_response( + request_id=request_id, + message=str(exc), + code="handler_error", + message_type=response_type, + ) + + async def _handle_ping( + self, + message_type: str, + request_id: Any, + payload: dict[str, Any], + ) -> dict[str, Any]: + del message_type, payload + return protocol.success_response( + request_id=request_id, + message_type=protocol.PING, + ) + + async def _handle_infer( + self, + message_type: str, + request_id: Any, + payload: dict[str, Any], + ) -> dict[str, Any]: + del message_type + async with self._inference_lock: + self._active_inferences += 1 + try: + output = await asyncio.to_thread(self._policy.predict_action, **payload) + finally: + self._active_inferences -= 1 + self._last_active = time.monotonic() + if not isinstance(output, dict): + raise TypeError("Policy predict_action must return a dict") + return protocol.success_response( + request_id=request_id, + message_type="inference_result", + data=output, + ) + + async def _handle_unsupported_capability( + self, + message_type: str, + request_id: Any, + payload: dict[str, Any], + ) -> dict[str, Any]: + del payload + return protocol.error_response( + request_id=request_id, + message=( + f"{message_type!r} is reserved for a future streaming backend " + "and isn't supported by this server" + ), + code="capability_not_supported", + message_type=message_type, + ) diff --git a/MiniCPM-RobotManip/evaluation/README.md b/MiniCPM-RobotManip/evaluation/README.md new file mode 100644 index 0000000..cde643e --- /dev/null +++ b/MiniCPM-RobotManip/evaluation/README.md @@ -0,0 +1,88 @@ +# MiniCPM-RobotManip simulation evaluation + +This package migrates the standard starVLA `data.actions` evaluation paths for +LIBERO, CALVIN, and RoboTwin. The simulator source code, datasets, and assets +remain external installations. + +The migration is based on starVLA commit +`631aae02afe6d95876e923ff518e8ff2ab9a2f88`. Evaluator control flow and +environment-specific action conversion are retained, while model loading and +wire transport use the MiniCPM-RobotManip deployment server. + +## Layout + +- `libero/`: serial evaluation and bounded multi-GPU suite scheduling. +- `calvin/`: serial 1,000-sequence long-horizon evaluation. +- `robotwin/`: single-task evaluation and multi-GPU FIFO scheduling. +- `common/`: source-compatible helpers shared by migrated evaluators. +- `tests/`: protocol, adapter, and launcher tests that don't require simulators. + +Each benchmark README documents its external environment and commands: + +- [LIBERO](libero/README.md) +- [CALVIN](calvin/README.md) +- [RoboTwin](robotwin/README.md) + +## Common policy contract + +All evaluators connect to the WebSocket + MessagePack server under +`deployment/model_server`. A request contains exactly one current time step +and one or more ordered camera views. The server returns execution-ready +`float32` actions with shape `(1, 30, 80)`. + +Neither the server nor these evaluators normalize or unnormalize model +actions. Environment adapters only perform operations required by the target +API: + +- LIBERO and CALVIN consume the first 7 dimensions and convert the gripper to + the simulator convention. +- RoboTwin applies the migrated 14-D joint/gripper reorder. + +The simulator clients don't send their incompatible low-dimensional robot +state, so the server uses MiniCPM's existing 80-D zero-state behavior. + +The public checkpoint doesn't document a reliable embodiment-ID mapping. +Always set the benchmark's `EMBODIMENT_ID` or +`--default-embodiment-id` to the value required by the selected checkpoint. + +## Environments + +Model server and simulator run in separate Python environments: + +- `MINICPM_PYTHON`: MiniCPM model dependencies and GPU inference. +- `LIBERO_PYTHON`, `CALVIN_PYTHON`, or `ROBOTWIN_PYTHON`: the corresponding + simulator plus the benchmark's `requirements-client.txt`. + +The lightweight client supports Python 3.8 simulator environments through +`websockets>=13.1,<14`; the MiniCPM model-server environment remains Python +3.10 with `websockets==16.0`. + +Launchers derive `MiniCPM-RobotManip` from their own path, so they can be +called from any working directory. Outputs default to +`MiniCPM-RobotManip/outputs/evaluation//`. + +Run manifests record the checkpoint reference and available local repository +HEAD revisions. A mutable Hub model ID isn't an immutable weight revision; use +a verified local snapshot when exact result reproduction is required. + +LIBERO and RoboTwin include multi-GPU schedulers. They use one model server +and one evaluator per GPU slot, unique ports, metadata/ping readiness checks, +and signal-safe process cleanup. CALVIN intentionally remains serial. + +## Tests + +Tests that don't require simulator installations live under +`evaluation/tests`: + +```bash +cd MiniCPM-RobotManip +python -m unittest discover -s evaluation/tests -p 'test_*.py' -v +python -m compileall -q evaluation +``` + +Run `bash -n` on launcher scripts before deployment. Real success-rate +validation still requires the external simulator, assets, a GPU checkpoint, +and confirmed embodiment/action semantics. + +See [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md) for provenance and +license notices. diff --git a/MiniCPM-RobotManip/evaluation/THIRD_PARTY_NOTICES.md b/MiniCPM-RobotManip/evaluation/THIRD_PARTY_NOTICES.md new file mode 100644 index 0000000..c7e4dd8 --- /dev/null +++ b/MiniCPM-RobotManip/evaluation/THIRD_PARTY_NOTICES.md @@ -0,0 +1,161 @@ +# Third-party notices for simulation evaluation + +The files under `evaluation/` include code adapted from starVLA commit +`631aae02afe6d95876e923ff518e8ff2ab9a2f88`. + +Migrated or adapted material includes: + +- LIBERO and CALVIN evaluation loops and launch structure. +- RoboTwin policy interface and multi-GPU launch structure. +- `common/adaptive_ensemble.py`. +- `calvin/eval_sequences.json`. + +Modified source files retain the starVLA copyright header and identify +OpenBMB modifications. + +## starVLA license text + +```text +MIT License + +Copyright (c) StarVLA Team. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +Rebases are allowed for forks and feature branches. When rebasing from +upstream StarVLA, use descriptive commit messages, e.g., +"chore: clone from StarVLA". +Preserve attribution: keep at least the two latest upstream StarVLA commits as +separate. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` + +The source license also requests that the two latest upstream commits remain +separate when preserving history. This repository records the exact source +commit above because the evaluation files are migrated as modified source +rather than by rebasing starVLA history. + +## External simulator projects + +The following projects are runtime dependencies and aren't vendored: + +- LIBERO: +- CALVIN: +- RoboFlamingo evaluation utilities: + +- RoboTwin: + +Their source code, datasets, assets, and generated outputs remain subject to +their respective licenses and citation requirements. + +The quaternion-to-axis-angle helper in `libero/eval_libero.py` is adapted from +the MIT-licensed robosuite implementation: +. + +## RoboFlamingo license + +The migrated CALVIN evaluation protocol is based in part on RoboFlamingo. + +```text +MIT License + +Copyright (c) 2023 XinghangLi + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` + +## robosuite license + +```text +MIT License + +Copyright (c) 2022 Stanford Vision and Learning Lab and UT Robot Perception +and Learning Lab + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +This software includes the partial implementation of Deepmind Mujoco +https://github.com/deepmind/mujoco. Deepmind Mujoco is licensed under the +Apache License, Version 2.0. +``` + +The NumPy MessagePack transport used by these evaluators is adapted through +starVLA from `msgpack-numpy`: +. The implementation rejects object +arrays rather than falling back to pickle. + +```text +Copyright (c) 2013-2022, Lev E. Givon. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +* Neither the name of Lev E. Givon nor the names of any contributors may be + used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +``` diff --git a/MiniCPM-RobotManip/evaluation/__init__.py b/MiniCPM-RobotManip/evaluation/__init__.py new file mode 100644 index 0000000..2584a9e --- /dev/null +++ b/MiniCPM-RobotManip/evaluation/__init__.py @@ -0,0 +1 @@ +"""Simulation benchmark evaluators for MiniCPM-RobotManip.""" diff --git a/MiniCPM-RobotManip/evaluation/calvin/README.md b/MiniCPM-RobotManip/evaluation/calvin/README.md new file mode 100644 index 0000000..7d1c783 --- /dev/null +++ b/MiniCPM-RobotManip/evaluation/calvin/README.md @@ -0,0 +1,128 @@ +# CALVIN serial evaluation + +This directory runs the standard 1,000-sequence CALVIN long-horizon evaluation +against the MiniCPM-RobotManip WebSocket policy server. It is a serial, +single-GPU path: there is no sequence sharding, multi-GPU launch, or result +aggregation. + +The rollout loop and `eval_sequences.json` come from starVLA commit +`631aae02afe6d95876e923ff518e8ff2ab9a2f88` under the MIT License. The sequence +file contains the same 1,000 entries as that baseline (source SHA-256: +`c99e041c255df3408ebf3bbb4feea8b407e17e60e7c07d172a6ea7ba6680e8ca`). +OpenBMB modifications adapt paths and the policy client for +MiniCPM-RobotManip. + +## Prerequisites + +CALVIN is not vendored here. Install the external CALVIN environment and +download the original evaluation dataset. `CALVIN_DATASET_PATH` must point to +the `task_D_D` directory that directly contains `validation/`. + +Use two Python environments: + +- `MINICPM_PYTHON`: the MiniCPM-RobotManip environment, used to load the + checkpoint and host the policy server. +- `CALVIN_PYTHON`: the external CALVIN environment, used for simulation and + evaluation. + +Install the thin-client dependencies into the CALVIN environment if they are +not already present: + +```bash +/path/to/calvin/python -m pip install -r \ + /path/to/MiniCPM-RobotManip/evaluation/calvin/requirements-client.txt +``` + +The requirements file intentionally does not install CALVIN itself. + +## Run server and evaluation + +The launcher resolves repository paths from its own location, so it can be +called from any working directory: + +```bash +MINICPM_PYTHON=/path/to/minicpm/python \ +CALVIN_PYTHON=/path/to/calvin/python \ +CHECKPOINT=openbmb/MiniCPM-RobotManip \ +EMBODIMENT_ID=0 \ +CALVIN_ROOT=/path/to/CALVIN \ +CALVIN_DATASET_PATH=/path/to/task_D_D \ +OUTPUT_ROOT=/path/to/calvin-results \ +bash /path/to/MiniCPM-RobotManip/evaluation/calvin/eval_calvin.sh +``` + +The default endpoint is `127.0.0.1:10093`. The launcher starts one server, +waits for both valid MiniCPM handshake metadata and a protocol ping, runs the +serial evaluator, and terminates the server on exit or interruption. Server +and evaluator logs, CALVIN result files, and optional debug GIFs are written +under a timestamped directory in `OUTPUT_ROOT`. + +`eval_calvin.sh` is a thin entry point. Environment validation, subprocess +groups, readiness, signal cleanup, and the run manifest are implemented in +`launcher.py`; the shared WebSocket probe lives in +`evaluation/common/probe_server.py`. + +Use `bash eval_calvin.sh --help` for all environment overrides. Common ones +include `NUM_SEQUENCES`, `RESIZE_SIZE`, `EVAL_LOG_DIR`, `DEBUG=1`, `RESET=1`, +and `DIVERSE_INST=1`. Diverse instructions additionally require +`LANG_ANNOTATION_CACHE`; otherwise the standard CALVIN validation annotations +under `CALVIN_ROOT/calvin_models/conf` are used. + +`EMBODIMENT_ID` is required. Set it to the ID that the selected checkpoint +uses for CALVIN; the public checkpoint does not provide a reliable ID-to-robot +mapping. + +## Run the server separately + +To keep the two environments in separate terminals, start only the policy +server with: + +```bash +MINICPM_PYTHON=/path/to/minicpm/python \ +CHECKPOINT=openbmb/MiniCPM-RobotManip \ +EMBODIMENT_ID=0 \ +HOST=127.0.0.1 \ +PORT=10093 \ +bash /path/to/MiniCPM-RobotManip/evaluation/calvin/run_policy_server.sh +``` + +`run_policy_server.sh` invokes +`python -m deployment.model_server.server_policy`. The checkpoint is loaded +only by that server; the evaluator has no checkpoint argument or +evaluator-side checkpoint semantics. + +## Evaluation contract + +For every CALVIN step, the client sends exactly two synchronized views in this +order: + +1. `rgb_static` +2. `rgb_gripper` + +The shared `evaluation.libero.model2libero_interface.ModelClient` manages +action-chunk scheduling. The evaluator consumes the first seven action values +as three translation deltas, three rotation deltas, and one gripper value. The +gripper value is converted to CALVIN's `+1` open / `-1` close convention before +`env.step`. + +MiniCPM actions are execution-ready. Neither the client nor server normalizes +or unnormalizes them, and no `unnorm_key=franka` is used. + +## Direct client invocation + +When a compatible server is already running, invoke the evaluator directly +from the CALVIN environment: + +```bash +PYTHONPATH=/path/to/MiniCPM-RobotManip \ +/path/to/calvin/python -m evaluation.calvin.eval_calvin \ + --args.host 127.0.0.1 \ + --args.port 10093 \ + --args.calvin-root /path/to/CALVIN \ + --args.dataset-path /path/to/task_D_D \ + --args.eval-log-dir /path/to/calvin-results \ + --args.num-sequences 1000 +``` + +The bundled `eval_sequences.json` is selected relative to `eval_calvin.py`, +independent of the current working directory. diff --git a/MiniCPM-RobotManip/evaluation/calvin/__init__.py b/MiniCPM-RobotManip/evaluation/calvin/__init__.py new file mode 100644 index 0000000..5eddc89 --- /dev/null +++ b/MiniCPM-RobotManip/evaluation/calvin/__init__.py @@ -0,0 +1,3 @@ +# Copyright 2026 The OpenBMB Team. All rights reserved. + +"""Serial CALVIN evaluation for MiniCPM-RobotManip.""" diff --git a/MiniCPM-RobotManip/evaluation/calvin/eval_calvin.py b/MiniCPM-RobotManip/evaluation/calvin/eval_calvin.py new file mode 100644 index 0000000..80e66db --- /dev/null +++ b/MiniCPM-RobotManip/evaluation/calvin/eval_calvin.py @@ -0,0 +1,511 @@ +# Copyright 2025 starVLA community. All rights reserved. +# Licensed under the MIT License. +# Modifications Copyright 2026 The OpenBMB Team. + +"""Run the standard serial CALVIN long-horizon evaluation. + +This is adapted from starVLA commit +631aae02afe6d95876e923ff518e8ff2ab9a2f88 and follows RoboFlamingo's +evaluation protocol: +https://github.com/RoboFlamingo/RoboFlamingo/blob/main/robot_flamingo/eval/eval_utils.py + +The evaluator is an environment-side WebSocket client. The checkpoint is +loaded only by ``deployment.model_server.server_policy``. +""" + +from __future__ import annotations + +import copy +import dataclasses +import json +import logging +import os +import time +from collections import defaultdict +from pathlib import Path +from typing import Any + +# Set headless-rendering defaults before importing CALVIN. Existing environment +# values still win, so EGL or another backend can be selected by the caller. +os.environ.setdefault("PYOPENGL_PLATFORM", "osmesa") +os.environ.setdefault("MUJOCO_GL", "osmesa") + +import hydra +import numpy as np +import tyro +from calvin_agent.evaluation.utils import ( + collect_plan, + count_success, + get_env_state_for_initial_condition, + get_log_dir, + print_and_save, +) +from moviepy.editor import ImageSequenceClip +from omegaconf import OmegaConf +from termcolor import colored +from tqdm import tqdm + +from evaluation.libero.model2libero_interface import ModelClient + + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +EP_LEN = 360 +DEFAULT_EVAL_SEQUENCES_PATH = Path(__file__).resolve().with_name( + "eval_sequences.json" +) + + +def _path_from_env(name: str) -> Path | None: + value = os.environ.get(name) + return Path(value).expanduser() if value else None + + +@dataclasses.dataclass +class Args: + """Command-line arguments exposed by tyro under the ``--args.*`` prefix.""" + + # Policy server connection. The evaluator never loads a checkpoint. + host: str = "127.0.0.1" + port: int = 10093 + resize_size: int = 448 + + # CALVIN paths. Explicit CLI values take precedence over environment values. + calvin_root: Path | None = dataclasses.field( + default_factory=lambda: _path_from_env("CALVIN_ROOT") + ) + dataset_path: Path | None = dataclasses.field( + default_factory=lambda: _path_from_env("CALVIN_DATASET_PATH") + ) + calvin_config_path: Path | None = dataclasses.field( + default_factory=lambda: _path_from_env("CALVIN_CONFIG_PATH") + ) + eval_sequences_path: Path = DEFAULT_EVAL_SEQUENCES_PATH + lang_annotation_cache: Path | None = dataclasses.field( + default_factory=lambda: _path_from_env("LANG_ANNOTATION_CACHE") + ) + + # Evaluation settings. + num_sequences: int = 1000 + seed: int = 0 + debug: bool = False + eval_log_dir: Path = Path("tmp/calvin/eval_logs") + reset: bool = False + diverse_inst: bool = False + + +@dataclasses.dataclass(frozen=True) +class CalvinPaths: + dataset: Path + config: Path + eval_sequences: Path + lang_annotation_cache: Path | None + + +def resolve_calvin_paths(args: Args) -> CalvinPaths: + """Resolve portable CALVIN paths without repository-specific constants.""" + + calvin_root = args.calvin_root + dataset_path = args.dataset_path + config_path = args.calvin_config_path + lang_annotation_cache = args.lang_annotation_cache + + if dataset_path is None and calvin_root is not None: + dataset_path = calvin_root / "dataset" / "task_D_D" + if config_path is None and calvin_root is not None: + config_path = calvin_root / "calvin_models" / "conf" + if lang_annotation_cache is None and calvin_root is not None: + lang_annotation_cache = calvin_root / "lang_annotation_cache.json" + + if dataset_path is None: + raise ValueError( + "CALVIN dataset path is required. Set --args.dataset-path, " + "CALVIN_DATASET_PATH, or --args.calvin-root/CALVIN_ROOT." + ) + if config_path is None: + raise ValueError( + "CALVIN config path is required. Set --args.calvin-config-path, " + "CALVIN_CONFIG_PATH, or --args.calvin-root/CALVIN_ROOT." + ) + + return CalvinPaths( + dataset=dataset_path.expanduser().resolve(), + config=config_path.expanduser().resolve(), + eval_sequences=args.eval_sequences_path.expanduser().resolve(), + lang_annotation_cache=( + lang_annotation_cache.expanduser().resolve() + if lang_annotation_cache is not None + else None + ), + ) + + +def _to_uint8(image: np.ndarray) -> np.ndarray: + """Match starVLA's wire-size conversion without changing image geometry.""" + + array = np.asarray(image) + if np.issubdtype(array.dtype, np.floating): + array = (255 * array).astype(np.uint8) + elif array.dtype != np.uint8: + array = array.astype(np.uint8) + return array + + +class CalvinPolicyClient: + """CALVIN-specific observation and action adapter for ``ModelClient``.""" + + def __init__( + self, + host: str, + port: int, + resize_size: int = 448, + ) -> None: + # ModelClient is transport-only: checkpoint loading and action scaling + # belong to the server. MiniCPM actions require no unnormalization. + self.client = ModelClient( + host=host, + port=port, + image_size=[resize_size, resize_size], + ) + self.step_count = 0 + # CALVIN's collect_plan helper checks this optional model attribute. + self.plan = None + + def reset(self) -> None: + """Reset action-chunk scheduling at each CALVIN subtask.""" + + self.step_count = 0 + + def step(self, obs: dict[str, Any], lang_annotation: str) -> np.ndarray: + """Return one 7-D CALVIN action from the two synchronized RGB views.""" + + rgb_static = _to_uint8(obs["rgb_obs"]["rgb_static"]) + rgb_gripper = _to_uint8(obs["rgb_obs"]["rgb_gripper"]) + example = { + "image": [rgb_static, rgb_gripper], + "lang": lang_annotation, + } + + model_output = self.client.step(example=example, step=self.step_count) + raw_action = model_output["raw_action"] + world_vector = np.asarray( + raw_action["world_vector"], dtype=np.float32 + ).reshape(-1) + rotation_delta = np.asarray( + raw_action["rotation_delta"], dtype=np.float32 + ).reshape(-1) + open_gripper = np.asarray( + raw_action["open_gripper"], dtype=np.float32 + ).reshape(-1) + action = np.concatenate( + [world_vector, rotation_delta, open_gripper], axis=0 + ).astype(np.float32) + if action.shape != (7,): + raise ValueError( + "CALVIN requires a 7-D action " + f"(3 translation + 3 rotation + 1 gripper), got {action.shape}" + ) + + self.step_count += 1 + return action + + +def make_env(dataset_path: Path): + """Initialize CALVIN without the tactile camera used by pyrender.""" + + val_folder = dataset_path / "validation" + config_path = val_folder / ".hydra" / "merged_config.yaml" + if not config_path.is_file(): + raise FileNotFoundError( + f"CALVIN validation config not found: {config_path}. " + "dataset_path must directly contain validation/." + ) + + cfg = OmegaConf.load(config_path) + if hasattr(cfg.env, "cameras") and "tactile" in cfg.env.cameras: + cfg.env.cameras = OmegaConf.create( + { + key: value + for key, value in cfg.env.cameras.items() + if key != "tactile" + } + ) + + return hydra.utils.instantiate( + cfg.env, + show_gui=False, + use_vr=False, + use_scene_info=True, + ) + + +def evaluate_policy( + policy: CalvinPolicyClient, + env: Any, + epoch: int, + calvin_conf_path: Path, + eval_sequences_path: Path, + num_sequences: int, + eval_log_dir: Path, + lang_annotation_cache: Path | None = None, + debug: bool = False, + reset: bool = False, + diverse_inst: bool = False, +) -> list[int]: + """Evaluate the first ``num_sequences`` standard CALVIN task chains.""" + + if num_sequences <= 0: + raise ValueError(f"num_sequences must be positive, got {num_sequences}") + + task_config = ( + calvin_conf_path / "callbacks" / "rollout" / "tasks" + / "new_playtable_tasks.yaml" + ) + annotation_config = ( + calvin_conf_path / "annotations" / "new_playtable_validation.yaml" + ) + if not task_config.is_file(): + raise FileNotFoundError(f"CALVIN task config not found: {task_config}") + + task_oracle = hydra.utils.instantiate(OmegaConf.load(task_config)) + if diverse_inst: + if lang_annotation_cache is None: + raise ValueError( + "diverse_inst requires --args.lang-annotation-cache, " + "LANG_ANNOTATION_CACHE, or CALVIN_ROOT/lang_annotation_cache.json" + ) + if not lang_annotation_cache.is_file(): + raise FileNotFoundError( + f"Language annotation cache not found: {lang_annotation_cache}" + ) + with lang_annotation_cache.open("r", encoding="utf-8") as file: + val_annotations = json.load(file) + else: + if not annotation_config.is_file(): + raise FileNotFoundError( + f"CALVIN validation annotations not found: {annotation_config}" + ) + val_annotations = OmegaConf.load(annotation_config) + + requested_log_dir = eval_log_dir.expanduser().resolve() + requested_log_dir.mkdir(parents=True, exist_ok=True) + resolved_log_dir = get_log_dir(str(requested_log_dir)) + + if not eval_sequences_path.is_file(): + raise FileNotFoundError( + f"CALVIN evaluation sequences not found: {eval_sequences_path}" + ) + with eval_sequences_path.open("r", encoding="utf-8") as file: + all_eval_sequences = json.load(file) + eval_sequences = all_eval_sequences[:num_sequences] + logger.info( + "Evaluating %d of %d CALVIN sequences; outputs: %s", + len(eval_sequences), + len(all_eval_sequences), + resolved_log_dir, + ) + + results: list[int] = [] + plans: defaultdict[str, list[Any]] = defaultdict(list) + sequence_iterator = ( + eval_sequences + if debug + else tqdm(eval_sequences, position=0, leave=True) + ) + + for sequence_i, (initial_state, eval_sequence) in enumerate( + sequence_iterator + ): + result = evaluate_sequence( + env, + policy, + task_oracle, + initial_state, + eval_sequence, + val_annotations, + plans, + debug, + resolved_log_dir, + sequence_i, + reset=reset, + diverse_inst=diverse_inst, + ) + results.append(result) + if not debug: + sequence_iterator.set_description( + " ".join( + [ + f"{index + 1}/5 : {value * 100:.1f}% |" + for index, value in enumerate(count_success(results)) + ] + ) + + "|" + ) + + print_and_save( + results, + eval_sequences, + resolved_log_dir, + epoch, + ) + return results + + +def evaluate_sequence( + env: Any, + policy: CalvinPolicyClient, + task_checker: Any, + initial_state: dict[str, Any], + eval_sequence: list[str], + val_annotations: Any, + plans: defaultdict[str, list[Any]], + debug: bool, + eval_log_dir: str | Path = "", + sequence_i: int = -1, + reset: bool = False, + diverse_inst: bool = False, +) -> int: + """Evaluate one five-instruction CALVIN sequence.""" + + robot_obs, scene_obs = get_env_state_for_initial_condition(initial_state) + env.reset(robot_obs=robot_obs, scene_obs=scene_obs) + + success_counter = 0 + if debug: + time.sleep(1) + print("\n\nEvaluating sequence: " + " -> ".join(eval_sequence)) + print("Subtask: ", end="") + + for subtask_i, subtask in enumerate(eval_sequence): + reset_kwargs = ( + {"robot_obs": robot_obs, "scene_obs": scene_obs} if reset else {} + ) + success = rollout( + env, + policy, + task_checker, + subtask, + val_annotations, + plans, + debug, + eval_log_dir, + subtask_i, + sequence_i, + diverse_inst=diverse_inst, + **reset_kwargs, + ) + if not success: + return success_counter + success_counter += 1 + + return success_counter + + +def rollout( + env: Any, + policy: CalvinPolicyClient, + task_oracle: Any, + subtask: str, + val_annotations: Any, + plans: defaultdict[str, list[Any]], + debug: bool, + eval_log_dir: str | Path = "", + subtask_i: int = -1, + sequence_i: int = -1, + robot_obs: np.ndarray | None = None, + scene_obs: np.ndarray | None = None, + diverse_inst: bool = False, +) -> bool: + """Roll out one language-conditioned subtask for at most ``EP_LEN`` steps.""" + + if debug: + print(f"{subtask} ", end="") + time.sleep(0.5) + if robot_obs is not None and scene_obs is not None: + env.reset(robot_obs=robot_obs, scene_obs=scene_obs) + + obs = env.get_obs() + if diverse_inst: + lang_annotation = val_annotations[sequence_i][subtask_i] + else: + lang_annotation = val_annotations[subtask][0] + lang_annotation = lang_annotation.split("\n")[0] + if "\u2019" in lang_annotation: + lang_annotation = lang_annotation.replace("\u2019", "'") + + policy.reset() + start_info = env.get_info() + if debug: + img_queue = [] + + for step in range(EP_LEN): + action = policy.step(obs, lang_annotation) + if not action.flags.writeable: + action = np.array(action, copy=True) + action[-1] = 1 if action[-1] > 0 else -1 + + obs, _, _, current_info = env.step(action) + if debug: + img_queue.append(copy.deepcopy(obs["rgb_obs"]["rgb_static"])) + if step == 0: + collect_plan(policy, plans, subtask) + + current_task_info = task_oracle.get_task_info_for_set( + start_info, + current_info, + {subtask}, + ) + if current_task_info: + if debug: + print(colored("success", "green"), end=" ") + image_clip = ImageSequenceClip(img_queue, fps=30) + image_clip.write_gif( + str( + Path(eval_log_dir) + / f"{sequence_i}-{subtask_i}-{subtask}-succ.gif" + ), + fps=30, + ) + return True + + if debug: + print(colored("fail", "red"), end=" ") + image_clip = ImageSequenceClip(img_queue, fps=30) + image_clip.write_gif( + str( + Path(eval_log_dir) + / f"{sequence_i}-{subtask_i}-{subtask}-fail.gif" + ), + fps=30, + ) + return False + + +def main(args: Args) -> None: + paths = resolve_calvin_paths(args) + np.random.seed(args.seed) + + policy = CalvinPolicyClient( + host=args.host, + port=args.port, + resize_size=args.resize_size, + ) + env = make_env(paths.dataset) + evaluate_policy( + policy=policy, + env=env, + epoch=0, + calvin_conf_path=paths.config, + eval_sequences_path=paths.eval_sequences, + num_sequences=args.num_sequences, + eval_log_dir=args.eval_log_dir, + lang_annotation_cache=paths.lang_annotation_cache, + debug=args.debug, + reset=args.reset, + diverse_inst=args.diverse_inst, + ) + + +if __name__ == "__main__": + tyro.cli(main) diff --git a/MiniCPM-RobotManip/evaluation/calvin/eval_calvin.sh b/MiniCPM-RobotManip/evaluation/calvin/eval_calvin.sh new file mode 100755 index 0000000..4343b58 --- /dev/null +++ b/MiniCPM-RobotManip/evaluation/calvin/eval_calvin.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +# Copyright 2025 starVLA community. All rights reserved. +# Licensed under the MIT License. +# Modifications Copyright 2026 The OpenBMB Team. + +set -euo pipefail + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +MINICPM_ROOT="$(cd -- "${SCRIPT_DIR}/../.." && pwd)" +LAUNCHER_PYTHON="${LAUNCHER_PYTHON:-python3}" + +export PYTHONPATH="${MINICPM_ROOT}${PYTHONPATH:+:${PYTHONPATH}}" +exec "${LAUNCHER_PYTHON}" -m evaluation.calvin.launcher "$@" diff --git a/MiniCPM-RobotManip/evaluation/calvin/eval_sequences.json b/MiniCPM-RobotManip/evaluation/calvin/eval_sequences.json new file mode 100644 index 0000000..05bcab8 --- /dev/null +++ b/MiniCPM-RobotManip/evaluation/calvin/eval_sequences.json @@ -0,0 +1,19002 @@ +[ + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "rotate_blue_block_right", + "move_slider_right", + "lift_red_block_slider", + "place_in_slider", + "turn_off_lightbulb" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "turn_off_led", + "push_into_drawer", + "lift_blue_block_drawer", + "place_in_slider", + "close_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "lift_pink_block_slider", + "place_in_slider", + "open_drawer", + "rotate_red_block_right", + "lift_red_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_blue_block_right", + "lift_pink_block_table", + "place_in_slider", + "move_slider_left", + "open_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "open_drawer", + "lift_pink_block_table", + "place_in_slider", + "turn_on_lightbulb", + "rotate_blue_block_left" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_blue_block_left", + "close_drawer", + "lift_red_block_slider", + "place_in_slider", + "turn_off_lightbulb" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "open_drawer", + "rotate_red_block_right", + "turn_on_led", + "lift_pink_block_table", + "stack_block" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_right", + "lift_pink_block_table", + "place_in_slider", + "push_into_drawer", + "close_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "open_drawer", + "push_red_block_right", + "move_slider_left", + "lift_pink_block_slider", + "place_in_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "push_red_block_left", + "lift_blue_block_table", + "place_in_slider", + "turn_on_lightbulb", + "move_slider_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "rotate_blue_block_left", + "lift_blue_block_table", + "place_in_drawer", + "move_slider_right", + "turn_off_lightbulb" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "lift_pink_block_slider", + "place_in_slider", + "turn_on_lightbulb", + "rotate_blue_block_right", + "open_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_left", + "rotate_red_block_left", + "turn_off_led", + "lift_pink_block_table", + "place_in_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "turn_on_led", + "close_drawer", + "push_red_block_right", + "lift_red_block_table", + "place_in_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_red_block_slider", + "stack_block", + "move_slider_left", + "turn_off_led", + "open_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_left", + "push_pink_block_left", + "open_drawer", + "turn_on_lightbulb", + "lift_blue_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "push_red_block_left", + "lift_pink_block_slider", + "stack_block", + "turn_off_led", + "open_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "push_red_block_left", + "move_slider_right", + "lift_blue_block_slider", + "place_in_slider", + "turn_on_lightbulb" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_lightbulb", + "push_red_block_right", + "move_slider_left", + "lift_red_block_table", + "stack_block" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "move_slider_left", + "lift_red_block_table", + "place_in_slider", + "turn_on_lightbulb", + "close_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "open_drawer", + "turn_on_lightbulb", + "push_red_block_left", + "move_slider_left", + "push_into_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_right", + "turn_off_lightbulb", + "lift_pink_block_table", + "place_in_drawer", + "lift_pink_block_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_pink_block_right", + "open_drawer", + "lift_blue_block_table", + "place_in_slider", + "turn_off_lightbulb" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_led", + "move_slider_left", + "lift_blue_block_table", + "stack_block", + "unstack_block" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "rotate_blue_block_right", + "open_drawer", + "turn_off_led", + "lift_blue_block_table", + "place_in_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "lift_blue_block_slider", + "stack_block", + "turn_off_led", + "unstack_block", + "push_blue_block_left" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "push_blue_block_right", + "open_drawer", + "lift_pink_block_slider", + "place_in_slider", + "push_into_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "push_red_block_right", + "turn_on_lightbulb", + "push_into_drawer", + "move_slider_left", + "close_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "turn_off_lightbulb", + "rotate_blue_block_left", + "move_slider_left", + "open_drawer", + "lift_red_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "lift_red_block_table", + "stack_block", + "unstack_block", + "open_drawer", + "turn_on_led" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "lift_blue_block_table", + "place_in_drawer", + "push_red_block_right", + "close_drawer", + "turn_off_led" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_pink_block_table", + "place_in_slider", + "push_into_drawer", + "close_drawer", + "turn_off_led" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_pink_block_left", + "lift_pink_block_table", + "stack_block", + "unstack_block", + "turn_off_led" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_off_led", + "open_drawer", + "push_pink_block_left", + "lift_blue_block_table", + "place_in_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "move_slider_right", + "push_blue_block_left", + "open_drawer", + "lift_red_block_table", + "stack_block" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "lift_red_block_slider", + "place_in_slider", + "move_slider_right", + "push_into_drawer", + "turn_off_led" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "move_slider_right", + "turn_off_lightbulb", + "lift_pink_block_slider", + "place_in_slider", + "push_red_block_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "turn_off_lightbulb", + "push_red_block_right", + "push_into_drawer", + "move_slider_left", + "close_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "open_drawer", + "move_slider_right", + "push_blue_block_right", + "lift_blue_block_table", + "place_in_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_led", + "rotate_pink_block_right", + "move_slider_right", + "close_drawer", + "lift_pink_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_right", + "close_drawer", + "push_pink_block_left", + "lift_pink_block_table", + "place_in_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_right", + "push_into_drawer", + "turn_off_led", + "lift_pink_block_drawer", + "place_in_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_left", + "push_pink_block_left", + "lift_blue_block_slider", + "place_in_slider", + "turn_off_led" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_pink_block_right", + "lift_pink_block_table", + "place_in_slider", + "open_drawer", + "lift_blue_block_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_blue_block_table", + "place_in_slider", + "turn_on_led", + "rotate_pink_block_right", + "lift_red_block_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_pink_block_right", + "turn_off_led", + "lift_pink_block_table", + "place_in_slider", + "open_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_off_lightbulb", + "push_pink_block_left", + "move_slider_left", + "open_drawer", + "lift_pink_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_red_block_slider", + "stack_block", + "turn_on_led", + "unstack_block", + "lift_red_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "rotate_blue_block_left", + "move_slider_left", + "open_drawer", + "lift_red_block_slider", + "place_in_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "lift_red_block_table", + "place_in_drawer", + "rotate_blue_block_right", + "lift_pink_block_slider", + "stack_block" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "open_drawer", + "push_blue_block_right", + "move_slider_left", + "push_into_drawer", + "lift_blue_block_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "rotate_red_block_right", + "lift_pink_block_slider", + "stack_block", + "turn_on_led", + "open_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_led", + "push_pink_block_left", + "push_into_drawer", + "lift_red_block_slider", + "place_in_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_led", + "open_drawer", + "rotate_pink_block_left", + "lift_blue_block_table", + "place_in_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_off_led", + "close_drawer", + "move_slider_left", + "push_pink_block_right", + "lift_red_block_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "close_drawer", + "push_red_block_left", + "lift_red_block_table", + "stack_block", + "turn_on_lightbulb" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "move_slider_right", + "push_into_drawer", + "lift_red_block_slider", + "place_in_slider", + "lift_blue_block_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "push_blue_block_left", + "push_into_drawer", + "lift_blue_block_drawer", + "place_in_slider", + "move_slider_right" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_right", + "turn_off_lightbulb", + "close_drawer", + "rotate_blue_block_right", + "lift_blue_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "turn_off_lightbulb", + "push_blue_block_left", + "open_drawer", + "move_slider_left", + "lift_blue_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_lightbulb", + "move_slider_left", + "push_blue_block_right", + "lift_blue_block_table", + "stack_block" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "push_blue_block_right", + "move_slider_right", + "turn_on_lightbulb", + "lift_red_block_table", + "stack_block" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_pink_block_left", + "turn_on_led", + "lift_red_block_slider", + "stack_block", + "close_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_right", + "lift_red_block_slider", + "place_in_slider", + "open_drawer", + "turn_on_led" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "open_drawer", + "push_pink_block_right", + "lift_red_block_table", + "place_in_slider", + "move_slider_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "open_drawer", + "turn_on_led", + "move_slider_right", + "lift_pink_block_table", + "place_in_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_pink_block_right", + "lift_pink_block_table", + "place_in_slider", + "move_slider_right", + "lift_blue_block_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_pink_block_table", + "stack_block", + "unstack_block", + "turn_on_lightbulb", + "push_red_block_left" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_left", + "lift_blue_block_table", + "place_in_drawer", + "close_drawer", + "move_slider_right" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "open_drawer", + "turn_off_led", + "push_pink_block_left", + "move_slider_left", + "lift_pink_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "close_drawer", + "rotate_blue_block_right", + "turn_off_led", + "move_slider_right", + "lift_pink_block_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "open_drawer", + "push_red_block_left", + "turn_off_lightbulb", + "lift_pink_block_slider", + "place_in_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_right", + "push_pink_block_left", + "lift_blue_block_slider", + "place_in_drawer", + "turn_off_led" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "push_red_block_left", + "turn_on_led", + "lift_pink_block_slider", + "place_in_drawer", + "move_slider_right" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_off_lightbulb", + "move_slider_right", + "rotate_blue_block_right", + "lift_blue_block_table", + "stack_block" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_right", + "rotate_pink_block_right", + "lift_red_block_table", + "stack_block", + "open_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_blue_block_left", + "turn_off_lightbulb", + "lift_pink_block_table", + "place_in_slider", + "lift_red_block_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "turn_off_led", + "move_slider_left", + "open_drawer", + "rotate_blue_block_right", + "lift_blue_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_blue_block_slider", + "place_in_slider", + "close_drawer", + "push_pink_block_right", + "move_slider_left" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_red_block_left", + "lift_pink_block_table", + "stack_block", + "open_drawer", + "lift_blue_block_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_left", + "turn_on_lightbulb", + "lift_pink_block_table", + "place_in_drawer", + "lift_red_block_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "open_drawer", + "move_slider_right", + "lift_red_block_slider", + "place_in_drawer", + "turn_off_lightbulb" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_red_block_slider", + "place_in_drawer", + "turn_on_led", + "close_drawer", + "lift_blue_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_into_drawer", + "lift_red_block_slider", + "place_in_slider", + "move_slider_right", + "turn_off_led" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "move_slider_right", + "push_red_block_left", + "turn_off_lightbulb", + "lift_blue_block_slider", + "stack_block" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "lift_pink_block_slider", + "place_in_slider", + "open_drawer", + "turn_on_lightbulb", + "lift_blue_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "move_slider_right", + "turn_on_led", + "lift_pink_block_slider", + "place_in_drawer", + "lift_pink_block_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_right", + "close_drawer", + "lift_red_block_slider", + "stack_block", + "move_slider_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "push_blue_block_left", + "close_drawer", + "turn_off_lightbulb", + "lift_blue_block_table", + "place_in_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "close_drawer", + "lift_red_block_slider", + "place_in_slider", + "push_blue_block_left", + "turn_off_lightbulb" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "rotate_blue_block_left", + "turn_off_lightbulb", + "move_slider_right", + "open_drawer", + "lift_red_block_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_led", + "rotate_blue_block_right", + "lift_blue_block_table", + "stack_block", + "lift_red_block_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "open_drawer", + "turn_off_led", + "push_pink_block_right", + "move_slider_right", + "push_into_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_right", + "lift_blue_block_slider", + "place_in_slider", + "turn_on_lightbulb", + "move_slider_right" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_pink_block_right", + "move_slider_left", + "close_drawer", + "turn_off_led", + "lift_pink_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "close_drawer", + "move_slider_right", + "turn_on_led", + "rotate_red_block_left", + "lift_red_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_pink_block_table", + "stack_block", + "turn_on_lightbulb", + "unstack_block", + "push_pink_block_left" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "close_drawer", + "move_slider_left", + "rotate_pink_block_left", + "turn_on_led", + "lift_red_block_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "move_slider_left", + "push_red_block_right", + "push_into_drawer", + "turn_on_led", + "close_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "rotate_red_block_right", + "turn_on_lightbulb", + "move_slider_right", + "open_drawer", + "lift_blue_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "open_drawer", + "rotate_red_block_left", + "move_slider_left", + "turn_off_lightbulb", + "lift_red_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "move_slider_left", + "rotate_blue_block_left", + "lift_blue_block_table", + "place_in_drawer", + "lift_blue_block_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "open_drawer", + "rotate_blue_block_left", + "push_into_drawer", + "lift_red_block_slider", + "place_in_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_red_block_right", + "lift_pink_block_table", + "stack_block", + "move_slider_right", + "lift_blue_block_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "turn_on_led", + "open_drawer", + "lift_pink_block_slider", + "place_in_drawer", + "lift_blue_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_off_led", + "rotate_pink_block_left", + "lift_red_block_slider", + "place_in_drawer", + "close_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "open_drawer", + "turn_on_lightbulb", + "lift_pink_block_slider", + "place_in_drawer", + "push_red_block_right" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "move_slider_left", + "push_red_block_right", + "open_drawer", + "turn_off_led", + "push_into_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "turn_off_lightbulb", + "push_red_block_right", + "lift_blue_block_slider", + "place_in_drawer", + "move_slider_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_right", + "lift_pink_block_table", + "place_in_slider", + "turn_on_lightbulb", + "lift_blue_block_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_red_block_table", + "place_in_slider", + "push_pink_block_right", + "close_drawer", + "turn_on_led" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "move_slider_left", + "push_blue_block_right", + "lift_pink_block_slider", + "place_in_drawer", + "turn_on_lightbulb" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "push_red_block_right", + "move_slider_right", + "lift_blue_block_table", + "place_in_slider", + "close_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "rotate_blue_block_right", + "open_drawer", + "turn_on_lightbulb", + "move_slider_left", + "lift_blue_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "lift_red_block_slider", + "place_in_drawer", + "turn_on_led", + "move_slider_right", + "lift_red_block_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_left", + "close_drawer", + "lift_blue_block_table", + "place_in_slider", + "turn_on_led" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "push_red_block_left", + "move_slider_left", + "open_drawer", + "lift_pink_block_slider", + "stack_block" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_right", + "lift_blue_block_slider", + "place_in_drawer", + "turn_on_led", + "close_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "rotate_red_block_left", + "lift_pink_block_slider", + "stack_block", + "unstack_block", + "lift_red_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "move_slider_right", + "push_blue_block_right", + "turn_off_lightbulb", + "push_into_drawer", + "lift_red_block_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "open_drawer", + "lift_pink_block_table", + "place_in_slider", + "move_slider_left", + "rotate_red_block_right" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_off_led", + "open_drawer", + "rotate_red_block_left", + "lift_red_block_table", + "stack_block" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_pink_block_left", + "close_drawer", + "turn_on_led", + "move_slider_left", + "lift_red_block_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "move_slider_right", + "lift_red_block_table", + "place_in_drawer", + "rotate_blue_block_left", + "turn_off_led" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "open_drawer", + "push_blue_block_left", + "move_slider_left", + "turn_on_lightbulb", + "lift_pink_block_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_left", + "move_slider_left", + "lift_pink_block_table", + "place_in_slider", + "turn_off_lightbulb" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_pink_block_right", + "close_drawer", + "turn_off_lightbulb", + "lift_blue_block_table", + "place_in_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "open_drawer", + "turn_on_led", + "push_red_block_right", + "move_slider_left", + "lift_pink_block_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_off_led", + "lift_blue_block_slider", + "stack_block", + "move_slider_right", + "unstack_block" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "lift_blue_block_slider", + "stack_block", + "unstack_block", + "move_slider_left", + "rotate_blue_block_left" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "lift_red_block_slider", + "stack_block", + "close_drawer", + "unstack_block", + "push_red_block_left" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "move_slider_left", + "push_blue_block_right", + "push_into_drawer", + "close_drawer", + "lift_pink_block_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "close_drawer", + "push_red_block_left", + "lift_pink_block_slider", + "stack_block", + "turn_on_lightbulb" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_blue_block_slider", + "place_in_drawer", + "turn_off_led", + "push_red_block_left", + "move_slider_right" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "push_into_drawer", + "lift_pink_block_slider", + "place_in_drawer", + "close_drawer", + "turn_on_lightbulb" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_blue_block_slider", + "place_in_slider", + "turn_off_led", + "move_slider_left", + "rotate_pink_block_left" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "open_drawer", + "turn_on_led", + "lift_blue_block_table", + "place_in_slider", + "push_pink_block_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "push_red_block_left", + "move_slider_left", + "turn_on_lightbulb", + "lift_blue_block_table", + "place_in_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "rotate_red_block_left", + "open_drawer", + "lift_blue_block_table", + "place_in_slider", + "move_slider_left" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_left", + "lift_pink_block_table", + "place_in_slider", + "close_drawer", + "turn_off_lightbulb" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_led", + "lift_pink_block_table", + "place_in_slider", + "rotate_red_block_left", + "close_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "turn_on_lightbulb", + "move_slider_left", + "push_blue_block_left", + "push_into_drawer", + "lift_red_block_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "turn_off_led", + "move_slider_left", + "push_red_block_left", + "lift_red_block_table", + "stack_block" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "rotate_red_block_right", + "turn_on_lightbulb", + "lift_pink_block_slider", + "stack_block", + "move_slider_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_pink_block_right", + "close_drawer", + "move_slider_right", + "lift_red_block_slider", + "stack_block" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_right", + "lift_blue_block_slider", + "place_in_slider", + "rotate_red_block_left", + "lift_red_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_right", + "turn_on_led", + "move_slider_left", + "push_into_drawer", + "lift_red_block_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_led", + "push_pink_block_right", + "lift_pink_block_table", + "place_in_drawer", + "close_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_blue_block_table", + "place_in_slider", + "turn_on_lightbulb", + "push_pink_block_right", + "open_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "turn_on_lightbulb", + "open_drawer", + "push_into_drawer", + "lift_red_block_drawer", + "place_in_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "push_into_drawer", + "lift_blue_block_drawer", + "place_in_slider", + "move_slider_right", + "turn_on_led" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "open_drawer", + "turn_on_led", + "rotate_red_block_left", + "lift_blue_block_slider", + "place_in_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "turn_on_led", + "rotate_red_block_left", + "open_drawer", + "lift_blue_block_table", + "place_in_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_lightbulb", + "rotate_pink_block_right", + "lift_blue_block_slider", + "place_in_drawer", + "close_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "turn_off_led", + "push_into_drawer", + "lift_blue_block_drawer", + "place_in_drawer", + "lift_red_block_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "turn_off_led", + "lift_pink_block_slider", + "place_in_slider", + "rotate_blue_block_right", + "move_slider_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_right", + "open_drawer", + "lift_blue_block_slider", + "stack_block", + "unstack_block" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_blue_block_slider", + "place_in_drawer", + "move_slider_left", + "rotate_red_block_right", + "lift_blue_block_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_right", + "move_slider_left", + "turn_off_led", + "push_into_drawer", + "lift_pink_block_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "close_drawer", + "rotate_red_block_right", + "lift_red_block_table", + "stack_block", + "turn_on_led" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "lift_red_block_table", + "stack_block", + "unstack_block", + "rotate_red_block_right", + "close_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_right", + "rotate_blue_block_right", + "lift_blue_block_table", + "place_in_drawer", + "lift_red_block_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_blue_block_left", + "open_drawer", + "turn_on_lightbulb", + "lift_pink_block_table", + "stack_block" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_blue_block_slider", + "place_in_drawer", + "close_drawer", + "turn_off_lightbulb", + "move_slider_left" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_blue_block_right", + "open_drawer", + "turn_off_led", + "move_slider_right", + "lift_blue_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "close_drawer", + "turn_on_led", + "push_blue_block_left", + "lift_blue_block_table", + "place_in_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "open_drawer", + "push_pink_block_right", + "lift_blue_block_slider", + "place_in_slider", + "move_slider_left" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "move_slider_right", + "rotate_red_block_right", + "turn_off_led", + "lift_red_block_table", + "stack_block" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_right", + "lift_red_block_table", + "stack_block", + "move_slider_left", + "unstack_block" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "open_drawer", + "turn_on_led", + "push_into_drawer", + "lift_red_block_drawer", + "place_in_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "turn_off_lightbulb", + "push_blue_block_left", + "move_slider_right", + "close_drawer", + "lift_blue_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "push_blue_block_left", + "open_drawer", + "move_slider_left", + "push_into_drawer", + "turn_off_lightbulb" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "rotate_blue_block_left", + "turn_on_lightbulb", + "lift_blue_block_table", + "place_in_slider", + "move_slider_right" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "lift_red_block_table", + "stack_block", + "close_drawer", + "turn_off_lightbulb", + "unstack_block" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_right", + "lift_red_block_slider", + "place_in_slider", + "lift_pink_block_table", + "stack_block" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "turn_off_led", + "push_blue_block_left", + "push_into_drawer", + "move_slider_right", + "lift_blue_block_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_red_block_left", + "move_slider_right", + "turn_on_lightbulb", + "lift_pink_block_table", + "place_in_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "turn_off_led", + "open_drawer", + "move_slider_right", + "lift_blue_block_table", + "stack_block" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "turn_off_lightbulb", + "move_slider_right", + "lift_blue_block_table", + "stack_block", + "lift_pink_block_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "push_red_block_left", + "turn_on_lightbulb", + "open_drawer", + "lift_blue_block_table", + "place_in_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "open_drawer", + "rotate_red_block_right", + "lift_blue_block_table", + "place_in_slider", + "turn_off_lightbulb" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_off_lightbulb", + "move_slider_right", + "lift_pink_block_table", + "place_in_slider", + "lift_red_block_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "move_slider_right", + "turn_on_led", + "rotate_blue_block_right", + "close_drawer", + "lift_pink_block_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "rotate_blue_block_left", + "move_slider_left", + "lift_blue_block_table", + "place_in_slider", + "lift_red_block_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_right", + "push_pink_block_right", + "close_drawer", + "turn_off_lightbulb", + "lift_blue_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_off_led", + "open_drawer", + "move_slider_right", + "rotate_pink_block_left", + "lift_pink_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "move_slider_right", + "rotate_red_block_right", + "lift_pink_block_slider", + "place_in_slider", + "lift_red_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_led", + "move_slider_right", + "rotate_pink_block_left", + "open_drawer", + "push_into_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_red_block_left", + "lift_pink_block_table", + "place_in_slider", + "open_drawer", + "turn_off_led" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "push_blue_block_right", + "turn_on_lightbulb", + "open_drawer", + "lift_blue_block_table", + "place_in_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_off_lightbulb", + "move_slider_left", + "rotate_pink_block_right", + "close_drawer", + "lift_blue_block_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_red_block_table", + "place_in_drawer", + "close_drawer", + "rotate_pink_block_left", + "move_slider_left" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "push_blue_block_right", + "lift_red_block_slider", + "place_in_slider", + "open_drawer", + "turn_off_lightbulb" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "turn_on_lightbulb", + "open_drawer", + "rotate_blue_block_right", + "move_slider_left", + "lift_pink_block_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "rotate_blue_block_left", + "open_drawer", + "lift_red_block_table", + "place_in_slider", + "move_slider_left" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "open_drawer", + "lift_red_block_slider", + "place_in_slider", + "push_into_drawer", + "lift_blue_block_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_right", + "lift_blue_block_slider", + "place_in_slider", + "turn_off_led", + "rotate_pink_block_left" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_right", + "open_drawer", + "push_pink_block_right", + "turn_on_lightbulb", + "push_into_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_pink_block_table", + "place_in_slider", + "turn_on_lightbulb", + "rotate_red_block_left", + "move_slider_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "rotate_blue_block_left", + "open_drawer", + "turn_on_lightbulb", + "lift_red_block_table", + "stack_block" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_lightbulb", + "lift_blue_block_table", + "place_in_slider", + "rotate_pink_block_right", + "open_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_pink_block_right", + "move_slider_right", + "turn_on_led", + "close_drawer", + "lift_pink_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "open_drawer", + "turn_on_led", + "push_blue_block_right", + "move_slider_left", + "lift_pink_block_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "turn_off_led", + "lift_red_block_slider", + "place_in_slider", + "move_slider_right", + "push_blue_block_left" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_off_led", + "move_slider_left", + "lift_blue_block_slider", + "place_in_slider", + "push_pink_block_left" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_pink_block_left", + "close_drawer", + "lift_blue_block_slider", + "place_in_slider", + "move_slider_left" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "rotate_blue_block_left", + "lift_red_block_slider", + "stack_block", + "unstack_block", + "turn_off_led" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_left", + "move_slider_right", + "push_into_drawer", + "lift_red_block_slider", + "place_in_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_right", + "push_into_drawer", + "turn_off_lightbulb", + "move_slider_right", + "lift_pink_block_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "close_drawer", + "turn_off_led", + "move_slider_right", + "rotate_blue_block_left", + "lift_blue_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_left", + "rotate_blue_block_left", + "lift_blue_block_table", + "stack_block", + "turn_on_lightbulb" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_off_led", + "lift_pink_block_table", + "stack_block", + "unstack_block", + "push_pink_block_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_led", + "move_slider_left", + "open_drawer", + "push_blue_block_right", + "lift_pink_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "open_drawer", + "rotate_pink_block_right", + "lift_blue_block_slider", + "place_in_drawer", + "turn_off_led" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "open_drawer", + "turn_off_led", + "move_slider_right", + "push_red_block_right", + "lift_red_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "turn_off_led", + "rotate_blue_block_right", + "lift_blue_block_table", + "place_in_drawer", + "close_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "open_drawer", + "lift_red_block_table", + "stack_block", + "unstack_block", + "turn_on_lightbulb" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "turn_on_led", + "rotate_blue_block_left", + "open_drawer", + "lift_red_block_table", + "stack_block" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_pink_block_left", + "open_drawer", + "lift_pink_block_table", + "stack_block", + "turn_on_lightbulb" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "turn_off_led", + "push_blue_block_right", + "move_slider_left", + "lift_red_block_table", + "stack_block" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_blue_block_left", + "turn_off_lightbulb", + "lift_red_block_slider", + "place_in_slider", + "lift_blue_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_left", + "close_drawer", + "move_slider_left", + "lift_pink_block_table", + "stack_block" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_off_led", + "rotate_red_block_left", + "close_drawer", + "move_slider_right", + "lift_pink_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_pink_block_right", + "lift_pink_block_table", + "place_in_slider", + "close_drawer", + "turn_on_lightbulb" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "move_slider_right", + "turn_on_led", + "lift_blue_block_table", + "place_in_slider", + "rotate_red_block_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "open_drawer", + "move_slider_left", + "rotate_pink_block_right", + "lift_pink_block_table", + "place_in_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "close_drawer", + "push_pink_block_left", + "lift_blue_block_slider", + "place_in_slider", + "lift_pink_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "open_drawer", + "turn_on_lightbulb", + "move_slider_left", + "lift_blue_block_table", + "stack_block" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "push_red_block_left", + "open_drawer", + "lift_pink_block_slider", + "place_in_drawer", + "lift_red_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "rotate_red_block_left", + "move_slider_right", + "turn_on_led", + "open_drawer", + "push_into_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "turn_off_led", + "move_slider_left", + "push_blue_block_left", + "open_drawer", + "lift_blue_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "rotate_red_block_right", + "lift_blue_block_slider", + "stack_block", + "move_slider_left", + "turn_on_led" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_blue_block_slider", + "stack_block", + "move_slider_left", + "unstack_block", + "push_pink_block_left" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_pink_block_right", + "turn_off_led", + "move_slider_left", + "open_drawer", + "lift_red_block_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "open_drawer", + "turn_on_lightbulb", + "rotate_blue_block_left", + "lift_red_block_slider", + "stack_block" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "turn_on_lightbulb", + "push_blue_block_right", + "lift_pink_block_slider", + "place_in_drawer", + "lift_blue_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_lightbulb", + "open_drawer", + "move_slider_right", + "rotate_pink_block_left", + "lift_blue_block_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_left", + "lift_red_block_slider", + "stack_block", + "turn_on_led", + "unstack_block" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "move_slider_right", + "lift_red_block_slider", + "place_in_slider", + "push_into_drawer", + "turn_off_led" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_right", + "rotate_pink_block_left", + "turn_on_lightbulb", + "open_drawer", + "lift_blue_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "rotate_red_block_right", + "open_drawer", + "turn_off_led", + "lift_red_block_table", + "place_in_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_led", + "push_red_block_right", + "move_slider_right", + "lift_red_block_table", + "place_in_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_blue_block_left", + "turn_on_lightbulb", + "move_slider_right", + "open_drawer", + "lift_pink_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_red_block_left", + "lift_pink_block_table", + "stack_block", + "close_drawer", + "lift_blue_block_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "rotate_blue_block_left", + "turn_off_lightbulb", + "move_slider_right", + "lift_red_block_slider", + "place_in_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "lift_blue_block_table", + "place_in_drawer", + "move_slider_right", + "turn_off_lightbulb", + "push_red_block_left" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "turn_off_lightbulb", + "rotate_red_block_left", + "lift_red_block_table", + "place_in_drawer", + "move_slider_left" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "move_slider_left", + "rotate_blue_block_right", + "lift_pink_block_slider", + "place_in_slider", + "open_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "lift_blue_block_slider", + "stack_block", + "turn_off_lightbulb", + "unstack_block", + "move_slider_right" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_right", + "turn_on_lightbulb", + "lift_red_block_slider", + "place_in_slider", + "open_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_pink_block_table", + "place_in_slider", + "turn_on_led", + "rotate_blue_block_right", + "move_slider_right" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "lift_pink_block_slider", + "place_in_slider", + "rotate_red_block_left", + "open_drawer", + "turn_off_led" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_left", + "turn_on_lightbulb", + "lift_pink_block_table", + "stack_block", + "move_slider_left" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "lift_pink_block_slider", + "place_in_slider", + "turn_off_lightbulb", + "close_drawer", + "push_red_block_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "rotate_red_block_right", + "close_drawer", + "move_slider_left", + "lift_blue_block_table", + "place_in_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "open_drawer", + "turn_off_lightbulb", + "move_slider_left", + "lift_pink_block_slider", + "stack_block" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "push_red_block_left", + "turn_on_lightbulb", + "open_drawer", + "push_into_drawer", + "lift_blue_block_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "turn_off_lightbulb", + "rotate_red_block_right", + "lift_red_block_table", + "stack_block", + "close_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_red_block_slider", + "place_in_slider", + "open_drawer", + "push_into_drawer", + "move_slider_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_blue_block_table", + "place_in_slider", + "move_slider_left", + "rotate_pink_block_right", + "lift_red_block_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "push_blue_block_left", + "lift_red_block_table", + "place_in_slider", + "push_into_drawer", + "move_slider_right" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "push_red_block_left", + "push_into_drawer", + "move_slider_right", + "turn_on_lightbulb", + "lift_blue_block_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "open_drawer", + "rotate_blue_block_right", + "move_slider_right", + "lift_pink_block_slider", + "place_in_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_into_drawer", + "lift_red_block_slider", + "place_in_drawer", + "turn_on_led", + "lift_pink_block_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_pink_block_table", + "place_in_slider", + "open_drawer", + "turn_off_led", + "lift_red_block_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "close_drawer", + "lift_red_block_table", + "stack_block", + "unstack_block", + "rotate_red_block_right" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_blue_block_right", + "turn_off_led", + "move_slider_right", + "lift_blue_block_table", + "place_in_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_right", + "turn_on_led", + "push_red_block_right", + "lift_pink_block_table", + "place_in_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "close_drawer", + "lift_blue_block_table", + "stack_block", + "unstack_block", + "rotate_blue_block_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "push_red_block_right", + "lift_blue_block_slider", + "stack_block", + "open_drawer", + "turn_on_led" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_red_block_right", + "close_drawer", + "lift_red_block_table", + "stack_block", + "lift_blue_block_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "push_blue_block_right", + "lift_pink_block_slider", + "stack_block", + "open_drawer", + "unstack_block" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_pink_block_left", + "open_drawer", + "move_slider_left", + "lift_red_block_slider", + "place_in_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "open_drawer", + "lift_blue_block_slider", + "place_in_slider", + "turn_on_lightbulb", + "push_red_block_left" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "open_drawer", + "lift_pink_block_slider", + "place_in_slider", + "push_blue_block_right", + "lift_blue_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "lift_pink_block_slider", + "place_in_drawer", + "close_drawer", + "push_blue_block_left", + "turn_off_led" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_off_lightbulb", + "rotate_pink_block_left", + "lift_pink_block_table", + "stack_block", + "close_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "lift_blue_block_table", + "stack_block", + "unstack_block", + "turn_on_lightbulb", + "rotate_blue_block_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_off_lightbulb", + "lift_red_block_slider", + "place_in_slider", + "open_drawer", + "rotate_pink_block_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "open_drawer", + "turn_on_led", + "lift_red_block_table", + "place_in_drawer", + "move_slider_right" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "push_red_block_left", + "lift_red_block_table", + "place_in_slider", + "move_slider_right", + "turn_off_lightbulb" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "lift_red_block_slider", + "stack_block", + "unstack_block", + "push_blue_block_left", + "lift_red_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_red_block_right", + "lift_red_block_table", + "stack_block", + "close_drawer", + "turn_off_lightbulb" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "push_blue_block_left", + "lift_pink_block_slider", + "place_in_slider", + "turn_off_led", + "move_slider_left" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "push_blue_block_left", + "move_slider_left", + "open_drawer", + "lift_blue_block_table", + "place_in_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "open_drawer", + "turn_on_lightbulb", + "lift_blue_block_table", + "place_in_drawer", + "push_pink_block_right" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_left", + "lift_blue_block_table", + "stack_block", + "move_slider_left", + "lift_red_block_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_red_block_slider", + "stack_block", + "close_drawer", + "turn_off_led", + "unstack_block" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_blue_block_slider", + "place_in_slider", + "turn_on_lightbulb", + "open_drawer", + "push_pink_block_left" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "close_drawer", + "rotate_red_block_left", + "lift_blue_block_table", + "place_in_slider", + "turn_on_lightbulb" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "lift_blue_block_table", + "place_in_slider", + "move_slider_right", + "turn_on_lightbulb", + "push_red_block_left" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_lightbulb", + "move_slider_right", + "open_drawer", + "lift_red_block_slider", + "place_in_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_lightbulb", + "move_slider_right", + "rotate_pink_block_left", + "lift_red_block_slider", + "place_in_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_red_block_right", + "lift_red_block_table", + "place_in_slider", + "push_into_drawer", + "lift_pink_block_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "turn_off_led", + "move_slider_right", + "rotate_red_block_right", + "close_drawer", + "lift_red_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "open_drawer", + "push_red_block_right", + "lift_blue_block_slider", + "stack_block", + "turn_on_led" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_blue_block_slider", + "place_in_drawer", + "lift_blue_block_drawer", + "stack_block", + "close_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_right", + "rotate_pink_block_right", + "lift_blue_block_slider", + "place_in_slider", + "close_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "close_drawer", + "push_pink_block_left", + "turn_on_led", + "move_slider_right", + "lift_pink_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "open_drawer", + "push_red_block_left", + "lift_pink_block_slider", + "place_in_slider", + "move_slider_left" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "rotate_red_block_right", + "turn_on_led", + "close_drawer", + "move_slider_left", + "lift_red_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "open_drawer", + "push_pink_block_right", + "turn_on_lightbulb", + "move_slider_left", + "lift_pink_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "push_red_block_right", + "push_into_drawer", + "lift_blue_block_slider", + "place_in_drawer", + "turn_off_lightbulb" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_off_lightbulb", + "lift_red_block_slider", + "place_in_slider", + "move_slider_left", + "push_pink_block_left" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_blue_block_left", + "lift_pink_block_table", + "stack_block", + "lift_red_block_slider", + "place_in_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "lift_red_block_slider", + "place_in_slider", + "turn_on_led", + "rotate_blue_block_right", + "lift_blue_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "move_slider_left", + "lift_blue_block_table", + "place_in_slider", + "close_drawer", + "turn_off_led" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "open_drawer", + "turn_off_led", + "rotate_red_block_right", + "move_slider_right", + "lift_red_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_pink_block_right", + "open_drawer", + "lift_blue_block_slider", + "place_in_slider", + "move_slider_left" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "lift_red_block_slider", + "place_in_slider", + "turn_off_lightbulb", + "rotate_blue_block_left", + "open_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_right", + "lift_red_block_slider", + "stack_block", + "open_drawer", + "unstack_block" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_pink_block_right", + "move_slider_right", + "turn_off_lightbulb", + "lift_blue_block_slider", + "stack_block" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "open_drawer", + "rotate_red_block_left", + "lift_red_block_table", + "place_in_slider", + "lift_pink_block_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "rotate_red_block_right", + "move_slider_left", + "lift_blue_block_slider", + "stack_block", + "open_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "rotate_red_block_right", + "open_drawer", + "move_slider_right", + "lift_blue_block_slider", + "stack_block" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_left", + "turn_on_led", + "push_into_drawer", + "move_slider_left", + "close_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_off_lightbulb", + "open_drawer", + "push_red_block_left", + "move_slider_right", + "lift_red_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "push_red_block_left", + "lift_blue_block_table", + "stack_block", + "unstack_block", + "turn_off_lightbulb" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_lightbulb", + "move_slider_left", + "push_pink_block_right", + "lift_blue_block_slider", + "place_in_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_blue_block_slider", + "stack_block", + "unstack_block", + "move_slider_left", + "lift_blue_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "turn_on_lightbulb", + "move_slider_right", + "rotate_red_block_right", + "lift_blue_block_slider", + "place_in_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_pink_block_left", + "turn_off_lightbulb", + "move_slider_right", + "lift_blue_block_table", + "place_in_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_blue_block_left", + "lift_pink_block_table", + "place_in_slider", + "move_slider_right", + "turn_off_led" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "push_blue_block_left", + "move_slider_left", + "turn_on_led", + "lift_blue_block_table", + "place_in_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_pink_block_right", + "move_slider_left", + "lift_red_block_table", + "place_in_slider", + "open_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "lift_blue_block_table", + "place_in_drawer", + "rotate_red_block_left", + "close_drawer", + "turn_on_led" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_blue_block_slider", + "place_in_slider", + "turn_on_led", + "open_drawer", + "rotate_pink_block_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_red_block_slider", + "stack_block", + "unstack_block", + "move_slider_right", + "push_red_block_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_left", + "move_slider_right", + "lift_blue_block_slider", + "place_in_drawer", + "close_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_red_block_slider", + "place_in_slider", + "move_slider_left", + "open_drawer", + "rotate_blue_block_right" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_blue_block_slider", + "stack_block", + "unstack_block", + "push_pink_block_right", + "close_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_blue_block_left", + "close_drawer", + "lift_pink_block_table", + "stack_block", + "move_slider_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "open_drawer", + "push_blue_block_left", + "lift_blue_block_table", + "place_in_drawer", + "turn_on_lightbulb" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "push_blue_block_left", + "open_drawer", + "push_into_drawer", + "lift_pink_block_slider", + "place_in_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_pink_block_left", + "open_drawer", + "turn_on_lightbulb", + "lift_blue_block_slider", + "place_in_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_right", + "open_drawer", + "turn_off_lightbulb", + "lift_pink_block_table", + "place_in_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_left", + "turn_on_led", + "close_drawer", + "push_pink_block_left", + "lift_red_block_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_pink_block_left", + "lift_red_block_table", + "place_in_slider", + "turn_on_lightbulb", + "lift_blue_block_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "move_slider_right", + "lift_blue_block_table", + "place_in_slider", + "turn_off_lightbulb", + "rotate_red_block_left" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "lift_blue_block_table", + "place_in_slider", + "move_slider_right", + "close_drawer", + "lift_red_block_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_off_lightbulb", + "rotate_pink_block_right", + "lift_blue_block_slider", + "place_in_drawer", + "close_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "turn_off_lightbulb", + "move_slider_right", + "close_drawer", + "lift_red_block_table", + "place_in_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "turn_on_led", + "rotate_blue_block_left", + "lift_blue_block_table", + "place_in_drawer", + "lift_pink_block_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_led", + "push_pink_block_left", + "lift_pink_block_table", + "stack_block", + "move_slider_left" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_pink_block_table", + "place_in_slider", + "open_drawer", + "turn_on_led", + "rotate_red_block_left" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "open_drawer", + "rotate_pink_block_right", + "lift_pink_block_table", + "place_in_slider", + "turn_off_lightbulb" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_red_block_slider", + "place_in_drawer", + "lift_pink_block_table", + "stack_block", + "turn_on_led" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "move_slider_left", + "push_blue_block_right", + "turn_on_lightbulb", + "open_drawer", + "lift_blue_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "lift_blue_block_slider", + "stack_block", + "close_drawer", + "unstack_block", + "rotate_blue_block_right" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "move_slider_left", + "rotate_red_block_right", + "lift_red_block_table", + "place_in_slider", + "turn_on_lightbulb" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_pink_block_right", + "lift_red_block_table", + "place_in_slider", + "turn_on_led", + "close_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_off_led", + "move_slider_left", + "lift_pink_block_table", + "place_in_drawer", + "rotate_red_block_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "move_slider_left", + "turn_on_led", + "push_red_block_left", + "lift_red_block_table", + "place_in_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "close_drawer", + "push_red_block_left", + "lift_blue_block_slider", + "place_in_slider", + "move_slider_right" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "push_red_block_left", + "close_drawer", + "turn_off_lightbulb", + "move_slider_left", + "lift_pink_block_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "open_drawer", + "move_slider_left", + "turn_off_led", + "push_pink_block_right", + "lift_blue_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "open_drawer", + "lift_pink_block_table", + "stack_block", + "unstack_block", + "turn_on_led" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "open_drawer", + "rotate_red_block_right", + "lift_red_block_table", + "place_in_drawer", + "turn_off_led" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "open_drawer", + "rotate_blue_block_left", + "lift_red_block_slider", + "place_in_drawer", + "turn_off_lightbulb" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_pink_block_table", + "place_in_drawer", + "rotate_red_block_left", + "move_slider_left", + "lift_pink_block_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_led", + "rotate_pink_block_right", + "lift_red_block_table", + "place_in_drawer", + "close_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_red_block_left", + "open_drawer", + "lift_red_block_table", + "place_in_slider", + "turn_on_lightbulb" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "open_drawer", + "turn_on_led", + "rotate_blue_block_left", + "lift_red_block_slider", + "place_in_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "open_drawer", + "rotate_blue_block_right", + "turn_off_lightbulb", + "lift_blue_block_table", + "place_in_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_led", + "push_pink_block_left", + "move_slider_left", + "lift_blue_block_slider", + "place_in_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "turn_on_led", + "lift_pink_block_slider", + "place_in_slider", + "open_drawer", + "move_slider_right" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_red_block_table", + "stack_block", + "turn_off_lightbulb", + "unstack_block", + "push_pink_block_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "push_blue_block_left", + "lift_red_block_table", + "stack_block", + "turn_off_lightbulb", + "unstack_block" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_led", + "open_drawer", + "push_pink_block_left", + "lift_blue_block_table", + "place_in_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_led", + "lift_red_block_table", + "place_in_slider", + "move_slider_right", + "close_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_off_lightbulb", + "push_blue_block_right", + "move_slider_right", + "lift_pink_block_table", + "stack_block" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "open_drawer", + "push_red_block_left", + "lift_pink_block_table", + "stack_block", + "turn_on_led" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "lift_red_block_slider", + "stack_block", + "unstack_block", + "push_blue_block_left", + "lift_blue_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "push_blue_block_right", + "move_slider_right", + "turn_off_led", + "open_drawer", + "lift_red_block_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "push_blue_block_left", + "turn_on_led", + "move_slider_left", + "close_drawer", + "lift_blue_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "open_drawer", + "push_pink_block_left", + "lift_red_block_table", + "place_in_slider", + "turn_off_lightbulb" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_right", + "push_blue_block_right", + "lift_blue_block_table", + "place_in_drawer", + "turn_on_lightbulb" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "open_drawer", + "rotate_blue_block_right", + "turn_on_lightbulb", + "lift_blue_block_table", + "place_in_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "open_drawer", + "push_blue_block_right", + "lift_pink_block_slider", + "stack_block", + "turn_off_lightbulb" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "move_slider_left", + "push_blue_block_right", + "turn_on_lightbulb", + "lift_blue_block_table", + "place_in_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "open_drawer", + "move_slider_left", + "rotate_blue_block_left", + "turn_on_lightbulb", + "lift_red_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "lift_pink_block_slider", + "place_in_slider", + "move_slider_left", + "turn_off_lightbulb", + "push_red_block_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_led", + "open_drawer", + "push_pink_block_left", + "lift_blue_block_table", + "place_in_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_blue_block_table", + "place_in_drawer", + "push_pink_block_right", + "close_drawer", + "move_slider_left" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "move_slider_left", + "open_drawer", + "lift_pink_block_slider", + "place_in_slider", + "rotate_red_block_left" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "turn_off_led", + "move_slider_right", + "rotate_blue_block_right", + "open_drawer", + "push_into_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "lift_red_block_table", + "place_in_slider", + "push_blue_block_right", + "push_into_drawer", + "lift_pink_block_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "open_drawer", + "rotate_blue_block_right", + "move_slider_right", + "lift_pink_block_table", + "stack_block" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "open_drawer", + "move_slider_left", + "rotate_red_block_right", + "lift_blue_block_slider", + "place_in_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_red_block_table", + "place_in_slider", + "turn_on_lightbulb", + "move_slider_right", + "rotate_pink_block_left" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "push_red_block_left", + "move_slider_right", + "lift_red_block_table", + "place_in_slider", + "turn_off_led" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_right", + "turn_on_lightbulb", + "push_pink_block_left", + "lift_blue_block_table", + "place_in_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_right", + "push_red_block_left", + "turn_on_lightbulb", + "lift_blue_block_slider", + "place_in_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "open_drawer", + "push_blue_block_left", + "move_slider_left", + "push_into_drawer", + "lift_pink_block_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "lift_blue_block_slider", + "place_in_slider", + "rotate_red_block_left", + "open_drawer", + "move_slider_left" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "close_drawer", + "lift_pink_block_slider", + "place_in_slider", + "lift_red_block_table", + "stack_block" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_led", + "lift_red_block_table", + "place_in_slider", + "open_drawer", + "push_into_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "move_slider_left", + "open_drawer", + "turn_off_lightbulb", + "lift_pink_block_slider", + "place_in_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "rotate_red_block_right", + "lift_red_block_table", + "place_in_drawer", + "turn_off_led", + "close_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_red_block_table", + "place_in_drawer", + "push_pink_block_left", + "turn_off_lightbulb", + "close_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_pink_block_right", + "lift_blue_block_table", + "place_in_slider", + "move_slider_right", + "turn_off_led" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_blue_block_left", + "move_slider_left", + "turn_on_lightbulb", + "close_drawer", + "lift_pink_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_right", + "open_drawer", + "move_slider_right", + "lift_red_block_table", + "place_in_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "lift_pink_block_slider", + "place_in_slider", + "turn_on_led", + "close_drawer", + "rotate_blue_block_left" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_pink_block_table", + "place_in_slider", + "close_drawer", + "push_blue_block_right", + "lift_red_block_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_red_block_left", + "close_drawer", + "turn_off_led", + "lift_pink_block_table", + "place_in_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "lift_pink_block_slider", + "place_in_slider", + "rotate_red_block_right", + "turn_on_led", + "push_into_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "turn_on_led", + "move_slider_right", + "push_blue_block_right", + "lift_blue_block_table", + "place_in_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_red_block_left", + "lift_pink_block_table", + "stack_block", + "turn_on_lightbulb", + "move_slider_right" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "move_slider_left", + "lift_pink_block_slider", + "place_in_slider", + "turn_off_lightbulb", + "rotate_blue_block_left" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "push_into_drawer", + "turn_off_lightbulb", + "lift_pink_block_slider", + "place_in_slider", + "move_slider_left" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "move_slider_left", + "rotate_blue_block_left", + "push_into_drawer", + "lift_pink_block_slider", + "place_in_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_left", + "open_drawer", + "lift_blue_block_slider", + "place_in_slider", + "lift_pink_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_pink_block_table", + "place_in_slider", + "push_into_drawer", + "turn_off_lightbulb", + "close_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_right", + "push_red_block_right", + "lift_red_block_table", + "stack_block", + "turn_off_lightbulb" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_blue_block_slider", + "place_in_slider", + "open_drawer", + "move_slider_right", + "turn_on_led" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_led", + "open_drawer", + "rotate_red_block_right", + "move_slider_right", + "lift_pink_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "open_drawer", + "push_into_drawer", + "turn_on_led", + "lift_blue_block_slider", + "place_in_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "turn_on_lightbulb", + "push_red_block_right", + "push_into_drawer", + "move_slider_right", + "close_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "move_slider_right", + "open_drawer", + "rotate_blue_block_left", + "turn_on_led", + "lift_pink_block_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_blue_block_left", + "move_slider_right", + "turn_off_led", + "open_drawer", + "lift_pink_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_pink_block_right", + "open_drawer", + "move_slider_left", + "push_into_drawer", + "turn_off_led" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "turn_off_lightbulb", + "move_slider_left", + "close_drawer", + "rotate_blue_block_left", + "lift_blue_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_lightbulb", + "lift_red_block_slider", + "place_in_drawer", + "rotate_pink_block_left", + "close_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "rotate_blue_block_right", + "open_drawer", + "push_into_drawer", + "lift_pink_block_slider", + "place_in_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "open_drawer", + "turn_on_lightbulb", + "lift_red_block_table", + "place_in_drawer", + "rotate_blue_block_left" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_off_led", + "rotate_pink_block_left", + "lift_red_block_table", + "place_in_drawer", + "lift_red_block_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "open_drawer", + "rotate_blue_block_left", + "lift_blue_block_table", + "stack_block", + "turn_on_lightbulb" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "close_drawer", + "push_red_block_right", + "lift_pink_block_slider", + "stack_block", + "unstack_block" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "lift_pink_block_slider", + "place_in_drawer", + "turn_on_lightbulb", + "rotate_red_block_left", + "close_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_blue_block_right", + "move_slider_right", + "turn_off_led", + "lift_blue_block_table", + "place_in_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_left", + "rotate_pink_block_left", + "turn_on_lightbulb", + "lift_pink_block_table", + "place_in_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "open_drawer", + "lift_red_block_slider", + "place_in_drawer", + "move_slider_left", + "push_pink_block_right" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "push_red_block_left", + "move_slider_right", + "lift_blue_block_slider", + "stack_block", + "unstack_block" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "move_slider_right", + "lift_red_block_slider", + "place_in_drawer", + "close_drawer", + "push_blue_block_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "rotate_blue_block_right", + "lift_red_block_table", + "stack_block", + "move_slider_right", + "turn_on_led" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "close_drawer", + "move_slider_right", + "rotate_blue_block_left", + "turn_on_led", + "lift_pink_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_pink_block_left", + "open_drawer", + "turn_on_led", + "lift_blue_block_slider", + "place_in_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "open_drawer", + "turn_off_led", + "lift_red_block_slider", + "place_in_drawer", + "move_slider_left" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "push_blue_block_left", + "lift_pink_block_slider", + "place_in_drawer", + "turn_off_led", + "lift_pink_block_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "push_blue_block_left", + "move_slider_right", + "open_drawer", + "push_into_drawer", + "lift_pink_block_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "push_red_block_right", + "lift_pink_block_slider", + "stack_block", + "move_slider_right", + "open_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_left", + "turn_off_lightbulb", + "move_slider_right", + "lift_red_block_table", + "place_in_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "push_red_block_right", + "move_slider_right", + "lift_pink_block_slider", + "stack_block", + "open_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_red_block_left", + "turn_on_led", + "lift_red_block_table", + "stack_block", + "close_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_pink_block_table", + "stack_block", + "close_drawer", + "turn_on_lightbulb", + "unstack_block" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_blue_block_right", + "turn_off_led", + "open_drawer", + "lift_pink_block_table", + "stack_block" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "close_drawer", + "move_slider_right", + "turn_on_led", + "push_pink_block_left", + "lift_red_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "turn_off_lightbulb", + "push_blue_block_right", + "lift_red_block_slider", + "stack_block", + "unstack_block" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_red_block_slider", + "place_in_slider", + "rotate_pink_block_left", + "move_slider_left", + "turn_off_led" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "move_slider_right", + "turn_off_led", + "push_red_block_left", + "lift_red_block_table", + "place_in_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_left", + "move_slider_left", + "lift_blue_block_slider", + "stack_block", + "turn_off_lightbulb" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_led", + "push_red_block_left", + "lift_red_block_table", + "stack_block", + "close_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "push_blue_block_left", + "move_slider_right", + "open_drawer", + "lift_pink_block_slider", + "place_in_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "move_slider_right", + "turn_on_led", + "close_drawer", + "push_blue_block_right", + "lift_red_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "close_drawer", + "turn_off_lightbulb", + "rotate_blue_block_left", + "lift_red_block_slider", + "stack_block" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_right", + "open_drawer", + "lift_blue_block_table", + "place_in_slider", + "move_slider_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "open_drawer", + "turn_on_led", + "move_slider_right", + "rotate_red_block_left", + "lift_red_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "turn_on_led", + "move_slider_left", + "push_red_block_right", + "open_drawer", + "lift_red_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "rotate_red_block_right", + "turn_off_led", + "open_drawer", + "push_into_drawer", + "lift_pink_block_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "open_drawer", + "turn_off_lightbulb", + "push_blue_block_right", + "lift_blue_block_table", + "place_in_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_left", + "lift_red_block_table", + "place_in_slider", + "move_slider_left", + "open_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_blue_block_table", + "place_in_slider", + "push_pink_block_right", + "turn_on_led", + "move_slider_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "push_red_block_left", + "move_slider_right", + "lift_blue_block_slider", + "place_in_slider", + "turn_on_led" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "push_red_block_right", + "move_slider_right", + "lift_pink_block_slider", + "place_in_slider", + "turn_off_led" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "push_blue_block_right", + "open_drawer", + "turn_on_led", + "lift_blue_block_table", + "place_in_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_blue_block_left", + "move_slider_left", + "close_drawer", + "turn_off_led", + "lift_blue_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_left", + "open_drawer", + "lift_blue_block_slider", + "place_in_drawer", + "push_pink_block_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_pink_block_right", + "lift_pink_block_table", + "place_in_slider", + "close_drawer", + "lift_red_block_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_pink_block_table", + "place_in_slider", + "close_drawer", + "push_red_block_right", + "turn_on_lightbulb" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "lift_pink_block_slider", + "stack_block", + "move_slider_left", + "close_drawer", + "turn_off_led" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "turn_off_led", + "push_blue_block_left", + "open_drawer", + "lift_blue_block_table", + "place_in_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "lift_blue_block_slider", + "place_in_drawer", + "turn_on_lightbulb", + "close_drawer", + "push_red_block_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_red_block_right", + "lift_pink_block_table", + "place_in_slider", + "lift_blue_block_slider", + "stack_block" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "open_drawer", + "rotate_red_block_left", + "lift_blue_block_slider", + "place_in_slider", + "turn_on_lightbulb" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "lift_pink_block_slider", + "stack_block", + "unstack_block", + "turn_off_led", + "rotate_red_block_left" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "push_blue_block_right", + "open_drawer", + "turn_on_lightbulb", + "push_into_drawer", + "move_slider_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_red_block_left", + "turn_on_led", + "lift_blue_block_slider", + "place_in_slider", + "lift_pink_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_pink_block_left", + "open_drawer", + "move_slider_left", + "turn_on_led", + "lift_pink_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "close_drawer", + "rotate_red_block_left", + "lift_red_block_table", + "place_in_slider", + "lift_pink_block_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_red_block_table", + "place_in_slider", + "close_drawer", + "move_slider_left", + "turn_off_lightbulb" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "lift_red_block_slider", + "stack_block", + "unstack_block", + "push_red_block_left", + "move_slider_left" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "turn_on_led", + "move_slider_right", + "push_blue_block_left", + "lift_red_block_table", + "place_in_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "lift_pink_block_slider", + "place_in_slider", + "move_slider_left", + "turn_on_lightbulb", + "rotate_red_block_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_blue_block_left", + "turn_off_lightbulb", + "lift_pink_block_table", + "place_in_slider", + "close_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "turn_on_lightbulb", + "push_red_block_left", + "open_drawer", + "lift_blue_block_slider", + "place_in_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "open_drawer", + "lift_red_block_slider", + "place_in_slider", + "push_into_drawer", + "lift_blue_block_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "rotate_red_block_right", + "turn_on_lightbulb", + "lift_red_block_table", + "place_in_drawer", + "lift_pink_block_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "push_into_drawer", + "close_drawer", + "move_slider_right", + "lift_pink_block_slider", + "place_in_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_right", + "lift_blue_block_table", + "stack_block", + "unstack_block", + "close_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "push_into_drawer", + "lift_blue_block_slider", + "place_in_drawer", + "turn_off_led", + "close_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_right", + "lift_blue_block_slider", + "place_in_slider", + "open_drawer", + "turn_off_led" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "lift_red_block_table", + "place_in_drawer", + "rotate_blue_block_right", + "close_drawer", + "turn_off_led" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_right", + "turn_off_led", + "lift_blue_block_slider", + "stack_block", + "open_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "move_slider_right", + "turn_on_led", + "rotate_blue_block_right", + "open_drawer", + "lift_blue_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "move_slider_right", + "lift_pink_block_slider", + "place_in_slider", + "push_red_block_left", + "turn_off_led" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "turn_on_led", + "rotate_blue_block_right", + "lift_red_block_slider", + "place_in_slider", + "open_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_red_block_table", + "place_in_drawer", + "rotate_pink_block_right", + "turn_off_lightbulb", + "close_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_left", + "open_drawer", + "turn_off_led", + "move_slider_right", + "lift_pink_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "push_into_drawer", + "lift_blue_block_drawer", + "place_in_slider", + "close_drawer", + "turn_on_led" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "push_blue_block_right", + "push_into_drawer", + "move_slider_left", + "close_drawer", + "turn_on_lightbulb" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "open_drawer", + "move_slider_right", + "push_pink_block_right", + "turn_on_lightbulb", + "lift_blue_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_right", + "push_pink_block_left", + "lift_blue_block_table", + "place_in_slider", + "turn_off_led" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "push_blue_block_left", + "lift_red_block_table", + "stack_block", + "turn_off_lightbulb", + "lift_pink_block_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_led", + "open_drawer", + "push_into_drawer", + "move_slider_right", + "lift_blue_block_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "open_drawer", + "move_slider_left", + "lift_pink_block_table", + "place_in_drawer", + "lift_red_block_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "open_drawer", + "lift_pink_block_slider", + "place_in_slider", + "rotate_blue_block_left", + "lift_blue_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_off_led", + "move_slider_left", + "push_red_block_left", + "lift_pink_block_table", + "stack_block" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "push_red_block_left", + "turn_off_led", + "lift_blue_block_slider", + "place_in_slider", + "lift_red_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_right", + "open_drawer", + "lift_blue_block_slider", + "place_in_slider", + "turn_on_led" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "lift_red_block_slider", + "place_in_drawer", + "move_slider_left", + "turn_on_led", + "close_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_red_block_right", + "lift_red_block_table", + "place_in_drawer", + "close_drawer", + "move_slider_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_pink_block_left", + "lift_pink_block_table", + "stack_block", + "move_slider_right", + "open_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "open_drawer", + "rotate_red_block_right", + "push_into_drawer", + "turn_on_led", + "lift_red_block_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "open_drawer", + "lift_pink_block_table", + "place_in_slider", + "turn_on_lightbulb", + "move_slider_left" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "turn_off_led", + "push_red_block_left", + "push_into_drawer", + "lift_pink_block_slider", + "place_in_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_lightbulb", + "move_slider_right", + "close_drawer", + "push_pink_block_right", + "lift_red_block_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_pink_block_table", + "stack_block", + "close_drawer", + "turn_on_lightbulb", + "lift_red_block_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_led", + "rotate_pink_block_right", + "close_drawer", + "lift_pink_block_table", + "stack_block" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_left", + "lift_pink_block_table", + "place_in_slider", + "push_red_block_left", + "open_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "push_blue_block_left", + "push_into_drawer", + "lift_red_block_slider", + "place_in_slider", + "turn_off_lightbulb" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "rotate_blue_block_right", + "lift_red_block_table", + "place_in_slider", + "turn_on_led", + "move_slider_left" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "close_drawer", + "turn_on_lightbulb", + "push_red_block_right", + "lift_red_block_table", + "stack_block" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "turn_on_lightbulb", + "rotate_red_block_right", + "lift_blue_block_slider", + "place_in_drawer", + "close_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "open_drawer", + "turn_on_lightbulb", + "move_slider_left", + "rotate_red_block_left", + "lift_red_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "turn_off_lightbulb", + "move_slider_left", + "push_blue_block_left", + "lift_pink_block_slider", + "stack_block" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "lift_blue_block_slider", + "place_in_drawer", + "lift_blue_block_drawer", + "stack_block", + "turn_off_lightbulb" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "turn_off_led", + "lift_pink_block_slider", + "place_in_slider", + "move_slider_left", + "push_blue_block_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_right", + "lift_pink_block_table", + "place_in_slider", + "close_drawer", + "turn_on_led" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "lift_blue_block_slider", + "place_in_slider", + "push_red_block_left", + "push_into_drawer", + "close_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "move_slider_right", + "rotate_red_block_left", + "lift_red_block_table", + "stack_block", + "turn_off_led" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "move_slider_left", + "push_blue_block_right", + "lift_pink_block_slider", + "place_in_drawer", + "lift_blue_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "push_blue_block_right", + "close_drawer", + "lift_blue_block_table", + "stack_block", + "unstack_block" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_left", + "open_drawer", + "turn_off_lightbulb", + "push_pink_block_right", + "lift_pink_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "turn_on_led", + "move_slider_left", + "rotate_red_block_right", + "lift_pink_block_slider", + "place_in_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "push_red_block_right", + "close_drawer", + "lift_red_block_table", + "place_in_slider", + "turn_on_lightbulb" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "push_red_block_right", + "lift_blue_block_slider", + "place_in_drawer", + "turn_off_lightbulb", + "lift_red_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "open_drawer", + "turn_on_lightbulb", + "move_slider_right", + "lift_red_block_slider", + "stack_block" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "close_drawer", + "push_blue_block_right", + "lift_red_block_slider", + "place_in_slider", + "lift_blue_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "push_red_block_right", + "lift_red_block_table", + "place_in_slider", + "close_drawer", + "turn_on_lightbulb" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_blue_block_right", + "move_slider_right", + "lift_blue_block_table", + "stack_block", + "unstack_block" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_off_led", + "open_drawer", + "push_pink_block_right", + "lift_blue_block_slider", + "place_in_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "move_slider_left", + "push_into_drawer", + "lift_red_block_slider", + "place_in_drawer", + "turn_off_led" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_right", + "lift_red_block_slider", + "stack_block", + "unstack_block", + "open_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "close_drawer", + "move_slider_left", + "turn_off_lightbulb", + "rotate_red_block_left", + "lift_red_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "push_blue_block_left", + "turn_off_lightbulb", + "lift_red_block_slider", + "stack_block", + "unstack_block" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_red_block_right", + "turn_on_lightbulb", + "lift_red_block_table", + "place_in_slider", + "open_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "push_red_block_left", + "lift_red_block_table", + "stack_block", + "open_drawer", + "unstack_block" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "move_slider_right", + "lift_pink_block_slider", + "place_in_slider", + "open_drawer", + "push_red_block_left" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "open_drawer", + "turn_off_lightbulb", + "lift_red_block_table", + "place_in_drawer", + "move_slider_right" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "open_drawer", + "turn_off_lightbulb", + "push_into_drawer", + "lift_pink_block_drawer", + "place_in_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "turn_on_led", + "push_red_block_left", + "move_slider_left", + "open_drawer", + "lift_red_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "turn_on_lightbulb", + "move_slider_left", + "lift_blue_block_table", + "place_in_drawer", + "lift_blue_block_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "rotate_red_block_right", + "push_into_drawer", + "lift_pink_block_slider", + "place_in_slider", + "lift_red_block_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_left", + "push_into_drawer", + "turn_off_led", + "move_slider_left", + "lift_pink_block_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "turn_off_led", + "lift_blue_block_table", + "place_in_slider", + "lift_pink_block_slider", + "stack_block" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "turn_off_led", + "push_red_block_right", + "push_into_drawer", + "move_slider_left", + "lift_red_block_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_off_led", + "lift_blue_block_slider", + "stack_block", + "unstack_block", + "push_blue_block_right" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_right", + "lift_pink_block_table", + "stack_block", + "open_drawer", + "move_slider_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "lift_blue_block_table", + "stack_block", + "close_drawer", + "unstack_block", + "rotate_blue_block_right" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "rotate_blue_block_right", + "turn_off_lightbulb", + "close_drawer", + "move_slider_left", + "lift_blue_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "push_red_block_left", + "push_into_drawer", + "turn_on_led", + "lift_red_block_drawer", + "place_in_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_off_led", + "rotate_pink_block_right", + "open_drawer", + "lift_red_block_slider", + "place_in_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "open_drawer", + "turn_off_lightbulb", + "rotate_pink_block_left", + "move_slider_left", + "push_into_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "move_slider_right", + "push_red_block_right", + "lift_red_block_table", + "place_in_slider", + "open_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "close_drawer", + "turn_off_led", + "move_slider_left", + "lift_red_block_table", + "place_in_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_right", + "lift_red_block_table", + "place_in_drawer", + "close_drawer", + "turn_off_lightbulb" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "move_slider_right", + "open_drawer", + "turn_off_lightbulb", + "push_into_drawer", + "lift_blue_block_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "lift_red_block_slider", + "stack_block", + "unstack_block", + "turn_on_lightbulb", + "rotate_blue_block_right" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_off_led", + "push_into_drawer", + "move_slider_right", + "lift_red_block_slider", + "place_in_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_pink_block_table", + "place_in_drawer", + "turn_off_lightbulb", + "lift_blue_block_slider", + "stack_block" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "move_slider_right", + "turn_on_led", + "push_red_block_right", + "close_drawer", + "lift_red_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_pink_block_left", + "lift_blue_block_table", + "place_in_drawer", + "move_slider_left", + "close_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "move_slider_right", + "push_red_block_right", + "open_drawer", + "lift_red_block_table", + "place_in_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_lightbulb", + "rotate_red_block_right", + "lift_pink_block_table", + "stack_block", + "move_slider_left" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_pink_block_right", + "turn_on_lightbulb", + "close_drawer", + "lift_red_block_slider", + "place_in_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "rotate_blue_block_right", + "push_into_drawer", + "lift_red_block_slider", + "place_in_drawer", + "move_slider_right" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_pink_block_right", + "close_drawer", + "lift_red_block_slider", + "place_in_slider", + "turn_on_lightbulb" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "lift_red_block_table", + "place_in_slider", + "open_drawer", + "turn_off_lightbulb", + "rotate_blue_block_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "turn_on_led", + "push_blue_block_right", + "lift_red_block_slider", + "place_in_drawer", + "close_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "rotate_blue_block_right", + "lift_blue_block_table", + "place_in_slider", + "open_drawer", + "turn_on_led" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "close_drawer", + "lift_red_block_slider", + "place_in_slider", + "turn_off_lightbulb", + "rotate_pink_block_left" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_off_led", + "rotate_red_block_left", + "move_slider_left", + "lift_red_block_table", + "place_in_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "rotate_blue_block_left", + "lift_blue_block_table", + "stack_block", + "move_slider_left", + "turn_off_lightbulb" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_left", + "lift_pink_block_table", + "stack_block", + "turn_off_lightbulb", + "move_slider_left" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "lift_blue_block_table", + "stack_block", + "unstack_block", + "turn_off_lightbulb", + "rotate_red_block_right" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "open_drawer", + "turn_on_lightbulb", + "lift_pink_block_slider", + "place_in_drawer", + "move_slider_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_red_block_table", + "stack_block", + "unstack_block", + "push_pink_block_left", + "close_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "push_red_block_right", + "move_slider_right", + "lift_pink_block_slider", + "place_in_slider", + "lift_blue_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_blue_block_right", + "lift_pink_block_table", + "stack_block", + "unstack_block", + "open_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "open_drawer", + "lift_pink_block_table", + "place_in_drawer", + "move_slider_left", + "turn_off_led" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "lift_red_block_table", + "place_in_slider", + "close_drawer", + "turn_off_lightbulb", + "push_blue_block_right" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "rotate_blue_block_right", + "turn_off_lightbulb", + "lift_blue_block_table", + "stack_block", + "open_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_blue_block_slider", + "stack_block", + "unstack_block", + "push_blue_block_left", + "lift_pink_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "close_drawer", + "push_red_block_right", + "turn_off_lightbulb", + "lift_red_block_table", + "stack_block" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "open_drawer", + "turn_on_lightbulb", + "rotate_pink_block_left", + "lift_blue_block_table", + "place_in_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "open_drawer", + "turn_off_lightbulb", + "lift_red_block_table", + "stack_block", + "move_slider_right" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "rotate_blue_block_right", + "move_slider_left", + "turn_off_led", + "lift_blue_block_table", + "place_in_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_red_block_right", + "close_drawer", + "turn_off_lightbulb", + "move_slider_right", + "lift_pink_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "rotate_red_block_left", + "move_slider_right", + "turn_on_led", + "lift_blue_block_table", + "place_in_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_right", + "rotate_pink_block_right", + "lift_red_block_table", + "place_in_slider", + "turn_off_led" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "open_drawer", + "rotate_red_block_left", + "push_into_drawer", + "turn_off_led", + "lift_pink_block_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_pink_block_left", + "turn_on_led", + "push_into_drawer", + "lift_pink_block_drawer", + "place_in_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_red_block_left", + "turn_off_led", + "move_slider_right", + "lift_red_block_table", + "place_in_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "move_slider_right", + "rotate_blue_block_right", + "turn_off_led", + "open_drawer", + "lift_red_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "move_slider_right", + "lift_red_block_slider", + "stack_block", + "open_drawer", + "turn_off_led" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_red_block_table", + "stack_block", + "open_drawer", + "unstack_block", + "push_pink_block_right" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "open_drawer", + "turn_off_led", + "lift_blue_block_table", + "place_in_drawer", + "lift_blue_block_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "turn_off_led", + "open_drawer", + "rotate_red_block_right", + "lift_pink_block_slider", + "stack_block" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "open_drawer", + "move_slider_left", + "rotate_blue_block_left", + "push_into_drawer", + "lift_pink_block_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "open_drawer", + "lift_red_block_table", + "place_in_slider", + "move_slider_right", + "turn_on_lightbulb" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_led", + "open_drawer", + "push_pink_block_left", + "move_slider_left", + "lift_blue_block_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_right", + "open_drawer", + "lift_red_block_slider", + "place_in_slider", + "lift_blue_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "move_slider_left", + "turn_off_led", + "close_drawer", + "push_red_block_right", + "lift_pink_block_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "open_drawer", + "rotate_blue_block_left", + "lift_blue_block_table", + "place_in_slider", + "lift_pink_block_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_right", + "lift_blue_block_table", + "stack_block", + "close_drawer", + "turn_on_led" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "move_slider_left", + "open_drawer", + "push_red_block_left", + "push_into_drawer", + "turn_off_lightbulb" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_off_lightbulb", + "open_drawer", + "move_slider_right", + "push_pink_block_right", + "lift_pink_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "push_red_block_right", + "lift_blue_block_table", + "stack_block", + "close_drawer", + "turn_on_lightbulb" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "open_drawer", + "lift_red_block_slider", + "stack_block", + "unstack_block", + "push_red_block_right" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_pink_block_left", + "lift_blue_block_slider", + "stack_block", + "unstack_block", + "lift_pink_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_pink_block_table", + "place_in_drawer", + "turn_on_lightbulb", + "rotate_red_block_right", + "lift_pink_block_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_pink_block_table", + "stack_block", + "move_slider_right", + "open_drawer", + "unstack_block" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "lift_red_block_table", + "place_in_drawer", + "turn_on_lightbulb", + "move_slider_right", + "push_blue_block_right" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "turn_off_led", + "open_drawer", + "lift_red_block_slider", + "stack_block", + "unstack_block" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "lift_pink_block_slider", + "place_in_drawer", + "move_slider_left", + "push_red_block_right", + "lift_red_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "push_blue_block_right", + "open_drawer", + "lift_blue_block_table", + "stack_block", + "turn_on_led" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "close_drawer", + "lift_pink_block_slider", + "place_in_slider", + "rotate_blue_block_left", + "lift_red_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_lightbulb", + "move_slider_right", + "open_drawer", + "push_blue_block_left", + "lift_pink_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "push_blue_block_left", + "open_drawer", + "lift_red_block_table", + "place_in_drawer", + "move_slider_left" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_pink_block_left", + "turn_off_led", + "push_into_drawer", + "close_drawer", + "move_slider_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "lift_blue_block_table", + "place_in_slider", + "push_red_block_left", + "push_into_drawer", + "close_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "turn_off_led", + "lift_pink_block_slider", + "place_in_slider", + "move_slider_left", + "lift_red_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_lightbulb", + "open_drawer", + "rotate_pink_block_right", + "move_slider_right", + "lift_blue_block_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_left", + "move_slider_left", + "lift_blue_block_slider", + "place_in_slider", + "lift_pink_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_blue_block_slider", + "stack_block", + "unstack_block", + "turn_on_led", + "push_blue_block_right" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "push_blue_block_left", + "open_drawer", + "move_slider_left", + "lift_blue_block_table", + "place_in_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_left", + "lift_pink_block_table", + "stack_block", + "unstack_block", + "lift_red_block_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_right", + "lift_pink_block_table", + "place_in_slider", + "rotate_red_block_left", + "turn_off_lightbulb" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_left", + "turn_off_led", + "lift_blue_block_slider", + "place_in_slider", + "push_pink_block_right" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_left", + "move_slider_right", + "lift_pink_block_table", + "place_in_slider", + "open_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_led", + "lift_blue_block_slider", + "place_in_slider", + "move_slider_right", + "rotate_pink_block_left" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "turn_on_led", + "lift_blue_block_table", + "stack_block", + "unstack_block", + "move_slider_left" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_right", + "rotate_red_block_right", + "turn_off_lightbulb", + "lift_red_block_table", + "place_in_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "move_slider_right", + "rotate_red_block_right", + "turn_on_led", + "open_drawer", + "lift_blue_block_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "rotate_red_block_left", + "turn_on_led", + "open_drawer", + "lift_red_block_table", + "stack_block" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_pink_block_right", + "move_slider_left", + "turn_on_lightbulb", + "close_drawer", + "lift_red_block_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_off_led", + "push_red_block_right", + "close_drawer", + "lift_red_block_table", + "place_in_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_right", + "lift_blue_block_slider", + "place_in_drawer", + "close_drawer", + "rotate_pink_block_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_pink_block_right", + "move_slider_left", + "lift_pink_block_table", + "stack_block", + "turn_on_led" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "move_slider_right", + "turn_on_lightbulb", + "push_red_block_right", + "lift_red_block_table", + "place_in_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_pink_block_table", + "place_in_slider", + "turn_on_led", + "move_slider_right", + "open_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "rotate_red_block_left", + "move_slider_left", + "turn_off_led", + "close_drawer", + "lift_blue_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "turn_off_led", + "push_red_block_right", + "move_slider_right", + "close_drawer", + "lift_red_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_lightbulb", + "move_slider_right", + "rotate_red_block_right", + "open_drawer", + "lift_pink_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "move_slider_right", + "lift_pink_block_slider", + "place_in_drawer", + "push_blue_block_right", + "lift_blue_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_right", + "push_pink_block_left", + "lift_pink_block_table", + "stack_block", + "turn_off_lightbulb" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "push_red_block_right", + "close_drawer", + "lift_blue_block_slider", + "place_in_slider", + "turn_on_lightbulb" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_pink_block_left", + "lift_pink_block_table", + "place_in_drawer", + "turn_on_lightbulb", + "close_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_left", + "turn_on_lightbulb", + "lift_red_block_slider", + "stack_block", + "move_slider_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "open_drawer", + "rotate_blue_block_right", + "lift_blue_block_table", + "place_in_drawer", + "move_slider_right" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_pink_block_table", + "place_in_slider", + "turn_on_lightbulb", + "move_slider_left", + "push_blue_block_left" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "push_red_block_right", + "turn_off_led", + "lift_red_block_table", + "place_in_slider", + "lift_pink_block_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "move_slider_right", + "turn_off_lightbulb", + "lift_red_block_table", + "place_in_slider", + "push_blue_block_left" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "turn_on_led", + "open_drawer", + "move_slider_right", + "lift_pink_block_slider", + "place_in_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "move_slider_left", + "rotate_red_block_right", + "turn_on_lightbulb", + "lift_pink_block_slider", + "place_in_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "open_drawer", + "lift_red_block_table", + "place_in_slider", + "turn_on_lightbulb", + "push_into_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_lightbulb", + "push_pink_block_right", + "open_drawer", + "lift_pink_block_table", + "place_in_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_off_lightbulb", + "rotate_pink_block_right", + "move_slider_right", + "open_drawer", + "lift_pink_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_blue_block_slider", + "place_in_slider", + "push_pink_block_left", + "move_slider_right", + "lift_red_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_red_block_left", + "lift_pink_block_table", + "place_in_slider", + "turn_off_led", + "move_slider_left" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_left", + "turn_off_lightbulb", + "open_drawer", + "move_slider_left", + "lift_blue_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_led", + "rotate_red_block_right", + "lift_red_block_table", + "place_in_drawer", + "lift_red_block_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "turn_off_lightbulb", + "push_blue_block_left", + "push_into_drawer", + "move_slider_right", + "lift_blue_block_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "push_into_drawer", + "lift_red_block_drawer", + "place_in_slider", + "close_drawer", + "lift_pink_block_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "turn_on_led", + "lift_red_block_table", + "stack_block", + "move_slider_right", + "open_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_left", + "push_into_drawer", + "lift_red_block_slider", + "place_in_drawer", + "turn_on_lightbulb" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "push_red_block_left", + "lift_red_block_table", + "place_in_drawer", + "turn_on_led", + "lift_red_block_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_pink_block_right", + "lift_pink_block_table", + "stack_block", + "turn_off_lightbulb", + "move_slider_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "move_slider_left", + "close_drawer", + "turn_on_lightbulb", + "rotate_blue_block_left", + "lift_red_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_pink_block_table", + "place_in_drawer", + "turn_off_led", + "move_slider_left", + "rotate_red_block_right" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_red_block_slider", + "place_in_slider", + "move_slider_right", + "push_pink_block_left", + "lift_pink_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "open_drawer", + "turn_on_lightbulb", + "push_red_block_right", + "push_into_drawer", + "move_slider_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_led", + "open_drawer", + "lift_pink_block_table", + "place_in_slider", + "rotate_red_block_right" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_pink_block_right", + "turn_on_lightbulb", + "lift_pink_block_table", + "stack_block", + "unstack_block" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "rotate_blue_block_right", + "turn_off_led", + "lift_red_block_slider", + "place_in_drawer", + "lift_red_block_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_left", + "lift_red_block_table", + "place_in_slider", + "push_pink_block_left", + "lift_blue_block_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "push_red_block_left", + "move_slider_right", + "lift_red_block_table", + "place_in_drawer", + "lift_red_block_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_right", + "open_drawer", + "push_blue_block_right", + "lift_blue_block_table", + "place_in_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_left", + "lift_red_block_slider", + "place_in_slider", + "open_drawer", + "lift_blue_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_right", + "move_slider_left", + "open_drawer", + "turn_on_led", + "lift_red_block_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "turn_on_lightbulb", + "lift_pink_block_slider", + "stack_block", + "open_drawer", + "move_slider_left" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "rotate_red_block_right", + "move_slider_left", + "lift_red_block_table", + "place_in_slider", + "turn_on_led" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_left", + "rotate_pink_block_left", + "open_drawer", + "turn_on_lightbulb", + "push_into_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "rotate_blue_block_left", + "push_into_drawer", + "turn_on_led", + "lift_blue_block_drawer", + "place_in_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_right", + "turn_off_lightbulb", + "rotate_pink_block_right", + "lift_pink_block_table", + "stack_block" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "lift_blue_block_table", + "place_in_slider", + "turn_on_led", + "move_slider_left", + "close_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "move_slider_right", + "turn_on_lightbulb", + "lift_red_block_table", + "place_in_drawer", + "lift_red_block_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_left", + "lift_blue_block_table", + "place_in_slider", + "open_drawer", + "move_slider_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "rotate_red_block_left", + "lift_pink_block_slider", + "place_in_slider", + "move_slider_right", + "lift_red_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "push_red_block_left", + "lift_pink_block_slider", + "place_in_slider", + "move_slider_left", + "turn_on_led" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_lightbulb", + "rotate_pink_block_right", + "lift_red_block_table", + "place_in_slider", + "lift_blue_block_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "rotate_blue_block_right", + "lift_pink_block_slider", + "place_in_slider", + "push_into_drawer", + "close_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_pink_block_right", + "lift_blue_block_table", + "stack_block", + "turn_on_led", + "close_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "lift_blue_block_table", + "place_in_slider", + "turn_off_lightbulb", + "move_slider_left", + "lift_blue_block_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "close_drawer", + "turn_off_lightbulb", + "push_pink_block_right", + "lift_pink_block_table", + "stack_block" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "rotate_red_block_right", + "open_drawer", + "push_into_drawer", + "lift_pink_block_slider", + "place_in_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_red_block_left", + "turn_on_led", + "lift_pink_block_table", + "place_in_slider", + "open_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "close_drawer", + "lift_red_block_table", + "stack_block", + "unstack_block", + "rotate_blue_block_left" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "rotate_blue_block_right", + "move_slider_left", + "turn_off_lightbulb", + "lift_red_block_slider", + "place_in_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "lift_blue_block_table", + "stack_block", + "unstack_block", + "open_drawer", + "turn_off_lightbulb" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "push_into_drawer", + "move_slider_left", + "lift_blue_block_drawer", + "place_in_slider", + "lift_pink_block_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "move_slider_right", + "open_drawer", + "rotate_blue_block_right", + "turn_off_lightbulb", + "lift_red_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_pink_block_table", + "place_in_slider", + "move_slider_left", + "rotate_red_block_left", + "turn_off_lightbulb" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "open_drawer", + "lift_red_block_slider", + "place_in_drawer", + "move_slider_right", + "rotate_blue_block_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "turn_on_led", + "push_blue_block_right", + "lift_red_block_slider", + "place_in_drawer", + "lift_blue_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_blue_block_right", + "open_drawer", + "lift_blue_block_table", + "place_in_drawer", + "move_slider_right" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "lift_pink_block_slider", + "place_in_drawer", + "turn_off_lightbulb", + "rotate_red_block_left", + "lift_red_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_blue_block_right", + "close_drawer", + "turn_on_led", + "lift_pink_block_table", + "place_in_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "lift_pink_block_slider", + "place_in_slider", + "rotate_blue_block_right", + "open_drawer", + "move_slider_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_left", + "push_pink_block_left", + "lift_pink_block_table", + "stack_block", + "turn_on_led" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "lift_pink_block_slider", + "place_in_drawer", + "turn_off_led", + "rotate_red_block_left", + "lift_pink_block_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_red_block_left", + "turn_off_lightbulb", + "move_slider_right", + "lift_red_block_table", + "place_in_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_red_block_left", + "move_slider_right", + "close_drawer", + "turn_on_led", + "lift_red_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "open_drawer", + "lift_red_block_table", + "place_in_slider", + "move_slider_right", + "rotate_blue_block_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "turn_on_led", + "open_drawer", + "lift_blue_block_slider", + "place_in_drawer", + "push_red_block_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_pink_block_table", + "place_in_drawer", + "close_drawer", + "rotate_red_block_right", + "move_slider_left" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "lift_pink_block_slider", + "stack_block", + "unstack_block", + "open_drawer", + "turn_on_led" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "rotate_blue_block_left", + "move_slider_left", + "lift_blue_block_table", + "stack_block", + "unstack_block" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "rotate_blue_block_left", + "turn_on_lightbulb", + "open_drawer", + "lift_blue_block_table", + "place_in_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_pink_block_right", + "turn_off_led", + "close_drawer", + "move_slider_right", + "lift_red_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_blue_block_right", + "turn_on_lightbulb", + "open_drawer", + "lift_blue_block_table", + "stack_block" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "rotate_red_block_left", + "move_slider_right", + "turn_off_lightbulb", + "open_drawer", + "push_into_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_right", + "turn_on_lightbulb", + "rotate_blue_block_left", + "lift_blue_block_table", + "place_in_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "close_drawer", + "move_slider_left", + "rotate_blue_block_right", + "lift_pink_block_slider", + "place_in_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_blue_block_left", + "turn_on_lightbulb", + "close_drawer", + "lift_blue_block_table", + "stack_block" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "lift_pink_block_slider", + "stack_block", + "unstack_block", + "push_red_block_left", + "lift_pink_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "push_blue_block_left", + "turn_on_lightbulb", + "move_slider_left", + "lift_red_block_table", + "place_in_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "push_blue_block_right", + "lift_red_block_slider", + "place_in_slider", + "turn_on_led", + "close_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "push_red_block_right", + "move_slider_left", + "open_drawer", + "turn_on_lightbulb", + "lift_red_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "open_drawer", + "push_into_drawer", + "lift_red_block_slider", + "place_in_drawer", + "lift_red_block_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "open_drawer", + "turn_off_led", + "rotate_pink_block_right", + "lift_pink_block_table", + "place_in_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_blue_block_table", + "place_in_slider", + "move_slider_left", + "rotate_pink_block_right", + "close_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_red_block_right", + "lift_red_block_table", + "place_in_slider", + "move_slider_left", + "turn_off_lightbulb" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_red_block_right", + "turn_off_lightbulb", + "lift_pink_block_table", + "stack_block", + "unstack_block" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "open_drawer", + "rotate_red_block_left", + "turn_off_led", + "lift_red_block_table", + "place_in_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "lift_red_block_table", + "place_in_slider", + "turn_on_lightbulb", + "move_slider_right", + "push_blue_block_left" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "open_drawer", + "lift_red_block_table", + "place_in_slider", + "move_slider_left", + "lift_blue_block_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "push_blue_block_right", + "turn_off_lightbulb", + "move_slider_left", + "close_drawer", + "lift_red_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_into_drawer", + "turn_on_lightbulb", + "move_slider_left", + "lift_pink_block_drawer", + "place_in_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "open_drawer", + "rotate_red_block_left", + "move_slider_right", + "turn_on_lightbulb", + "lift_blue_block_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "turn_off_led", + "move_slider_left", + "rotate_red_block_left", + "lift_red_block_table", + "place_in_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_right", + "open_drawer", + "rotate_pink_block_right", + "lift_pink_block_table", + "place_in_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "open_drawer", + "push_blue_block_right", + "move_slider_right", + "turn_off_led", + "lift_red_block_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_lightbulb", + "close_drawer", + "rotate_pink_block_right", + "lift_red_block_slider", + "place_in_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_led", + "move_slider_right", + "open_drawer", + "push_pink_block_left", + "lift_pink_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "move_slider_left", + "lift_red_block_slider", + "place_in_drawer", + "rotate_blue_block_left", + "lift_red_block_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_off_led", + "close_drawer", + "rotate_pink_block_left", + "move_slider_left", + "lift_pink_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "move_slider_left", + "push_into_drawer", + "lift_blue_block_slider", + "place_in_drawer", + "close_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_pink_block_right", + "turn_off_lightbulb", + "lift_pink_block_table", + "place_in_slider", + "lift_red_block_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "push_red_block_right", + "turn_off_led", + "lift_red_block_table", + "place_in_drawer", + "lift_red_block_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "move_slider_right", + "push_red_block_left", + "turn_off_lightbulb", + "lift_blue_block_slider", + "place_in_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "rotate_red_block_right", + "lift_blue_block_table", + "place_in_drawer", + "turn_off_led", + "move_slider_right" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "turn_off_led", + "close_drawer", + "lift_pink_block_slider", + "place_in_slider", + "push_red_block_left" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "move_slider_right", + "rotate_blue_block_left", + "lift_pink_block_slider", + "place_in_slider", + "lift_blue_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "turn_off_led", + "lift_blue_block_slider", + "place_in_slider", + "close_drawer", + "rotate_red_block_left" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "turn_on_led", + "push_into_drawer", + "close_drawer", + "lift_blue_block_slider", + "place_in_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_pink_block_left", + "open_drawer", + "move_slider_left", + "push_into_drawer", + "turn_on_led" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_left", + "open_drawer", + "turn_on_lightbulb", + "rotate_red_block_right", + "lift_pink_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_pink_block_left", + "lift_blue_block_slider", + "place_in_drawer", + "turn_off_lightbulb", + "lift_pink_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "lift_blue_block_table", + "place_in_drawer", + "push_red_block_right", + "move_slider_right", + "turn_on_lightbulb" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "lift_blue_block_table", + "place_in_slider", + "close_drawer", + "move_slider_right", + "lift_blue_block_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_blue_block_right", + "turn_on_led", + "lift_red_block_slider", + "place_in_drawer", + "lift_blue_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "lift_pink_block_slider", + "place_in_slider", + "close_drawer", + "rotate_red_block_left", + "lift_red_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_lightbulb", + "push_blue_block_right", + "lift_blue_block_table", + "place_in_drawer", + "close_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "open_drawer", + "move_slider_right", + "turn_on_led", + "push_into_drawer", + "lift_pink_block_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "turn_on_led", + "open_drawer", + "move_slider_right", + "lift_blue_block_table", + "place_in_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "open_drawer", + "move_slider_right", + "rotate_blue_block_right", + "turn_off_lightbulb", + "lift_pink_block_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_left", + "rotate_blue_block_left", + "open_drawer", + "turn_on_lightbulb", + "lift_pink_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "move_slider_right", + "lift_red_block_slider", + "place_in_slider", + "turn_on_lightbulb", + "lift_blue_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_blue_block_table", + "place_in_slider", + "push_pink_block_left", + "turn_off_lightbulb", + "move_slider_right" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "close_drawer", + "rotate_pink_block_left", + "turn_off_led", + "move_slider_left", + "lift_blue_block_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_pink_block_right", + "turn_off_led", + "open_drawer", + "lift_blue_block_slider", + "place_in_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_off_lightbulb", + "move_slider_right", + "rotate_pink_block_right", + "lift_red_block_slider", + "place_in_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "push_red_block_left", + "move_slider_left", + "open_drawer", + "lift_red_block_table", + "stack_block" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "lift_pink_block_slider", + "place_in_slider", + "move_slider_left", + "open_drawer", + "push_blue_block_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "turn_on_led", + "rotate_blue_block_right", + "lift_blue_block_table", + "place_in_drawer", + "lift_blue_block_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_red_block_slider", + "place_in_slider", + "open_drawer", + "turn_on_led", + "push_blue_block_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_blue_block_slider", + "place_in_slider", + "rotate_pink_block_right", + "close_drawer", + "turn_on_lightbulb" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "rotate_blue_block_left", + "lift_pink_block_slider", + "place_in_slider", + "close_drawer", + "turn_off_led" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_blue_block_left", + "lift_blue_block_table", + "place_in_drawer", + "move_slider_right", + "turn_on_led" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_blue_block_slider", + "place_in_drawer", + "rotate_red_block_left", + "turn_on_lightbulb", + "lift_red_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_off_led", + "push_pink_block_right", + "open_drawer", + "move_slider_right", + "lift_pink_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "push_red_block_left", + "turn_off_led", + "push_into_drawer", + "lift_red_block_drawer", + "place_in_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_blue_block_left", + "move_slider_right", + "open_drawer", + "turn_on_led", + "lift_red_block_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "push_blue_block_left", + "turn_on_led", + "lift_pink_block_slider", + "stack_block", + "unstack_block" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "move_slider_right", + "push_red_block_left", + "close_drawer", + "turn_on_led", + "lift_blue_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "open_drawer", + "push_red_block_left", + "lift_pink_block_table", + "place_in_slider", + "turn_on_led" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_lightbulb", + "push_pink_block_left", + "lift_blue_block_table", + "place_in_slider", + "close_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "open_drawer", + "lift_pink_block_slider", + "place_in_drawer", + "turn_on_lightbulb", + "push_blue_block_left" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "rotate_blue_block_right", + "move_slider_right", + "lift_red_block_table", + "stack_block", + "turn_on_lightbulb" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "turn_off_led", + "move_slider_left", + "rotate_blue_block_right", + "lift_red_block_table", + "stack_block" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_lightbulb", + "move_slider_right", + "lift_red_block_slider", + "stack_block", + "open_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_left", + "close_drawer", + "turn_off_led", + "lift_blue_block_slider", + "place_in_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "move_slider_right", + "rotate_red_block_right", + "lift_red_block_table", + "place_in_slider", + "close_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "turn_off_led", + "push_into_drawer", + "lift_red_block_drawer", + "place_in_slider", + "move_slider_left" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "close_drawer", + "lift_pink_block_table", + "place_in_slider", + "turn_on_led", + "move_slider_left" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "close_drawer", + "turn_on_lightbulb", + "move_slider_right", + "rotate_pink_block_left", + "lift_red_block_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_left", + "turn_on_led", + "move_slider_right", + "push_into_drawer", + "lift_blue_block_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "close_drawer", + "push_red_block_right", + "lift_pink_block_table", + "place_in_slider", + "move_slider_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_left", + "open_drawer", + "lift_pink_block_table", + "place_in_slider", + "turn_on_led" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "push_red_block_right", + "lift_blue_block_slider", + "stack_block", + "turn_off_led", + "open_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "rotate_red_block_right", + "move_slider_right", + "lift_red_block_table", + "place_in_slider", + "turn_on_led" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "open_drawer", + "move_slider_right", + "turn_on_led", + "rotate_blue_block_right", + "lift_red_block_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_left", + "turn_on_led", + "lift_red_block_table", + "stack_block", + "lift_blue_block_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "rotate_red_block_left", + "turn_on_lightbulb", + "push_into_drawer", + "move_slider_left", + "lift_pink_block_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "move_slider_left", + "push_red_block_left", + "open_drawer", + "lift_red_block_table", + "place_in_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_off_lightbulb", + "rotate_blue_block_right", + "lift_pink_block_table", + "stack_block", + "lift_red_block_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "rotate_red_block_right", + "open_drawer", + "move_slider_left", + "push_into_drawer", + "turn_off_lightbulb" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_right", + "lift_red_block_slider", + "place_in_drawer", + "turn_on_lightbulb", + "rotate_blue_block_left" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "lift_red_block_table", + "place_in_slider", + "close_drawer", + "rotate_blue_block_right", + "turn_on_lightbulb" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "rotate_red_block_left", + "lift_pink_block_slider", + "place_in_drawer", + "move_slider_left", + "lift_pink_block_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_right", + "rotate_pink_block_right", + "lift_red_block_slider", + "place_in_slider", + "lift_pink_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_off_led", + "rotate_red_block_right", + "lift_blue_block_slider", + "place_in_slider", + "lift_pink_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_left", + "rotate_pink_block_left", + "turn_on_lightbulb", + "lift_blue_block_slider", + "place_in_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "rotate_blue_block_right", + "turn_off_lightbulb", + "open_drawer", + "move_slider_left", + "lift_blue_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "open_drawer", + "turn_off_lightbulb", + "lift_blue_block_table", + "place_in_drawer", + "rotate_red_block_left" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "push_red_block_right", + "close_drawer", + "lift_red_block_table", + "place_in_slider", + "lift_pink_block_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "rotate_blue_block_left", + "open_drawer", + "push_into_drawer", + "lift_blue_block_drawer", + "place_in_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_pink_block_right", + "lift_red_block_slider", + "stack_block", + "move_slider_right", + "open_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_led", + "push_pink_block_right", + "open_drawer", + "lift_pink_block_table", + "place_in_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_led", + "lift_blue_block_table", + "place_in_slider", + "rotate_pink_block_right", + "close_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "rotate_blue_block_right", + "move_slider_left", + "turn_off_lightbulb", + "lift_pink_block_slider", + "place_in_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "move_slider_right", + "lift_pink_block_slider", + "place_in_drawer", + "push_blue_block_left", + "close_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_into_drawer", + "lift_blue_block_slider", + "place_in_slider", + "move_slider_right", + "turn_on_lightbulb" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_red_block_slider", + "place_in_slider", + "open_drawer", + "push_blue_block_left", + "move_slider_left" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_off_led", + "push_pink_block_right", + "lift_blue_block_slider", + "place_in_slider", + "lift_pink_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_right", + "turn_on_lightbulb", + "rotate_blue_block_left", + "lift_blue_block_table", + "place_in_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "push_red_block_right", + "move_slider_left", + "turn_on_lightbulb", + "open_drawer", + "lift_pink_block_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "rotate_blue_block_right", + "lift_pink_block_slider", + "place_in_slider", + "turn_on_led", + "lift_blue_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "push_red_block_left", + "move_slider_left", + "lift_red_block_table", + "place_in_slider", + "open_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "open_drawer", + "lift_red_block_table", + "place_in_slider", + "rotate_pink_block_left", + "move_slider_left" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "rotate_red_block_right", + "move_slider_left", + "lift_red_block_table", + "stack_block", + "unstack_block" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "move_slider_left", + "open_drawer", + "rotate_blue_block_right", + "turn_on_led", + "push_into_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "close_drawer", + "move_slider_left", + "push_red_block_left", + "lift_red_block_table", + "stack_block" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "open_drawer", + "turn_off_led", + "rotate_red_block_right", + "lift_pink_block_slider", + "place_in_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_blue_block_right", + "lift_blue_block_table", + "place_in_slider", + "turn_on_lightbulb", + "open_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_right", + "rotate_pink_block_right", + "lift_blue_block_slider", + "place_in_slider", + "push_into_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_lightbulb", + "rotate_blue_block_right", + "lift_blue_block_table", + "stack_block", + "open_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_lightbulb", + "push_pink_block_right", + "open_drawer", + "lift_blue_block_table", + "place_in_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_red_block_right", + "turn_off_led", + "open_drawer", + "lift_pink_block_table", + "stack_block" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_left", + "open_drawer", + "lift_blue_block_slider", + "place_in_slider", + "lift_red_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_off_led", + "move_slider_left", + "open_drawer", + "push_pink_block_left", + "push_into_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "lift_pink_block_slider", + "place_in_slider", + "move_slider_right", + "push_blue_block_left", + "turn_on_led" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "move_slider_right", + "push_blue_block_right", + "lift_blue_block_table", + "place_in_slider", + "turn_on_lightbulb" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "move_slider_right", + "push_blue_block_right", + "lift_blue_block_table", + "place_in_drawer", + "turn_off_led" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_lightbulb", + "push_pink_block_left", + "move_slider_left", + "lift_pink_block_table", + "place_in_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_left", + "turn_on_lightbulb", + "rotate_pink_block_right", + "lift_pink_block_table", + "place_in_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_red_block_slider", + "place_in_slider", + "move_slider_right", + "turn_on_lightbulb", + "push_blue_block_right" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "lift_red_block_slider", + "stack_block", + "unstack_block", + "turn_off_led", + "rotate_red_block_left" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_red_block_table", + "place_in_slider", + "lift_blue_block_slider", + "stack_block", + "close_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "rotate_blue_block_right", + "push_into_drawer", + "lift_pink_block_slider", + "place_in_drawer", + "move_slider_left" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_led", + "move_slider_left", + "rotate_pink_block_right", + "close_drawer", + "lift_pink_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_pink_block_left", + "turn_off_led", + "lift_blue_block_slider", + "place_in_slider", + "open_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "rotate_red_block_left", + "lift_pink_block_slider", + "place_in_slider", + "turn_on_lightbulb", + "move_slider_left" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_left", + "close_drawer", + "turn_off_lightbulb", + "rotate_pink_block_left", + "lift_blue_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "move_slider_left", + "open_drawer", + "lift_red_block_slider", + "place_in_slider", + "turn_on_lightbulb" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_blue_block_table", + "stack_block", + "close_drawer", + "unstack_block", + "rotate_pink_block_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "lift_pink_block_slider", + "place_in_slider", + "move_slider_right", + "rotate_blue_block_right", + "turn_off_lightbulb" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_left", + "lift_red_block_slider", + "stack_block", + "unstack_block", + "push_red_block_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_blue_block_left", + "lift_blue_block_table", + "stack_block", + "turn_on_lightbulb", + "move_slider_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "turn_off_lightbulb", + "lift_blue_block_slider", + "place_in_drawer", + "lift_blue_block_drawer", + "stack_block" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_left", + "push_blue_block_right", + "open_drawer", + "turn_off_led", + "lift_pink_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "close_drawer", + "turn_off_led", + "lift_blue_block_table", + "place_in_slider", + "push_pink_block_left" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_blue_block_left", + "lift_blue_block_table", + "stack_block", + "turn_off_lightbulb", + "move_slider_left" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_right", + "turn_off_led", + "move_slider_left", + "push_into_drawer", + "close_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_pink_block_left", + "turn_off_led", + "open_drawer", + "lift_pink_block_table", + "place_in_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "open_drawer", + "turn_on_led", + "push_red_block_right", + "lift_pink_block_table", + "place_in_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "open_drawer", + "move_slider_right", + "lift_red_block_slider", + "place_in_drawer", + "turn_on_lightbulb" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "turn_on_lightbulb", + "close_drawer", + "lift_red_block_slider", + "place_in_slider", + "rotate_blue_block_left" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "move_slider_left", + "push_red_block_left", + "push_into_drawer", + "turn_off_led", + "lift_red_block_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_led", + "rotate_pink_block_left", + "open_drawer", + "move_slider_right", + "push_into_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "rotate_red_block_left", + "turn_on_led", + "lift_pink_block_slider", + "stack_block", + "move_slider_left" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_off_led", + "push_pink_block_right", + "lift_red_block_table", + "place_in_drawer", + "close_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "turn_off_lightbulb", + "rotate_blue_block_left", + "lift_pink_block_slider", + "place_in_drawer", + "lift_red_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_right", + "turn_on_led", + "lift_pink_block_table", + "place_in_slider", + "open_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "turn_on_lightbulb", + "push_blue_block_left", + "close_drawer", + "move_slider_left", + "lift_blue_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "lift_blue_block_slider", + "place_in_slider", + "open_drawer", + "rotate_red_block_right", + "turn_on_led" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_right", + "move_slider_left", + "lift_red_block_slider", + "place_in_slider", + "turn_on_lightbulb" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_blue_block_right", + "lift_blue_block_table", + "place_in_drawer", + "close_drawer", + "turn_off_lightbulb" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_off_lightbulb", + "rotate_pink_block_right", + "move_slider_left", + "lift_red_block_slider", + "place_in_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "rotate_red_block_left", + "move_slider_right", + "lift_red_block_table", + "place_in_slider", + "turn_off_led" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_off_lightbulb", + "move_slider_left", + "push_pink_block_left", + "lift_blue_block_table", + "place_in_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "push_blue_block_right", + "turn_on_lightbulb", + "move_slider_right", + "close_drawer", + "lift_pink_block_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_red_block_table", + "place_in_slider", + "turn_off_led", + "open_drawer", + "push_into_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_into_drawer", + "turn_on_led", + "move_slider_left", + "close_drawer", + "lift_blue_block_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "rotate_red_block_right", + "lift_blue_block_slider", + "place_in_slider", + "push_into_drawer", + "turn_off_lightbulb" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_blue_block_left", + "open_drawer", + "lift_pink_block_table", + "place_in_drawer", + "turn_on_led" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_blue_block_left", + "lift_pink_block_table", + "place_in_drawer", + "close_drawer", + "lift_red_block_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_left", + "rotate_pink_block_right", + "lift_pink_block_table", + "place_in_drawer", + "turn_on_lightbulb" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "rotate_blue_block_left", + "move_slider_right", + "turn_off_lightbulb", + "close_drawer", + "lift_pink_block_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "turn_on_lightbulb", + "open_drawer", + "rotate_blue_block_left", + "lift_pink_block_slider", + "place_in_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "open_drawer", + "move_slider_left", + "lift_blue_block_slider", + "place_in_slider", + "rotate_pink_block_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_blue_block_slider", + "stack_block", + "move_slider_left", + "open_drawer", + "turn_off_lightbulb" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_pink_block_table", + "place_in_slider", + "open_drawer", + "push_blue_block_right", + "move_slider_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_left", + "move_slider_left", + "lift_blue_block_slider", + "place_in_drawer", + "close_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "rotate_blue_block_right", + "lift_blue_block_table", + "place_in_drawer", + "close_drawer", + "lift_red_block_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_pink_block_right", + "lift_pink_block_table", + "place_in_slider", + "move_slider_left", + "lift_pink_block_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_pink_block_table", + "stack_block", + "unstack_block", + "push_pink_block_right", + "close_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_pink_block_right", + "close_drawer", + "lift_pink_block_table", + "place_in_slider", + "turn_off_lightbulb" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "rotate_red_block_right", + "turn_off_led", + "open_drawer", + "push_into_drawer", + "lift_red_block_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "close_drawer", + "rotate_red_block_left", + "move_slider_left", + "turn_on_lightbulb", + "lift_red_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "move_slider_left", + "lift_blue_block_table", + "place_in_slider", + "push_into_drawer", + "turn_on_lightbulb" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "open_drawer", + "lift_pink_block_table", + "place_in_drawer", + "move_slider_right", + "push_blue_block_right" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "rotate_red_block_right", + "lift_pink_block_slider", + "place_in_slider", + "turn_off_lightbulb", + "lift_red_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "close_drawer", + "turn_on_led", + "rotate_blue_block_left", + "lift_pink_block_slider", + "place_in_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "push_blue_block_right", + "lift_red_block_slider", + "stack_block", + "unstack_block", + "lift_blue_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_left", + "rotate_red_block_right", + "turn_on_led", + "lift_blue_block_slider", + "place_in_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_red_block_slider", + "place_in_slider", + "turn_on_lightbulb", + "move_slider_left", + "rotate_pink_block_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_right", + "move_slider_left", + "open_drawer", + "push_into_drawer", + "lift_red_block_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "turn_on_led", + "push_blue_block_right", + "move_slider_right", + "close_drawer", + "lift_blue_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "rotate_blue_block_left", + "lift_red_block_table", + "place_in_slider", + "open_drawer", + "move_slider_right" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "rotate_red_block_right", + "turn_off_lightbulb", + "lift_red_block_table", + "stack_block", + "move_slider_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_right", + "open_drawer", + "push_into_drawer", + "move_slider_right", + "lift_pink_block_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "push_blue_block_left", + "lift_blue_block_table", + "place_in_slider", + "open_drawer", + "move_slider_left" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "move_slider_left", + "lift_red_block_slider", + "place_in_slider", + "turn_off_led", + "push_into_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_red_block_slider", + "place_in_slider", + "rotate_blue_block_left", + "move_slider_left", + "close_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_left", + "turn_on_led", + "open_drawer", + "rotate_pink_block_left", + "push_into_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "rotate_blue_block_right", + "turn_on_lightbulb", + "push_into_drawer", + "move_slider_right", + "lift_red_block_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_lightbulb", + "rotate_pink_block_right", + "lift_pink_block_table", + "place_in_drawer", + "close_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "push_into_drawer", + "lift_pink_block_slider", + "place_in_slider", + "move_slider_right", + "turn_off_lightbulb" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_left", + "rotate_blue_block_left", + "turn_on_lightbulb", + "lift_pink_block_table", + "stack_block" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "open_drawer", + "turn_off_lightbulb", + "rotate_red_block_right", + "lift_red_block_table", + "place_in_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_led", + "push_pink_block_left", + "open_drawer", + "lift_pink_block_table", + "place_in_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "rotate_blue_block_left", + "turn_off_led", + "open_drawer", + "lift_pink_block_slider", + "place_in_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_off_led", + "rotate_pink_block_left", + "lift_pink_block_table", + "place_in_slider", + "open_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_left", + "lift_pink_block_table", + "place_in_slider", + "turn_on_lightbulb", + "push_blue_block_left" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_right", + "lift_pink_block_table", + "place_in_slider", + "turn_off_led", + "open_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_led", + "push_red_block_right", + "open_drawer", + "lift_blue_block_slider", + "place_in_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_lightbulb", + "open_drawer", + "push_pink_block_left", + "lift_blue_block_table", + "place_in_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "lift_pink_block_slider", + "place_in_slider", + "push_blue_block_left", + "turn_off_lightbulb", + "lift_red_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_right", + "move_slider_right", + "turn_on_led", + "lift_pink_block_table", + "place_in_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "turn_off_lightbulb", + "move_slider_right", + "push_blue_block_right", + "close_drawer", + "lift_blue_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "turn_on_lightbulb", + "push_blue_block_right", + "move_slider_right", + "open_drawer", + "lift_blue_block_table" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "lift_blue_block_table", + "place_in_slider", + "turn_on_led", + "move_slider_right", + "lift_pink_block_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_right", + "lift_red_block_table", + "place_in_slider", + "rotate_pink_block_left", + "turn_on_lightbulb" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_blue_block_slider", + "place_in_slider", + "push_red_block_left", + "move_slider_right", + "turn_on_lightbulb" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "open_drawer", + "move_slider_left", + "lift_red_block_table", + "place_in_drawer", + "lift_red_block_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_red_block_left", + "open_drawer", + "lift_pink_block_table", + "stack_block", + "move_slider_left" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_pink_block_left", + "push_into_drawer", + "move_slider_left", + "close_drawer", + "turn_off_led" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_blue_block_slider", + "place_in_slider", + "open_drawer", + "push_pink_block_right", + "move_slider_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_lightbulb", + "move_slider_right", + "push_pink_block_left", + "lift_blue_block_table", + "place_in_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "lift_blue_block_table", + "place_in_slider", + "lift_pink_block_slider", + "stack_block", + "move_slider_left" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_right", + "push_pink_block_left", + "lift_blue_block_slider", + "place_in_slider", + "open_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_pink_block_table", + "place_in_slider", + "turn_off_led", + "move_slider_right", + "lift_pink_block_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "turn_on_led", + "move_slider_right", + "rotate_blue_block_left", + "lift_blue_block_table", + "place_in_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "open_drawer", + "push_red_block_left", + "move_slider_right", + "lift_pink_block_slider", + "place_in_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "open_drawer", + "turn_off_led", + "push_into_drawer", + "move_slider_left", + "lift_red_block_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "lift_pink_block_slider", + "place_in_drawer", + "close_drawer", + "rotate_red_block_left", + "move_slider_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "rotate_red_block_left", + "lift_pink_block_slider", + "stack_block", + "unstack_block", + "move_slider_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_blue_block_slider", + "place_in_drawer", + "turn_on_led", + "rotate_pink_block_left", + "lift_pink_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "close_drawer", + "turn_off_lightbulb", + "rotate_blue_block_right", + "lift_blue_block_table", + "place_in_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_led", + "open_drawer", + "push_blue_block_left", + "lift_blue_block_table", + "place_in_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_right", + "lift_blue_block_table", + "stack_block", + "turn_off_lightbulb", + "open_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "rotate_red_block_right", + "close_drawer", + "move_slider_left", + "turn_off_lightbulb", + "lift_pink_block_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "move_slider_right", + "lift_red_block_table", + "place_in_slider", + "push_blue_block_right", + "push_into_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_into_drawer", + "close_drawer", + "lift_blue_block_slider", + "place_in_slider", + "move_slider_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "move_slider_left", + "close_drawer", + "turn_on_lightbulb", + "rotate_blue_block_right", + "lift_blue_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "rotate_blue_block_left", + "lift_blue_block_table", + "place_in_slider", + "turn_off_led", + "open_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "turn_off_lightbulb", + "lift_pink_block_slider", + "stack_block", + "unstack_block", + "rotate_pink_block_left" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "lift_red_block_table", + "place_in_slider", + "move_slider_left", + "turn_on_led", + "rotate_blue_block_right" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "turn_off_led", + "push_red_block_left", + "move_slider_left", + "push_into_drawer", + "close_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "open_drawer", + "lift_pink_block_slider", + "place_in_slider", + "turn_off_led", + "push_red_block_left" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "push_blue_block_right", + "open_drawer", + "lift_blue_block_table", + "place_in_slider", + "turn_off_led" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "push_red_block_right", + "close_drawer", + "turn_off_led", + "lift_blue_block_slider", + "stack_block" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_right", + "lift_blue_block_slider", + "place_in_slider", + "push_pink_block_right", + "turn_off_led" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "move_slider_right", + "turn_on_led", + "lift_blue_block_slider", + "place_in_slider", + "push_red_block_left" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_red_block_right", + "lift_red_block_table", + "place_in_slider", + "move_slider_left", + "lift_red_block_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_on_lightbulb", + "push_blue_block_right", + "lift_blue_block_table", + "stack_block", + "unstack_block" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "rotate_red_block_left", + "close_drawer", + "turn_on_led", + "lift_pink_block_slider", + "place_in_slider" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "lift_red_block_table", + "place_in_drawer", + "push_blue_block_left", + "turn_off_led", + "lift_red_block_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "turn_off_lightbulb", + "push_pink_block_left", + "lift_pink_block_table", + "place_in_slider", + "open_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_left", + "move_slider_left", + "lift_blue_block_slider", + "place_in_drawer", + "turn_off_lightbulb" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_red_block_left", + "turn_on_lightbulb", + "close_drawer", + "lift_pink_block_table", + "stack_block" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "close_drawer", + "lift_pink_block_table", + "place_in_slider", + "move_slider_right", + "rotate_red_block_right" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_pink_block_left", + "open_drawer", + "lift_pink_block_table", + "place_in_drawer", + "turn_on_led" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "move_slider_left", + "turn_on_lightbulb", + "rotate_red_block_right", + "lift_blue_block_slider", + "stack_block" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "rotate_pink_block_right", + "turn_off_lightbulb", + "open_drawer", + "lift_pink_block_table", + "place_in_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "turn_off_led", + "move_slider_right", + "push_blue_block_left", + "lift_red_block_slider", + "place_in_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "slider_right", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_left", + "push_into_drawer", + "move_slider_right", + "lift_red_block_slider", + "place_in_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "lift_pink_block_table", + "place_in_slider", + "turn_off_led", + "move_slider_right", + "push_into_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "push_blue_block_right", + "open_drawer", + "lift_blue_block_table", + "place_in_slider", + "push_into_drawer" + ] + ], + [ + { + "led": 0, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "push_red_block_right", + "lift_red_block_table", + "place_in_drawer", + "turn_off_lightbulb", + "close_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "open", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "push_red_block_left", + "turn_off_led", + "move_slider_right", + "lift_red_block_table", + "stack_block" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "closed", + "red_block": "table", + "blue_block": "table", + "pink_block": "slider_right", + "grasped": 0 + }, + [ + "move_slider_left", + "turn_off_lightbulb", + "rotate_blue_block_left", + "open_drawer", + "lift_blue_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 1, + "slider": "right", + "drawer": "open", + "red_block": "table", + "blue_block": "slider_right", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "push_red_block_right", + "close_drawer", + "turn_off_lightbulb", + "move_slider_left", + "lift_blue_block_slider" + ] + ], + [ + { + "led": 0, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "push_pink_block_right", + "lift_pink_block_table", + "place_in_slider", + "move_slider_right", + "open_drawer" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "open", + "red_block": "slider_left", + "blue_block": "table", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_left", + "lift_blue_block_table", + "place_in_drawer", + "close_drawer", + "push_pink_block_left" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "right", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "table", + "pink_block": "slider_left", + "grasped": 0 + }, + [ + "move_slider_left", + "turn_on_lightbulb", + "open_drawer", + "rotate_blue_block_right", + "lift_blue_block_table" + ] + ], + [ + { + "led": 1, + "lightbulb": 0, + "slider": "left", + "drawer": "closed", + "red_block": "slider_right", + "blue_block": "slider_left", + "pink_block": "table", + "grasped": 0 + }, + [ + "move_slider_right", + "turn_on_lightbulb", + "push_pink_block_left", + "open_drawer", + "push_into_drawer" + ] + ] +] \ No newline at end of file diff --git a/MiniCPM-RobotManip/evaluation/calvin/launcher.py b/MiniCPM-RobotManip/evaluation/calvin/launcher.py new file mode 100644 index 0000000..65fc59c --- /dev/null +++ b/MiniCPM-RobotManip/evaluation/calvin/launcher.py @@ -0,0 +1,276 @@ +# Copyright 2025 starVLA community. All rights reserved. +# Licensed under the MIT License. +# Modifications Copyright 2026 The OpenBMB Team. + +"""Launch one MiniCPM server and the serial CALVIN evaluator.""" + +from __future__ import annotations + +import argparse +import os +import signal +import subprocess +import sys +from datetime import datetime +from pathlib import Path +from typing import Sequence + +from evaluation.common.launcher_utils import ( + git_revision, + prepend_pythonpath, + resolve_checkpoint, + resolve_executable, + terminate_process_group, + wait_for_policy_server, +) + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + description=( + "Run the serial CALVIN evaluation. Runtime paths and model " + "settings are provided through environment variables; see README.md." + ) + ) + return parser + + +def require_env(name: str) -> str: + value = os.environ.get(name, "") + if not value: + raise ValueError(f"Set required environment variable {name}") + return value + + +def env_int(name: str, default: int) -> int: + value = os.environ.get(name) + try: + return default if value is None else int(value) + except ValueError as exc: + raise ValueError(f"{name} must be an integer") from exc + + +def enabled(name: str) -> bool: + return os.environ.get(name, "0") == "1" + + +def main(argv: Sequence[str] | None = None) -> int: + build_parser().parse_args(argv) + root = Path(__file__).resolve().parents[2] + script_dir = Path(__file__).resolve().parent + + try: + minicpm_python = resolve_executable( + require_env("MINICPM_PYTHON"), + "MINICPM_PYTHON", + ) + calvin_python = resolve_executable( + require_env("CALVIN_PYTHON"), + "CALVIN_PYTHON", + ) + calvin_root = Path(require_env("CALVIN_ROOT")).expanduser().resolve() + dataset_path = Path( + require_env("CALVIN_DATASET_PATH") + ).expanduser().resolve() + checkpoint = resolve_checkpoint( + os.environ.get("CHECKPOINT", "openbmb/MiniCPM-RobotManip") + ) + embodiment_id = int(require_env("EMBODIMENT_ID")) + port = env_int("PORT", 10093) + num_sequences = env_int("NUM_SEQUENCES", 1000) + resize_size = env_int("RESIZE_SIZE", 448) + seed = env_int("SEED", 0) + timeout = env_int("READY_TIMEOUT", 600) + except ValueError as exc: + print(str(exc), file=sys.stderr) + return 2 + + if embodiment_id < 0: + print("EMBODIMENT_ID must be non-negative.", file=sys.stderr) + return 2 + if not 1 <= port <= 65535: + print("PORT must be in [1, 65535].", file=sys.stderr) + return 2 + if num_sequences < 1 or resize_size < 1 or timeout < 1: + print( + "NUM_SEQUENCES, RESIZE_SIZE, and READY_TIMEOUT must be positive.", + file=sys.stderr, + ) + return 2 + + host = os.environ.get("HOST", "127.0.0.1") + config_path = Path( + os.environ.get( + "CALVIN_CONFIG_PATH", + calvin_root / "calvin_models" / "conf", + ) + ).expanduser() + sequences_path = Path( + os.environ.get( + "EVAL_SEQUENCES_PATH", + script_dir / "eval_sequences.json", + ) + ).expanduser() + output_root = Path( + os.environ.get( + "OUTPUT_ROOT", + root / "outputs" / "evaluation" / "calvin", + ) + ).expanduser() + run_id = os.environ.get( + "RUN_ID", + datetime.now().strftime("%Y%m%d_%H%M%S"), + ) + log_dir = Path( + os.environ.get("EVAL_LOG_DIR", output_root / run_id) + ).expanduser() + log_dir.mkdir(parents=True, exist_ok=True) + server_log = log_dir / "policy_server.log" + evaluator_log = log_dir / "evaluator.log" + + manifest_fields = { + "checkpoint": checkpoint, + "default_embodiment_id": str(embodiment_id), + "minicpm_revision": git_revision(root), + "calvin_revision": git_revision(calvin_root), + "calvin_dataset_path": str(dataset_path), + "eval_sequences_path": str(sequences_path), + "num_sequences": str(num_sequences), + "host": host, + "port": str(port), + } + with (log_dir / "run_manifest.tsv").open("w", encoding="utf-8") as file: + for key, value in manifest_fields.items(): + file.write(f"{key}\t{value}\n") + + server: subprocess.Popen | None = None + evaluator: subprocess.Popen | None = None + interrupted = 0 + + def handle_signal(signum, frame) -> None: + del frame + nonlocal interrupted + interrupted = signum + terminate_process_group(evaluator) + terminate_process_group(server) + + previous_handlers = {} + for signum in (signal.SIGINT, signal.SIGTERM, signal.SIGHUP): + previous_handlers[signum] = signal.signal(signum, handle_signal) + + try: + with server_log.open("w", encoding="utf-8") as server_stream: + server = subprocess.Popen( + ["bash", str(script_dir / "run_policy_server.sh")], + stdout=server_stream, + stderr=subprocess.STDOUT, + env={ + **os.environ, + "MINICPM_PYTHON": minicpm_python, + "CHECKPOINT": checkpoint, + "EMBODIMENT_ID": str(embodiment_id), + "HOST": host, + "PORT": str(port), + }, + start_new_session=True, + ) + wait_for_policy_server( + server=server, + minicpm_python=minicpm_python, + root=root, + host=host, + port=port, + checkpoint=checkpoint, + embodiment_id=embodiment_id, + min_action_dim=7, + request_id="calvin-readiness", + timeout=timeout, + ) + if interrupted: + raise RuntimeError("Evaluation interrupted") + + eval_command = [ + calvin_python, + "-m", + "evaluation.calvin.eval_calvin", + "--args.host", + host, + "--args.port", + str(port), + "--args.resize-size", + str(resize_size), + "--args.calvin-root", + str(calvin_root), + "--args.dataset-path", + str(dataset_path), + "--args.calvin-config-path", + str(config_path), + "--args.eval-sequences-path", + str(sequences_path), + "--args.num-sequences", + str(num_sequences), + "--args.seed", + str(seed), + "--args.eval-log-dir", + str(log_dir), + ] + annotation_cache = os.environ.get("LANG_ANNOTATION_CACHE") + if annotation_cache: + eval_command.extend( + ["--args.lang-annotation-cache", annotation_cache] + ) + if enabled("DEBUG"): + eval_command.append("--args.debug") + if enabled("RESET"): + eval_command.append("--args.reset") + if enabled("DIVERSE_INST"): + eval_command.append("--args.diverse-inst") + + eval_env = prepend_pythonpath( + os.environ, + root, + calvin_root, + calvin_root / "calvin_models", + calvin_root / "calvin_env", + ) + with evaluator_log.open("w", encoding="utf-8") as eval_stream: + evaluator = subprocess.Popen( + eval_command, + stdout=eval_stream, + stderr=subprocess.STDOUT, + env=eval_env, + start_new_session=True, + ) + if interrupted: + raise RuntimeError("Evaluation interrupted") + status = evaluator.wait() + + if status: + print( + f"CALVIN evaluator failed with status {status}; " + f"see {evaluator_log}", + file=sys.stderr, + ) + return status + if server.poll() is not None: + print( + "MiniCPM server exited before CALVIN completed.", + file=sys.stderr, + ) + return 1 + print(f"CALVIN evaluation completed; outputs: {log_dir}") + return 0 + except Exception as exc: + if interrupted: + return 128 + interrupted + print(f"CALVIN launcher failed: {exc}; see {server_log}", file=sys.stderr) + return 1 + finally: + terminate_process_group(evaluator) + terminate_process_group(server) + for signum, handler in previous_handlers.items(): + signal.signal(signum, handler) + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/MiniCPM-RobotManip/evaluation/calvin/requirements-client.txt b/MiniCPM-RobotManip/evaluation/calvin/requirements-client.txt new file mode 100644 index 0000000..9ba2999 --- /dev/null +++ b/MiniCPM-RobotManip/evaluation/calvin/requirements-client.txt @@ -0,0 +1,12 @@ +# CALVIN itself is external and must be installed separately. +hydra-core +matplotlib +moviepy<2 +msgpack +numpy +omegaconf +Pillow +termcolor +tqdm +tyro>=0.8,<0.9 +websockets>=13.1,<14 diff --git a/MiniCPM-RobotManip/evaluation/calvin/run_policy_server.sh b/MiniCPM-RobotManip/evaluation/calvin/run_policy_server.sh new file mode 100755 index 0000000..4bc3200 --- /dev/null +++ b/MiniCPM-RobotManip/evaluation/calvin/run_policy_server.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash +# Copyright 2025 starVLA community. All rights reserved. +# Licensed under the MIT License. +# Modifications Copyright 2026 The OpenBMB Team. + +set -euo pipefail + +usage() { + cat <<'EOF' +Launch the MiniCPM-RobotManip WebSocket policy server for CALVIN. + +Usage: + bash run_policy_server.sh + bash run_policy_server.sh --help + +Environment: + MINICPM_PYTHON Python from the MiniCPM-RobotManip environment (default: python) + CHECKPOINT Hugging Face model ID or local checkpoint directory + (default: openbmb/MiniCPM-RobotManip) + EMBODIMENT_ID MiniCPM embodiment ID used for CALVIN requests (required) + HOST Listening address (default: 127.0.0.1) + PORT Listening port (default: 10093) + CUDA_VISIBLE_DEVICES + Single visible GPU (default: 0) +EOF +} + +case "${1:-}" in + -h|--help) + usage + exit 0 + ;; + "") + ;; + *) + echo "Unknown argument: $1" >&2 + usage >&2 + exit 2 + ;; +esac + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +MINICPM_ROOT="$(cd -- "${SCRIPT_DIR}/../.." && pwd)" + +MINICPM_PYTHON="${MINICPM_PYTHON:-python}" +CHECKPOINT="${CHECKPOINT:-openbmb/MiniCPM-RobotManip}" +: "${EMBODIMENT_ID:?Set EMBODIMENT_ID for the selected CALVIN checkpoint}" +HOST="${HOST:-127.0.0.1}" +PORT="${PORT:-10093}" +export CUDA_VISIBLE_DEVICES="${CUDA_VISIBLE_DEVICES:-0}" +export PYTHONPATH="${MINICPM_ROOT}${PYTHONPATH:+:${PYTHONPATH}}" + +cd "${MINICPM_ROOT}" +exec "${MINICPM_PYTHON}" -m deployment.model_server.server_policy \ + --checkpoint "${CHECKPOINT}" \ + --device cuda \ + --host "${HOST}" \ + --port "${PORT}" \ + --default-embodiment-id "${EMBODIMENT_ID}" diff --git a/MiniCPM-RobotManip/evaluation/common/__init__.py b/MiniCPM-RobotManip/evaluation/common/__init__.py new file mode 100644 index 0000000..2255250 --- /dev/null +++ b/MiniCPM-RobotManip/evaluation/common/__init__.py @@ -0,0 +1 @@ +"""Shared evaluator-side utilities.""" diff --git a/MiniCPM-RobotManip/evaluation/common/adaptive_ensemble.py b/MiniCPM-RobotManip/evaluation/common/adaptive_ensemble.py new file mode 100644 index 0000000..e8aa804 --- /dev/null +++ b/MiniCPM-RobotManip/evaluation/common/adaptive_ensemble.py @@ -0,0 +1,51 @@ +# Copyright 2025 starVLA community. All rights reserved. +# Licensed under the MIT License. +# Modifications Copyright 2026 The OpenBMB Team. + +"""Adaptive action ensembling shared by the migrated evaluators.""" + +from collections import deque + +import numpy as np + + +class AdaptiveEnsembler: + def __init__(self, pred_action_horizon, adaptive_ensemble_alpha=0.0): + self.pred_action_horizon = pred_action_horizon + self.action_history = deque(maxlen=self.pred_action_horizon) + self.adaptive_ensemble_alpha = adaptive_ensemble_alpha + + def reset(self): + self.action_history.clear() + + def ensemble_action(self, cur_action): + self.action_history.append(cur_action) + num_actions = len(self.action_history) + if cur_action.ndim == 1: + curr_act_preds = np.stack(self.action_history) + else: + curr_act_preds = np.stack( + [ + pred_actions[i] + for (i, pred_actions) in zip( + range(num_actions - 1, -1, -1), self.action_history + ) + ] + ) + + # Calculate cosine similarity between the current prediction and all previous predictions. + ref = curr_act_preds[num_actions - 1, :] + previous_pred = curr_act_preds + dot_product = np.sum(previous_pred * ref, axis=1) + norm_previous_pred = np.linalg.norm(previous_pred, axis=1) + norm_ref = np.linalg.norm(ref) + cos_similarity = dot_product / (norm_previous_pred * norm_ref + 1e-7) + + # Compute the weights for each prediction. + weights = np.exp(self.adaptive_ensemble_alpha * cos_similarity) + weights = weights / weights.sum() + + # Compute the weighted average across all predictions for this timestep. + cur_action = np.sum(weights[:, None] * curr_act_preds, axis=0) + + return cur_action diff --git a/MiniCPM-RobotManip/evaluation/common/launcher_utils.py b/MiniCPM-RobotManip/evaluation/common/launcher_utils.py new file mode 100644 index 0000000..460a636 --- /dev/null +++ b/MiniCPM-RobotManip/evaluation/common/launcher_utils.py @@ -0,0 +1,172 @@ +# Copyright 2026 The OpenBMB Team. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. + +"""Standard-library helpers shared by simulation launchers.""" + +from __future__ import annotations + +import os +import shutil +import signal +import socket +import subprocess +import time +from pathlib import Path + + +def resolve_executable(value: str, label: str) -> str: + candidate = Path(value).expanduser() + if "/" in value: + if not candidate.is_file() or not os.access(candidate, os.X_OK): + raise ValueError(f"{label} is not executable: {candidate}") + return str(candidate.resolve()) + resolved = shutil.which(value) + if resolved is None: + raise ValueError(f"{label} is not available on PATH: {value}") + return resolved + + +def resolve_checkpoint(value: str) -> str: + candidate = Path(value).expanduser() + if candidate.exists(): + if not candidate.is_dir(): + raise ValueError(f"Local checkpoint must be a directory: {candidate}") + return str(candidate.resolve()) + if value.startswith(("/", "./", "../")): + raise ValueError(f"Local checkpoint directory does not exist: {value}") + return value + + +def git_revision(path: Path) -> str: + result = subprocess.run( + ["git", "-C", str(path), "rev-parse", "HEAD"], + capture_output=True, + text=True, + check=False, + ) + return result.stdout.strip() if result.returncode == 0 else "unknown" + + +def port_available(host: str, port: int) -> bool: + with socket.socket() as sock: + try: + sock.bind((host, port)) + except OSError: + return False + return True + + +def terminate_process_group( + process: subprocess.Popen | None, + *, + timeout: float = 5, +) -> None: + if process is None: + return + process_group = process.pid + try: + os.killpg(process_group, 0) + except ProcessLookupError: + if process.poll() is None: + process.wait(timeout=timeout) + return + try: + os.killpg(process_group, signal.SIGTERM) + except ProcessLookupError: + return + if process.poll() is not None: + time.sleep(0.05) + try: + os.killpg(process_group, signal.SIGKILL) + except ProcessLookupError: + pass + return + deadline = time.monotonic() + timeout + while time.monotonic() < deadline: + try: + os.killpg(process_group, 0) + except ProcessLookupError: + break + time.sleep(0.05) + try: + os.killpg(process_group, signal.SIGKILL) + except ProcessLookupError: + pass + if process.poll() is None: + try: + process.wait(timeout=timeout) + except subprocess.TimeoutExpired: + pass + + +def prepend_pythonpath(env: dict[str, str], *paths: Path) -> dict[str, str]: + result = dict(env) + parts = [str(path) for path in paths] + if result.get("PYTHONPATH"): + parts.append(result["PYTHONPATH"]) + result["PYTHONPATH"] = ":".join(parts) + return result + + +def wait_for_policy_server( + *, + server: subprocess.Popen, + minicpm_python: str, + root: Path, + host: str, + port: int, + checkpoint: str, + embodiment_id: int, + min_action_dim: int, + request_id: str, + timeout: int, +) -> None: + deadline = time.monotonic() + timeout + last_error = "" + env = prepend_pythonpath(os.environ, root) + while time.monotonic() < deadline: + status = server.poll() + if status is not None: + raise RuntimeError( + f"Policy server exited before readiness with status {status}" + ) + remaining = max(0.1, deadline - time.monotonic()) + try: + probe = subprocess.run( + [ + minicpm_python, + "-m", + "evaluation.common.probe_server", + "--host", + host, + "--port", + str(port), + "--checkpoint", + checkpoint, + "--embodiment-id", + str(embodiment_id), + "--min-action-dim", + str(min_action_dim), + "--request-id", + request_id, + "--timeout", + str(min(2.0, remaining)), + ], + env=env, + capture_output=True, + text=True, + timeout=min(5.0, remaining + 1), + check=False, + ) + except subprocess.TimeoutExpired: + last_error = "readiness probe timed out" + else: + if probe.returncode == 0: + return + last_error = (probe.stderr or probe.stdout).strip() + if probe.returncode == 3: + raise RuntimeError(last_error or "Server metadata mismatch") + time.sleep(min(1.0, max(0.0, deadline - time.monotonic()))) + raise RuntimeError(f"Timed out waiting for {host}:{port}: {last_error}") diff --git a/MiniCPM-RobotManip/evaluation/common/probe_server.py b/MiniCPM-RobotManip/evaluation/common/probe_server.py new file mode 100644 index 0000000..6fa2015 --- /dev/null +++ b/MiniCPM-RobotManip/evaluation/common/probe_server.py @@ -0,0 +1,90 @@ +# Copyright 2026 The OpenBMB Team. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. + +"""Validate MiniCPM policy metadata and ping readiness.""" + +from __future__ import annotations + +import argparse +import sys +from typing import Sequence + +from deployment.model_server.tools.websocket_policy_client import ( + WebsocketClientPolicy, +) + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser() + parser.add_argument("--host", required=True) + parser.add_argument("--port", type=int, required=True) + parser.add_argument("--checkpoint", required=True) + parser.add_argument("--embodiment-id", type=int, required=True) + parser.add_argument("--min-action-dim", type=int, required=True) + parser.add_argument("--request-id", default="evaluation-readiness") + parser.add_argument("--timeout", type=float, default=2) + return parser + + +def main(argv: Sequence[str] | None = None) -> int: + args = build_parser().parse_args(argv) + try: + client = WebsocketClientPolicy( + args.host, + args.port, + open_timeout=args.timeout, + response_timeout=args.timeout, + ) + except Exception as exc: + print(f"Policy server isn't ready: {exc}", file=sys.stderr) + return 2 + + try: + metadata = client.get_server_metadata() + checks = { + "server": metadata.get("server") == "minicpm_robot_manip", + "checkpoint": metadata.get("ckpt_path") == args.checkpoint, + "embodiment": ( + metadata.get("default_embodiment_id") == args.embodiment_id + ), + "normalization": metadata.get("action_normalization") == "none", + "execution_ready": ( + metadata.get("actions_ready_for_execution") is True + ), + } + action_dim = metadata.get("action_dim") + chunk_size = metadata.get("action_chunk_size") + checks["action_dim"] = ( + isinstance(action_dim, int) + and not isinstance(action_dim, bool) + and action_dim >= args.min_action_dim + ) + checks["action_chunk_size"] = ( + isinstance(chunk_size, int) + and not isinstance(chunk_size, bool) + and chunk_size > 0 + ) + failed = [name for name, valid in checks.items() if not valid] + if failed: + print( + f"Policy metadata mismatch ({', '.join(failed)}): {metadata!r}", + file=sys.stderr, + ) + return 3 + + response = client.ping(request_id=args.request_id) + if response.get("ok") is not True or response.get("type") != "ping": + print(f"Policy ping failed: {response!r}", file=sys.stderr) + return 3 + return 0 + except Exception as exc: + print(f"Policy readiness validation failed: {exc}", file=sys.stderr) + return 2 + finally: + client.close() + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/MiniCPM-RobotManip/evaluation/libero/README.md b/MiniCPM-RobotManip/evaluation/libero/README.md new file mode 100644 index 0000000..20ef33b --- /dev/null +++ b/MiniCPM-RobotManip/evaluation/libero/README.md @@ -0,0 +1,109 @@ +# Standard LIBERO evaluation + +This directory ports the standard starVLA LIBERO evaluator from commit +`631aae02afe6d95876e923ff518e8ff2ab9a2f88`. The derived source files retain +the starVLA MIT attribution and identify OpenBMB modifications. OpenPI, +LIBERO-plus, training code, simulator assets, success-rate scanners, and video +cleanup helpers are intentionally excluded. + +## Environments + +Use two Python environments: + +1. `MINICPM_PYTHON`: the MiniCPM-RobotManip model/server environment. +2. `LIBERO_PYTHON`: an environment containing an externally installed LIBERO + checkout plus the evaluator-side packages: + +```bash +/path/to/libero/python -m pip install \ + -r evaluation/libero/requirements-client.txt +``` + +`LIBERO_HOME` must point to the external LIBERO checkout. The launchers resolve +`MINICPM_ROOT` from their own location, so they can be called from any working +directory. + +## Single server and evaluator + +Start the model server in one terminal: + +```bash +CHECKPOINT=openbmb/MiniCPM-RobotManip \ +EMBODIMENT_ID=0 \ +MINICPM_PYTHON=/path/to/minicpm/python \ +bash evaluation/libero/run_policy_server.sh +``` + +Run LIBERO in another terminal: + +```bash +LIBERO_HOME=/path/to/LIBERO \ +LIBERO_PYTHON=/path/to/libero/python \ +OUTPUT_ROOT=/path/to/outputs/evaluation/libero/manual \ +TASK_SUITE_NAME=libero_goal \ +bash evaluation/libero/eval_libero.sh +``` + +Both scripts default to `127.0.0.1:10093`. Useful smoke-test variables are +`MAX_TASKS=1` and `NUM_TRIALS_PER_TASK=1`. The evaluator never loads a +checkpoint and never derives output paths from one. + +## Bounded multi-GPU auto-eval + +The auto-evaluator creates fixed GPU slots. Slot `N` always uses +`GPU_LIST[N]` and `BASE_PORT + N`, and runs at most one server/evaluator job at +a time. Different slots run concurrently. + +`auto_eval_libero.sh` keeps the top-level slot scheduler. Each slot invokes the +thin `eval_libero_parallel.sh` entry point; server readiness, process groups, +signals, and per-job manifests are implemented in `parallel_worker.py`. + +```bash +LIBERO_HOME=/path/to/LIBERO \ +MINICPM_PYTHON=/path/to/minicpm/python \ +LIBERO_PYTHON=/path/to/libero/python \ +GPU_LIST="0 1" \ +EGL_DEVICE_LIST="0 1" \ +EMBODIMENT_ID=0 \ +TASK_SUITES="libero_10 libero_goal libero_object libero_spatial" \ +OUTPUT_ROOT=/path/to/outputs/evaluation/libero \ +bash evaluation/libero/auto_eval_scripts/auto_eval_libero.sh \ + --checkpoint openbmb/MiniCPM-RobotManip \ + --checkpoint /path/to/local/checkpoint-directory +``` + +Inspect configuration without launching a model or simulator: + +```bash +EMBODIMENT_ID=0 bash evaluation/libero/auto_eval_scripts/auto_eval_libero.sh \ + --dry-run --checkpoint openbmb/MiniCPM-RobotManip +``` + +`EGL_DEVICE_LIST` maps one physical EGL device to each `GPU_LIST` slot. It +defaults to the same list but can be overridden on systems whose EGL and CUDA +device numbering differ. + +Use `--help` for all environment variables. Each run writes scheduling/status +manifests and per-job server/evaluator logs below +`OUTPUT_ROOT/RUN_ID/`. Readiness requires the server process to remain alive +and the repository WebSocket client to complete both metadata validation and a +protocol ping. Any failed job makes the top-level command return nonzero. +`EXIT`, `INT`, `TERM`, and `HUP` handlers terminate and reap active evaluators +and servers. + +## Evaluation contract + +- Camera order is `[agentview, eye_in_hand]`. +- Both LIBERO images are rotated 180 degrees, then resized to 448×448. +- Proprioceptive state is not sent; MiniCPM uses its existing 80-D zero state. +- Requests contain only `examples=[{"image": ..., "lang": ...}]`; no + normalization, un-normalization, DDIM, sampling, or CFG fields are sent. +- Responses must report `ok=true` and contain finite floating actions with + shape `(1, T, D)`, where `D >= 7`. +- Model action values remain unchanged in the client. LIBERO consumes the + first seven dimensions, with its standard binary gripper conversion + immediately before `env.step`. + +`EMBODIMENT_ID` is required. Confirm it and the checkpoint's action-slot +semantics for LIBERO before treating success rates as valid. The published +model metadata does not provide a reliable public robot-to-embodiment mapping. diff --git a/MiniCPM-RobotManip/evaluation/libero/__init__.py b/MiniCPM-RobotManip/evaluation/libero/__init__.py new file mode 100644 index 0000000..c02b59c --- /dev/null +++ b/MiniCPM-RobotManip/evaluation/libero/__init__.py @@ -0,0 +1 @@ +"""Standard LIBERO evaluation for MiniCPM-RobotManip.""" diff --git a/MiniCPM-RobotManip/evaluation/libero/auto_eval_scripts/auto_eval_libero.sh b/MiniCPM-RobotManip/evaluation/libero/auto_eval_scripts/auto_eval_libero.sh new file mode 100755 index 0000000..866f5aa --- /dev/null +++ b/MiniCPM-RobotManip/evaluation/libero/auto_eval_scripts/auto_eval_libero.sh @@ -0,0 +1,409 @@ +#!/usr/bin/env bash +# Copyright 2025 starVLA community. All rights reserved. +# Licensed under the MIT License. +# Modifications Copyright 2026 The OpenBMB Team. + +set -euo pipefail + +usage() { + cat <<'EOF' +Usage: + auto_eval_libero.sh [--dry-run] [--checkpoint ID_OR_DIR ...] + auto_eval_libero.sh [--dry-run] [ID_OR_DIR ...] + +Runs the checkpoint × task-suite matrix with one bounded worker per fixed GPU +slot. A checkpoint may be a Hugging Face model ID or local checkpoint +directory. + +Checkpoint selection precedence: + 1. Positional arguments or repeated --checkpoint options + 2. CHECKPOINTS (space-separated; use CLI for paths containing spaces) + 3. CHECKPOINT + 4. CKPT_DIR immediate child directories + 5. openbmb/MiniCPM-RobotManip + +Environment: + LIBERO_HOME External LIBERO checkout (required unless --dry-run) + MINICPM_PYTHON Model-server Python (default: python) + LIBERO_PYTHON LIBERO Python (default: python) + OUTPUT_ROOT Output base (default: outputs/evaluation/libero) + TASK_SUITES Space/comma-separated suites + (default: libero_10 libero_goal libero_object libero_spatial) + GPU_LIST Space/comma-separated fixed slots (default: 0) + EGL_DEVICE_LIST EGL device per GPU slot (default: GPU_LIST) + HOST Server host (default: 127.0.0.1) + BASE_PORT Port for slot 0; slot N uses BASE_PORT+N (default: 10093) + SERVER_TIMEOUT Metadata/ping readiness timeout (default: 300) + EMBODIMENT_ID MiniCPM default embodiment ID (required) + RUN_ID Unique output run name (default: UTC timestamp + PID) + NUM_TRIALS_PER_TASK, NUM_STEPS_WAIT, MAX_TASKS, SEED + +Examples: + LIBERO_HOME=/opt/LIBERO GPU_LIST="0 1" EMBODIMENT_ID=0 \ + bash auto_eval_libero.sh --checkpoint openbmb/MiniCPM-RobotManip + + LIBERO_HOME=/opt/LIBERO TASK_SUITES=libero_goal EMBODIMENT_ID=0 \ + bash auto_eval_libero.sh --checkpoint /models/minicpm-libero + + GPU_LIST="0 1" EMBODIMENT_ID=0 bash auto_eval_libero.sh --dry-run \ + --checkpoint openbmb/MiniCPM-RobotManip +EOF +} + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +MINICPM_ROOT="${MINICPM_ROOT:-$(cd -- "${SCRIPT_DIR}/../../.." && pwd)}" +WORKER_SCRIPT="${SCRIPT_DIR}/eval_libero_parallel.sh" + +dry_run=0 +declare -a cli_checkpoints=() +while (($# > 0)); do + case "$1" in + -h|--help) + usage + exit 0 + ;; + --dry-run) + dry_run=1 + shift + ;; + --checkpoint) + if (($# < 2)); then + echo "--checkpoint requires a Hugging Face ID or local path." >&2 + exit 2 + fi + cli_checkpoints+=("$2") + shift 2 + ;; + --checkpoint=*) + cli_checkpoints+=("${1#*=}") + shift + ;; + --) + shift + while (($# > 0)); do + cli_checkpoints+=("$1") + shift + done + ;; + -*) + echo "Unknown option: $1" >&2 + usage >&2 + exit 2 + ;; + *) + cli_checkpoints+=("$1") + shift + ;; + esac +done + +MINICPM_PYTHON="${MINICPM_PYTHON:-python}" +LIBERO_PYTHON="${LIBERO_PYTHON:-python}" +LIBERO_HOME="${LIBERO_HOME:-}" +OUTPUT_ROOT="${OUTPUT_ROOT:-${MINICPM_ROOT}/outputs/evaluation/libero}" +TASK_SUITES="${TASK_SUITES:-libero_10 libero_goal libero_object libero_spatial}" +GPU_LIST="${GPU_LIST:-0}" +EGL_DEVICE_LIST="${EGL_DEVICE_LIST:-${GPU_LIST}}" +HOST="${HOST:-127.0.0.1}" +BASE_PORT="${BASE_PORT:-10093}" +SERVER_TIMEOUT="${SERVER_TIMEOUT:-360}" +EMBODIMENT_ID="${EMBODIMENT_ID:-}" +RUN_ID="${RUN_ID:-$(date -u +%Y%m%dT%H%M%SZ)-$$}" +NUM_TRIALS_PER_TASK="${NUM_TRIALS_PER_TASK:-50}" +NUM_STEPS_WAIT="${NUM_STEPS_WAIT:-10}" +MAX_TASKS="${MAX_TASKS:--1}" +SEED="${SEED:-7}" + +declare -a checkpoints=() +if ((${#cli_checkpoints[@]} > 0)); then + checkpoints=("${cli_checkpoints[@]}") +elif [[ -n "${CHECKPOINTS:-}" ]]; then + read -r -a checkpoints <<<"${CHECKPOINTS}" +elif [[ -n "${CHECKPOINT:-}" ]]; then + checkpoints=("${CHECKPOINT}") +elif [[ -n "${CKPT_DIR:-}" ]]; then + shopt -s nullglob + for candidate in "${CKPT_DIR}"/*/; do + if [[ -d "${candidate}" ]]; then + checkpoints+=("${candidate%/}") + fi + done + shopt -u nullglob +else + checkpoints=("openbmb/MiniCPM-RobotManip") +fi + +task_suite_text="${TASK_SUITES//,/ }" +gpu_list_text="${GPU_LIST//,/ }" +egl_device_list_text="${EGL_DEVICE_LIST//,/ }" +read -r -a task_suites <<<"${task_suite_text}" +read -r -a gpu_slots <<<"${gpu_list_text}" +read -r -a egl_slots <<<"${egl_device_list_text}" + +if ((${#checkpoints[@]} == 0)); then + echo "No checkpoints were selected." >&2 + exit 2 +fi +if ((${#task_suites[@]} == 0)); then + echo "TASK_SUITES must contain at least one suite." >&2 + exit 2 +fi +if ((${#gpu_slots[@]} == 0)); then + echo "GPU_LIST must contain at least one GPU." >&2 + exit 2 +fi +if ((${#egl_slots[@]} != ${#gpu_slots[@]})); then + echo "EGL_DEVICE_LIST must contain exactly one entry per GPU_LIST slot." >&2 + exit 2 +fi +if [[ ! "${BASE_PORT}" =~ ^[0-9]+$ ]]; then + echo "BASE_PORT must be an integer; got '${BASE_PORT}'." >&2 + exit 2 +fi +if [[ ! "${SERVER_TIMEOUT}" =~ ^[0-9]+$ ]] || ((SERVER_TIMEOUT < 1)); then + echo "SERVER_TIMEOUT must be a positive integer." >&2 + exit 2 +fi +if [[ -z "${EMBODIMENT_ID}" || ! "${EMBODIMENT_ID}" =~ ^[0-9]+$ ]]; then + echo "EMBODIMENT_ID is required and must be a non-negative integer." >&2 + exit 2 +fi +if [[ ! "${RUN_ID}" =~ ^[A-Za-z0-9._-]+$ ]]; then + echo "RUN_ID may contain only letters, digits, dot, underscore, and hyphen." >&2 + exit 2 +fi + +num_slots=${#gpu_slots[@]} +if ((BASE_PORT < 1 || BASE_PORT + num_slots - 1 > 65535)); then + echo "The fixed slot ports must all be in [1, 65535]." >&2 + exit 2 +fi +for ((left = 0; left < num_slots; left++)); do + if [[ -z "${gpu_slots[$left]}" ]]; then + echo "GPU_LIST contains an empty slot." >&2 + exit 2 + fi + if [[ -z "${egl_slots[$left]}" ]]; then + echo "EGL_DEVICE_LIST contains an empty slot." >&2 + exit 2 + fi + for ((right = left + 1; right < num_slots; right++)); do + if [[ "${gpu_slots[$left]}" == "${gpu_slots[$right]}" ]]; then + echo "GPU_LIST contains duplicate GPU '${gpu_slots[$left]}'." >&2 + exit 2 + fi + done +done + +declare -a job_checkpoints=() +declare -a job_suites=() +for checkpoint in "${checkpoints[@]}"; do + if [[ -z "${checkpoint}" ]]; then + echo "Checkpoint values must be non-empty." >&2 + exit 2 + fi + if [[ -e "${checkpoint}" && ! -d "${checkpoint}" ]]; then + echo "Local MiniCPM checkpoint must be a directory: ${checkpoint}" >&2 + exit 2 + fi + if [[ ! -e "${checkpoint}" ]] \ + && [[ "${checkpoint}" == /* || "${checkpoint}" == ./* || "${checkpoint}" == ../* ]]; then + echo "Local checkpoint directory does not exist: ${checkpoint}" >&2 + exit 2 + fi + for task_suite in "${task_suites[@]}"; do + job_checkpoints+=("${checkpoint}") + job_suites+=("${task_suite}") + done +done +num_jobs=${#job_checkpoints[@]} + +echo "==========================================" +echo " MiniCPM LIBERO auto evaluation" +echo "==========================================" +echo " Run ID : ${RUN_ID}" +echo " Checkpoints : ${checkpoints[*]}" +echo " Task suites : ${task_suites[*]}" +echo " GPU slots : ${gpu_slots[*]}" +echo " EGL devices : ${egl_slots[*]}" +echo " Slot ports : ${BASE_PORT}..$((BASE_PORT + num_slots - 1))" +echo " Output root : ${OUTPUT_ROOT}" +echo " Jobs : ${num_jobs}" +echo "==========================================" + +if ((dry_run == 1)); then + for ((job_id = 0; job_id < num_jobs; job_id++)); do + slot=$((job_id % num_slots)) + printf '[dry-run job %d] slot=%d gpu=%s egl=%s port=%d suite=%s checkpoint=%s\n' \ + "${job_id}" \ + "${slot}" \ + "${gpu_slots[$slot]}" \ + "${egl_slots[$slot]}" \ + "$((BASE_PORT + slot))" \ + "${job_suites[$job_id]}" \ + "${job_checkpoints[$job_id]}" + done + exit 0 +fi + +if [[ -z "${LIBERO_HOME}" ]]; then + echo "LIBERO_HOME is required and must point to the external LIBERO checkout." >&2 + exit 2 +fi + +RUN_ROOT="${OUTPUT_ROOT}/${RUN_ID}" +SCHEDULE_MANIFEST="${RUN_ROOT}/manifest.tsv" +STATUS_MANIFEST="${RUN_ROOT}/status.tsv" +mkdir -p "${RUN_ROOT}" +{ + printf 'job_id\tslot\tgpu\tegl_device\tport\ttask_suite\tcheckpoint\n' + for ((job_id = 0; job_id < num_jobs; job_id++)); do + slot=$((job_id % num_slots)) + printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \ + "${job_id}" \ + "${slot}" \ + "${gpu_slots[$slot]}" \ + "${egl_slots[$slot]}" \ + "$((BASE_PORT + slot))" \ + "${job_suites[$job_id]}" \ + "${job_checkpoints[$job_id]}" + done +} >"${SCHEDULE_MANIFEST}" +printf 'job_id\texit_status\n' >"${STATUS_MANIFEST}" + +declare -a slot_pids=() +declare -a slot_job_ids=() +declare -a slot_next_jobs=() +for ((slot = 0; slot < num_slots; slot++)); do + slot_pids[$slot]="" + slot_job_ids[$slot]="" + slot_next_jobs[$slot]="${slot}" +done + +cleanup_workers() { + local status=$? + local pid + local deadline + local any_running + + trap - EXIT INT TERM HUP + for ((slot = 0; slot < num_slots; slot++)); do + pid="${slot_pids[$slot]:-}" + if [[ -n "${pid}" ]] && kill -0 "${pid}" 2>/dev/null; then + kill -TERM "${pid}" 2>/dev/null || true + fi + done + + deadline=$((SECONDS + 15)) + while ((SECONDS < deadline)); do + any_running=0 + for ((slot = 0; slot < num_slots; slot++)); do + pid="${slot_pids[$slot]:-}" + if [[ -n "${pid}" ]] && kill -0 "${pid}" 2>/dev/null; then + any_running=1 + fi + done + if ((any_running == 0)); then + break + fi + sleep 0.1 + done + + for ((slot = 0; slot < num_slots; slot++)); do + pid="${slot_pids[$slot]:-}" + if [[ -n "${pid}" ]]; then + if kill -0 "${pid}" 2>/dev/null; then + kill -KILL "${pid}" 2>/dev/null || true + fi + wait "${pid}" 2>/dev/null || true + fi + done + exit "${status}" +} + +handle_signal() { + local signal_name="$1" + case "${signal_name}" in + INT) exit 130 ;; + TERM) exit 143 ;; + HUP) exit 129 ;; + esac +} + +trap cleanup_workers EXIT +trap 'handle_signal INT' INT +trap 'handle_signal TERM' TERM +trap 'handle_signal HUP' HUP + +remaining_jobs=${num_jobs} +failed_jobs=0 +while ((remaining_jobs > 0)); do + for ((slot = 0; slot < num_slots; slot++)); do + pid="${slot_pids[$slot]:-}" + if [[ -n "${pid}" ]] && ! kill -0 "${pid}" 2>/dev/null; then + completed_job="${slot_job_ids[$slot]}" + if wait "${pid}"; then + worker_status=0 + else + worker_status=$? + fi + printf '%s\t%s\n' "${completed_job}" "${worker_status}" >>"${STATUS_MANIFEST}" + if ((worker_status != 0)); then + failed_jobs=$((failed_jobs + 1)) + echo "[job ${completed_job}] failed with status ${worker_status}." >&2 + else + echo "[job ${completed_job}] completed." + fi + slot_pids[$slot]="" + slot_job_ids[$slot]="" + remaining_jobs=$((remaining_jobs - 1)) + fi + + if [[ -z "${slot_pids[$slot]:-}" ]]; then + next_job="${slot_next_jobs[$slot]}" + if ((next_job < num_jobs)); then + gpu_id="${gpu_slots[$slot]}" + egl_device_id="${egl_slots[$slot]}" + port=$((BASE_PORT + slot)) + checkpoint="${job_checkpoints[$next_job]}" + task_suite="${job_suites[$next_job]}" + echo "[job ${next_job}] slot=${slot} gpu=${gpu_id} egl=${egl_device_id} port=${port} suite=${task_suite}" + + env \ + MINICPM_ROOT="${MINICPM_ROOT}" \ + MINICPM_PYTHON="${MINICPM_PYTHON}" \ + LIBERO_HOME="${LIBERO_HOME}" \ + LIBERO_PYTHON="${LIBERO_PYTHON}" \ + OUTPUT_ROOT="${OUTPUT_ROOT}" \ + RUN_ID="${RUN_ID}" \ + JOB_ID="${next_job}" \ + HOST="${HOST}" \ + SERVER_TIMEOUT="${SERVER_TIMEOUT}" \ + EMBODIMENT_ID="${EMBODIMENT_ID}" \ + NUM_TRIALS_PER_TASK="${NUM_TRIALS_PER_TASK}" \ + NUM_STEPS_WAIT="${NUM_STEPS_WAIT}" \ + MAX_TASKS="${MAX_TASKS}" \ + SEED="${SEED}" \ + EGL_DEVICE_ID="${egl_device_id}" \ + MUJOCO_EGL_DEVICE_ID="${egl_device_id}" \ + bash "${WORKER_SCRIPT}" \ + "${checkpoint}" "${task_suite}" "${gpu_id}" "${port}" & + slot_pids[$slot]=$! + slot_job_ids[$slot]="${next_job}" + slot_next_jobs[$slot]=$((next_job + num_slots)) + fi + fi + done + + if ((remaining_jobs > 0)); then + sleep 0.2 + fi +done + +if ((failed_jobs > 0)); then + echo "${failed_jobs} of ${num_jobs} LIBERO jobs failed. See ${STATUS_MANIFEST}." >&2 + exit 1 +fi + +echo "All ${num_jobs} LIBERO jobs completed successfully." +echo "Outputs: ${RUN_ROOT}" diff --git a/MiniCPM-RobotManip/evaluation/libero/auto_eval_scripts/eval_libero_parallel.sh b/MiniCPM-RobotManip/evaluation/libero/auto_eval_scripts/eval_libero_parallel.sh new file mode 100755 index 0000000..c0842f7 --- /dev/null +++ b/MiniCPM-RobotManip/evaluation/libero/auto_eval_scripts/eval_libero_parallel.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +# Copyright 2025 starVLA community. All rights reserved. +# Licensed under the MIT License. +# Modifications Copyright 2026 The OpenBMB Team. + +set -euo pipefail + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +MINICPM_ROOT="$(cd -- "${SCRIPT_DIR}/../../.." && pwd)" +LAUNCHER_PYTHON="${LAUNCHER_PYTHON:-python3}" + +export PYTHONPATH="${MINICPM_ROOT}${PYTHONPATH:+:${PYTHONPATH}}" +exec "${LAUNCHER_PYTHON}" -m evaluation.libero.parallel_worker "$@" diff --git a/MiniCPM-RobotManip/evaluation/libero/eval_libero.py b/MiniCPM-RobotManip/evaluation/libero/eval_libero.py new file mode 100644 index 0000000..f93dbd3 --- /dev/null +++ b/MiniCPM-RobotManip/evaluation/libero/eval_libero.py @@ -0,0 +1,319 @@ +# Copyright 2025 starVLA community. All rights reserved. +# Licensed under the MIT License. +# Modifications Copyright 2026 The OpenBMB Team. + +from __future__ import annotations + +import dataclasses +import json +import logging +import math +import os +import pathlib +import time + +import imageio +import numpy as np +import tqdm +import tyro +from libero.libero import benchmark, get_libero_path +from libero.libero.envs import OffScreenRenderEnv + +os.environ["TOKENIZERS_PARALLELISM"] = "false" + +from evaluation.libero.model2libero_interface import ModelClient + + +LIBERO_DUMMY_ACTION = [0.0] * 6 + [-1.0] +LIBERO_ENV_RESOLUTION = 256 # Resolution used to render training data. + + +def _binarize_gripper_open(open_val: np.ndarray | float) -> np.ndarray: + arr = np.asarray(open_val, dtype=np.float32).reshape(-1) + value = float(arr[0]) + bin_val = 1.0 - 2.0 * (value > 0.5) + return np.asarray([bin_val], dtype=np.float32) + + +@dataclasses.dataclass +class Args: + host: str = "127.0.0.1" + port: int = 10093 + + ################################################################################################################# + # LIBERO environment-specific parameters + ################################################################################################################# + task_suite_name: str = ( + "libero_goal" # Options: libero_spatial, libero_object, libero_goal, libero_10, libero_90 + ) + num_steps_wait: int = 10 # Wait for objects to stabilize in simulation. + num_trials_per_task: int = 50 + max_tasks: int = -1 # Positive values limit tasks for smoke tests. + + ################################################################################################################# + # Utils + ################################################################################################################# + video_out_path: str = "outputs/evaluation/libero" + seed: int = 7 + + +def eval_libero(args: Args) -> None: + logging.info("Arguments: %s", json.dumps(dataclasses.asdict(args), indent=4)) + + # Set random seed. + np.random.seed(args.seed) + + # Initialize LIBERO task suite. + benchmark_dict = benchmark.get_benchmark_dict() + task_suite = benchmark_dict[args.task_suite_name]() + num_tasks_in_suite = task_suite.n_tasks + logging.info("Task suite: %s", args.task_suite_name) + + pathlib.Path(args.video_out_path).mkdir(parents=True, exist_ok=True) + + if args.task_suite_name == "libero_spatial": + max_steps = 220 # Longest training demo has 193 steps. + elif args.task_suite_name == "libero_object": + max_steps = 280 # Longest training demo has 254 steps. + elif args.task_suite_name == "libero_goal": + max_steps = 300 # Longest training demo has 270 steps. + elif args.task_suite_name == "libero_10": + max_steps = 520 # Longest training demo has 505 steps. + elif args.task_suite_name == "libero_90": + max_steps = 400 # Longest training demo has 373 steps. + else: + raise ValueError(f"Unknown task suite: {args.task_suite_name}") + + client_model = ModelClient(host=args.host, port=args.port) + + # Optional smoke-test cap; -1 evaluates the complete suite. + n_eval_tasks = ( + num_tasks_in_suite + if args.max_tasks <= 0 + else min(args.max_tasks, num_tasks_in_suite) + ) + logging.info( + "Evaluating %s of %s tasks (max_tasks=%s)", + n_eval_tasks, + num_tasks_in_suite, + args.max_tasks, + ) + + # Start evaluation. + total_episodes, total_successes = 0, 0 + for task_id in tqdm.tqdm(range(n_eval_tasks)): + # Get task. + task = task_suite.get_task(task_id) + + # Get default LIBERO initial states. + initial_states = task_suite.get_task_init_states(task_id) + + # Initialize LIBERO environment and task description. + env, task_description = _get_libero_env( + task, LIBERO_ENV_RESOLUTION, args.seed + ) + + # Start episodes. + task_episodes, task_successes = 0, 0 + for episode_idx in tqdm.tqdm(range(args.num_trials_per_task)): + logging.info("\nTask: %s", task_description) + + # Reset environment. + client_model.reset(task_description=task_description) + env.reset() + + # Set initial states. + obs = env.set_init_state(initial_states[episode_idx]) + + # Setup. + t = 0 + replay_images = [] + full_actions = [] + + logging.info("Starting episode %s...", task_episodes + 1) + step = 0 + + while t < max_steps + args.num_steps_wait: + # Do nothing initially because the simulator drops objects and + # they need time to settle. + if t < args.num_steps_wait: + obs, reward, done, info = env.step(LIBERO_DUMMY_ACTION) + t += 1 + continue + + # Rotate both views 180 degrees to match training preprocessing. + img = np.ascontiguousarray(obs["agentview_image"][::-1, ::-1]) + wrist_img = np.ascontiguousarray( + obs["robot0_eye_in_hand_image"][::-1, ::-1] + ) + + # Save the preprocessed agent view for replay video. + replay_images.append(img) + + state = np.concatenate( + ( + obs["robot0_eef_pos"], + _quat2axisangle(obs["robot0_eef_quat"]), + obs["robot0_gripper_qpos"], + ) + ) + + observation = { + "observation.primary": np.expand_dims(img, axis=0), + "observation.wrist_image": np.expand_dims( + wrist_img, axis=0 + ), + "observation.state": np.expand_dims(state, axis=0), + "instruction": [str(task_description)], + } + + # Keep the source camera order: [agentview, eye_in_hand]. + # Standard MiniCPM LIBERO evaluation intentionally does not send + # observation.state; the server supplies its zero80 behavior. + example_dict = { + "image": [ + observation["observation.primary"][0], + observation["observation.wrist_image"][0], + ], + "lang": observation["instruction"][0], + } + + start_time = time.time() + response = client_model.step(example=example_dict, step=step) + end_time = time.time() + del start_time, end_time + + raw_action = response["raw_action"] + world_vector_delta = np.asarray( + raw_action.get("world_vector") + ).reshape(-1) + rotation_delta = np.asarray( + raw_action.get("rotation_delta") + ).reshape(-1) + open_gripper = np.asarray( + raw_action.get("open_gripper") + ).reshape(-1) + gripper = _binarize_gripper_open(open_gripper) + + if not ( + world_vector_delta.size == 3 + and rotation_delta.size == 3 + and open_gripper.size == 1 + ): + raise ValueError( + f"Invalid action sizes: world_vector={world_vector_delta.shape}, " + f"rotation_delta={rotation_delta.shape}, " + f"gripper={gripper.shape}" + ) + + delta_action = np.concatenate( + [world_vector_delta, rotation_delta, gripper], axis=0 + ) + full_actions.append(delta_action) + + # LIBERO consumes [xyz, axis-angle delta, binary gripper]. + obs, reward, done, info = env.step(delta_action.tolist()) + if done: + task_successes += 1 + total_successes += 1 + break + t += 1 + step += 1 + + task_episodes += 1 + total_episodes += 1 + + # Save a replay video for every episode. + suffix = "success" if done else "failure" + task_segment = task_description.replace(" ", "_") + imageio.mimwrite( + pathlib.Path(args.video_out_path) + / f"rollout_{task_segment}_episode{episode_idx}_{suffix}.mp4", + [np.asarray(x) for x in replay_images], + fps=10, + ) + + full_actions = np.stack(full_actions) + del full_actions + + logging.info("Success: %s", done) + logging.info("# episodes completed so far: %s", total_episodes) + logging.info( + "# successes: %s (%.1f%%)", + total_successes, + total_successes / total_episodes * 100, + ) + + logging.info( + "Current task success rate: %s", + float(task_successes) / float(task_episodes), + ) + logging.info( + "Current total success rate: %s", + float(total_successes) / float(total_episodes), + ) + + logging.info( + "Total success rate: %s", + float(total_successes) / float(total_episodes), + ) + logging.info("Total episodes: %s", total_episodes) + + +def _get_libero_env(task, resolution, seed): + """Initialize the LIBERO environment and return its task description.""" + task_description = task.language + task_bddl_file = ( + pathlib.Path(get_libero_path("bddl_files")) + / task.problem_folder + / task.bddl_file + ) + env_args = { + "bddl_file_name": task_bddl_file, + "camera_heights": resolution, + "camera_widths": resolution, + } + env = OffScreenRenderEnv(**env_args) + env.seed(seed) + return env, task_description + + +def _quat2axisangle(quat): + """Convert quaternion to axis-angle, copied from robosuite. + + Source: + https://github.com/ARISE-Initiative/robosuite/blob/eafb81f54ffc104f905ee48a16bb15f059176ad3/robosuite/utils/transform_utils.py#L490C1-L512C55 + """ + if quat[3] > 1.0: + quat[3] = 1.0 + elif quat[3] < -1.0: + quat[3] = -1.0 + + den = np.sqrt(1.0 - quat[3] * quat[3]) + if math.isclose(den, 0.0): + return np.zeros(3) + + return (quat[:3] * 2.0 * math.acos(quat[3])) / den + + +def start_debugpy_once(): + import debugpy + + if getattr(start_debugpy_once, "_started", False): + return + debugpy.listen(("0.0.0.0", 10092)) + print("Waiting for VSCode attach on 0.0.0.0:10092 ...") + debugpy.wait_for_client() + start_debugpy_once._started = True + + +if __name__ == "__main__": + logging.basicConfig( + level=logging.INFO, + format="%(asctime)s %(levelname)-8s | %(message)s", + datefmt="%m/%d [%H:%M:%S]", + force=True, + ) + if os.getenv("DEBUG", False): + start_debugpy_once() + tyro.cli(eval_libero) diff --git a/MiniCPM-RobotManip/evaluation/libero/eval_libero.sh b/MiniCPM-RobotManip/evaluation/libero/eval_libero.sh new file mode 100755 index 0000000..1627b6f --- /dev/null +++ b/MiniCPM-RobotManip/evaluation/libero/eval_libero.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash +# Copyright 2025 starVLA community. All rights reserved. +# Licensed under the MIT License. +# Modifications Copyright 2026 The OpenBMB Team. + +set -euo pipefail + +usage() { + cat <<'EOF' +Run one serial LIBERO task suite against an existing MiniCPM policy server. + +Usage: + bash eval_libero.sh [additional tyro arguments] + +Required environment: + LIBERO_HOME + +Common environment: + LIBERO_PYTHON, OUTPUT_ROOT, HOST, PORT, TASK_SUITE_NAME, + NUM_TRIALS_PER_TASK, NUM_STEPS_WAIT, MAX_TASKS, SEED +EOF +} + +if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then + usage + exit 0 +fi + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +MINICPM_ROOT="${MINICPM_ROOT:-$(cd -- "${SCRIPT_DIR}/../.." && pwd)}" +LIBERO_HOME="${LIBERO_HOME:-}" +LIBERO_PYTHON="${LIBERO_PYTHON:-python}" +OUTPUT_ROOT="${OUTPUT_ROOT:-${MINICPM_ROOT}/outputs/evaluation/libero}" +HOST="${HOST:-127.0.0.1}" +PORT="${PORT:-10093}" +TASK_SUITE_NAME="${TASK_SUITE_NAME:-libero_goal}" +NUM_TRIALS_PER_TASK="${NUM_TRIALS_PER_TASK:-50}" +NUM_STEPS_WAIT="${NUM_STEPS_WAIT:-10}" +MAX_TASKS="${MAX_TASKS:--1}" +SEED="${SEED:-7}" +MUJOCO_GL_VALUE="${MUJOCO_GL_VALUE:-egl}" +PYOPENGL_PLATFORM_VALUE="${PYOPENGL_PLATFORM_VALUE:-egl}" + +if [[ -z "${LIBERO_HOME}" ]]; then + echo "LIBERO_HOME is required and must point to the external LIBERO checkout." >&2 + echo "Example: LIBERO_HOME=/path/to/LIBERO LIBERO_PYTHON=/path/to/python bash $0" >&2 + exit 2 +fi + +VIDEO_OUT_PATH="${OUTPUT_ROOT}/${TASK_SUITE_NAME}" +mkdir -p "${VIDEO_OUT_PATH}" + +cd "${MINICPM_ROOT}" +export LIBERO_CONFIG_PATH="${LIBERO_HOME}/libero" +export PYTHONPATH="${MINICPM_ROOT}:${LIBERO_HOME}${PYTHONPATH:+:${PYTHONPATH}}" +export MUJOCO_GL="${MUJOCO_GL_VALUE}" +export PYOPENGL_PLATFORM="${PYOPENGL_PLATFORM_VALUE}" +export EGL_DEVICE_ID="${EGL_DEVICE_ID:-0}" +export MUJOCO_EGL_DEVICE_ID="${MUJOCO_EGL_DEVICE_ID:-${EGL_DEVICE_ID}}" + +exec "${LIBERO_PYTHON}" -m evaluation.libero.eval_libero \ + --args.host "${HOST}" \ + --args.port "${PORT}" \ + --args.task-suite-name "${TASK_SUITE_NAME}" \ + --args.num-trials-per-task "${NUM_TRIALS_PER_TASK}" \ + --args.num-steps-wait "${NUM_STEPS_WAIT}" \ + --args.max-tasks "${MAX_TASKS}" \ + --args.seed "${SEED}" \ + --args.video-out-path "${VIDEO_OUT_PATH}" \ + "$@" diff --git a/MiniCPM-RobotManip/evaluation/libero/model2libero_interface.py b/MiniCPM-RobotManip/evaluation/libero/model2libero_interface.py new file mode 100644 index 0000000..296fa97 --- /dev/null +++ b/MiniCPM-RobotManip/evaluation/libero/model2libero_interface.py @@ -0,0 +1,236 @@ +# Copyright 2025 starVLA community. All rights reserved. +# Licensed under the MIT License. +# Modifications Copyright 2026 The OpenBMB Team. + +"""LIBERO env-side adapter for the MiniCPM policy server. + +The server returns execution-ready model actions. This client only keeps the +source evaluator's image resizing, action-chunk scheduling, optional ensemble +state, visualization, and LIBERO-specific seven-dimensional action slicing. +It performs no normalization or un-normalization. +""" + +from __future__ import annotations + +from collections import deque +from typing import Optional, Sequence + +import matplotlib.pyplot as plt +import numpy as np +from PIL import Image + +from deployment.model_server.tools.websocket_policy_client import ( + WebsocketClientPolicy, +) +from evaluation.common.adaptive_ensemble import AdaptiveEnsembler + + +class ModelClient: + def __init__( + self, + policy_setup: str = "franka", + horizon: int = 0, + action_ensemble: bool = True, + action_ensemble_horizon: Optional[int] = 3, + adaptive_ensemble_alpha: float = 0.1, + host: str = "127.0.0.1", + port: int = 10093, + image_size: Sequence[int] = (448, 448), + ) -> None: + # Connect and receive model-invariant handshake metadata. + self.client = WebsocketClientPolicy(host, port) + meta = self.client.get_server_metadata() + action_chunk_size = meta.get("action_chunk_size") + if ( + isinstance(action_chunk_size, bool) + or not isinstance(action_chunk_size, (int, np.integer)) + or int(action_chunk_size) <= 0 + ): + self.client.close() + raise ValueError( + "Server metadata action_chunk_size must be a positive integer; " + f"got {action_chunk_size!r}" + ) + self.action_chunk_size = int(action_chunk_size) + self._server_metadata = meta + + self.image_size = tuple(image_size) + if len(self.image_size) != 2 or any( + isinstance(size, bool) or not isinstance(size, int) or size <= 0 + for size in self.image_size + ): + self.client.close() + raise ValueError( + f"image_size must contain two positive integers, got {image_size!r}" + ) + self.policy_setup = policy_setup + print( + f"*** policy_setup: {policy_setup}, " + f"action_chunk_size: {self.action_chunk_size}, " + f"server_meta: {meta} ***" + ) + + self.horizon = horizon + self.action_ensemble = action_ensemble + self.adaptive_ensemble_alpha = adaptive_ensemble_alpha + self.action_ensemble_horizon = action_ensemble_horizon + + # Gripper sticky state is retained for parity with the source adapter. + self.sticky_action_is_on = False + self.gripper_action_repeat = 0 + self.sticky_gripper_action = 0.0 + self.previous_gripper_action = None + + self.task_description = None + self.image_history = deque(maxlen=self.horizon) + if self.action_ensemble: + self.action_ensembler = AdaptiveEnsembler( + self.action_ensemble_horizon, self.adaptive_ensemble_alpha + ) + else: + self.action_ensembler = None + self.num_image_history = 0 + + # Cached execution-ready model chunk, refreshed every action_chunk_size steps. + self.raw_actions: Optional[np.ndarray] = None + + def _add_image_to_history(self, image: np.ndarray) -> None: + self.image_history.append(image) + self.num_image_history = min(self.num_image_history + 1, self.horizon) + + def reset(self, task_description: str) -> None: + self.task_description = task_description + self.image_history.clear() + if self.action_ensemble: + self.action_ensembler.reset() + self.num_image_history = 0 + self.sticky_action_is_on = False + self.gripper_action_repeat = 0 + self.sticky_gripper_action = 0.0 + self.previous_gripper_action = None + self.raw_actions = None + + def _validate_actions_response(self, response: object) -> np.ndarray: + if not isinstance(response, dict): + raise TypeError( + f"Policy response must be a dict, got {type(response).__name__}" + ) + if response.get("ok") is not True: + raise RuntimeError( + "Policy response did not report ok=true: " + f"{response.get('error', response)!r}" + ) + + data = response.get("data") + if not isinstance(data, dict): + raise TypeError( + f"Policy response data must be a dict, got {type(data).__name__}" + ) + if "actions" not in data: + raise KeyError( + f"Key 'actions' not found in response data: keys={list(data)}" + ) + + actions = np.asarray(data["actions"]) + if actions.ndim != 3 or actions.shape[0] != 1: + raise ValueError( + "Policy actions must have shape (1, T, D); " + f"got {actions.shape}" + ) + if actions.shape[1] <= 0 or actions.shape[2] < 7: + raise ValueError( + "Policy actions must have shape (1, T, D) with T > 0 and D >= 7; " + f"got {actions.shape}" + ) + if actions.shape[1] != self.action_chunk_size: + raise ValueError( + "Policy action horizon disagrees with server metadata: " + f"actions T={actions.shape[1]}, " + f"action_chunk_size={self.action_chunk_size}" + ) + if not np.issubdtype(actions.dtype, np.floating): + raise TypeError( + f"Policy actions must have a floating dtype, got {actions.dtype}" + ) + if not np.isfinite(actions).all(): + raise ValueError("Policy actions must contain only finite values") + return actions + + def step(self, example: dict, step: int = 0, **kwargs) -> dict: + """Run one environment step and return the first seven action dimensions.""" + del kwargs + task_description = example.get("lang", None) + if task_description != self.task_description: + self.reset(task_description) + + # Resize both synchronized camera views to MiniCPM's training resolution. + if self.image_size and example.get("image"): + resized = [] + target_hw = self.image_size + for img in example["image"]: + arr = np.asarray(img) + if arr.shape[:2] != target_hw: + arr = np.asarray( + Image.fromarray(arr).resize( + (target_hw[1], target_hw[0]) + ) + ) + resized.append(arr) + example = {**example, "image": resized} + + # Refresh the chunk when its cached actions have been consumed. + if step % self.action_chunk_size == 0 or self.raw_actions is None: + # State is intentionally absent. The MiniCPM server uses its existing + # 80-dimensional zero-state behavior for standard LIBERO evaluation. + vla_input = {"examples": [example]} + response = self.client.predict_action(vla_input) + actions_batch = self._validate_actions_response(response) + self.raw_actions = actions_batch[0] + + # Preserve model values here; LIBERO-only gripper conversion happens in + # eval_libero.py immediately before env.step(). + raw_actions = self.raw_actions[step % self.action_chunk_size][None] + raw_action = { + "world_vector": np.array(raw_actions[0, :3]), + "rotation_delta": np.array(raw_actions[0, 3:6]), + "open_gripper": np.array(raw_actions[0, 6:7]), + } + return {"raw_action": raw_action} + + def visualize_epoch( + self, + predicted_raw_actions: Sequence[np.ndarray], + images: Sequence[np.ndarray], + save_path: str, + ) -> None: + action_dim_labels = ["x", "y", "z", "roll", "pitch", "yaw", "grasp"] + img_strip = np.concatenate(np.array(images[::3]), axis=1) + figure_layout = [["image"] * len(action_dim_labels), action_dim_labels] + plt.rcParams.update({"font.size": 12}) + fig, axs = plt.subplot_mosaic(figure_layout) + fig.set_size_inches([45, 10]) + + pred_actions = np.array( + [ + np.concatenate( + [ + action["world_vector"], + action["rotation_delta"], + action["open_gripper"], + ], + axis=-1, + ) + for action in predicted_raw_actions + ] + ) + for action_dim, action_label in enumerate(action_dim_labels): + axs[action_label].plot( + pred_actions[:, action_dim], label="predicted action" + ) + axs[action_label].set_title(action_label) + axs[action_label].set_xlabel("Time in one episode") + + axs["image"].imshow(img_strip) + axs["image"].set_xlabel("Time in one episode (subsampled)") + plt.legend() + plt.savefig(save_path) diff --git a/MiniCPM-RobotManip/evaluation/libero/parallel_worker.py b/MiniCPM-RobotManip/evaluation/libero/parallel_worker.py new file mode 100644 index 0000000..388b727 --- /dev/null +++ b/MiniCPM-RobotManip/evaluation/libero/parallel_worker.py @@ -0,0 +1,235 @@ +# Copyright 2025 starVLA community. All rights reserved. +# Licensed under the MIT License. +# Modifications Copyright 2026 The OpenBMB Team. + +"""Run one MiniCPM server and one LIBERO suite evaluation.""" + +from __future__ import annotations + +import argparse +import os +import re +import signal +import subprocess +import sys +from pathlib import Path +from typing import Sequence + +from evaluation.common.launcher_utils import ( + git_revision, + port_available, + resolve_checkpoint, + resolve_executable, + terminate_process_group, + wait_for_policy_server, +) + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser() + parser.add_argument("checkpoint") + parser.add_argument("task_suite") + parser.add_argument("gpu_id") + parser.add_argument("port", type=int) + return parser + + +def safe_label(value: str) -> str: + return re.sub(r"[^A-Za-z0-9_.-]", "_", value) or "unnamed" + + +def main(argv: Sequence[str] | None = None) -> int: + args = build_parser().parse_args(argv) + if not 1 <= args.port <= 65535: + print("port must be in [1, 65535]", file=sys.stderr) + return 2 + + root = Path(__file__).resolve().parents[2] + script_dir = Path(__file__).resolve().parent + libero_value = os.environ.get("LIBERO_HOME", "") + if not libero_value: + print("LIBERO_HOME is required.", file=sys.stderr) + return 2 + libero_home = Path(libero_value).expanduser() + if not libero_home.is_dir(): + print(f"LIBERO_HOME doesn't exist: {libero_home}", file=sys.stderr) + return 2 + + embodiment_value = os.environ.get("EMBODIMENT_ID", "") + try: + embodiment_id = int(embodiment_value) + except ValueError: + print("EMBODIMENT_ID is required and must be an integer.", file=sys.stderr) + return 2 + if embodiment_id < 0: + print("EMBODIMENT_ID must be non-negative.", file=sys.stderr) + return 2 + + try: + checkpoint = resolve_checkpoint(args.checkpoint) + minicpm_python = resolve_executable( + os.environ.get("MINICPM_PYTHON", "python"), + "MINICPM_PYTHON", + ) + libero_python = resolve_executable( + os.environ.get("LIBERO_PYTHON", "python"), + "LIBERO_PYTHON", + ) + except ValueError as exc: + print(str(exc), file=sys.stderr) + return 2 + + host = os.environ.get("HOST", "127.0.0.1") + timeout = int(os.environ.get("SERVER_TIMEOUT", "300")) + if timeout < 1: + print("SERVER_TIMEOUT must be positive.", file=sys.stderr) + return 2 + if not port_available(host, args.port): + print(f"Port {host}:{args.port} is already in use.", file=sys.stderr) + return 1 + + output_root = Path( + os.environ.get( + "OUTPUT_ROOT", + root / "outputs" / "evaluation" / "libero", + ) + ).expanduser() + run_id = os.environ.get("RUN_ID", "manual") + job_id = os.environ.get("JOB_ID", "") + checkpoint_label = safe_label(checkpoint) + prefix = f"job-{job_id}-" if job_id else "" + job_root = output_root / run_id / f"{prefix}{checkpoint_label}" + job_dir = job_root / args.task_suite + job_dir.mkdir(parents=True, exist_ok=True) + server_log = job_dir / "server.log" + eval_log = job_dir / "eval.log" + manifest = job_dir / "manifest.tsv" + + fields = { + "source": "starVLA@631aae02afe6d95876e923ff518e8ff2ab9a2f88", + "checkpoint": checkpoint, + "minicpm_revision": git_revision(root), + "libero_revision": git_revision(libero_home), + "task_suite": args.task_suite, + "gpu": args.gpu_id, + "egl_device": os.environ.get("EGL_DEVICE_ID", "0"), + "host": host, + "port": str(args.port), + "embodiment_id": str(embodiment_id), + "server_python": minicpm_python, + "libero_python": libero_python, + "server_log": str(server_log), + "eval_log": str(eval_log), + } + with manifest.open("w", encoding="utf-8") as file: + for key, value in fields.items(): + file.write(f"{key}\t{value}\n") + + server: subprocess.Popen | None = None + evaluator: subprocess.Popen | None = None + interrupted = 0 + final_status = 1 + + def handle_signal(signum, frame) -> None: + del frame + nonlocal interrupted + interrupted = signum + terminate_process_group(evaluator) + terminate_process_group(server) + + previous_handlers = {} + for signum in (signal.SIGINT, signal.SIGTERM, signal.SIGHUP): + previous_handlers[signum] = signal.signal(signum, handle_signal) + + try: + with server_log.open("w", encoding="utf-8") as server_stream: + server = subprocess.Popen( + ["bash", str(script_dir / "run_policy_server.sh")], + stdout=server_stream, + stderr=subprocess.STDOUT, + env={ + **os.environ, + "MINICPM_ROOT": str(root), + "MINICPM_PYTHON": minicpm_python, + "CHECKPOINT": checkpoint, + "EMBODIMENT_ID": str(embodiment_id), + "HOST": host, + "PORT": str(args.port), + "GPU_ID": args.gpu_id, + }, + start_new_session=True, + ) + wait_for_policy_server( + server=server, + minicpm_python=minicpm_python, + root=root, + host=host, + port=args.port, + checkpoint=checkpoint, + embodiment_id=embodiment_id, + min_action_dim=7, + request_id="libero-readiness", + timeout=timeout, + ) + if interrupted: + raise RuntimeError("Evaluation interrupted") + + with eval_log.open("w", encoding="utf-8") as eval_stream: + evaluator = subprocess.Popen( + ["bash", str(script_dir / "eval_libero.sh")], + stdout=eval_stream, + stderr=subprocess.STDOUT, + env={ + **os.environ, + "MINICPM_ROOT": str(root), + "LIBERO_HOME": str(libero_home), + "LIBERO_PYTHON": libero_python, + "OUTPUT_ROOT": str(job_root), + "HOST": host, + "PORT": str(args.port), + "TASK_SUITE_NAME": args.task_suite, + "CUDA_VISIBLE_DEVICES": args.gpu_id, + }, + start_new_session=True, + ) + if interrupted: + raise RuntimeError("Evaluation interrupted") + eval_status = evaluator.wait() + + server_alive = server.poll() is None + if eval_status: + final_status = eval_status + print( + f"LIBERO evaluation failed with status {eval_status}; " + f"see {eval_log}", + file=sys.stderr, + ) + return eval_status + if not server_alive: + final_status = 1 + print( + "MiniCPM server exited before evaluation completed.", + file=sys.stderr, + ) + return 1 + print(f"Evaluation completed. Outputs: {job_dir}") + final_status = 0 + return 0 + except Exception as exc: + if interrupted: + final_status = 128 + interrupted + return final_status + print(f"LIBERO worker failed: {exc}; see {server_log}", file=sys.stderr) + final_status = 1 + return 1 + finally: + terminate_process_group(evaluator) + terminate_process_group(server) + with manifest.open("a", encoding="utf-8") as file: + file.write(f"exit_status\t{final_status}\n") + for signum, handler in previous_handlers.items(): + signal.signal(signum, handler) + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/MiniCPM-RobotManip/evaluation/libero/requirements-client.txt b/MiniCPM-RobotManip/evaluation/libero/requirements-client.txt new file mode 100644 index 0000000..b342559 --- /dev/null +++ b/MiniCPM-RobotManip/evaluation/libero/requirements-client.txt @@ -0,0 +1,9 @@ +# Evaluator-side additions only. Install LIBERO separately. +imageio[ffmpeg]>=2.34 +matplotlib>=3.7 +msgpack>=1.0 +numpy>=1.24,<2 +Pillow>=10 +tqdm>=4.66 +tyro>=0.8,<0.9 +websockets>=13.1,<14 diff --git a/MiniCPM-RobotManip/evaluation/libero/run_policy_server.sh b/MiniCPM-RobotManip/evaluation/libero/run_policy_server.sh new file mode 100755 index 0000000..b42f672 --- /dev/null +++ b/MiniCPM-RobotManip/evaluation/libero/run_policy_server.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +# Copyright 2025 starVLA community. All rights reserved. +# Licensed under the MIT License. +# Modifications Copyright 2026 The OpenBMB Team. + +set -euo pipefail + +usage() { + cat <<'EOF' +Launch the MiniCPM-RobotManip policy server for LIBERO. + +Usage: + bash run_policy_server.sh [additional server arguments] + +Environment: + MINICPM_PYTHON Model-server Python (default: python) + CHECKPOINT Hub ID or local checkpoint directory + EMBODIMENT_ID LIBERO embodiment ID (required) + HOST Listening host (default: 127.0.0.1) + PORT Listening port (default: 10093) + GPU_ID CUDA device exposed to the server (default: 0) +EOF +} + +if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then + usage + exit 0 +fi + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +MINICPM_ROOT="${MINICPM_ROOT:-$(cd -- "${SCRIPT_DIR}/../.." && pwd)}" +MINICPM_PYTHON="${MINICPM_PYTHON:-python}" +CHECKPOINT="${CHECKPOINT:-openbmb/MiniCPM-RobotManip}" +: "${EMBODIMENT_ID:?Set EMBODIMENT_ID for the selected LIBERO checkpoint}" +HOST="${HOST:-127.0.0.1}" +PORT="${PORT:-10093}" +GPU_ID="${GPU_ID:-0}" + +cd "${MINICPM_ROOT}" +export PYTHONPATH="${MINICPM_ROOT}${PYTHONPATH:+:${PYTHONPATH}}" + +exec env CUDA_VISIBLE_DEVICES="${GPU_ID}" \ + "${MINICPM_PYTHON}" -m deployment.model_server.server_policy \ + --checkpoint "${CHECKPOINT}" \ + --device cuda \ + --host "${HOST}" \ + --port "${PORT}" \ + --default-embodiment-id "${EMBODIMENT_ID}" \ + "$@" diff --git a/MiniCPM-RobotManip/evaluation/robotwin/README.md b/MiniCPM-RobotManip/evaluation/robotwin/README.md new file mode 100644 index 0000000..3a73836 --- /dev/null +++ b/MiniCPM-RobotManip/evaluation/robotwin/README.md @@ -0,0 +1,167 @@ + + +# MiniCPM-RobotManip RoboTwin evaluation + +This directory adapts the standard RoboTwin policy interface and the starVLA +multi-GPU evaluation flow to MiniCPM-RobotManip. RoboTwin remains an external +installation; no RoboTwin source patch is required. The launcher uses +RoboTwin's generic `--overrides` support. + +`start_eval.sh` is intentionally a thin entry point. CLI parsing, GPU slots, +ports, subprocess groups, readiness, cleanup, and manifests live in +`launcher.py`; `evaluation/common/probe_server.py` performs the WebSocket +metadata/ping check in the MiniCPM environment. + +The migration is based on starVLA commit +`631aae02afe6d95876e923ff518e8ff2ab9a2f88`. See the license note below. + +## Environments + +Use two separate Python environments: + +1. `MINICPM_PYTHON` loads the checkpoint and runs the WebSocket policy server. +2. `ROBOTWIN_PYTHON` runs the externally installed RoboTwin simulator and + imports the lightweight policy client from this repository. + +`LAUNCHER_PYTHON` runs the standard-library-only scheduler and defaults to +`python3`. + +Install the client-only wire dependencies in the RoboTwin environment: + +```bash +/path/to/robotwin/bin/python -m pip install \ + -r MiniCPM-RobotManip/evaluation/robotwin/requirements-client.txt +``` + +Set the runtime paths before evaluation: + +```bash +export ROBOTWIN_PATH=/path/to/RoboTwin +export MINICPM_PYTHON=/path/to/minicpm/bin/python +export ROBOTWIN_PYTHON=/path/to/robotwin/bin/python +``` + +`ROBOTWIN_PATH/script/eval_policy.py` must exist and support the standard +`--config ... --overrides ...` interface. + +## Multi-GPU evaluation + +From any directory: + +```bash +bash MiniCPM-RobotManip/evaluation/robotwin/start_eval.sh \ + --mode demo_clean \ + --run-name minicpm_clean \ + --checkpoint openbmb/MiniCPM-RobotManip \ + --default-embodiment-id 0 \ + adjust_bottle +``` + +`--checkpoint` accepts either a Hugging Face Hub model ID or a local checkpoint +directory. A local checkpoint file is not supported by the MiniCPM loader. + +Tasks can be supplied in four forms: + +```bash +# Separate arguments +... adjust_bottle open_laptop + +# Comma-separated +... adjust_bottle,open_laptop + +# A file containing task names, comments, and/or comma-separated lines +... tasks.txt + +# The built-in RoboTwin 2.0 list of 50 tasks +... all +``` + +The scheduler detects `CUDA_VISIBLE_DEVICES` first, then `nvidia-smi`. It builds +FIFO execution slots, allocates an available port to each slot starting at +`10093`, and starts a fresh server/evaluator pair for every task. The default is +one slot per GPU. More than one `--jobs-per-gpu` slot is allowed but warned +because each slot loads another model and simulator on the same GPU. + +Useful options: + +```text +--seed N +--jobs-per-gpu N +--base-port PORT +--server-timeout SECONDS +--default-embodiment-id ID +--dry-run +``` + +`--name` is an alias for `--run-name`. Run `start_eval.sh --help` for all +arguments and environment variables. + +Before starting RoboTwin, readiness checks connect with this repository's +`WebsocketClientPolicy`, read server metadata, verify the checkpoint and +default embodiment ID, and issue a real protocol `ping`. The server PID is +checked throughout startup. Task failures are collected, and any failure makes +the launcher exit non-zero. SIGINT/SIGTERM recursively stop server, simulator, +and logging subprocesses. + +Logs default to: + +```text +MiniCPM-RobotManip/outputs/evaluation/robotwin/____/ +``` + +Set `OUTPUT_ROOT` to use another root. Every task has separate server and +evaluator logs; `run_manifest.tsv`, `schedule.tsv`, and `status.tsv` record the +run configuration, slot assignment, and final exit status. + +## Manual single-task evaluation + +Start the server in the MiniCPM environment: + +```bash +MINICPM_PYTHON=/path/to/minicpm/bin/python \ +bash MiniCPM-RobotManip/evaluation/robotwin/run_policy_server.sh \ + openbmb/MiniCPM-RobotManip 0 10093 127.0.0.1 cuda 0 +``` + +Then run one task with the RoboTwin environment: + +```bash +ROBOTWIN_PATH=/path/to/RoboTwin \ +ROBOTWIN_PYTHON=/path/to/robotwin/bin/python \ +bash MiniCPM-RobotManip/evaluation/robotwin/eval.sh \ + adjust_bottle demo_clean manual_run 0 0 10093 127.0.0.1 +``` + +`eval.sh` creates a unique temporary deployment YAML, injects task, mode, run +name, seed, and dotted policy module through RoboTwin overrides, runs from the +external checkout, and removes the temporary file on exit. + +## Policy contract + +- Policy module: + `evaluation.robotwin.model2robotwin_interface` +- Endpoint default: `127.0.0.1:10093` +- Camera order: `[head, left_wrist, right_wrist]` +- Client image size: `448 x 448` +- State: omitted from the request; RoboTwin's incompatible 14-dimensional + joint vector is not forwarded, so the server supplies its zero80 default +- Action mode: absolute only; other modes fail before evaluation +- Request: no unnormalization key, DDIM, or normalization parameters +- Response: must be successful, finite, and shaped + `(1, action_chunk_size, D)` with `D >= 14` +- RoboTwin action order: + `[0, 1, 2, 3, 4, 5, 12, 6, 7, 8, 9, 10, 11, 13]` + +`--default-embodiment-id` is required because the published checkpoint does +not currently document a reliable embodiment-ID-to-robot mapping. Set it to +the value appropriate for the checkpoint and RoboTwin setup. + +## License and provenance + +The interface and launch structure were migrated from starVLA, copyright 2025 +the starVLA community, under the MIT License. Modifications for +MiniCPM-RobotManip are copyright 2026 The OpenBMB Team. diff --git a/MiniCPM-RobotManip/evaluation/robotwin/__init__.py b/MiniCPM-RobotManip/evaluation/robotwin/__init__.py new file mode 100644 index 0000000..8c78490 --- /dev/null +++ b/MiniCPM-RobotManip/evaluation/robotwin/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2025 starVLA community. All rights reserved. +# Licensed under the MIT License. +# Modifications Copyright 2026 The OpenBMB Team. + +"""MiniCPM-RobotManip integration for the external RoboTwin evaluator.""" diff --git a/MiniCPM-RobotManip/evaluation/robotwin/deploy_policy.yml b/MiniCPM-RobotManip/evaluation/robotwin/deploy_policy.yml new file mode 100644 index 0000000..6421ca2 --- /dev/null +++ b/MiniCPM-RobotManip/evaluation/robotwin/deploy_policy.yml @@ -0,0 +1,15 @@ +# Copyright 2025 starVLA community. All rights reserved. +# Licensed under the MIT License. +# Modifications Copyright 2026 The OpenBMB Team. + +policy_name: evaluation.robotwin.model2robotwin_interface +task_name: null +task_config: null +ckpt_setting: null +seed: null +instruction_type: unseen + +# MiniCPM policy server parameters +host: "127.0.0.1" +port: 10093 +action_mode: "abs" diff --git a/MiniCPM-RobotManip/evaluation/robotwin/eval.sh b/MiniCPM-RobotManip/evaluation/robotwin/eval.sh new file mode 100755 index 0000000..aa0425f --- /dev/null +++ b/MiniCPM-RobotManip/evaluation/robotwin/eval.sh @@ -0,0 +1,101 @@ +#!/usr/bin/env bash +# Copyright 2025 starVLA community. All rights reserved. +# Licensed under the MIT License. +# Modifications Copyright 2026 The OpenBMB Team. + +set -euo pipefail + +usage() { + cat >&2 <<'EOF' +Usage: + bash eval.sh [policy_port] [policy_host] + +Environment: + ROBOTWIN_PATH External RoboTwin checkout (required) + ROBOTWIN_PYTHON Python from the RoboTwin environment (default: python) + ROBOTWIN_POLICY_MODULE + Dotted RoboTwin policy module + DEPLOY_POLICY_TEMPLATE_PATH + Optional deploy_policy.yml template override +EOF +} + +if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then + usage + exit 0 +fi + +if (( $# < 5 || $# > 7 )); then + usage + exit 1 +fi + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +MINICPM_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" + +task_name="$1" +task_config="$2" +run_name="$3" +seed="$4" +gpu_id="$5" +policy_port="${6:-${ROBOTWIN_POLICY_PORT:-10093}}" +policy_host="${7:-${ROBOTWIN_POLICY_HOST:-127.0.0.1}}" +policy_module="${ROBOTWIN_POLICY_MODULE:-evaluation.robotwin.model2robotwin_interface}" +robotwin_python="${ROBOTWIN_PYTHON:-python}" +robotwin_path="${ROBOTWIN_PATH:-}" +deploy_policy_template="${DEPLOY_POLICY_TEMPLATE_PATH:-${SCRIPT_DIR}/deploy_policy.yml}" + +if [[ -z "${robotwin_path}" || ! -d "${robotwin_path}" ]]; then + echo "ROBOTWIN_PATH must point to an existing RoboTwin checkout: ${robotwin_path:-}" >&2 + exit 1 +fi +if [[ ! -f "${robotwin_path}/script/eval_policy.py" ]]; then + echo "RoboTwin eval entry does not exist: ${robotwin_path}/script/eval_policy.py" >&2 + exit 1 +fi +if [[ ! -f "${deploy_policy_template}" ]]; then + echo "Deploy policy template does not exist: ${deploy_policy_template}" >&2 + exit 1 +fi +if ! command -v "${robotwin_python}" >/dev/null 2>&1; then + echo "ROBOTWIN_PYTHON is not executable: ${robotwin_python}" >&2 + exit 1 +fi +if [[ ! "${policy_port}" =~ ^[0-9]+$ ]] || (( policy_port < 1 || policy_port > 65535 )); then + echo "Invalid policy port: ${policy_port}" >&2 + exit 1 +fi +if [[ ! "${policy_host}" =~ ^[A-Za-z0-9.-]+$ ]]; then + echo "Invalid policy host: ${policy_host}" >&2 + exit 1 +fi + +runtime_deploy_policy="$( + mktemp "${TMPDIR:-/tmp}/minicpm_robotwin_deploy.${BASHPID}.XXXXXX.yml" +)" +cleanup() { + rm -f "${runtime_deploy_policy}" +} +trap cleanup EXIT + +sed \ + -e "s|^host:.*|host: \"${policy_host}\"|" \ + -e "s|^port:.*|port: ${policy_port}|" \ + "${deploy_policy_template}" > "${runtime_deploy_policy}" + +export CUDA_VISIBLE_DEVICES="${gpu_id}" +export PYTHONPATH="${MINICPM_ROOT}:${robotwin_path}${PYTHONPATH:+:${PYTHONPATH}}" + +echo "[INFO] RoboTwin task=${task_name} config=${task_config} run=${run_name}" +echo "[INFO] GPU=${gpu_id} policy=${policy_module} endpoint=${policy_host}:${policy_port}" + +cd "${robotwin_path}" +PYTHONWARNINGS=ignore::UserWarning \ +"${robotwin_python}" script/eval_policy.py \ + --config "${runtime_deploy_policy}" \ + --overrides \ + --task_name "${task_name}" \ + --task_config "${task_config}" \ + --ckpt_setting "${run_name}" \ + --seed "${seed}" \ + --policy_name "${policy_module}" diff --git a/MiniCPM-RobotManip/evaluation/robotwin/launcher.py b/MiniCPM-RobotManip/evaluation/robotwin/launcher.py new file mode 100644 index 0000000..2054e56 --- /dev/null +++ b/MiniCPM-RobotManip/evaluation/robotwin/launcher.py @@ -0,0 +1,799 @@ +# Copyright 2025 starVLA community. All rights reserved. +# Licensed under the MIT License. +# Modifications Copyright 2026 The OpenBMB Team. + +"""Multi-GPU RoboTwin evaluation launcher. + +This module replaces the large Bash scheduler migrated from starVLA. One +worker thread owns each GPU slot and runs server/evaluator subprocesses +serially; different slots execute concurrently. +""" + +from __future__ import annotations + +import argparse +import os +import queue +import re +import shutil +import signal +import socket +import subprocess +import sys +import threading +import time +from dataclasses import dataclass +from datetime import datetime +from pathlib import Path +from typing import IO, Sequence + + +ALL_TASKS = ( + "adjust_bottle", + "beat_block_hammer", + "blocks_ranking_rgb", + "blocks_ranking_size", + "click_alarmclock", + "click_bell", + "dump_bin_bigbin", + "grab_roller", + "handover_block", + "handover_mic", + "hanging_mug", + "lift_pot", + "move_can_pot", + "move_pillbottle_pad", + "move_playingcard_away", + "move_stapler_pad", + "open_laptop", + "open_microwave", + "pick_diverse_bottles", + "pick_dual_bottles", + "place_a2b_left", + "place_a2b_right", + "place_bread_basket", + "place_bread_skillet", + "place_burger_fries", + "place_can_basket", + "place_cans_plasticbox", + "place_container_plate", + "place_dual_shoes", + "place_empty_cup", + "place_fan", + "place_mouse_pad", + "place_object_basket", + "place_object_scale", + "place_object_stand", + "place_phone_stand", + "place_shoe", + "press_stapler", + "put_bottles_dustbin", + "put_object_cabinet", + "rotate_qrcode", + "scan_object", + "shake_bottle_horizontally", + "shake_bottle", + "stack_blocks_three", + "stack_blocks_two", + "stack_bowls_three", + "stack_bowls_two", + "stamp_seal", + "turn_switch", +) + + +@dataclass(frozen=True) +class Slot: + index: int + gpu: str + port: int + + +@dataclass(frozen=True) +class WorkItem: + number: int + task: str + + +@dataclass +class Config: + root: Path + script_dir: Path + robotwin_path: Path | None + minicpm_python: str + robotwin_python: str + checkpoint: str + embodiment_id: int + mode: str + run_name: str + seed: int + device: str + host: str + server_timeout: int + output_root: Path + dry_run: bool + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + description="Run RoboTwin tasks with bounded MiniCPM GPU slots." + ) + parser.add_argument( + "-m", + "--mode", + required=True, + choices=("demo_clean", "demo_randomized"), + ) + parser.add_argument( + "-n", + "--run-name", + "--name", + dest="run_name", + required=True, + ) + parser.add_argument( + "-c", + "--checkpoint", + "--ckpt", + dest="checkpoint", + required=True, + help="Hugging Face model ID or local checkpoint directory", + ) + parser.add_argument( + "--default-embodiment-id", + type=int, + default=None, + help="Required MiniCPM RoboTwin embodiment ID", + ) + parser.add_argument( + "-s", + "--seed", + type=int, + default=int(os.environ.get("ROBOTWIN_SEED", "0")), + ) + parser.add_argument( + "-j", + "--jobs-per-gpu", + type=int, + default=int(os.environ.get("ROBOTWIN_JOBS_PER_GPU", "1")), + ) + parser.add_argument( + "-p", + "--base-port", + type=int, + default=int(os.environ.get("ROBOTWIN_BASE_PORT", "10093")), + ) + parser.add_argument( + "--server-timeout", + type=int, + default=int(os.environ.get("ROBOTWIN_SERVER_TIMEOUT", "600")), + ) + parser.add_argument("--dry-run", action="store_true") + parser.add_argument( + "tasks", + nargs="+", + help="Task names, comma-separated tasks, task files, or 'all'", + ) + return parser + + +def resolve_executable(value: str, label: str) -> str: + candidate = Path(value).expanduser() + if "/" in value: + if not candidate.is_file() or not os.access(candidate, os.X_OK): + raise ValueError(f"{label} is not executable: {candidate}") + return str(candidate.resolve()) + resolved = shutil.which(value) + if resolved is None: + raise ValueError(f"{label} is not available on PATH: {value}") + return resolved + + +def resolve_checkpoint(value: str) -> str: + candidate = Path(value).expanduser() + if candidate.exists(): + if not candidate.is_dir(): + raise ValueError(f"Local checkpoint must be a directory: {candidate}") + return str(candidate.resolve()) + if value.startswith(("/", "./", "../")): + raise ValueError(f"Local checkpoint directory does not exist: {value}") + return value + + +def _add_task_text(tasks: list[str], text: str) -> None: + for value in text.split(","): + value = value.strip() + if not value: + continue + if value == "all": + tasks.extend(ALL_TASKS) + else: + tasks.append(value) + + +def resolve_tasks(values: Sequence[str]) -> list[str]: + tasks: list[str] = [] + for value in values: + path = Path(value).expanduser() + if path.is_file(): + for line in path.read_text(encoding="utf-8").splitlines(): + _add_task_text(tasks, line.split("#", 1)[0].strip()) + else: + _add_task_text(tasks, value) + if not tasks: + raise ValueError("No RoboTwin tasks were resolved from input") + return tasks + + +def detect_cuda_devices() -> list[str]: + if "CUDA_VISIBLE_DEVICES" in os.environ: + visible = os.environ["CUDA_VISIBLE_DEVICES"].strip() + if not visible or visible == "-1": + raise ValueError("CUDA_VISIBLE_DEVICES explicitly disables all GPUs") + devices = [value.strip() for value in visible.split(",") if value.strip()] + if devices: + return devices + raise ValueError("CUDA_VISIBLE_DEVICES contains no usable GPU IDs") + + if shutil.which("nvidia-smi"): + result = subprocess.run( + ["nvidia-smi", "--query-gpu=index", "--format=csv,noheader"], + capture_output=True, + text=True, + check=False, + ) + devices = [] + if result.returncode == 0: + devices = [ + line.strip() + for line in result.stdout.splitlines() + if line.strip().isdigit() + ] + if devices: + return devices + + raise ValueError( + "No GPU was detected. Set CUDA_VISIBLE_DEVICES to the intended GPU IDs." + ) + + +def port_available(host: str, port: int) -> bool: + with socket.socket() as sock: + try: + sock.bind((host, port)) + except OSError: + return False + return True + + +def allocate_slots( + devices: Sequence[str], + jobs_per_gpu: int, + base_port: int, + host: str, +) -> list[Slot]: + if jobs_per_gpu < 1: + raise ValueError("jobs-per-gpu must be positive") + slots: list[Slot] = [] + candidate = base_port + for device in devices: + for _ in range(jobs_per_gpu): + while candidate <= 65535 and not port_available(host, candidate): + candidate += 1 + if candidate > 65535: + raise ValueError("No available policy port remains") + slots.append(Slot(len(slots), device, candidate)) + candidate += 1 + return slots + + +def safe_label(value: str) -> str: + label = re.sub(r"[^A-Za-z0-9_.-]", "_", value) + return label or "unnamed" + + +def git_revision(path: Path) -> str: + result = subprocess.run( + ["git", "-C", str(path), "rev-parse", "HEAD"], + capture_output=True, + text=True, + check=False, + ) + return result.stdout.strip() if result.returncode == 0 else "unknown" + + +def terminate_process( + process: subprocess.Popen | None, + *, + timeout: float = 5, +) -> None: + if process is None or process.poll() is not None: + return + try: + os.killpg(process.pid, signal.SIGTERM) + except ProcessLookupError: + return + try: + process.wait(timeout=timeout) + return + except subprocess.TimeoutExpired: + pass + try: + os.killpg(process.pid, signal.SIGKILL) + except ProcessLookupError: + return + process.wait(timeout=timeout) + + +class Coordinator: + def __init__( + self, + config: Config, + slots: Sequence[Slot], + tasks: Sequence[str], + log_dir: Path, + ) -> None: + self.config = config + self.slots = list(slots) + self.log_dir = log_dir + self.work: queue.Queue[WorkItem] = queue.Queue() + for index, task in enumerate(tasks, start=1): + self.work.put(WorkItem(index, task)) + self.interrupted = threading.Event() + self.lock = threading.Lock() + self.active: dict[int, tuple[subprocess.Popen | None, subprocess.Popen | None]] = {} + self.failures: list[str] = [] + self.internal_errors: list[str] = [] + self.schedule_path = log_dir / "schedule.tsv" + self.status_path = log_dir / "status.tsv" + + def register( + self, + slot: Slot, + server: subprocess.Popen | None, + evaluator: subprocess.Popen | None, + ) -> None: + with self.lock: + self.active[slot.index] = (server, evaluator) + + def stop_all(self) -> None: + self.interrupted.set() + with self.lock: + processes = list(self.active.values()) + for server, evaluator in processes: + terminate_process(evaluator) + terminate_process(server) + + def append_schedule( + self, + item: WorkItem, + slot: Slot, + server_log: Path, + eval_log: Path, + ) -> None: + with self.lock, self.schedule_path.open("a", encoding="utf-8") as file: + file.write( + f"{item.number}\t{item.task}\t{slot.index}\t{slot.gpu}\t" + f"{slot.port}\t{server_log}\t{eval_log}\n" + ) + + def append_status(self, item: WorkItem, slot: Slot, status: int) -> None: + with self.lock, self.status_path.open("a", encoding="utf-8") as file: + file.write(f"{item.task}\t{slot.index}\t{status}\n") + if status: + self.failures.append(item.task) + + def probe(self, slot: Slot) -> subprocess.CompletedProcess: + env = dict(os.environ) + env["PYTHONPATH"] = ( + f"{self.config.root}" + + (f":{env['PYTHONPATH']}" if env.get("PYTHONPATH") else "") + ) + return subprocess.run( + [ + self.config.minicpm_python, + "-m", + "evaluation.common.probe_server", + "--host", + self.config.host, + "--port", + str(slot.port), + "--checkpoint", + self.config.checkpoint, + "--embodiment-id", + str(self.config.embodiment_id), + "--min-action-dim", + "14", + "--request-id", + "robotwin-readiness", + "--timeout", + "2", + ], + env=env, + capture_output=True, + text=True, + timeout=5, + check=False, + ) + + def wait_ready(self, slot: Slot, server: subprocess.Popen) -> None: + deadline = time.monotonic() + self.config.server_timeout + last_error = "" + while time.monotonic() < deadline: + if self.interrupted.is_set(): + raise RuntimeError("Evaluation interrupted") + status = server.poll() + if status is not None: + raise RuntimeError( + f"Policy server exited before readiness with status {status}" + ) + try: + probe = self.probe(slot) + except subprocess.TimeoutExpired: + last_error = "readiness probe timed out" + else: + if probe.returncode == 0: + return + last_error = (probe.stderr or probe.stdout).strip() + if probe.returncode == 3: + raise RuntimeError(last_error or "Server metadata mismatch") + time.sleep(1) + raise RuntimeError( + f"Timed out waiting for {self.config.host}:{slot.port}: {last_error}" + ) + + def run_task(self, slot: Slot, item: WorkItem) -> int: + task_safe = safe_label(item.task) + slot_label = f"slot{slot.index}_gpu{safe_label(slot.gpu)}_port{slot.port}" + server_log = ( + self.log_dir + / f"{item.number:03d}_{task_safe}_{self.config.mode}_{slot_label}_server.log" + ) + eval_log = ( + self.log_dir + / f"{item.number:03d}_{task_safe}_{self.config.mode}_{slot_label}_eval.log" + ) + self.append_schedule(item, slot, server_log, eval_log) + print( + f"[INFO] Launching task={item.task} mode={self.config.mode} " + f"gpu={slot.gpu} port={slot.port}", + flush=True, + ) + + server: subprocess.Popen | None = None + evaluator: subprocess.Popen | None = None + status = 1 + try: + with server_log.open( + "w", encoding="utf-8" + ) as server_stream, eval_log.open( + "w", encoding="utf-8" + ) as eval_stream: + server = subprocess.Popen( + [ + "bash", + str(self.config.script_dir / "run_policy_server.sh"), + self.config.checkpoint, + slot.gpu, + str(slot.port), + self.config.host, + self.config.device, + str(self.config.embodiment_id), + ], + stdout=server_stream, + stderr=subprocess.STDOUT, + env={ + **os.environ, + "MINICPM_PYTHON": self.config.minicpm_python, + }, + start_new_session=True, + ) + self.register(slot, server, None) + self.wait_ready(slot, server) + + if self.interrupted.is_set(): + raise RuntimeError("Evaluation interrupted") + evaluator = subprocess.Popen( + [ + "bash", + str(self.config.script_dir / "eval.sh"), + item.task, + self.config.mode, + self.config.run_name, + str(self.config.seed), + slot.gpu, + str(slot.port), + self.config.host, + ], + stdout=eval_stream, + stderr=subprocess.STDOUT, + env={ + **os.environ, + "ROBOTWIN_PATH": str(self.config.robotwin_path), + "ROBOTWIN_PYTHON": self.config.robotwin_python, + "ROBOTWIN_POLICY_MODULE": ( + "evaluation.robotwin.model2robotwin_interface" + ), + }, + start_new_session=True, + ) + self.register(slot, server, evaluator) + if self.interrupted.is_set(): + raise RuntimeError("Evaluation interrupted") + status = evaluator.wait() + + if status == 0 and server.poll() is not None: + status = 1 + print( + f"[ERROR] Server exited before task={item.task} completed.", + file=sys.stderr, + ) + except Exception as exc: + print(f"[ERROR] Task {item.task}: {exc}", file=sys.stderr) + status = 1 + finally: + terminate_process(evaluator) + terminate_process(server) + self.register(slot, None, None) + + if eval_log.is_file(): + for line in eval_log.read_text( + encoding="utf-8", errors="replace" + ).splitlines(): + if "Success rate" in line: + print(f"[RESULT] {item.task}: {line}") + self.append_status(item, slot, status) + return status + + def worker(self, slot: Slot) -> None: + while not self.interrupted.is_set(): + try: + item = self.work.get_nowait() + except queue.Empty: + return + try: + try: + self.run_task(slot, item) + except Exception as exc: + message = ( + f"slot={slot.index} task={item.task}: " + f"internal worker error: {exc}" + ) + print(f"[ERROR] {message}", file=sys.stderr) + with self.lock: + self.internal_errors.append(message) + finally: + self.work.task_done() + + +def write_manifests( + config: Config, + slots: Sequence[Slot], + tasks: Sequence[str], + log_dir: Path, +) -> None: + with (log_dir / "run_manifest.tsv").open("w", encoding="utf-8") as file: + fields = { + "source": "starVLA@631aae02afe6d95876e923ff518e8ff2ab9a2f88", + "checkpoint": config.checkpoint, + "minicpm_revision": git_revision(config.root), + "robotwin_revision": ( + git_revision(config.robotwin_path) + if config.robotwin_path is not None + else "unknown" + ), + "run_name": config.run_name, + "task_config": config.mode, + "seed": str(config.seed), + "default_embodiment_id": str(config.embodiment_id), + "gpus": ",".join(slot.gpu for slot in slots), + "ports": ",".join(str(slot.port) for slot in slots), + "total_tasks": str(len(tasks)), + "minicpm_python": config.minicpm_python, + "robotwin_python": config.robotwin_python, + } + for key, value in fields.items(): + file.write(f"{key}\t{value}\n") + (log_dir / "schedule.tsv").write_text( + "task_number\ttask\tslot\tgpu\tport\tserver_log\teval_log\n", + encoding="utf-8", + ) + (log_dir / "status.tsv").write_text( + "task\tslot\texit_status\n", + encoding="utf-8", + ) + + +def main(argv: Sequence[str] | None = None) -> int: + args = build_parser().parse_args(argv) + embodiment_id = args.default_embodiment_id + if embodiment_id is None: + value = os.environ.get("ROBOTWIN_DEFAULT_EMBODIMENT_ID", "") + if not value: + print( + "--default-embodiment-id is required because the checkpoint " + "doesn't publish a RoboTwin mapping.", + file=sys.stderr, + ) + return 2 + try: + embodiment_id = int(value) + except ValueError: + print( + "ROBOTWIN_DEFAULT_EMBODIMENT_ID must be an integer.", + file=sys.stderr, + ) + return 2 + if embodiment_id < 0: + print("--default-embodiment-id must be non-negative.", file=sys.stderr) + return 2 + if args.jobs_per_gpu < 1: + print("--jobs-per-gpu must be positive.", file=sys.stderr) + return 2 + if not 1 <= args.base_port <= 65535: + print("--base-port must be in [1, 65535].", file=sys.stderr) + return 2 + if args.server_timeout < 1: + print("--server-timeout must be positive.", file=sys.stderr) + return 2 + + root = Path(__file__).resolve().parents[2] + script_dir = Path(__file__).resolve().parent + try: + checkpoint = resolve_checkpoint(args.checkpoint) + tasks = resolve_tasks(args.tasks) + minicpm_python = resolve_executable( + os.environ.get("MINICPM_PYTHON", "python"), + "MINICPM_PYTHON", + ) + robotwin_python = resolve_executable( + os.environ.get("ROBOTWIN_PYTHON", "python"), + "ROBOTWIN_PYTHON", + ) + slots = allocate_slots( + detect_cuda_devices(), + args.jobs_per_gpu, + args.base_port, + "127.0.0.1", + ) + except ValueError as exc: + print(str(exc), file=sys.stderr) + return 2 + + robotwin_value = os.environ.get("ROBOTWIN_PATH", "") + robotwin_path = Path(robotwin_value).expanduser() if robotwin_value else None + if not args.dry_run: + if ( + robotwin_path is None + or not (robotwin_path / "script" / "eval_policy.py").is_file() + ): + print( + "ROBOTWIN_PATH must contain script/eval_policy.py.", + file=sys.stderr, + ) + return 2 + + if args.jobs_per_gpu > 1: + print( + f"[WARN] jobs-per-gpu={args.jobs_per_gpu} loads multiple models " + "and simulators on each GPU.", + file=sys.stderr, + ) + print(f"[INFO] MiniCPM python: {minicpm_python}") + print(f"[INFO] RoboTwin python: {robotwin_python}") + print( + f"[INFO] mode={args.mode} run_name={args.run_name} seed={args.seed}" + ) + print( + f"[INFO] checkpoint={checkpoint} " + f"default_embodiment_id={embodiment_id}" + ) + print( + f"[INFO] GPUs={','.join(slot.gpu for slot in slots)} " + f"slots={len(slots)}" + ) + print(f"[INFO] tasks ({len(tasks)}): {', '.join(tasks)}") + + if args.dry_run: + print("[DRY-RUN] No server, simulator, or output directory will start.") + for slot in slots: + print( + f"[DRY-RUN] slot={slot.index} gpu={slot.gpu} port={slot.port}" + ) + for index, task in enumerate(tasks, start=1): + print(f"[DRY-RUN] FIFO[{index}]={task}") + return 0 + + output_root = Path( + os.environ.get( + "OUTPUT_ROOT", + root / "outputs" / "evaluation" / "robotwin", + ) + ).expanduser() + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + log_dir = output_root / ( + f"{safe_label(args.run_name)}_{args.mode}_" + f"{safe_label(Path(checkpoint).name)}_{timestamp}_{os.getpid()}" + ) + log_dir.mkdir(parents=True) + + config = Config( + root=root, + script_dir=script_dir, + robotwin_path=robotwin_path, + minicpm_python=minicpm_python, + robotwin_python=robotwin_python, + checkpoint=checkpoint, + embodiment_id=embodiment_id, + mode=args.mode, + run_name=args.run_name, + seed=args.seed, + device=os.environ.get("MINICPM_DEVICE", "cuda"), + host="127.0.0.1", + server_timeout=args.server_timeout, + output_root=output_root, + dry_run=False, + ) + write_manifests(config, slots, tasks, log_dir) + coordinator = Coordinator(config, slots, tasks, log_dir) + + interrupted_signal = 0 + + def handle_signal(signum, frame) -> None: + del frame + nonlocal interrupted_signal + interrupted_signal = signum + print( + f"[WARN] Received signal {signum}; stopping all RoboTwin jobs.", + file=sys.stderr, + ) + coordinator.stop_all() + + previous_handlers = {} + for signum in (signal.SIGINT, signal.SIGTERM, signal.SIGHUP): + previous_handlers[signum] = signal.signal(signum, handle_signal) + + try: + threads = [ + threading.Thread( + target=coordinator.worker, + args=(slot,), + name=f"robotwin-slot-{slot.index}", + ) + for slot in slots + ] + for thread in threads: + thread.start() + for thread in threads: + thread.join() + finally: + coordinator.stop_all() + for signum, handler in previous_handlers.items(): + signal.signal(signum, handler) + + if interrupted_signal: + return 128 + interrupted_signal + if coordinator.internal_errors or not coordinator.work.empty(): + for error in coordinator.internal_errors: + print(f"[ERROR] {error}", file=sys.stderr) + if not coordinator.work.empty(): + print( + f"[ERROR] {coordinator.work.qsize()} RoboTwin tasks were not run.", + file=sys.stderr, + ) + return 1 + if coordinator.failures: + print( + "[ERROR] RoboTwin evaluation failures: " + + ", ".join(coordinator.failures), + file=sys.stderr, + ) + print(f"[ERROR] Logs are under {log_dir}", file=sys.stderr) + return 1 + print("[INFO] RoboTwin evaluation finished successfully.") + print(f"[INFO] Logs are under {log_dir}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/MiniCPM-RobotManip/evaluation/robotwin/model2robotwin_interface.py b/MiniCPM-RobotManip/evaluation/robotwin/model2robotwin_interface.py new file mode 100644 index 0000000..20f1249 --- /dev/null +++ b/MiniCPM-RobotManip/evaluation/robotwin/model2robotwin_interface.py @@ -0,0 +1,201 @@ +# Copyright 2025 starVLA community. All rights reserved. +# Licensed under the MIT License. +# Modifications Copyright 2026 The OpenBMB Team. + +"""RoboTwin policy interface backed by the MiniCPM WebSocket client.""" + +from __future__ import annotations + +from collections import deque +from typing import Any, Sequence + +import numpy as np +from PIL import Image + +from deployment.model_server.tools.websocket_policy_client import ( + WebsocketClientPolicy, +) + + +ROBOTWIN_ACTION_DIM = 14 +ACTION_REORDER = (0, 1, 2, 3, 4, 5, 12, 6, 7, 8, 9, 10, 11, 13) + + +class ModelClient: + """Adapt RoboTwin observations to MiniCPM's stateless policy protocol.""" + + def __init__( + self, + policy_setup: str = "robotwin", + horizon: int = 0, + image_size: Sequence[int] = (448, 448), + host: str = "127.0.0.1", + port: int = 10093, + action_mode: str = "abs", + ) -> None: + if action_mode != "abs": + raise ValueError( + "MiniCPM RoboTwin evaluation only supports action_mode='abs'; " + f"got {action_mode!r}" + ) + if len(image_size) != 2 or any(int(size) <= 0 for size in image_size): + raise ValueError( + f"image_size must contain two positive values, got {image_size!r}" + ) + if isinstance(horizon, bool) or not isinstance(horizon, int) or horizon < 0: + raise ValueError(f"horizon must be a non-negative integer, got {horizon!r}") + + self.client = WebsocketClientPolicy(host, port) + self.policy_setup = policy_setup + self.action_mode = action_mode + self.image_size = (int(image_size[0]), int(image_size[1])) + self.horizon = horizon + self.task_description: str | None = None + self.image_history: deque[Any] = deque(maxlen=horizon) + self.num_image_history = 0 + self.raw_actions: np.ndarray | None = None + + server_meta = self.client.get_server_metadata() + action_chunk_size = server_meta.get("action_chunk_size") + if ( + isinstance(action_chunk_size, bool) + or not isinstance(action_chunk_size, (int, np.integer)) + or int(action_chunk_size) <= 0 + ): + self.client.close() + raise RuntimeError( + "Server metadata must contain a positive integer action_chunk_size" + ) + self.action_chunk_size = int(action_chunk_size) + print( + f"*** policy_setup: {policy_setup}, action_mode: {action_mode}, " + f"image_size: {self.image_size}, server_meta: {server_meta} ***" + ) + + def reset(self, task_description: str) -> None: + self.task_description = task_description + self.image_history.clear() + self.num_image_history = 0 + self.raw_actions = None + + def step(self, example: dict[str, Any], step: int = 0) -> np.ndarray: + task_description = example.get("lang") + if not isinstance(task_description, str): + raise TypeError("example['lang'] must be a string") + + images = example.get("image") + if not isinstance(images, (list, tuple)) or not images: + raise ValueError("example['image'] must be a non-empty image sequence") + + if task_description != self.task_description: + self.reset(task_description) + + if step % self.action_chunk_size == 0 or self.raw_actions is None: + resized_images = [self._resize_image(image) for image in images] + # RoboTwin exposes a 14-D joint vector, while MiniCPM was trained with + # an 80-D state. Keep state absent so the server supplies zero80. + model_example = { + "lang": task_description, + "image": resized_images, + } + response = self.client.predict_action({"examples": [model_example]}) + self.raw_actions = self._validate_response(response) + + action_idx = step % self.action_chunk_size + current_action = self.raw_actions[action_idx] + return current_action[list(ACTION_REORDER)] + + def _validate_response(self, response: Any) -> np.ndarray: + if not isinstance(response, dict): + raise RuntimeError("Policy response must be a dict") + if response.get("ok") is not True: + raise RuntimeError(f"Policy request failed: {response.get('error', response)!r}") + + data = response.get("data") + if not isinstance(data, dict) or "actions" not in data: + raise RuntimeError("Policy response is missing data.actions") + + actions = np.asarray(data["actions"]) + if actions.ndim != 3 or actions.shape[0] != 1: + raise ValueError( + "Policy actions must have shape (1, action_chunk_size, D); " + f"got {actions.shape}" + ) + + raw_actions = actions[0] + if raw_actions.shape[0] != self.action_chunk_size: + raise ValueError( + "Policy action chunk does not match server metadata: " + f"expected {self.action_chunk_size}, got {raw_actions.shape[0]}" + ) + if raw_actions.shape[1] < ROBOTWIN_ACTION_DIM: + raise ValueError( + f"Policy action dimension must be at least {ROBOTWIN_ACTION_DIM}; " + f"got {raw_actions.shape[1]}" + ) + if not np.issubdtype(raw_actions.dtype, np.floating): + raise TypeError(f"Policy actions must be floating, got {raw_actions.dtype}") + if not np.isfinite(raw_actions).all(): + raise ValueError("Policy actions contain non-finite values") + return raw_actions + + def _resize_image(self, image: np.ndarray) -> np.ndarray: + if not isinstance(image, np.ndarray): + raise TypeError( + f"RoboTwin camera image must be a NumPy array, got {type(image)!r}" + ) + if image.ndim != 3 or image.shape[-1] != 3: + raise ValueError( + f"RoboTwin camera image must have shape HxWx3, got {image.shape}" + ) + if image.dtype != np.uint8: + raise ValueError( + f"RoboTwin camera image must have dtype uint8, got {image.dtype}" + ) + return np.asarray( + Image.fromarray(image).resize( + (self.image_size[1], self.image_size[0]) + ) + ) + + +def get_model(usr_args: dict[str, Any]) -> ModelClient: + """RoboTwin policy factory.""" + + action_mode = usr_args.get("action_mode", "abs") + if action_mode != "abs": + raise ValueError( + "MiniCPM RoboTwin evaluation only supports action_mode='abs'; " + f"got {action_mode!r}" + ) + return ModelClient( + host=usr_args.get("host", "127.0.0.1"), + port=int(usr_args.get("port", 10093)), + action_mode=action_mode, + ) + + +def reset_model(model: ModelClient) -> None: + """Reset action-chunk state between RoboTwin episodes.""" + + model.reset(task_description="") + + +def eval(TASK_ENV: Any, model: ModelClient, observation: dict[str, Any]) -> None: + """Run one RoboTwin policy step.""" + + instruction = TASK_ENV.get_instruction() + camera_observations = observation["observation"] + + # Training camera order: [head, left_wrist, right_wrist]. + images = [ + camera_observations["head_camera"]["rgb"], + camera_observations["left_camera"]["rgb"], + camera_observations["right_camera"]["rgb"], + ] + example = { + "lang": str(instruction), + "image": images, + } + action = model.step(example, step=TASK_ENV.take_action_cnt) + TASK_ENV.take_action(action) diff --git a/MiniCPM-RobotManip/evaluation/robotwin/probe_server.py b/MiniCPM-RobotManip/evaluation/robotwin/probe_server.py new file mode 100644 index 0000000..fdcf535 --- /dev/null +++ b/MiniCPM-RobotManip/evaluation/robotwin/probe_server.py @@ -0,0 +1,11 @@ +"""Backward-compatible RoboTwin readiness entry point.""" + +import sys + +from evaluation.common.probe_server import main + + +if __name__ == "__main__": + raise SystemExit( + main([*sys.argv[1:], "--min-action-dim", "14"]) + ) diff --git a/MiniCPM-RobotManip/evaluation/robotwin/requirements-client.txt b/MiniCPM-RobotManip/evaluation/robotwin/requirements-client.txt new file mode 100644 index 0000000..3d321db --- /dev/null +++ b/MiniCPM-RobotManip/evaluation/robotwin/requirements-client.txt @@ -0,0 +1,9 @@ +# Copyright 2025 starVLA community. All rights reserved. +# Licensed under the MIT License. +# Modifications Copyright 2026 The OpenBMB Team. + +# Install these in the external RoboTwin environment. RoboTwin itself provides +# NumPy; these packages implement image resizing and the MiniCPM wire client. +msgpack==1.1.2 +Pillow>=10 +websockets>=13.1,<14 diff --git a/MiniCPM-RobotManip/evaluation/robotwin/run_policy_server.sh b/MiniCPM-RobotManip/evaluation/robotwin/run_policy_server.sh new file mode 100755 index 0000000..68c07dd --- /dev/null +++ b/MiniCPM-RobotManip/evaluation/robotwin/run_policy_server.sh @@ -0,0 +1,85 @@ +#!/usr/bin/env bash +# Copyright 2025 starVLA community. All rights reserved. +# Licensed under the MIT License. +# Modifications Copyright 2026 The OpenBMB Team. + +set -euo pipefail + +usage() { + cat >&2 <<'EOF' +Usage: + bash run_policy_server.sh [gpu_id] [port] [host] [device] [default_embodiment_id] + + may be a Hugging Face Hub model ID or a local checkpoint directory. + +Environment defaults: + MINICPM_PYTHON python + ROBOTWIN_SERVER_GPU 0 + ROBOTWIN_SERVER_PORT 10093 + ROBOTWIN_POLICY_HOST 127.0.0.1 + MINICPM_DEVICE cuda + ROBOTWIN_DEFAULT_EMBODIMENT_ID Required unless passed as argument 6 +EOF +} + +if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then + usage + exit 0 +fi + +if (( $# < 1 || $# > 6 )); then + usage + exit 1 +fi + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +MINICPM_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" + +checkpoint="$1" +gpu_id="${2:-${ROBOTWIN_SERVER_GPU:-0}}" +port="${3:-${ROBOTWIN_SERVER_PORT:-10093}}" +host="${4:-${ROBOTWIN_POLICY_HOST:-127.0.0.1}}" +device="${5:-${MINICPM_DEVICE:-cuda}}" +default_embodiment_id="${6:-${ROBOTWIN_DEFAULT_EMBODIMENT_ID:-}}" +minicpm_python="${MINICPM_PYTHON:-python}" + +if [[ -e "${checkpoint}" ]]; then + if [[ ! -d "${checkpoint}" ]]; then + echo "Local checkpoint must be a directory: ${checkpoint}" >&2 + exit 1 + fi + checkpoint="$(cd "${checkpoint}" && pwd -P)" +elif [[ "${checkpoint}" == /* || "${checkpoint}" == ./* || "${checkpoint}" == ../* ]]; then + echo "Local checkpoint directory does not exist: ${checkpoint}" >&2 + exit 1 +fi +if ! command -v "${minicpm_python}" >/dev/null 2>&1; then + echo "MINICPM_PYTHON is not executable: ${minicpm_python}" >&2 + exit 1 +fi +if [[ ! "${port}" =~ ^[0-9]+$ ]] || (( port < 1 || port > 65535 )); then + echo "Invalid server port: ${port}" >&2 + exit 1 +fi +if [[ ! "${host}" =~ ^[A-Za-z0-9.-]+$ ]]; then + echo "Invalid server host: ${host}" >&2 + exit 1 +fi +if [[ -z "${default_embodiment_id}" || ! "${default_embodiment_id}" =~ ^[0-9]+$ ]]; then + echo "default_embodiment_id is required and must be a non-negative integer." >&2 + exit 1 +fi + +export CUDA_VISIBLE_DEVICES="${gpu_id}" +export PYTHONPATH="${MINICPM_ROOT}${PYTHONPATH:+:${PYTHONPATH}}" + +echo "[INFO] Starting MiniCPM RoboTwin policy server" +echo "[INFO] checkpoint=${checkpoint} gpu=${gpu_id} device=${device}" +echo "[INFO] endpoint=${host}:${port} default_embodiment_id=${default_embodiment_id}" + +exec "${minicpm_python}" -m deployment.model_server.server_policy \ + --checkpoint "${checkpoint}" \ + --device "${device}" \ + --host "${host}" \ + --port "${port}" \ + --default-embodiment-id "${default_embodiment_id}" diff --git a/MiniCPM-RobotManip/evaluation/robotwin/start_eval.sh b/MiniCPM-RobotManip/evaluation/robotwin/start_eval.sh new file mode 100755 index 0000000..6dd0b98 --- /dev/null +++ b/MiniCPM-RobotManip/evaluation/robotwin/start_eval.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +# Copyright 2025 starVLA community. All rights reserved. +# Licensed under the MIT License. +# Modifications Copyright 2026 The OpenBMB Team. + +set -euo pipefail + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +MINICPM_ROOT="$(cd -- "${SCRIPT_DIR}/../.." && pwd)" +LAUNCHER_PYTHON="${LAUNCHER_PYTHON:-python3}" + +export PYTHONPATH="${MINICPM_ROOT}${PYTHONPATH:+:${PYTHONPATH}}" +exec "${LAUNCHER_PYTHON}" -m evaluation.robotwin.launcher "$@" diff --git a/MiniCPM-RobotManip/evaluation/tests/__init__.py b/MiniCPM-RobotManip/evaluation/tests/__init__.py new file mode 100644 index 0000000..3acd2d3 --- /dev/null +++ b/MiniCPM-RobotManip/evaluation/tests/__init__.py @@ -0,0 +1 @@ +"""Tests for migrated simulation evaluators.""" diff --git a/MiniCPM-RobotManip/evaluation/tests/test_calvin_contract.py b/MiniCPM-RobotManip/evaluation/tests/test_calvin_contract.py new file mode 100644 index 0000000..afe3dea --- /dev/null +++ b/MiniCPM-RobotManip/evaluation/tests/test_calvin_contract.py @@ -0,0 +1,223 @@ +from __future__ import annotations + +import importlib +import json +import sys +import tempfile +import types +import unittest +from collections import defaultdict +from pathlib import Path +from unittest import mock + +import numpy as np + + +def _module(name: str, **attributes): + module = types.ModuleType(name) + for key, value in attributes.items(): + setattr(module, key, value) + return module + + +def _import_calvin_evaluator(): + fake_omega = types.SimpleNamespace( + load=lambda path: {}, + create=lambda value: value, + ) + fake_modules = { + "hydra": _module( + "hydra", + utils=types.SimpleNamespace(instantiate=lambda config, **kwargs: config), + ), + "calvin_agent": _module("calvin_agent"), + "calvin_agent.evaluation": _module("calvin_agent.evaluation"), + "calvin_agent.evaluation.utils": _module( + "calvin_agent.evaluation.utils", + collect_plan=lambda *args, **kwargs: None, + count_success=lambda results: [0.0] * 5, + get_env_state_for_initial_condition=lambda state: ( + np.zeros(1), + np.zeros(1), + ), + get_log_dir=lambda path: path, + print_and_save=lambda *args, **kwargs: None, + ), + "moviepy": _module("moviepy"), + "moviepy.editor": _module( + "moviepy.editor", + ImageSequenceClip=object, + ), + "omegaconf": _module("omegaconf", OmegaConf=fake_omega), + "termcolor": _module( + "termcolor", + colored=lambda text, color: text, + ), + "tqdm": _module("tqdm", tqdm=lambda iterable, **kwargs: iterable), + "tyro": _module("tyro", cli=lambda function: function), + } + with mock.patch.dict(sys.modules, fake_modules): + sys.modules.pop("evaluation.calvin.eval_calvin", None) + return importlib.import_module("evaluation.calvin.eval_calvin") + + +class FakeSharedModelClient: + def __init__(self, *args, **kwargs) -> None: + self.init_args = args + self.init_kwargs = kwargs + self.calls: list[tuple[dict, int]] = [] + + def step(self, example: dict, step: int = 0) -> dict: + self.calls.append((example, step)) + return { + "raw_action": { + "world_vector": np.array([1, 2, 3], dtype=np.float32), + "rotation_delta": np.array([4, 5, 6], dtype=np.float32), + "open_gripper": np.array([0.75], dtype=np.float32), + } + } + + +class CalvinContractTest(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.module = _import_calvin_evaluator() + + def test_two_view_policy_adapter_and_step_counter(self) -> None: + with mock.patch.object( + self.module, + "ModelClient", + FakeSharedModelClient, + ): + policy = self.module.CalvinPolicyClient( + host="127.0.0.1", + port=10093, + resize_size=448, + ) + + static = np.full((8, 8, 3), 10, dtype=np.uint8) + gripper = np.full((8, 8, 3), 20, dtype=np.uint8) + observation = { + "rgb_obs": { + "rgb_static": static, + "rgb_gripper": gripper, + } + } + + action0 = policy.step(observation, "open the drawer") + action1 = policy.step(observation, "open the drawer") + + np.testing.assert_array_equal(action0, [1, 2, 3, 4, 5, 6, 0.75]) + np.testing.assert_array_equal(action1, action0) + self.assertEqual( + [step for _, step in policy.client.calls], + [0, 1], + ) + sent = policy.client.calls[0][0] + self.assertEqual([int(view[0, 0, 0]) for view in sent["image"]], [10, 20]) + self.assertNotIn("state", sent) + + def test_rollout_converts_gripper_to_calvin_sign(self) -> None: + class FakePolicy: + plan = None + + def reset(self): + pass + + def step(self, obs, annotation): + del obs, annotation + return np.array([0, 0, 0, 0, 0, 0, -0.25], dtype=np.float32) + + class FakeEnv: + def __init__(self) -> None: + self.actions = [] + + def reset(self, **kwargs): + del kwargs + + def get_obs(self): + return {"rgb_obs": {"rgb_static": np.zeros((2, 2, 3))}} + + def get_info(self): + return {"before": True} + + def step(self, action): + self.actions.append(action.copy()) + return self.get_obs(), 0, False, {"after": True} + + class FakeOracle: + def get_task_info_for_set(self, start, current, tasks): + del start, current + return set(tasks) + + env = FakeEnv() + success = self.module.rollout( + env=env, + policy=FakePolicy(), + task_oracle=FakeOracle(), + subtask="open_drawer", + val_annotations={"open_drawer": ["Open the drawer."]}, + plans=defaultdict(list), + debug=False, + ) + + self.assertTrue(success) + self.assertEqual(float(env.actions[0][-1]), -1.0) + + def test_bundled_sequences_and_num_sequences_slice(self) -> None: + sequence_path = ( + Path(self.module.__file__).resolve().with_name("eval_sequences.json") + ) + sequences = json.loads(sequence_path.read_text(encoding="utf-8")) + self.assertEqual(len(sequences), 1000) + self.assertTrue(all(len(entry) == 2 for entry in sequences)) + self.assertTrue(all(len(entry[1]) == 5 for entry in sequences)) + + with tempfile.TemporaryDirectory() as temp_dir: + root = Path(temp_dir) + task_config = ( + root + / "callbacks" + / "rollout" + / "tasks" + / "new_playtable_tasks.yaml" + ) + annotation_config = ( + root / "annotations" / "new_playtable_validation.yaml" + ) + task_config.parent.mkdir(parents=True) + annotation_config.parent.mkdir(parents=True) + task_config.write_text("{}\n", encoding="utf-8") + annotation_config.write_text("{}\n", encoding="utf-8") + small_sequences = root / "sequences.json" + small_sequences.write_text( + json.dumps(sequences[:4]), + encoding="utf-8", + ) + + with ( + mock.patch.object( + self.module, + "evaluate_sequence", + return_value=1, + ) as evaluate_sequence, + mock.patch.object(self.module, "print_and_save") as print_and_save, + ): + results = self.module.evaluate_policy( + policy=object(), + env=object(), + epoch=0, + calvin_conf_path=root, + eval_sequences_path=small_sequences, + num_sequences=2, + eval_log_dir=root / "logs", + debug=True, + ) + + self.assertEqual(results, [1, 1]) + self.assertEqual(evaluate_sequence.call_count, 2) + self.assertEqual(len(print_and_save.call_args.args[1]), 2) + + +if __name__ == "__main__": + unittest.main() diff --git a/MiniCPM-RobotManip/evaluation/tests/test_launchers.py b/MiniCPM-RobotManip/evaluation/tests/test_launchers.py new file mode 100644 index 0000000..ea6729c --- /dev/null +++ b/MiniCPM-RobotManip/evaluation/tests/test_launchers.py @@ -0,0 +1,797 @@ +from __future__ import annotations + +import ast +import json +import os +import socket +import stat +import subprocess +import sys +import tempfile +import textwrap +import time +import unittest +from pathlib import Path +from unittest import mock + + +MINICPM_ROOT = Path(__file__).resolve().parents[2] + + +def run(command, *, env=None, timeout=30): + merged_env = dict(os.environ) + if env: + merged_env.update({key: str(value) for key, value in env.items()}) + return subprocess.run( + command, + cwd=MINICPM_ROOT, + env=merged_env, + capture_output=True, + text=True, + timeout=timeout, + ) + + +def available_port_range(count: int) -> int: + for base in range(21000, 61000): + sockets = [] + try: + for offset in range(count): + sock = socket.socket() + sockets.append(sock) + sock.bind(("127.0.0.1", base + offset)) + except OSError: + pass + else: + return base + finally: + for sock in sockets: + sock.close() + raise RuntimeError(f"Unable to reserve {count} consecutive test ports") + + +def assert_ports_available(test: unittest.TestCase, base: int, count: int) -> None: + for offset in range(count): + with socket.socket() as sock: + sock.settimeout(0.2) + test.assertNotEqual( + sock.connect_ex(("127.0.0.1", base + offset)), + 0, + f"Port {base + offset} still has a live listener", + ) + + +def process_is_running(pid: int) -> bool: + stat_path = Path(f"/proc/{pid}/stat") + try: + fields = stat_path.read_text(encoding="utf-8").split() + except FileNotFoundError: + return False + return len(fields) > 2 and fields[2] != "Z" + + +def write_executable(path: Path, content: str) -> None: + path.write_text(content, encoding="utf-8") + path.chmod(path.stat().st_mode | stat.S_IXUSR) + + +def make_fake_minicpm_python(root: Path) -> Path: + fake_server = root / "fake_server.py" + fake_server.write_text( + textwrap.dedent( + """ + import argparse + from deployment.model_server import protocol + from deployment.model_server.tools.websocket_policy_server import WebsocketPolicyServer + + parser = argparse.ArgumentParser() + parser.add_argument("--checkpoint", required=True) + parser.add_argument("--device") + parser.add_argument("--host", default="127.0.0.1") + parser.add_argument("--port", type=int, required=True) + parser.add_argument("--default-embodiment-id", type=int, default=0) + args, _ = parser.parse_known_args() + + class FakePolicy: + def predict_action(self, **payload): + raise RuntimeError("readiness-only fake policy") + + metadata = protocol.build_server_metadata( + { + "action_chunk_size": 30, + "action_dim": 80, + "state_dim": 80, + "default_embodiment_id": args.default_embodiment_id, + "action_normalization": "none", + "actions_ready_for_execution": True, + }, + checkpoint=args.checkpoint, + ) + WebsocketPolicyServer( + FakePolicy(), + host=args.host, + port=args.port, + metadata=metadata, + ).serve_forever() + """ + ), + encoding="utf-8", + ) + wrapper = root / "fake_minicpm_python" + write_executable( + wrapper, + textwrap.dedent( + f"""\ + #!{sys.executable} + import os + import sys + + if sys.argv[1:3] == ["-m", "deployment.model_server.server_policy"]: + os.execv({sys.executable!r}, [{sys.executable!r}, {str(fake_server)!r}, *sys.argv[3:]]) + os.execv({sys.executable!r}, [{sys.executable!r}, *sys.argv[1:]]) + """ + ), + ) + return wrapper + + +def make_fake_libero_python(root: Path) -> Path: + wrapper = root / "fake_libero_python" + write_executable( + wrapper, + textwrap.dedent( + f"""\ + #!{sys.executable} + import json + import os + import sys + import time + + if sys.argv[1:3] == ["-m", "evaluation.libero.eval_libero"]: + port = sys.argv[sys.argv.index("--args.port") + 1] + trace = os.environ["FAKE_EVAL_TRACE"] + with open(trace, "a", encoding="utf-8") as file: + file.write(json.dumps({{"event": "start", "port": port, "time": time.time()}}) + "\\n") + expected = int(os.environ.get("FAKE_EVAL_EXPECTED", "1")) + deadline = time.time() + 10 + while time.time() < deadline: + with open(trace, encoding="utf-8") as file: + started = sum('"event": "start"' in line for line in file) + if started >= expected: + break + time.sleep(0.05) + else: + raise SystemExit(8) + time.sleep(float(os.environ.get("FAKE_EVAL_SLEEP", "0.3"))) + with open(trace, "a", encoding="utf-8") as file: + file.write(json.dumps({{"event": "end", "port": port, "time": time.time()}}) + "\\n") + if port == os.environ.get("FAKE_FAIL_PORT"): + raise SystemExit(7) + raise SystemExit(0) + os.execv({sys.executable!r}, [{sys.executable!r}, *sys.argv[1:]]) + """ + ), + ) + return wrapper + + +def make_fake_calvin_python(root: Path) -> Path: + wrapper = root / "fake_calvin_python" + write_executable( + wrapper, + textwrap.dedent( + f"""\ + #!{sys.executable} + import os + import subprocess + import sys + import time + + if sys.argv[1:3] == ["-m", "evaluation.calvin.eval_calvin"]: + child = subprocess.Popen([{sys.executable!r}, "-c", "import time; time.sleep(30)"]) + with open(os.environ["FAKE_CALVIN_PID_FILE"], "w", encoding="utf-8") as file: + file.write(str(child.pid)) + time.sleep(30) + raise SystemExit(0) + os.execv({sys.executable!r}, [{sys.executable!r}, *sys.argv[1:]]) + """ + ), + ) + return wrapper + + +def read_intervals(trace_path: Path): + records = [ + json.loads(line) + for line in trace_path.read_text(encoding="utf-8").splitlines() + ] + starts = {record["port"]: record["time"] for record in records if record["event"] == "start"} + ends = {record["port"]: record["time"] for record in records if record["event"] == "end"} + return starts, ends + + +class LauncherStaticTest(unittest.TestCase): + def test_all_shell_scripts_parse_and_help(self) -> None: + scripts = [ + "evaluation/libero/eval_libero.sh", + "evaluation/libero/run_policy_server.sh", + "evaluation/libero/auto_eval_scripts/eval_libero_parallel.sh", + "evaluation/libero/auto_eval_scripts/auto_eval_libero.sh", + "evaluation/calvin/eval_calvin.sh", + "evaluation/calvin/run_policy_server.sh", + "evaluation/robotwin/eval.sh", + "evaluation/robotwin/run_policy_server.sh", + "evaluation/robotwin/start_eval.sh", + ] + for script in scripts: + with self.subTest(script=script, mode="syntax"): + result = run(["bash", "-n", script]) + self.assertEqual(result.returncode, 0, result.stderr) + with self.subTest(script=script, mode="help"): + result = run(["bash", script, "--help"]) + self.assertEqual( + result.returncode, + 0, + f"stdout={result.stdout}\nstderr={result.stderr}", + ) + + def test_multi_gpu_dry_runs(self) -> None: + libero = run( + [ + "bash", + "evaluation/libero/auto_eval_scripts/auto_eval_libero.sh", + "--dry-run", + "--checkpoint", + "fake/model", + ], + env={ + "GPU_LIST": "2 5", + "TASK_SUITES": "libero_goal libero_object", + "RUN_ID": "dry-run", + "EMBODIMENT_ID": "0", + }, + ) + self.assertEqual(libero.returncode, 0, libero.stderr) + self.assertIn("slot=0 gpu=2 egl=2 port=10093", libero.stdout) + self.assertIn("slot=1 gpu=5 egl=5 port=10094", libero.stdout) + + robotwin = run( + [ + "bash", + "evaluation/robotwin/start_eval.sh", + "--mode", + "demo_clean", + "--run-name", + "dry-run", + "--checkpoint", + "fake/model", + "--default-embodiment-id", + "0", + "--dry-run", + "adjust_bottle,open_laptop", + ], + env={ + "CUDA_VISIBLE_DEVICES": "2,5", + "MINICPM_PYTHON": sys.executable, + "ROBOTWIN_PYTHON": sys.executable, + }, + ) + self.assertEqual(robotwin.returncode, 0, robotwin.stderr) + self.assertIn("slot=0 gpu=2", robotwin.stdout) + self.assertIn("slot=1 gpu=5", robotwin.stdout) + self.assertIn("FIFO[2]=open_laptop", robotwin.stdout) + + missing_libero_id = run( + [ + "bash", + "evaluation/libero/auto_eval_scripts/auto_eval_libero.sh", + "--dry-run", + "--checkpoint", + "fake/model", + ], + env={"EMBODIMENT_ID": ""}, + ) + self.assertNotEqual(missing_libero_id.returncode, 0) + self.assertIn("EMBODIMENT_ID is required", missing_libero_id.stderr) + + missing_robotwin_id = run( + [ + "bash", + "evaluation/robotwin/start_eval.sh", + "--mode", + "demo_clean", + "--run-name", + "dry-run", + "--checkpoint", + "fake/model", + "--dry-run", + "adjust_bottle", + ], + env={ + "MINICPM_PYTHON": sys.executable, + "ROBOTWIN_PYTHON": sys.executable, + "ROBOTWIN_DEFAULT_EMBODIMENT_ID": "", + }, + ) + self.assertNotEqual(missing_robotwin_id.returncode, 0) + self.assertIn( + "--default-embodiment-id is required", + missing_robotwin_id.stderr, + ) + + def test_python38_syntax_and_gpu_disable(self) -> None: + from evaluation.robotwin import launcher + + for path in ( + MINICPM_ROOT / "evaluation" / "robotwin" / "launcher.py", + MINICPM_ROOT / "evaluation" / "robotwin" / "probe_server.py", + ): + ast.parse( + path.read_text(encoding="utf-8"), + filename=str(path), + feature_version=(3, 8), + ) + + with mock.patch.dict( + os.environ, + {"CUDA_VISIBLE_DEVICES": "-1"}, + clear=False, + ): + with self.assertRaisesRegex(ValueError, "disables all GPUs"): + launcher.detect_cuda_devices() + + def test_worker_internal_error_is_recorded(self) -> None: + from evaluation.robotwin import launcher + + with tempfile.TemporaryDirectory() as temp_dir: + root = Path(temp_dir) + config = launcher.Config( + root=MINICPM_ROOT, + script_dir=MINICPM_ROOT / "evaluation" / "robotwin", + robotwin_path=None, + minicpm_python=sys.executable, + robotwin_python=sys.executable, + checkpoint="fake/model", + embodiment_id=0, + mode="demo_clean", + run_name="test", + seed=0, + device="cuda", + host="127.0.0.1", + server_timeout=1, + output_root=root, + dry_run=False, + ) + coordinator = launcher.Coordinator( + config, + [launcher.Slot(0, "0", 10093)], + ["adjust_bottle"], + root, + ) + with mock.patch.object( + coordinator, + "run_task", + side_effect=OSError("manifest unavailable"), + ): + coordinator.worker(launcher.Slot(0, "0", 10093)) + + self.assertEqual(coordinator.work.qsize(), 0) + self.assertEqual(len(coordinator.internal_errors), 1) + self.assertIn("manifest unavailable", coordinator.internal_errors[0]) + + def test_cleanup_kills_children_after_group_leader_exits(self) -> None: + from evaluation.common.launcher_utils import terminate_process_group + + with tempfile.TemporaryDirectory() as temp_dir: + root = Path(temp_dir) + child_pid_file = root / "child.pid" + script = root / "leader.py" + script.write_text( + textwrap.dedent( + """\ + import os + import subprocess + import sys + + child = subprocess.Popen( + [sys.executable, "-c", "import time; time.sleep(30)"] + ) + with open(os.environ["CHILD_PID_FILE"], "w", encoding="utf-8") as file: + file.write(str(child.pid)) + raise SystemExit(7) + """ + ), + encoding="utf-8", + ) + process = subprocess.Popen( + [sys.executable, str(script)], + env={**os.environ, "CHILD_PID_FILE": str(child_pid_file)}, + start_new_session=True, + ) + self.assertEqual(process.wait(timeout=5), 7) + child_pid = int(child_pid_file.read_text(encoding="utf-8")) + self.assertTrue(process_is_running(child_pid)) + terminate_process_group(process) + for _ in range(50): + if not process_is_running(child_pid): + break + time.sleep(0.1) + else: + self.fail(f"orphaned process-group child PID {child_pid}") + + +class MultiGpuLauncherIntegrationTest(unittest.TestCase): + def test_libero_jobs_overlap_and_failure_propagates(self) -> None: + with tempfile.TemporaryDirectory() as temp_dir: + root = Path(temp_dir) + minicpm_python = make_fake_minicpm_python(root) + libero_python = make_fake_libero_python(root) + libero_home = root / "LIBERO" + libero_home.mkdir() + trace = root / "trace.jsonl" + base_port = available_port_range(2) + environment = { + "MINICPM_PYTHON": minicpm_python, + "LIBERO_PYTHON": libero_python, + "LIBERO_HOME": libero_home, + "OUTPUT_ROOT": root / "outputs", + "GPU_LIST": "0 1", + "TASK_SUITES": "libero_goal libero_object", + "BASE_PORT": base_port, + "SERVER_TIMEOUT": 10, + "EMBODIMENT_ID": 0, + "RUN_ID": "parallel-test", + "FAKE_EVAL_TRACE": trace, + "FAKE_EVAL_EXPECTED": 2, + "FAKE_EVAL_SLEEP": 0.2, + } + command = [ + "bash", + "evaluation/libero/auto_eval_scripts/auto_eval_libero.sh", + "--checkpoint", + "fake/model", + ] + result = run(command, env=environment, timeout=30) + self.assertEqual( + result.returncode, + 0, + f"stdout={result.stdout}\nstderr={result.stderr}", + ) + starts, ends = read_intervals(trace) + self.assertEqual(len(starts), 2) + self.assertLess(max(starts.values()), min(ends.values())) + assert_ports_available(self, base_port, 2) + + trace.write_text("", encoding="utf-8") + failure_environment = dict(environment) + failure_environment.update( + { + "GPU_LIST": "0", + "TASK_SUITES": "libero_goal", + "BASE_PORT": available_port_range(1), + "RUN_ID": "failure-test", + "FAKE_FAIL_PORT": str(available_port_range(1)), + "FAKE_EVAL_EXPECTED": 1, + "FAKE_EVAL_SLEEP": 0.1, + } + ) + failure_environment["BASE_PORT"] = failure_environment["FAKE_FAIL_PORT"] + failed = run(command, env=failure_environment, timeout=30) + self.assertNotEqual(failed.returncode, 0) + self.assertIn("jobs failed", failed.stderr) + assert_ports_available( + self, + int(failure_environment["BASE_PORT"]), + 1, + ) + + def test_robotwin_tasks_overlap_and_write_manifests(self) -> None: + with tempfile.TemporaryDirectory() as temp_dir: + root = Path(temp_dir) + minicpm_python = make_fake_minicpm_python(root) + robotwin_root = root / "RoboTwin" + script_dir = robotwin_root / "script" + script_dir.mkdir(parents=True) + trace = root / "robotwin-trace.jsonl" + (script_dir / "eval_policy.py").write_text( + textwrap.dedent( + """\ + import json + import os + import sys + import time + + config_path = sys.argv[sys.argv.index("--config") + 1] + port = "" + with open(config_path, encoding="utf-8") as file: + for line in file: + if line.startswith("port:"): + port = line.split(":", 1)[1].strip() + trace = os.environ["FAKE_ROBOTWIN_TRACE"] + with open(trace, "a", encoding="utf-8") as file: + file.write(json.dumps({"event": "start", "port": port, "time": time.time()}) + "\\n") + expected = int(os.environ.get("FAKE_ROBOTWIN_EXPECTED", "1")) + deadline = time.time() + 10 + while time.time() < deadline: + with open(trace, encoding="utf-8") as file: + started = sum('"event": "start"' in line for line in file) + if started >= expected: + break + time.sleep(0.05) + else: + raise SystemExit(8) + time.sleep(0.2) + with open(trace, "a", encoding="utf-8") as file: + file.write(json.dumps({"event": "end", "port": port, "time": time.time()}) + "\\n") + print("Success rate: 1.0", flush=True) + """ + ), + encoding="utf-8", + ) + base_port = available_port_range(2) + output_root = root / "outputs" + result = run( + [ + "bash", + "evaluation/robotwin/start_eval.sh", + "--mode", + "demo_clean", + "--run-name", + "integration", + "--checkpoint", + "fake/model", + "--base-port", + str(base_port), + "--server-timeout", + "10", + "--default-embodiment-id", + "0", + "adjust_bottle", + "open_laptop", + ], + env={ + "CUDA_VISIBLE_DEVICES": "0,1", + "MINICPM_PYTHON": minicpm_python, + "ROBOTWIN_PYTHON": sys.executable, + "ROBOTWIN_PATH": robotwin_root, + "OUTPUT_ROOT": output_root, + "FAKE_ROBOTWIN_TRACE": trace, + "FAKE_ROBOTWIN_EXPECTED": 2, + }, + timeout=30, + ) + self.assertEqual( + result.returncode, + 0, + f"stdout={result.stdout}\nstderr={result.stderr}", + ) + starts, ends = read_intervals(trace) + self.assertEqual(len(starts), 2) + self.assertLess(max(starts.values()), min(ends.values())) + run_dirs = [path for path in output_root.iterdir() if path.is_dir()] + self.assertEqual(len(run_dirs), 1) + status_lines = ( + run_dirs[0] / "status.tsv" + ).read_text(encoding="utf-8").splitlines() + self.assertEqual(len(status_lines), 3) + self.assertTrue(all(line.endswith("\t0") for line in status_lines[1:])) + self.assertTrue((run_dirs[0] / "run_manifest.tsv").is_file()) + self.assertTrue((run_dirs[0] / "schedule.tsv").is_file()) + assert_ports_available(self, base_port, 2) + + def test_libero_sigterm_reaps_server_and_evaluator(self) -> None: + with tempfile.TemporaryDirectory() as temp_dir: + root = Path(temp_dir) + minicpm_python = make_fake_minicpm_python(root) + libero_python = make_fake_libero_python(root) + libero_home = root / "LIBERO" + libero_home.mkdir() + trace = root / "signal-trace.jsonl" + base_port = available_port_range(1) + environment = dict(os.environ) + environment.update( + { + "MINICPM_PYTHON": str(minicpm_python), + "LIBERO_PYTHON": str(libero_python), + "LIBERO_HOME": str(libero_home), + "OUTPUT_ROOT": str(root / "outputs"), + "GPU_LIST": "0", + "TASK_SUITES": "libero_goal", + "BASE_PORT": str(base_port), + "SERVER_TIMEOUT": "10", + "EMBODIMENT_ID": "0", + "RUN_ID": "signal-test", + "FAKE_EVAL_TRACE": str(trace), + "FAKE_EVAL_SLEEP": "30", + } + ) + process = subprocess.Popen( + [ + "bash", + "evaluation/libero/auto_eval_scripts/auto_eval_libero.sh", + "--checkpoint", + "fake/model", + ], + cwd=MINICPM_ROOT, + env=environment, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) + try: + for _ in range(150): + if trace.is_file() and '"event": "start"' in trace.read_text( + encoding="utf-8" + ): + break + if process.poll() is not None: + break + time.sleep(0.1) + self.assertIsNone( + process.poll(), + "launcher exited before the fake evaluator started", + ) + process.terminate() + stdout, stderr = process.communicate(timeout=25) + self.assertNotEqual( + process.returncode, + 0, + f"stdout={stdout}\nstderr={stderr}", + ) + assert_ports_available(self, base_port, 1) + finally: + if process.poll() is None: + process.kill() + process.communicate(timeout=5) + + def test_calvin_sigterm_reaps_evaluator_descendants(self) -> None: + with tempfile.TemporaryDirectory() as temp_dir: + root = Path(temp_dir) + minicpm_python = make_fake_minicpm_python(root) + calvin_python = make_fake_calvin_python(root) + calvin_root = root / "CALVIN" + dataset = root / "dataset" + calvin_root.mkdir() + dataset.mkdir() + child_pid_file = root / "calvin-child.pid" + base_port = available_port_range(1) + environment = dict(os.environ) + environment.update( + { + "MINICPM_PYTHON": str(minicpm_python), + "CALVIN_PYTHON": str(calvin_python), + "CALVIN_ROOT": str(calvin_root), + "CALVIN_DATASET_PATH": str(dataset), + "CHECKPOINT": "fake/model", + "EMBODIMENT_ID": "0", + "PORT": str(base_port), + "READY_TIMEOUT": "10", + "OUTPUT_ROOT": str(root / "outputs"), + "FAKE_CALVIN_PID_FILE": str(child_pid_file), + } + ) + process = subprocess.Popen( + ["bash", "evaluation/calvin/eval_calvin.sh"], + cwd=MINICPM_ROOT, + env=environment, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) + try: + for _ in range(150): + if child_pid_file.is_file(): + break + if process.poll() is not None: + break + time.sleep(0.1) + self.assertTrue( + child_pid_file.is_file(), + "fake CALVIN evaluator didn't start its child process", + ) + child_pid = int(child_pid_file.read_text(encoding="utf-8")) + process.terminate() + stdout, stderr = process.communicate(timeout=25) + self.assertNotEqual( + process.returncode, + 0, + f"stdout={stdout}\nstderr={stderr}", + ) + for _ in range(50): + if not process_is_running(child_pid): + break + time.sleep(0.1) + else: + self.fail(f"CALVIN evaluator child PID {child_pid} leaked") + assert_ports_available(self, base_port, 1) + finally: + if process.poll() is None: + process.kill() + process.communicate(timeout=5) + + def test_robotwin_sigterm_reaps_server_and_evaluator(self) -> None: + with tempfile.TemporaryDirectory() as temp_dir: + root = Path(temp_dir) + minicpm_python = make_fake_minicpm_python(root) + robotwin_root = root / "RoboTwin" + script_dir = robotwin_root / "script" + script_dir.mkdir(parents=True) + started_file = root / "robotwin-started" + (script_dir / "eval_policy.py").write_text( + textwrap.dedent( + """\ + import os + import time + from pathlib import Path + + Path(os.environ["FAKE_ROBOTWIN_STARTED"]).write_text( + "started", encoding="utf-8" + ) + time.sleep(30) + """ + ), + encoding="utf-8", + ) + base_port = available_port_range(1) + environment = dict(os.environ) + environment.update( + { + "CUDA_VISIBLE_DEVICES": "0", + "MINICPM_PYTHON": str(minicpm_python), + "ROBOTWIN_PYTHON": sys.executable, + "ROBOTWIN_PATH": str(robotwin_root), + "OUTPUT_ROOT": str(root / "outputs"), + "FAKE_ROBOTWIN_STARTED": str(started_file), + } + ) + process = subprocess.Popen( + [ + "bash", + "evaluation/robotwin/start_eval.sh", + "--mode", + "demo_clean", + "--run-name", + "signal", + "--checkpoint", + "fake/model", + "--base-port", + str(base_port), + "--server-timeout", + "10", + "--default-embodiment-id", + "0", + "adjust_bottle", + ], + cwd=MINICPM_ROOT, + env=environment, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) + try: + for _ in range(150): + if started_file.is_file(): + break + if process.poll() is not None: + break + time.sleep(0.1) + self.assertTrue( + started_file.is_file(), + "fake RoboTwin evaluator didn't start", + ) + process.terminate() + stdout, stderr = process.communicate(timeout=25) + self.assertNotEqual( + process.returncode, + 0, + f"stdout={stdout}\nstderr={stderr}", + ) + assert_ports_available(self, base_port, 1) + finally: + if process.poll() is None: + process.kill() + process.communicate(timeout=5) + + +if __name__ == "__main__": + unittest.main() diff --git a/MiniCPM-RobotManip/evaluation/tests/test_policy_clients.py b/MiniCPM-RobotManip/evaluation/tests/test_policy_clients.py new file mode 100644 index 0000000..8c7b903 --- /dev/null +++ b/MiniCPM-RobotManip/evaluation/tests/test_policy_clients.py @@ -0,0 +1,325 @@ +from __future__ import annotations + +import unittest +from unittest import mock + +import numpy as np + + +def action_chunk() -> np.ndarray: + steps = np.arange(30, dtype=np.float32)[:, None] * 100 + dims = np.arange(80, dtype=np.float32)[None, :] + return (steps + dims)[None, ...] + + +class FakeWireClient: + def __init__(self, *args, **kwargs) -> None: + del args, kwargs + self.calls: list[dict] = [] + self.closed = False + self.actions = action_chunk() + + def get_server_metadata(self) -> dict: + return { + "action_chunk_size": 30, + "action_normalization": "none", + "actions_ready_for_execution": True, + } + + def predict_action(self, payload: dict) -> dict: + self.calls.append(payload) + return { + "ok": True, + "status": "ok", + "type": "inference_result", + "data": {"actions": self.actions}, + } + + def close(self) -> None: + self.closed = True + + +class LiberoModelClientTest(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + try: + from evaluation.libero import model2libero_interface + except ModuleNotFoundError as exc: + raise unittest.SkipTest(f"LIBERO client dependency unavailable: {exc}") from exc + cls.module = model2libero_interface + + def test_two_views_and_chunk_cache_without_normalization_fields(self) -> None: + fake = FakeWireClient() + with mock.patch.object( + self.module, + "WebsocketClientPolicy", + return_value=fake, + ): + client = self.module.ModelClient( + action_ensemble=False, + image_size=(8, 8), + ) + + first_view = np.full((8, 8, 3), 11, dtype=np.uint8) + wrist_view = np.full((8, 8, 3), 22, dtype=np.uint8) + example = { + "image": [first_view, wrist_view], + "lang": "pick up the block", + } + + first = client.step(example, step=0)["raw_action"] + second = client.step(example, step=1)["raw_action"] + client.step(example, step=30) + + self.assertEqual(len(fake.calls), 2) + payload = fake.calls[0] + self.assertEqual(set(payload), {"examples"}) + sent_example = payload["examples"][0] + self.assertEqual(set(sent_example), {"image", "lang"}) + np.testing.assert_array_equal(sent_example["image"][0], first_view) + np.testing.assert_array_equal(sent_example["image"][1], wrist_view) + np.testing.assert_array_equal(first["world_vector"], [0, 1, 2]) + np.testing.assert_array_equal(first["rotation_delta"], [3, 4, 5]) + np.testing.assert_array_equal(first["open_gripper"], [6]) + np.testing.assert_array_equal(second["world_vector"], [100, 101, 102]) + + def test_rejects_unsuccessful_or_nonfinite_response(self) -> None: + fake = FakeWireClient() + with mock.patch.object( + self.module, + "WebsocketClientPolicy", + return_value=fake, + ): + client = self.module.ModelClient( + action_ensemble=False, + image_size=(8, 8), + ) + + example = { + "image": [np.zeros((8, 8, 3), dtype=np.uint8)], + "lang": "test", + } + fake.predict_action = lambda payload: { + "ok": False, + "error": {"message": "failed"}, + } + with self.assertRaisesRegex(RuntimeError, "ok=true"): + client.step(example, step=0) + + fake.predict_action = lambda payload: { + "ok": True, + "data": { + "actions": np.full((1, 30, 80), np.nan, dtype=np.float32) + }, + } + with self.assertRaisesRegex(ValueError, "finite"): + client.step(example, step=0) + + +class RoboTwinModelClientTest(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + try: + from evaluation.robotwin import model2robotwin_interface + except ModuleNotFoundError as exc: + raise unittest.SkipTest( + f"RoboTwin client dependency unavailable: {exc}" + ) from exc + cls.module = model2robotwin_interface + + def test_three_view_order_chunk_cache_and_action_reorder(self) -> None: + fake = FakeWireClient() + with mock.patch.object( + self.module, + "WebsocketClientPolicy", + return_value=fake, + ): + client = self.module.ModelClient(image_size=(8, 8)) + + class FakeTaskEnv: + take_action_cnt = 0 + + def __init__(self) -> None: + self.action = None + + def get_instruction(self): + return "stack the blocks" + + def take_action(self, action): + self.action = action + + task_env = FakeTaskEnv() + observation = { + "observation": { + "head_camera": { + "rgb": np.full((8, 8, 3), 1, dtype=np.uint8) + }, + "left_camera": { + "rgb": np.full((8, 8, 3), 2, dtype=np.uint8) + }, + "right_camera": { + "rgb": np.full((8, 8, 3), 3, dtype=np.uint8) + }, + }, + "joint_action": {"vector": np.arange(14, dtype=np.float32)}, + } + + self.module.eval(task_env, client, observation) + client.step( + { + "image": [ + observation["observation"]["head_camera"]["rgb"], + observation["observation"]["left_camera"]["rgb"], + observation["observation"]["right_camera"]["rgb"], + ], + "lang": "stack the blocks", + }, + step=1, + ) + client.step( + { + "image": [ + observation["observation"]["head_camera"]["rgb"], + observation["observation"]["left_camera"]["rgb"], + observation["observation"]["right_camera"]["rgb"], + ], + "lang": "stack the blocks", + }, + step=30, + ) + + self.assertEqual(len(fake.calls), 2) + sent = fake.calls[0]["examples"][0] + self.assertNotIn("state", sent) + self.assertEqual([int(view[0, 0, 0]) for view in sent["image"]], [1, 2, 3]) + np.testing.assert_array_equal( + task_env.action, + [0, 1, 2, 3, 4, 5, 12, 6, 7, 8, 9, 10, 11, 13], + ) + + def test_non_absolute_mode_and_bad_image_fail_fast(self) -> None: + fake = FakeWireClient() + with mock.patch.object( + self.module, + "WebsocketClientPolicy", + return_value=fake, + ): + with self.assertRaisesRegex(ValueError, "action_mode='abs'"): + self.module.ModelClient(action_mode="delta") + + client = self.module.ModelClient(image_size=(8, 8)) + + with self.assertRaisesRegex(ValueError, "dtype uint8"): + client.step( + { + "image": [np.zeros((8, 8, 3), dtype=np.float32)], + "lang": "test", + }, + step=0, + ) + + +class LegacyWebsocketsClientCompatibilityTest(unittest.TestCase): + def test_connect_without_proxy_parameter(self) -> None: + from deployment.model_server.tools import msgpack_numpy + from deployment.model_server.tools import websocket_policy_client + + class FakeConnection: + def __init__(self) -> None: + self.closed = False + + def recv(self, timeout=None): + del timeout + return msgpack_numpy.packb({"action_chunk_size": 30}) + + def close(self): + self.closed = True + + connection = FakeConnection() + + # Mirrors the websockets 13 connect signature relevant to this client: + # there is no `proxy` keyword. + def legacy_connect( + uri, + *, + compression, + max_size, + open_timeout, + ping_interval, + ): + del uri, compression, max_size, open_timeout, ping_interval + return connection + + with mock.patch.object( + websocket_policy_client.websockets.sync.client, + "connect", + legacy_connect, + ): + client = websocket_policy_client.WebsocketClientPolicy() + + self.assertEqual(client.get_server_metadata()["action_chunk_size"], 30) + client.close() + self.assertTrue(connection.closed) + + +class ReadinessProbeTest(unittest.TestCase): + def test_transport_exception_is_retryable_but_metadata_mismatch_is_not(self) -> None: + from evaluation.common import probe_server + + class FakeClient: + def __init__(self, metadata, *, fail_ping=False) -> None: + self.metadata = metadata + self.fail_ping = fail_ping + + def get_server_metadata(self): + return self.metadata + + def ping(self, **kwargs): + del kwargs + if self.fail_ping: + raise TimeoutError("transient timeout") + return {"ok": True, "type": "ping"} + + def close(self): + pass + + valid_metadata = { + "server": "minicpm_robot_manip", + "ckpt_path": "fake/model", + "default_embodiment_id": 0, + "action_normalization": "none", + "actions_ready_for_execution": True, + "action_dim": 80, + "action_chunk_size": 30, + } + arguments = [ + "--host", + "127.0.0.1", + "--port", + "10093", + "--checkpoint", + "fake/model", + "--embodiment-id", + "0", + "--min-action-dim", + "14", + ] + + with mock.patch.object( + probe_server, + "WebsocketClientPolicy", + return_value=FakeClient(valid_metadata, fail_ping=True), + ): + self.assertEqual(probe_server.main(arguments), 2) + + mismatched = dict(valid_metadata, ckpt_path="wrong/model") + with mock.patch.object( + probe_server, + "WebsocketClientPolicy", + return_value=FakeClient(mismatched), + ): + self.assertEqual(probe_server.main(arguments), 3) + + +if __name__ == "__main__": + unittest.main() diff --git a/README.md b/README.md index 6044a46..7eb15e3 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,22 @@ python vla_infer.py \ --text "Pick up the red block." \ --checkpoint ./checkpoint +### starVLA-compatible WebSocket server + +

The deployment server accepts one current frame with one or more synchronized camera views and is wire-compatible with the standard starVLA data.actions evaluators. The initial target integrations are LIBERO, CALVIN, and RoboTwin. MiniCPM actions are returned directly without normalization or un-normalization.

+ +
cd MiniCPM-RobotManip
+python -m deployment.model_server.server_policy \
+    --checkpoint ./checkpoint \
+    --device cuda \
+    --host 127.0.0.1 \
+    --port 10093 \
+    --default-embodiment-id 0
+ +

Current inference is stateless. Versioned session and stream message types are reserved for future native streaming-context support. See the deployment guide for the request schema, evaluator camera order, metadata, and extension contract.

+ +

Migrated LIBERO, CALVIN, and RoboTwin evaluators—including multi-GPU launchers for LIBERO and RoboTwin—are documented in the simulation evaluation guide. Simulator code, datasets, and assets remain external installations.

+ ## MiniCPM-RobotTrack MiniCPM-RobotTrack is a compact vision-language-action policy for embodied target tracking built on MiniCPM4-0.5B with following highlights: diff --git a/README_zh.md b/README_zh.md index d1a085d..be4908f 100644 --- a/README_zh.md +++ b/README_zh.md @@ -125,6 +125,22 @@ python vla_infer.py \ --text "Pick up the red block." \ --checkpoint ./checkpoint +### starVLA 兼容 WebSocket 服务 + +

部署服务接收一个当前时间帧以及该时刻的一个或多个同步相机视角,并兼容 starVLA 标准 data.actions 评测协议。首版优先适配 LIBERO、CALVIN 和 RoboTwin。MiniCPM 的 action 无需归一化或反归一化,服务端直接返回模型预测值。

+ +
cd MiniCPM-RobotManip
+python -m deployment.model_server.server_policy \
+    --checkpoint ./checkpoint \
+    --device cuda \
+    --host 127.0.0.1 \
+    --port 10093 \
+    --default-embodiment-id 0
+ +

当前推理是无状态的;协议已为未来原生流式上下文预留版本化 session/stream 消息类型。请求结构、评测相机顺序、metadata 和扩展契约详见部署指南

+ +

已迁移的 LIBERO、CALVIN、RoboTwin 评测代码,以及 LIBERO 和 RoboTwin 的多 GPU 启动脚本,详见仿真评测指南。仿真器代码、数据集与资产仍需外部安装。

+ ## MiniCPM-RobotTrack MiniCPM-RobotTrack 是基于 MiniCPM4-0.5B 的轻量视觉-语言-动作策略,面向具身目标跟踪,主要亮点如下: