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)