diff --git a/.github/workflows/test_build.yml b/.github/workflows/test_build.yml index dc805c7a..26f7f571 100644 --- a/.github/workflows/test_build.yml +++ b/.github/workflows/test_build.yml @@ -467,6 +467,43 @@ jobs: with: name: test-scenario-execution-pybullet path: test_scenario_execution_pybullet/test.xml + test-scenario-execution-x11: + needs: [build] + runs-on: intellabs-01 + timeout-minutes: 3 + container: + image: ghcr.io/intellabs/scenario-execution:${{ github.event.pull_request.base.ref }} + credentials: + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + steps: + - name: Restore cache + uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 + with: + key: ${{ runner.os }}-build-${{ github.run_number }} + path: . + - name: Test Scenario Execution X11 + shell: bash + run: | + source /opt/ros/${{ github.event.pull_request.base.ref == 'main' && 'humble' || github.event.pull_request.base.ref }}/setup.bash + source install/setup.bash + sudo apt -y install mesa-utils + Xvfb :1 -screen 0 800x600x16 & + export DISPLAY=:1.0 + ros2 run scenario_execution_ros scenario_execution_ros libs/scenario_execution_x11/scenarios/example_capture.osc -t -o test_scenario_execution_x11 + find test_scenario_execution_x11 + - name: Upload result + uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4 + if: always() + with: + name: test-scenario-execution-x11 + path: test_scenario_execution_x11/test.xml + - name: Upload video + uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4 + if: always() + with: + name: test-scenario-execution-x11-video + path: test_scenario_execution_x11/capture.mp4 tests: needs: - test-scenario-execution @@ -482,6 +519,7 @@ jobs: - test-scenario-execution-gazebo - test-scenario-execution-nav2 - test-scenario-execution-pybullet + - test-scenario-execution-x11 runs-on: intellabs-01 if: ${{ always() }} permissions: diff --git a/docs/libraries.rst b/docs/libraries.rst index 9df4723d..a3f15014 100644 --- a/docs/libraries.rst +++ b/docs/libraries.rst @@ -24,6 +24,8 @@ Beside ``osc.standard`` provided by OpenSCENARIO 2 (which we divide into ``osc.s - Robotics Library (provided with :repo_link:`scenario_execution`) * - ``osc.ros`` - ROS Library (provided with :repo_link:`scenario_execution_ros`) + * - ``osc.x11`` + - X11 Library (provided with :repo_link:`libs/scenario_execution_x11`) Additional features can be implemented by defining your own library. @@ -33,11 +35,8 @@ Gazebo The library contains actions to interact with the `Gazebo Simulation `_. Import it with ``import osc.gazebo``. It's provided by the package :repo_link:`libs/scenario_execution_gazebo`. -Actions -^^^^^^^ - ``actor_exists()`` -"""""""""""""""""" +^^^^^^^^^^^^^^^^^^ Waits for an actor to exist within simulation. @@ -60,7 +59,7 @@ Waits for an actor to exist within simulation. - Gazebo world name ``osc_object.delete()`` -""""""""""""""""""""""" +^^^^^^^^^^^^^^^^^^^^^^^ Delete an object from the simulation. @@ -83,7 +82,7 @@ Delete an object from the simulation. - Gazebo world name ``osc_object.relative_spawn()`` -""""""""""""""""""""""""""""""" +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Spawn an actor relative to a given ``frame_id`` within simulation (at a specified ``distance`` in front of ``frame_id``). @@ -122,7 +121,7 @@ Spawn an actor relative to a given ``frame_id`` within simulation (at a specifie - (optional) Comma-separated list of argument key:=value pairs ``osc_object.spawn()`` -"""""""""""""""""""""" +^^^^^^^^^^^^^^^^^^^^^^ Spawn an actor within simulation. @@ -165,7 +164,8 @@ Spawn an actor within simulation. If the file ending is ``.xacro`` the model is forwarded to `xacro `__ before getting spawned. ``wait_for_sim()`` -"""""""""""""""""" +^^^^^^^^^^^^^^^^^^ + Wait for simulation to become active (checks for simulation clock). .. list-table:: @@ -192,62 +192,6 @@ Helpers The library contains basic helper methods. Import it with ``import osc.helpers``. -Actions -^^^^^^^ - -``log()`` -""""""""" - -For debugging purposes, log a string using the available log mechanism. - -.. list-table:: - :widths: 15 15 5 65 - :header-rows: 1 - :class: tight-table - - * - Parameter - - Type - - Default - - Description - * - ``msg`` - - ``string`` - - - - String to log - -``run_process()`` -""""""""""""""""" - -Run a process. Reports `running` while the process has not finished. - -If ``wait_for_shutdown`` is ``false`` and the process is still running on scenario shutdown, ``shutdown_signal`` is sent. If the process does not shutdown within shutdown_timeout, ``signal.sigkill`` is sent. - -.. list-table:: - :widths: 15 15 5 65 - :header-rows: 1 - :class: tight-table - - * - Parameter - - Type - - Default - - Description - * - ``command`` - - ``string`` - - - - Command to execute - * - ``wait_for_shutdown`` - - ``bool`` - - ``true`` - - Wait for the process to be finished. If false, the action immediately finishes - * - ``shutdown_signal`` - - ``signal`` - - ``signal!sigterm`` - - (Only used if ``wait_for_shutdown`` is ``false``) Signal that is sent if a process is still running on scenario shutdown - * - ``shutdown_timeout`` - - ``time`` - - ``10s`` - - (Only used if ``wait_for_shutdown`` is ``false``) time to wait between ``shutdown_signal`` and SIGKILL getting sent, if process is still running on scenario shutdown - - Modifiers ^^^^^^^^^ @@ -341,16 +285,66 @@ Be depressed, always fail. The tickling never ends... +``log()`` +^^^^^^^^^ + +For debugging purposes, log a string using the available log mechanism. + +.. list-table:: + :widths: 15 15 5 65 + :header-rows: 1 + :class: tight-table + + * - Parameter + - Type + - Default + - Description + * - ``msg`` + - ``string`` + - + - String to log + +``run_process()`` +^^^^^^^^^^^^^^^^^ + +Run a process. Reports `running` while the process has not finished. + +If ``wait_for_shutdown`` is ``false`` and the process is still running on scenario shutdown, ``shutdown_signal`` is sent. If the process does not shutdown within shutdown_timeout, ``signal.sigkill`` is sent. + +.. list-table:: + :widths: 15 15 5 65 + :header-rows: 1 + :class: tight-table + + * - Parameter + - Type + - Default + - Description + * - ``command`` + - ``string`` + - + - Command to execute + * - ``wait_for_shutdown`` + - ``bool`` + - ``true`` + - Wait for the process to be finished. If false, the action immediately finishes + * - ``shutdown_signal`` + - ``signal`` + - ``signal!sigterm`` + - (Only used if ``wait_for_shutdown`` is ``false``) Signal that is sent if a process is still running on scenario shutdown + * - ``shutdown_timeout`` + - ``time`` + - ``10s`` + - (Only used if ``wait_for_shutdown`` is ``false``) time to wait between ``shutdown_signal`` and SIGKILL getting sent, if process is still running on scenario shutdown + + Kubernetes ---------- The library contains actions to interact with the `Kubernetes API `_. Import it with ``import osc.kubernetes``. It's provided by the package :repo_link:`libs/scenario_execution_kubernetes`. -Actions -^^^^^^^ - ``kubernetes_create_from_yaml()`` -""""""""""""""""""""""""""""""""" +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Create a Kubernetes object (e.g., a pod or network policy) from a yaml file. @@ -378,7 +372,7 @@ Create a Kubernetes object (e.g., a pod or network policy) from a yaml file. ``kubernetes_delete()`` -""""""""""""""""""""""" +^^^^^^^^^^^^^^^^^^^^^^^ Delete a Kubernetes element (e.g., a pod or network policy). @@ -418,7 +412,7 @@ Delete a Kubernetes element (e.g., a pod or network policy). ``kubernetes_patch_network_policy()`` -""""""""""""""""""""""""""""""""""""" +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Patch an existing Kubernetes network policy. @@ -454,7 +448,7 @@ Patch an existing Kubernetes network policy. ``kubernetes_wait_for_network_policy_status()`` -""""""""""""""""""""""""""""""""""""""""""""""" +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Wait for an existing Kubernetes network policy to reach a specified state. @@ -486,7 +480,7 @@ Wait for an existing Kubernetes network policy to reach a specified state. ``kubernetes_wait_for_pod_status()`` -"""""""""""""""""""""""""""""""""""" +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Wait for a Kubernetes pod to reach a specified state. @@ -526,11 +520,8 @@ Nav2 The library contains actions to interact with the `Nav2 `__ navigation stack. Import it with ``import osc.nav2``. It is provided by the package :repo_link:`libs/scenario_execution_nav2`. -Actions -^^^^^^^ - ``differential_drive_robot.init_nav2()`` -"""""""""""""""""""""""""""""""""""""""" +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Initialize nav2. @@ -565,7 +556,7 @@ Initialize nav2. - If true the initial pose needs to be set externally (e.g. manually through rviz) ``differential_drive_robot.nav_through_poses()`` -"""""""""""""""""""""""""""""""""""""""""""""""" +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use nav2 to navigate through poses. @@ -592,7 +583,8 @@ Use nav2 to navigate through poses. - Action name ``differential_drive_robot.nav_to_pose()`` -"""""""""""""""""""""""""""""""""""""""""" +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + Use nav2 to navigate to goal pose. .. list-table:: @@ -623,11 +615,8 @@ OS The library contains actions to interact with the operating system. Import it with ``import osc.os``. It is provided by the package :repo_link:`libs/scenario_execution_os`. -Actions -^^^^^^^ - ``check_file_exists()`` -""""""""""""""""""""""" +^^^^^^^^^^^^^^^^^^^^^^^ Report success if a file exists. @@ -671,11 +660,10 @@ Actors """""""""""""""""""""""""""" A differential drive robot actor inheriting from the more general ``robot`` actor -Actions -^^^^^^^ ``action_call()`` -""""""""""""""""" +^^^^^^^^^^^^^^^^^ + Call a ROS action and wait for the result. .. list-table:: @@ -701,7 +689,8 @@ Call a ROS action and wait for the result. - Call content (e.g. ``{\"order\": 3}``) ``assert_lifecycle_state()`` -"""""""""""""""""""""""""""" +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + Checks for the state of a `lifecycle-managed `__ node. .. list-table:: @@ -736,7 +725,7 @@ Checks for the state of a `lifecycle-managed + + + scenario_execution_x11 + 1.2.0 + Scenario Execution library for X11 + Intel Labs + Intel Labs + Apache-2.0 + + scenario_execution + + ffmpeg + + + ament_python + + diff --git a/libs/scenario_execution_x11/resource/scenario_execution_x11 b/libs/scenario_execution_x11/resource/scenario_execution_x11 new file mode 100644 index 00000000..e69de29b diff --git a/libs/scenario_execution_x11/scenario_execution_x11/__init__.py b/libs/scenario_execution_x11/scenario_execution_x11/__init__.py new file mode 100644 index 00000000..ec44632b --- /dev/null +++ b/libs/scenario_execution_x11/scenario_execution_x11/__init__.py @@ -0,0 +1,21 @@ +# Copyright (C) 2024 Intel Corporation +# +# 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. +# +# SPDX-License-Identifier: Apache-2.0 + +from . import actions + +__all__ = [ + 'actions' +] diff --git a/libs/scenario_execution_x11/scenario_execution_x11/actions/__init__.py b/libs/scenario_execution_x11/scenario_execution_x11/actions/__init__.py new file mode 100644 index 00000000..3ba13780 --- /dev/null +++ b/libs/scenario_execution_x11/scenario_execution_x11/actions/__init__.py @@ -0,0 +1,15 @@ +# Copyright (C) 2024 Intel Corporation +# +# 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. +# +# SPDX-License-Identifier: Apache-2.0 diff --git a/libs/scenario_execution_x11/scenario_execution_x11/actions/capture_screen.py b/libs/scenario_execution_x11/scenario_execution_x11/actions/capture_screen.py new file mode 100644 index 00000000..432c49aa --- /dev/null +++ b/libs/scenario_execution_x11/scenario_execution_x11/actions/capture_screen.py @@ -0,0 +1,75 @@ +# Copyright (C) 2024 Intel Corporation +# +# 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. +# +# SPDX-License-Identifier: Apache-2.0 + +from enum import Enum +import py_trees +import os +from scenario_execution.actions.run_process import RunProcess + + +class CaptureScreenState(Enum): + IDLE = 1 + CAPTURING = 2 + DONE = 11 + FAILURE = 12 + + +class CaptureScreen(RunProcess): + + def __init__(self, output_filename: str, frame_rate: float): + super().__init__("", wait_for_shutdown=True) + self.current_state = None + self.output_dir = "." + + def setup(self, **kwargs): + if "DISPLAY" not in os.environ: + raise ValueError("capture_screen() requires environment variable 'DISPLAY' to be set.") + + if kwargs['output_dir']: + if not os.path.exists(kwargs['output_dir']): + raise ValueError( + f"Specified destination dir '{kwargs['output_dir']}' does not exist") + self.output_dir = kwargs['output_dir'] + + def execute(self, output_filename: str, frame_rate: float): # pylint: disable=arguments-differ + self.current_state = CaptureScreenState.IDLE + cmd = ["ffmpeg", + "-f", "x11grab", + "-draw_mouse", "0", + "-framerate", str(frame_rate), + "-i", os.environ["DISPLAY"], + "-c:v", "libx264", + "-preset", "veryfast", + "-f", "mp4", + "-nostdin", + "-y", os.path.join(self.output_dir, output_filename)] + self.set_command(cmd) + + def get_logger_stdout(self): + return self.logger.debug + + def get_logger_stderr(self): + return self.logger.debug + + def on_executed(self): + self.current_state = CaptureScreenState.CAPTURING + self.feedback_message = f"Capturing screen..." # pylint: disable= attribute-defined-outside-init + + def on_process_finished(self, ret): + if self.current_state == CaptureScreenState.CAPTURING: + self.feedback_message = f"Capturing screen failed. {self.output[-1]}" # pylint: disable= attribute-defined-outside-init + return py_trees.common.Status.FAILURE + return py_trees.common.Status.SUCCESS diff --git a/libs/scenario_execution_x11/scenario_execution_x11/get_osc_library.py b/libs/scenario_execution_x11/scenario_execution_x11/get_osc_library.py new file mode 100644 index 00000000..3e515072 --- /dev/null +++ b/libs/scenario_execution_x11/scenario_execution_x11/get_osc_library.py @@ -0,0 +1,19 @@ +# Copyright (C) 2024 Intel Corporation +# +# 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. +# +# SPDX-License-Identifier: Apache-2.0 + + +def get_osc_library(): + return 'scenario_execution_x11', 'x11.osc' diff --git a/libs/scenario_execution_x11/scenario_execution_x11/lib_osc/x11.osc b/libs/scenario_execution_x11/scenario_execution_x11/lib_osc/x11.osc new file mode 100644 index 00000000..de8faaf7 --- /dev/null +++ b/libs/scenario_execution_x11/scenario_execution_x11/lib_osc/x11.osc @@ -0,0 +1,6 @@ +import osc.standard.base + +action capture_screen: + # Capture the screen content within a video + output_filename: string = "capture.mp4" # name of the resulting video file (use ``--output-dir`` command-line argument to store the file within a specific directory) + frame_rate: float = 25.0 # frame-rate of the resulting video diff --git a/libs/scenario_execution_x11/scenarios/example_capture.osc b/libs/scenario_execution_x11/scenarios/example_capture.osc new file mode 100644 index 00000000..a098d539 --- /dev/null +++ b/libs/scenario_execution_x11/scenarios/example_capture.osc @@ -0,0 +1,10 @@ +import osc.helpers +import osc.x11 + +scenario example_x11: + do parallel: + capture_screen() + run_process("glxgears") + serial: + wait elapsed(10s) + emit end diff --git a/libs/scenario_execution_x11/setup.cfg b/libs/scenario_execution_x11/setup.cfg new file mode 100644 index 00000000..0e7f1c2b --- /dev/null +++ b/libs/scenario_execution_x11/setup.cfg @@ -0,0 +1,4 @@ +[develop] +script_dir=$base/lib/scenario_execution_x11 +[install] +install_scripts=$base/lib/scenario_execution_x11 diff --git a/libs/scenario_execution_x11/setup.py b/libs/scenario_execution_x11/setup.py new file mode 100644 index 00000000..59abec37 --- /dev/null +++ b/libs/scenario_execution_x11/setup.py @@ -0,0 +1,47 @@ +# Copyright (C) 2024 Intel Corporation +# +# 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. +# +# SPDX-License-Identifier: Apache-2.0 + +from setuptools import find_namespace_packages, setup + +PACKAGE_NAME = 'scenario_execution_x11' + +setup( + name=PACKAGE_NAME, + version='1.2.0', + packages=find_namespace_packages(), + data_files=[ + ('share/ament_index/resource_index/packages', + ['resource/' + PACKAGE_NAME]), + ('share/' + PACKAGE_NAME, ['package.xml']) + ], + install_requires=['setuptools'], + zip_safe=True, + maintainer='Intel Labs', + maintainer_email='scenario-execution@intel.com', + description='Scenario Execution library for X11', + license='Apache License 2.0', + tests_require=['pytest'], + include_package_data=True, + entry_points={ + 'scenario_execution.actions': [ + 'capture_screen = scenario_execution_x11.actions.capture_screen:CaptureScreen', + ], + 'scenario_execution.osc_libraries': [ + 'x11 = ' + 'scenario_execution_x11.get_osc_library:get_osc_library', + ] + }, +) diff --git a/test/scenario_execution_nav2_test/scenarios/test_scenario_execution_nav2.osc b/test/scenario_execution_nav2_test/scenarios/test_scenario_execution_nav2.osc index 6e6f4667..d9103e2f 100644 --- a/test/scenario_execution_nav2_test/scenarios/test_scenario_execution_nav2.osc +++ b/test/scenario_execution_nav2_test/scenarios/test_scenario_execution_nav2.osc @@ -3,7 +3,7 @@ import osc.ros import osc.nav2 scenario example_nav2: - timeout(600s) + timeout(900s) robot: differential_drive_robot do serial: ros_launch("gazebo_static_camera", "spawn_static_camera_launch.py", [ key_value('z', '10'), key_value('pitch', '1.57')], wait_for_shutdown: false)