Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/scan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ jobs:
- name: Check for license tags
shell: bash
run: |
find . -type f \( -name "*.py" -o -name "*.cpp" -o -name "*.h" \) -exec /go/bin/addlicense -check {} +
find . -regextype posix-extended -type f \( \( -name "*.py" -a ! -regex '\./scenario_execution/scenario_execution/osc2_parsing/.*\.py' \) -o -name "*.cpp" -o -name "*.h" \) -exec /go/bin/addlicense -check {} +
- name: Run ros_license_toolkit for each Package
shell: bash
run: |
Expand Down
1 change: 1 addition & 0 deletions libs/scenario_execution_network/MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include scenario_execution_network/lib_osc/*.osc
8 changes: 8 additions & 0 deletions libs/scenario_execution_network/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Scenario Execution Library for network

The `scenario_execution_network` package provides actions to interact with network services.

It provides the following scenario execution library:

- `network.osc`: Actions specific to network functions

21 changes: 21 additions & 0 deletions libs/scenario_execution_network/package.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>scenario_execution_network</name>
<version>1.3.0</version>
<description>Scenario Execution library for network functionality</description>
<author email="fred-labs@mailbox.org">Frederik Pasch</author>
<maintainer email="fred-labs@mailbox.org">Frederik Pasch</maintainer>
<license>Apache-2.0</license>

<depend>scenario_execution</depend>

<test_depend>ament_copyright</test_depend>
<test_depend>ament_flake8</test_depend>
<test_depend>ament_pep257</test_depend>
<test_depend>python3-pytest</test_depend>

<export>
<build_type>ament_python</build_type>
</export>
</package>
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Copyright (C) 2025 Frederik Pasch
#
# 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'
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Copyright (C) 2025 Frederik Pasch
#
# 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Copyright (C) 2025 Frederik Pasch
#
# 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
from scenario_execution.actions.base_action import BaseAction
import py_trees
from concurrent.futures import ThreadPoolExecutor
import requests

class RequestStatus(Enum):
IDLE = 1
REQUESTING = 2
DONE = 3


class HttpGet(BaseAction):
"""
Class to perform an HTTP GET request asynchronously
"""

def __init__(self) -> None:
super().__init__()
self.response = None
self.future = None
self.parameters = {}
self.executor = ThreadPoolExecutor(max_workers=1)
self.current_state = RequestStatus.IDLE

def execute(self, url: str, parameters: list) -> None: # pylint: disable=arguments-differ,arguments-renamed
self.url = url
self.parameters = {}
for param in parameters:
self.parameters[param["key"]] = param["value"]

def update(self) -> py_trees.common.Status:
if self.current_state == RequestStatus.IDLE:
# Start the async HTTP request in a separate thread
self.future = self.executor.submit(self._make_request)
self.current_state = RequestStatus.REQUESTING
return py_trees.common.Status.RUNNING

elif self.current_state == RequestStatus.REQUESTING:
if self.future.done():
try:
self.response = self.future.result()
if self.response.status_code == 200:
self.current_state = RequestStatus.DONE
return py_trees.common.Status.SUCCESS
else:
self.logger.error(f"HTTP GET returned status code {self.response.status_code}")
self.current_state = RequestStatus.DONE
return py_trees.common.Status.FAILURE
except Exception as e:
self.logger.error(f"HTTP GET request failed: {e}")
return py_trees.common.Status.FAILURE
else:
return py_trees.common.Status.RUNNING

elif self.current_state == RequestStatus.DONE:
return py_trees.common.Status.SUCCESS

return py_trees.common.Status.FAILURE

def _make_request(self):
"""Make the HTTP request synchronously in a thread pool"""
return requests.get(self.url, params=self.parameters)

def shutdown(self):
"""Clean up the thread pool on shutdown"""
if self.executor:
self.executor.shutdown(wait=False)
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Copyright (C) 2025 Frederik Pasch
#
# 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_network', 'network.osc'
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import osc.types

###########
# Actions
###########

action http_get:
# Perform an HTTP GET request to the specified URL
url: string # URL to send the GET request to
parameters: list of key_value # Optional query parameters for the request
4 changes: 4 additions & 0 deletions libs/scenario_execution_network/setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[develop]
script_dir=$base/lib/scenario_execution_network
[install]
install_scripts=$base/lib/scenario_execution_network
47 changes: 47 additions & 0 deletions libs/scenario_execution_network/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Copyright (C) 2025 Frederik Pasch
#
# 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_network'

setup(
name=PACKAGE_NAME,
version='1.3.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='Frederik Pasch',
maintainer_email='fred-labs@mailbox.org',
description='Scenario Execution library for network functionality',
license='Apache License 2.0',
tests_require=['pytest'],
include_package_data=True,
entry_points={
'scenario_execution.actions': [
'http_get = scenario_execution_network.actions.http_get:HttpGet',
],
'scenario_execution.osc_libraries': [
'network = '
'scenario_execution_network.get_osc_library:get_osc_library',
]
},
)
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,13 @@ def visit_do_member(self, node: DoMember):
behavior = py_trees.composites.Parallel(name=name, policy=py_trees.common.ParallelPolicy.SuccessOnAll())
elif composition_operator == "one_of":
behavior = py_trees.composites.Parallel(name=name, policy=py_trees.common.ParallelPolicy.SuccessOnOne())
# support for composites not available in osc2
elif composition_operator == "selector":
behavior = py_trees.composites.Selector(name=name, memory=True)
elif composition_operator == "selector_no_memory":
behavior = py_trees.composites.Selector(name=name, memory=False)
elif composition_operator == "serial_no_memory":
behavior = py_trees.composites.Sequence(name=name, memory=False)
else:
raise NotImplementedError(f"scenario operator {composition_operator} not yet supported.")

Expand Down
34 changes: 28 additions & 6 deletions scenario_execution/scenario_execution/model/osc2_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,28 @@


class OpenScenario2Parser(object):
""" Helper class for parsing openscenario 2 files """
"""
Helper class for parsing OpenSCENARIO 2 files with optional extensions.

File extension determines default extension behavior:
- .osc files: Standard OpenSCENARIO 2 (extensions=False)
- .scx files: OpenSCENARIO 2 + Extensions (extensions=True)

Extensions can be explicitly overridden via enable_extensions parameter.
"""

def __init__(self, logger) -> None:
self.logger = logger
self.parsed_files = []

def process_file(self, file, log_model: bool = False, debug: bool = False, scenario_parameter_overrides: dict = None):
def process_file(self, file, log_model: bool = False, debug: bool = False, scenario_parameter_overrides: dict = None, enable_extensions: bool = None):
""" Convenience method to execute the parsing and print out tree """

parsed_model = self.parse_file(file, log_model)
# Auto-detect extensions based on file extension if not explicitly set
if enable_extensions is None:
enable_extensions = file.lower().endswith('.scx')

parsed_model = self.parse_file(file, log_model, enable_extensions=enable_extensions)

tree = py_trees.composites.Sequence(name="", memory=True)
model = self.create_internal_model(parsed_model, tree, file, log_model, debug, scenario_parameter_overrides)
Expand Down Expand Up @@ -335,23 +347,33 @@ def set_override_value(self, param, override_value):
raise ValueError(
f"Unknown override type (supported: FunctionApplicationExpression, BaseLiteral, PhysicalLiteral, ListExpression) {param}")

def parse_file(self, file: str, log_model: bool = False, error_prefix=""):
def parse_file(self, file: str, log_model: bool = False, error_prefix="", enable_extensions: bool = None):
""" Execute the parsing """
if file in self.parsed_files: # skip already parsed/imported files
return None
self.parsed_files.append(file)

# Auto-detect extensions based on file extension if not explicitly set
if enable_extensions is None:
enable_extensions = file.lower().endswith('.scx')

try:
input_stream = FileStream(file)
except (OSError, UnicodeDecodeError) as e:
raise ValueError(f'{e}') from e
return self.parse_input_stream(input_stream, log_model, error_prefix)
return self.parse_input_stream(input_stream, log_model, error_prefix, enable_extensions)

def parse_input_stream(self, input_stream, log_model=False, error_prefix=""):
def parse_input_stream(self, input_stream, log_model=False, error_prefix="", enable_extensions=False):
""" Execute the parsing """
lexer = OpenSCENARIO2Lexer(input_stream)

lexer.extensions_enabled = enable_extensions

stream = CommonTokenStream(lexer)

parser = OpenSCENARIO2Parser(stream)
parser.extensions_enabled = enable_extensions

# if quiet:
parser.removeErrorListeners()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,12 +95,25 @@ def lastToken(self):
def lastToken(self, value):
self._lastToken = value

@property
def extensions_enabled(self):
try:
return self._extensions_enabled
except AttributeError:
self._extensions_enabled = False
return self._extensions_enabled

@extensions_enabled.setter
def extensions_enabled(self, value):
self._extensions_enabled = value

def reset(self):
super().reset()
self.tokens = []
self.indents = []
self.opened = 0
self.lastToken = None
self.extensions_enabled = False

def emitToken(self, t):
super().emitToken(t)
Expand Down Expand Up @@ -446,8 +459,15 @@ composition : compositionOperator (OPEN_PAREN argumentList? CLOSE_PAREN)?':' NEW
doMember+ DEDENT (behaviorWithDeclaration)?;

compositionOperator
: standardCompositionOperator
| extendedCompositionOperator;

standardCompositionOperator
: 'serial' | 'one_of' | 'parallel';

extendedCompositionOperator
: {self._input.LT(1).getInputStream().name != '<unknown>' and self.extensions_enabled}? ('serial_no_memory' | 'selector' | 'selector_no_memory');

behaviorInvocation
: (actorExpression '.')? behaviorName OPEN_PAREN (argumentList)? CLOSE_PAREN (behaviorWithDeclaration | NEWLINE);

Expand Down Expand Up @@ -758,3 +778,4 @@ fragment
HexDigit
: [0-9A-Fa-f]
;

Loading
Loading