From 53623f9ae96d614f8dc885bc824b28445c426d91 Mon Sep 17 00:00:00 2001 From: Nikolai Morin Date: Mon, 23 Nov 2020 14:45:13 +0100 Subject: [PATCH] WIP --- CMakeLists.txt | 58 ----- README.md | 16 +- launch.py | 4 +- launch/default.launch | 9 +- launch/scenario_launcher.launch | 58 +---- package.xml | 12 +- .../__init__.py | 0 planning_simulator_launcher/launch.py | 153 +++++++++++++ planning_simulator_launcher/launch_script.py | 91 ++++++++ .../scenario_generator.py | 63 +++--- .../scenario_parameter.py | 2 - .../scenario_parser.py | 29 +-- .../show_result.py | 107 ++++++--- .../planning_simulator_launcher | 0 scenario_roslaunch_server.py | 4 +- scripts/planning_simulator_launcher/launch.py | 207 ------------------ .../scenario_loader.py.cmake | 50 ----- .../scenario_roslaunch_server.py | 102 --------- setup.cfg | 4 + setup.py | 39 ++++ show_result.py | 4 +- 21 files changed, 429 insertions(+), 583 deletions(-) delete mode 100644 CMakeLists.txt rename {scripts/planning_simulator_launcher => planning_simulator_launcher}/__init__.py (100%) create mode 100755 planning_simulator_launcher/launch.py create mode 100644 planning_simulator_launcher/launch_script.py rename {scripts/planning_simulator_launcher => planning_simulator_launcher}/scenario_generator.py (72%) rename {scripts/planning_simulator_launcher => planning_simulator_launcher}/scenario_parameter.py (98%) rename {scripts/planning_simulator_launcher => planning_simulator_launcher}/scenario_parser.py (59%) rename {scripts/planning_simulator_launcher => planning_simulator_launcher}/show_result.py (61%) rename COLCON_IGNORE => resource/planning_simulator_launcher (100%) delete mode 100755 scripts/planning_simulator_launcher/launch.py delete mode 100644 scripts/planning_simulator_launcher/scenario_loader.py.cmake delete mode 100755 scripts/planning_simulator_launcher/scenario_roslaunch_server.py create mode 100644 setup.cfg create mode 100644 setup.py diff --git a/CMakeLists.txt b/CMakeLists.txt deleted file mode 100644 index 3c2e403..0000000 --- a/CMakeLists.txt +++ /dev/null @@ -1,58 +0,0 @@ -cmake_minimum_required(VERSION 2.8.3) - -project(planning_simulator_launcher) - -find_package(catkin REQUIRED COMPONENTS rospy) - -catkin_package(CATKIN_DEPENDS rospy) - -# ==== Install Python Scripts ================================================== - -configure_file( - ${CMAKE_CURRENT_SOURCE_DIR}/scripts/${PROJECT_NAME}/scenario_loader.py.cmake - ${CMAKE_CURRENT_SOURCE_DIR}/scripts/${PROJECT_NAME}/scenario_loader.py) - -file( - GLOB ${PROJECT_NAME}_SCRIPTS - ${CMAKE_CURRENT_SOURCE_DIR}/scripts/${PROJECT_NAME}/*.py) - -catkin_install_python( - PROGRAMS ${${PROJECT_NAME}_SCRIPTS} - DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}) - -# ==== Generate Default scenario_database.json ================================= - -file( - GLOB ${PROJECT_NAME}_DEFAULT_SCENARIOS_GLOB - ${CMAKE_CURRENT_SOURCE_DIR}/scenario/debug/*.yaml) - -function(convert reference delimiter) - list(GET ARGV 2 RESULT) - math(EXPR LENGTH "${ARGC}-1") - foreach(INDEX RANGE 3 ${LENGTH}) - list(GET ARGV ${INDEX} EACH) - set(RESULT "${RESULT}\",\n \"${EACH}") - endforeach() - set(${reference} "${RESULT}" PARENT_SCOPE) -endfunction() - -convert(${PROJECT_NAME}_DEFAULT_SCENARIOS ${${PROJECT_NAME}_DEFAULT_SCENARIOS_GLOB}) - -configure_file( - ${CMAKE_CURRENT_SOURCE_DIR}/scenario_database_template.json - ${CMAKE_CURRENT_SOURCE_DIR}/scenario_database_autoconf.json) - -# install( -# FILES ${CMAKE_CURRENT_SOURCE_DIR}/scenario_database.json -# DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}) - -# ==== Miscallenous ============================================================ - -install( - DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/launch - DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}) - -# install( -# DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/rviz -# DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION} -# ) diff --git a/README.md b/README.md index 5b82b81..04a5075 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ See [https://github.com/tier4/AutowareArchitectureProposal#how-to-setup](https:/ ## 2. Usage - (2020/07/01 last modified) + (2020/11/23 last modified) ### 2.1 Get Sample Scenario @@ -66,22 +66,12 @@ Note that the Map path must be rewritten when sharing a scenario with multiple u #### (3) Execute -##### (a) Terminal A - -``` shell -source /opt/ros/melodic/setup.bash -source /path/to/AutowareArchitectureProposal/install/setup.bash - -rosrun planning_simulator_launcher scenario_roslaunch_server.py -``` - -#### (b) Terminal B ``` shell -source /opt/ros/melodic/setup.bash +source /opt/ros/foxy/setup.bash source /path/to/AutowareArchitectureProposal/install/setup.bash -rosrun planning_simulator_launcher launch.py --timeout=180 && rosrun planning_simulator_launcher show_result.py ./scenario/generated ./scenario/log +ros2 run planning_simulator_launcher launch.py --timeout=180 && ros2 run planning_simulator_launcher show_result.py ./scenario/generated ./scenario/log ``` Command-line arguments of script `python show_result.py` is depends on the path you written in `scenario_database.json`. 1st argument is path to your directory your scenarios placed on + `/generated`. diff --git a/launch.py b/launch.py index 7fa8fd8..6cf04ef 100644 --- a/launch.py +++ b/launch.py @@ -2,9 +2,9 @@ print('\x1B[1;31mThis script has been deprecated.\x1B[0m') print('\x1B[1;31mPlease use the following command.\x1B[0m') -print('\x1B[1;32m> rosrun planning_simulator_launcher launch.py\x1B[0m') +print('\x1B[1;32m> ros2 run planning_simulator_launcher launch.py\x1B[0m') import os import sys -os.system('rosrun planning_simulator_launcher launch.py ' + ' '.join(sys.argv[1:])) +os.system('ros2 run planning_simulator_launcher launch.py ' + ' '.join(sys.argv[1:])) diff --git a/launch/default.launch b/launch/default.launch index c8065f2..d6f73fe 100644 --- a/launch/default.launch +++ b/launch/default.launch @@ -5,10 +5,7 @@ - - - - - - + + + diff --git a/launch/scenario_launcher.launch b/launch/scenario_launcher.launch index b445f34..0d02612 100644 --- a/launch/scenario_launcher.launch +++ b/launch/scenario_launcher.launch @@ -3,72 +3,34 @@ - + - - - - - - + - - + - - - + + + - - + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/package.xml b/package.xml index fa615db..9f9e176 100644 --- a/package.xml +++ b/package.xml @@ -1,5 +1,6 @@ - - + + + planning_simulator_launcher 0.0.0 The planning_simulator_launcher package @@ -9,8 +10,9 @@ Tatsuya Yamasaki Apache 2 + scenario_runner - catkin - - rospy + + ament_python + diff --git a/scripts/planning_simulator_launcher/__init__.py b/planning_simulator_launcher/__init__.py similarity index 100% rename from scripts/planning_simulator_launcher/__init__.py rename to planning_simulator_launcher/__init__.py diff --git a/planning_simulator_launcher/launch.py b/planning_simulator_launcher/launch.py new file mode 100755 index 0000000..0f9704f --- /dev/null +++ b/planning_simulator_launcher/launch.py @@ -0,0 +1,153 @@ +import argparse +import json +from pathlib import Path +from typing import Optional, Union + +import launch +from planning_simulator_launcher.launch_script import launch_description +from planning_simulator_launcher.scenario_parser import ScenarioParser + + +# If a base dir is given, resolves relative paths relative to the base dir. +# Otherwise, ensures that the path is absolute. +def absolute_path(path: Union[Path, str], base_dir: Optional[Path] = None): + path = Path(path) + if path.is_absolute(): + return path.resolve() + if base_dir: + return (base_dir / path).resolve() + raise ValueError(f"Relative path '{path}' is not allowed.") + + +class Database: + def __init__(self, database_path: Path, *, base_dir: Optional[Path] = None): + print('Loading Database') + print(f' Opening \x1b[36m{database_path}\x1b[0m => ', end='') + with database_path.open() as file: + database = json.load(file) + print('\x1b[32mSuccess\x1b[0m') + + print(f' Validating => ', end='') + self.launch_path = absolute_path(database['launch_path'], base_dir) + # Check if it exists, because the error message if we try to access it + # later is not helpful. + if not self.launch_path.is_file(): + raise ValueError(f"launch_path '{self.launch_path}' is not a file") + + self.log_output_base_dir = absolute_path(database['log_output_base_dir'], base_dir) + + self.map = {name: absolute_path(path, base_dir) for name, path in database['map'].items()} + for path in self.map.values(): + if not path.is_file(): + raise ValueError(f"map '{path}' is not a file") + + self.scenario = [absolute_path(path, base_dir) for path in database['scenario']] + for path in self.scenario: + if not path.is_file(): + raise ValueError(f"scenario '{path}' is not a file") + print('\x1b[32mSuccess\x1b[0m') + + +class Launcher: + def __init__(self, args): + # Only created for validation, doesn't need to be stored + database = Database(args.database) + + # Only use database if command line arg is None + self.launch_path = args.launch_path or database.launch_path + self.log_output_base_dir = args.log_output_base_dir or database.log_output_base_dir + # Similar, but wrap it in a list + self.scenarios = [args.scenario] if args.scenario else database.scenario + self.map = database.map + self.timeout = args.timeout + self.log_output_type = args.log_output + + + # Look up the map name in the scenario in scenario_database.json + def map_path(self, parser: ScenarioParser) -> Path: + scenario_map_path = parser.getMapPath() + print(f' Loading map \x1b[36m{scenario_map_path}\x1b[0m as ', end='') + + # If the key is not found, return the key itself + map_path = self.map.get( + parser.getMapPath(), + parser.getMapPath() + ) + + if map_path == parser.getMapPath(): + print('is') + else: + print(f'\x1b[36m{map_path}\x1b[0m') + + return absolute_path(map_path) + + + def run_all_scenarios(self): + # This allows running launch files from this script + ls = launch.LaunchService() + + if not self.log_output_base_dir.is_dir(): + self.log_output_base_dir.mkdir(parents=True) + + for scenario_path in self.scenarios: + print(f'\nRunning scenario \x1b[36m{scenario_path}\x1b[0m') + print(' Parameters') + parser = ScenarioParser(str(scenario_path), generate_mode=True) + + n_total = len(parser.scenario_files_path) + for n, scenario_path in enumerate(parser.scenario_files_path, start=1): + scenario_path = Path(scenario_path) + print(f'\nLaunch {n} of {n_total}') + print(f' Scenario \x1b[36m{scenario_path}\x1b[0m') + + ld = launch_description( + scenario_path=str(scenario_path), + scenario_id=parser.getScenarioId(), + log_dir=str(self.log_output_base_dir), + map_path=str(self.map_path(parser)), + uuid=scenario_path.stem, + launch_path=str(self.launch_path), + ) + ls.include_launch_description(ld) + ls.run() # TODO: Timeout + ls.shutdown() + + +def main(): + parser = argparse.ArgumentParser(description='launch simulator') + parser.add_argument( + '--timeout', type=int, default=180, + help='Specify simulation time limit in seconds. \ + The default is 180 seconds.') + parser.add_argument( + '--database', + type=Path, + required=True, + help='Specify path to your scenario_database.json.') + parser.add_argument( + '--launch-path', + type=absolute_path, + help='Overrides the path to your launch file from scenario_database.json') + parser.add_argument( + '--log-output', + choices=['screen', 'log', 'both', 'own_log', 'full'], + default='screen', + help='Specify the type of log output.') + parser.add_argument( + '--log-output-base-dir', + type=absolute_path, + help='Overrides the log directory output from scenario_database.json') + parser.add_argument( + '--scenario', + help='Override the path to the scenario to be executed from scenario_database.json') + parser.add_argument( + '--base-path', + help='Allow relative paths in scenario_database.json and command line arguments, which will be interpreted') + args = parser.parse_args() + + launcher = Launcher(args) + launcher.run_all_scenarios() + + +if __name__ == "__main__": + main() diff --git a/planning_simulator_launcher/launch_script.py b/planning_simulator_launcher/launch_script.py new file mode 100644 index 0000000..af50dfa --- /dev/null +++ b/planning_simulator_launcher/launch_script.py @@ -0,0 +1,91 @@ +from pathlib import Path + +from launch_ros.actions import Node +from launch_ros.substitutions import FindPackageShare +from launch_xml import Parser + +from launch import LaunchContext, LaunchDescription +from launch.actions import (ExecuteProcess, IncludeLaunchDescription, + RegisterEventHandler, Shutdown) +from launch.event import Event +from launch.event_handlers import OnProcessExit +from launch.launch_description_sources import FrontendLaunchDescriptionSource +from launch.substitutions import LaunchConfiguration + + +def launch_description(*, scenario_path, scenario_id, log_dir, map_path, uuid, launch_path): + vehicle_info_param_path = FindPackageShare('lexus_description').find('lexus_description') + '/config/vehicle_info.yaml' + scenario_runner = Node( + package='scenario_runner', + executable='scenario_runner_node', + parameters=[ + {'scenario_id': scenario_id}, + {'scenario_path': scenario_path}, + {'log_output_path': f'{log_dir}/{scenario_id}.json'}, + {'json_dump_path': f'{log_dir}/result-of-{uuid}.json'}, + # base link of front camera + {'camera_frame_id': 'camera5/camera_optical_link'}, + {'initial_engage_state': True}, + Path(vehicle_info_param_path) + ], + + remappings=[ + ("input/pointcloud", "/sensing/lidar/no_ground/pointcloud"), + ("input/vectormap", "/map/vector_map"), + ("input/route", "/planning/mission_planning/route"), + ("input/autoware_state", "/autoware/state"), + ("input/vehicle_twist", "/vehicle/status/twist"), + ("input/signal_command", "/vehicle/status/turn_signal"), + ("output/start_point", "/initialpose"), + ("output/initial_velocity", "/initialtwist"), + ("output/goal_point", "/planning/mission_planning/goal"), + ("output/check_point", "/planning/mission_planning/checkpoint"), + ("output/autoware_engage", "/autoware/engage"), + ("output/simulator_engage", "/vehicle/engage"), + ("output/npc_simulator_engage", "/simulation/npc_simulator/engage"), # noqa: E501 + ("output/limit_velocity", "/planning/scenario_planning/max_velocity"), # noqa: E501 + ("output/object_info", "/simulation/npc_simulator/object_info"), + ("output/traffic_detection_result", "/perception/traffic_light_recognition/traffic_light_states"), # noqa: E501 + ("output/lane_change_permission", "/planning/scenario_planning/lane_driving/lane_change_approval"), # noqa: E501 + # rosparam: add simulator noise or not + ("rosparam/add_simulator_noise", "/simple_planning_simulator/add_measurement_noise"), # noqa: E501 + # rosparam: std of simulator pos noise + ("rosparam/simulator_pos_noise", "/simple_planning_simulator/pos_noise_stddev"), # noqa: E501 + # rosparam: max velocity + ("rosparam/max_velocity", "/planning/scenario_planning/motion_velocity_optimizer/max_velocity"), # noqa: E501 + ] + ) + + # When the scenario_runner exits, all nodes should be shut down. + def on_exit(event: Event, context: LaunchContext): + return Shutdown(reason="Simulation complete") + + shutdown_handler = RegisterEventHandler( + OnProcessExit( + target_action=scenario_runner, + on_exit=on_exit + ) + ) + + scenario_api = IncludeLaunchDescription( + FrontendLaunchDescriptionSource( + launch_file_path=launch_path, parser=Parser()), # noqa: E501 + launch_arguments=[ + ('map_path', map_path), + ('log_dir', log_dir), + ('scenario_id', scenario_id), + ('scenario_path', scenario_path), + ] + ) + + # rosbag_record = ExecuteProcess( + # cmd=['ros2', 'bag', 'record', '-a'], + # output='screen' + # ) + + return LaunchDescription([ + scenario_runner, + shutdown_handler, + # scenario_api, + # rosbag_record, + ]) diff --git a/scripts/planning_simulator_launcher/scenario_generator.py b/planning_simulator_launcher/scenario_generator.py similarity index 72% rename from scripts/planning_simulator_launcher/scenario_generator.py rename to planning_simulator_launcher/scenario_generator.py index 6dfa843..85304d5 100644 --- a/scripts/planning_simulator_launcher/scenario_generator.py +++ b/planning_simulator_launcher/scenario_generator.py @@ -1,14 +1,10 @@ -#!/usr/bin/env python - -from copy import deepcopy -import datetime import itertools import json -import math import os import re -import shutil import uuid +from copy import deepcopy + import yaml @@ -36,39 +32,38 @@ def __init__(self, params, path): with open(path, "r+") as file: self.yaml_str = file.read() - self.__yaml_generate_dir = self.generated_directory + "/" + scenario_name + "/" + self._yaml_generate_dir = self.generated_directory + "/" + \ + scenario_name + "/" - if os.path.exists(self.__yaml_generate_dir): + if os.path.exists(self._yaml_generate_dir): + rel_dir = os.path.relpath(self._yaml_generate_dir) print('') - print('Cleanup target directory ' + os.path.relpath(self.__yaml_generate_dir)) + print('Cleanup target directory ' + rel_dir) - for each in os.listdir(self.__yaml_generate_dir): + for each in os.listdir(self._yaml_generate_dir): print(' Removing ' + each) - os.remove(os.path.join(self.__yaml_generate_dir, each)) + os.remove(os.path.join(self._yaml_generate_dir, each)) else: - os.makedirs(self.__yaml_generate_dir) + os.makedirs(self._yaml_generate_dir) print('') print("Generating scenario " + scenario_name) - self.__generate() + self._generate() def __del__(self): self.generate_log.close() def getScenarioFilesPath(self): - return self.__yaml_files_path + return self._yaml_files_path - def __generate(self): - self.__yaml_files_path = [] + def _generate(self): + self._yaml_files_path = [] self.num_scenarios = 0 print(' Parameters') for variable, configuration in self.params.items(): - print(' '), - print(variable), - print('='), - print(configuration.values) + print(f' {variable} = {configuration.values}'), generate_logs = [] @@ -86,21 +81,19 @@ def __generate(self): for bindings in itertools.product(*list_of_list_of_bindings): scenario_id = str(uuid.uuid4()) - yaml_path = self.__yaml_generate_dir + scenario_id + ".yaml" - self.__yaml_files_path.append(yaml_path) + yaml_path = self._yaml_generate_dir + scenario_id + ".yaml" + self._yaml_files_path.append(yaml_path) print('') print(' ' + os.path.relpath(yaml_path)) result = deepcopy(self.yaml_str) for variable, value in bindings: - print(' '), - print(variable), - print('=>'), - print(value) + print(f' {variable} = {value}'), result = result.replace(variable, str(value)) - # NOTE: Expand macro after variable assignment (to use variable in expression) + # NOTE: Expand macro after variable assignment + # (to use variable in expression) result = self.macroexpand(result) with open(yaml_path, 'w') as file: @@ -122,8 +115,8 @@ def __generate(self): else: scenario_id = str(uuid.uuid4()) - yaml_path = self.__yaml_generate_dir + scenario_id + ".yaml" - self.__yaml_files_path.append(yaml_path) + yaml_path = self._yaml_generate_dir + scenario_id + ".yaml" + self._yaml_files_path.append(yaml_path) print(' ' + os.path.relpath(yaml_path)) result = deepcopy(self.yaml_str) @@ -143,7 +136,7 @@ def __generate(self): json.dump(generate_logs, self.generate_log) - def __getDefaultValueKeys(self, used_key): + def _getDefaultValueKeys(self, used_key): variables = [] for variable in self.params.keys(): @@ -156,8 +149,8 @@ def __getDefaultValueKeys(self, used_key): return variables - def __update_default_params(self, name, yaml_string): - for variable in self.__getDefaultValueKeys(name): + def _update_default_params(self, name, yaml_string): + for variable in self._getDefaultValueKeys(name): print(' '), print(variable), print('=>'), @@ -169,8 +162,10 @@ def __update_default_params(self, name, yaml_string): return yaml_string def evaluate(self, match): - pi = math.pi return match.group(1) + str(eval(match.group(2))) + match.group(3) def macroexpand(self, source): - return re.sub(r"^(.*)\${{\s+((?:pi|[\d\s+\-\*/%\(\).])*)\s+}}(.*)$", self.evaluate, source, flags=re.M) + return re.sub( + r"^(.*)\${{\s+((?:pi|[\d\s+\-\*/%\(\).])*)\s+}}(.*)$", + self.evaluate, source, flags=re.M + ) diff --git a/scripts/planning_simulator_launcher/scenario_parameter.py b/planning_simulator_launcher/scenario_parameter.py similarity index 98% rename from scripts/planning_simulator_launcher/scenario_parameter.py rename to planning_simulator_launcher/scenario_parameter.py index 46a1b60..4f1c119 100644 --- a/scripts/planning_simulator_launcher/scenario_parameter.py +++ b/planning_simulator_launcher/scenario_parameter.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python - import numpy as np diff --git a/scripts/planning_simulator_launcher/scenario_parser.py b/planning_simulator_launcher/scenario_parser.py similarity index 59% rename from scripts/planning_simulator_launcher/scenario_parser.py rename to planning_simulator_launcher/scenario_parser.py index 4ff67c2..b06a04e 100644 --- a/scripts/planning_simulator_launcher/scenario_parser.py +++ b/planning_simulator_launcher/scenario_parser.py @@ -1,10 +1,10 @@ -#!/usr/bin/env python - -from scenario_generator import ScenarioGenerator -from scenario_parameter import ScenarioParameter import os + import yaml +from planning_simulator_launcher.scenario_generator import ScenarioGenerator +from planning_simulator_launcher.scenario_parameter import ScenarioParameter + class ScenarioParser: @@ -12,15 +12,13 @@ def __init__(self, yaml_path, generate_mode=False): self.yaml_path = yaml_path with open(yaml_path, "r+") as f: - self.yaml = yaml.load(f) + self.yaml = yaml.load(f, Loader=yaml.FullLoader) if not self.parseScenarioParameter(): print(' No parameter specified.') if generate_mode: - generator = ScenarioGenerator( - self.scenario_params, - self.yaml_path) + generator = ScenarioGenerator(self.scenario_params, self.yaml_path) print('') print(str(generator.num_scenarios) + " Scenarios Generated") @@ -39,18 +37,13 @@ def parseScenarioParameter(self): self.scenario_params = {} try: for param in self.yaml["ParameterDeclaration"]: - scenario_parameter = ScenarioParameter(param) - if scenario_parameter.is_valid: - self.scenario_params[scenario_parameter.name] = scenario_parameter + sp = ScenarioParameter(param) + if sp.is_valid: + self.scenario_params[sp.name] = sp else: - print("Failed to load parameter: " + scenario_parameter.name) + print("Failed to load parameter: " + sp.name) return False - except Exception as exception: - # print(exception) + except Exception: return False return True - - -if __name__ == "__main__": - pass diff --git a/scripts/planning_simulator_launcher/show_result.py b/planning_simulator_launcher/show_result.py similarity index 61% rename from scripts/planning_simulator_launcher/show_result.py rename to planning_simulator_launcher/show_result.py index c8caef4..45a1d4d 100755 --- a/scripts/planning_simulator_launcher/show_result.py +++ b/planning_simulator_launcher/show_result.py @@ -1,16 +1,18 @@ -#!/usr/bin/env python - import argparse import glob import json + import termcolor + class Viewer: - def __init__(self,scenario_generated_path,log_path,verbose,quiet,output): + def __init__(self, scenario_generated_path, + log_path, verbose, quiet, output): self.quiet = quiet self.verbose = verbose self.log_path = log_path - generate_log_f = open(scenario_generated_path+'/generate_log.json', 'r') + log_file_path = scenario_generated_path + '/generate_log.json' + generate_log_f = open(log_file_path, 'r') self.generate_log = json.load(generate_log_f) generate_log_f.close() self.raw_data = {} @@ -23,51 +25,56 @@ def __init__(self,scenario_generated_path,log_path,verbose,quiet,output): self.raw_data[scenario_id] = json_load if self.getGenerateLog(scenario_id) is not None: self.output_data.append(self.getResultDict(scenario_id)) - if self.quiet == False: + if self.quiet is False: self.outputResult(output) self.printResult() + def printResult(self): if self.verbose: self.printVerboseResult() else: self.printSimpleResult() self.printSummary() + def printVerboseResult(self): i = 1 for data in self.output_data: message = "[" + str(i) + "/" + str(len(self.output_data)) + "] " if data["passed"]: - message = message + "OK \n" + message += "OK \n" else: - message = message + "NG \n" - message = message + "id = " + data["id"] + "\n" - message = message + "parent = " + data["parent"] + "\n" - message = message + "rosbg = " + data["rosbag"] + "\n" - message = message + "json = " + data["json_log"] + "\n" + message += "NG \n" + message += "id = " + data["id"] + "\n" + message += "parent = " + data["parent"] + "\n" + message += "rosbg = " + data["rosbag"] + "\n" + message += "json = " + data["json_log"] + "\n" metadata = data["metadata"] - message = message + "start_datetime = " + metadata["start_datetime"] + "\n" - message = message + "end_datetime = " + metadata["end_datetime"] + "\n" - message = message + "duration = " + metadata["duration"] + "\n" - message = message + "params\n" + message += "start_datetime = " + metadata["start_datetime"] + "\n" + message += "end_datetime = " + metadata["end_datetime"] + "\n" + message += "duration = " + metadata["duration"] + "\n" + message += "params\n" if len(data["params"]) == 0: - message = message + " no scenario parameters" + message += " no scenario parameters" param_index = 1 for param in data["params"]: - message = message + " (" + str(param_index) + "/" + str(len(data["params"])) + ") " + param + " : " + str(data["params"][param]) + message += " (" + str(param_index) + "/" + \ + str(len(data["params"])) + ") " + param + \ + " : " + str(data["params"][param]) param_index = param_index + 1 - message = message + "\n" + message += "\n" if data["passed"]: message = termcolor.colored(message, 'green') else: message = termcolor.colored(message, 'red') print(message) i = i + 1 + def printSimpleResult(self): i = 1 for data in self.output_data: message = "[" + str(i) + "/" + str(len(self.output_data)) + "] " if data["passed"]: - message = message + "OK " + message = message + "OK " else: message = message + "NG " message = message + "id = " + data["id"] @@ -77,23 +84,28 @@ def printSimpleResult(self): message = termcolor.colored(message, 'red') print(message) i = i + 1 + def printSummary(self): print("[summary]") i = 0 for data in self.output_data: if data["passed"]: i = i + 1 - print("passed " + str(i) + "/" + str(len(self.output_data)) + " test cases.") - def outputResult(self,output): - if output != None: + print("passed " + str(i) + "/" + + str(len(self.output_data)) + " test cases.") + + def outputResult(self, output): + if output is not None: with open(output, 'w') as f: json.dump(self.output_data, f, indent=4) - def getGenerateLog(self,id): + + def getGenerateLog(self, id): for log in self.generate_log: if log["id"] == id: return log return None - def getResultDict(self,id): + + def getResultDict(self, id): ret = {} if self.getResult(id): ret["passed"] = True @@ -102,7 +114,7 @@ def getResultDict(self,id): gen_log = self.getGenerateLog(id) ret["parent"] = gen_log["parent_path"] ret["id"] = id - ret["json_log"] = self.log_path + id + ".json" + ret["json_log"] = self.log_path + id + ".json" ret["rosbag"] = self.log_path + id + ".bag" metadata = self.raw_data[id]["metadata"] ret["metadata"] = metadata @@ -113,7 +125,8 @@ def getResultDict(self,id): else: ret["params"] = {} return ret - def getResult(self,id): + + def getResult(self, id): logs = self.raw_data[id]["log"] scenario_succeded = False for log in logs: @@ -126,12 +139,38 @@ def getResult(self,id): return scenario_succeded -if __name__ == "__main__": - parser = argparse.ArgumentParser(description='Show result') - parser.add_argument('scenario_generated_path', type=str, help='path of the generated scenario') - parser.add_argument('log_path', type=str,help='path of the log') - parser.add_argument('--verbose', help='show detail or not', action="store_true") - parser.add_argument('--quiet', help='does not show output', action="store_true") - parser.add_argument('--output',type=str,help='output path of the result(if not set, only output result to the screen)') +def main(): + parser = argparse.ArgumentParser( + description='Show result') + parser.add_argument( + 'scenario_generated_path', + type=str, + help='path of the generated scenario') + parser.add_argument( + 'log_path', + type=str, + help='path of the log') + parser.add_argument( + '--verbose', + help='show detail or not', + action="store_true") + parser.add_argument( + '--quiet', + help='does not show output', + action="store_true") + parser.add_argument( + '--output', + type=str, + help='output path of the result (if not set, only output result to \ + the screen)') args = parser.parse_args() - viwer = Viewer(args.scenario_generated_path,args.log_path,args.verbose,args.quiet,args.output) + viwer = Viewer( + args.scenario_generated_path, + args.log_path, + args.verbose, + args.quiet, + args.output + ) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/COLCON_IGNORE b/resource/planning_simulator_launcher similarity index 100% rename from COLCON_IGNORE rename to resource/planning_simulator_launcher diff --git a/scenario_roslaunch_server.py b/scenario_roslaunch_server.py index 9879be9..762526a 100644 --- a/scenario_roslaunch_server.py +++ b/scenario_roslaunch_server.py @@ -2,9 +2,9 @@ print('\x1B[1;31mThis script has been deprecated.\x1B[0m') print('\x1B[1;31mPlease use the following command.\x1B[0m') -print('\x1B[1;32m> rosrun planning_simulator_launcher scenario_roslaunch_server.py\x1B[0m') +print('\x1B[1;32m> ros2 run planning_simulator_launcher scenario_roslaunch_server.py\x1B[0m') import os import sys -os.system('rosrun planning_simulator_launcher scenario_roslaunch_server.py ' + ' '.join(sys.argv[1:])) +os.system('ros2 run planning_simulator_launcher scenario_roslaunch_server.py ' + ' '.join(sys.argv[1:])) diff --git a/scripts/planning_simulator_launcher/launch.py b/scripts/planning_simulator_launcher/launch.py deleted file mode 100755 index d4aed7f..0000000 --- a/scripts/planning_simulator_launcher/launch.py +++ /dev/null @@ -1,207 +0,0 @@ -#!/usr/bin/env python - -from scenario_loader import ScenarioLoader -from scenario_parser import ScenarioParser -import argparse -import json -import os -import roslaunch -import sys -import time -import xmlrpclib - - -class Launcher: - - def __init__(self, args, loader): - self.timeout = args.timeout - self.log_output_type = args.log_output - self.server = None - self.client = xmlrpclib.ServerProxy('http://0.0.0.0:10000') - self.loader = loader - self.args = args - - print('Loading Databse') - - self.json_path = args.database - sys.stdout.write(' Opening \x1b[36m') - sys.stdout.write(self.json_path) - sys.stdout.write('\x1b[0m => ') - - with open(self.json_path) as file: - print('\x1b[32mSuccess\x1b[0m') - self.database = json.load(file) - - def map_path(self): - sys.stdout.write(' load map \x1b[36m') - sys.stdout.write(self.parser.getMapPath()) - sys.stdout.write('\x1b[0m as ') - - map_path = self.database['map'].get( - self.parser.getMapPath(), - self.parser.getMapPath()); - - if map_path == self.parser.getMapPath(): - print('is') - else: - sys.stdout.write('\x1b[36m') - sys.stdout.write(map_path) - print('\x1b[0m') - - return self.loader.absolute(map_path) - - def launch_path(self): - sys.stdout.write(' via \x1b[36m') - - path = None - - if self.args.launch: - path = os.path.abspath(self.args.launch) - else: - path = self.loader.absolute(self.database["launch_path"]) - - sys.stdout.write(path) - print('\x1b[0m') - return path - - def start(self, scenario_path): - if self.args.log_output_base_dir: - log_output_base_dir = os.path.abspath(self.args.log_output_base_dir) - else: - log_output_base_dir = self.loader.absolute(self.database["log_output_base_dir"]) - - if not os.path.exists(log_output_base_dir): - os.makedirs(log_output_base_dir) - - self.client.start( - self.launch_path(), - scenario_path, - self.parser.getScenarioId(), - self.map_path(), - log_output_base_dir, - self.log_output_type) - - def terminate(self): - self.client.terminate(1) - - def waitUntilSimulationFinished(self): - start = time.time() - - print(' The simulation is forced to terminate after'), - print(str(self.timeout)), - print('seconds.') - - while self.client.simulationRunnig() and (time.time() - start) < self.timeout: - time.sleep(1) - - if self.client.simulationRunnig(): - print(' The specified maximum simulation time (option --timeout) has been reached.') - print(' Terminate simulation.') - print(' The scenario is likely to be in a deadlock.') - self.terminate() - - def write_result(self, path, uuid): - result = {} - - result['code'] = self.client.status() - result['duration'] = self.client.current_duration() - result['mileage'] = self.client.current_mileage() - - if self.client.status() == 0: # boost::exit_success - result['message'] = 'exit_success' - print(' \x1b[1;32m=> Success\x1b[0m') - - elif self.client.status() == 201: # boost::exit_test_failure - result['message'] = 'exit_test_failure' - print(' \x1b[1;31m=> Failure\x1b[0m') - - elif self.client.status() == 1: # boost::exit_failure - result['message'] = 'exit_failure' - print(' \x1b[1;31m=> Aborted\x1b[0m') - - elif self.client.status() == 200: # boost::exit_exception_failure - result['message'] = 'exit_exception_failure' - print(' \x1b[1;31m=> Invalid\x1b[0m') - - else: - result['message'] = 'scenario_runner broken' - sys.stdout.write(' \x1b[1;33m=> Broken (') - sys.stdout.write(str(self.client.status())) - print(')\x1b[0m') - - if self.args.log_output_base_dir: - log_output_base_dir = os.path.abspath(self.args.log_output_base_dir) - else: - log_output_base_dir = self.loader.absolute(self.database["log_output_base_dir"]) - - if not os.path.exists(log_output_base_dir): - os.makedirs(log_output_base_dir) - - result_path = os.path.join(log_output_base_dir, 'result-of-' + uuid + '.json') - - with open(result_path, 'w') as file: - json.dump(result, file, indent=2, ensure_ascii=False, sort_keys=True, separators=(',', ': ')) - - def run_all_scenario(self): - if self.args.scenario: - scenarios = [ os.path.abspath(self.args.scenario) ] - else: - scenarios = self.database["scenario"] - - for scenario in scenarios: - scenario_path = self.loader.absolute(scenario) - - print('') - print('Running scenario \x1b[36m' + os.path.relpath(scenario_path) + '\x1b[0m') - - print(' Parameters') - self.parser = ScenarioParser(scenario_path, generate_mode=True) - - n = 0 - - for path in self.parser.scenario_files_path: - n = n + 1 - - print('') - print('Launch ' + str(n) + ' of ' + str(len(self.parser.scenario_files_path))) - - print(' scenario \x1b[36m' + path + '\x1b[0m') - - self.start(path) - - self.waitUntilSimulationFinished() - - self.write_result( - scenario_path, - os.path.splitext(os.path.basename(path))[0]) - - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description='launch simulator') - - parser.add_argument('--timeout', type=int, default=180, - help='Specify simulation time limit in seconds. \ - The default is 180 seconds.') - - loader = ScenarioLoader() - - parser.add_argument('--database', - default=os.path.relpath(loader.default_scenario_database_pathname), - help='Specify relative path to your scenario_database.json.') - - parser.add_argument('--launch', - help='Specify relative path to your launch file.') - - parser.add_argument('--log-output', default='screen', - help='Specify the type of log output.') - - parser.add_argument('--log-output-base-dir', - help='Specify the directory log output to.') - - parser.add_argument('--scenario', - help='Specify the scenario you want to execute.') - - args = parser.parse_args() - - launcher = Launcher(args, loader) - launcher.run_all_scenario() diff --git a/scripts/planning_simulator_launcher/scenario_loader.py.cmake b/scripts/planning_simulator_launcher/scenario_loader.py.cmake deleted file mode 100644 index 689651c..0000000 --- a/scripts/planning_simulator_launcher/scenario_loader.py.cmake +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env python - -import os -import rospkg - - -class ScenarioLoader: - - def __init__(self): - print('Load Paths') - - self.package_name = '${PROJECT_NAME}' - print(' Package Name =\x1B[36m'), - print(self.package_name), - print('\x1B[0m') - - self.package_source_directory = '${CMAKE_CURRENT_SOURCE_DIR}' - print(' Package Source Directory =\x1B[36m'), - print(self.package_source_directory), - print('\x1B[0m') - - self.package_install_directory = rospkg.RosPack().get_path( - self.package_name) - print(' Package Install Directory =\x1B[36m'), - print(self.package_install_directory), - print('\x1B[0m') - - self.default_scenario_database_basename = 'scenario_database.json' - self.default_scenario_database_pathname = os.path.join( - self.package_source_directory, - self.default_scenario_database_basename) - print(' Default Scenario Database =\x1B[36m'), - print(self.default_scenario_database_pathname), - print('\x1B[0m') - - self.default_launch_basename = 'planning_simulator.launch' - self.default_launch_pathname = os.path.join( - self.package_source_directory, 'launch', - self.default_launch_basename) - print(' Default Launch File =\x1B[36m'), - print(self.default_launch_pathname), - print('\x1B[0m') - - print('') - - def absolute(self, path): - if os.path.isabs(path): - return path - else: - return os.path.join(self.package_source_directory, path) diff --git a/scripts/planning_simulator_launcher/scenario_roslaunch_server.py b/scripts/planning_simulator_launcher/scenario_roslaunch_server.py deleted file mode 100755 index af915d5..0000000 --- a/scripts/planning_simulator_launcher/scenario_roslaunch_server.py +++ /dev/null @@ -1,102 +0,0 @@ -#!/usr/bin/env python - -from os.path import join, dirname -from scenario_parser import ScenarioParser -import SimpleXMLRPCServer as xmlrpc_server -import json -import os -import roslaunch -import signal -import sys -import time - - -class ScenarioRoslaunchServer(): - - def __init__(self): - signal.signal(signal.SIGINT, self.signal_handler) - self.server = xmlrpc_server.SimpleXMLRPCServer( - ("localhost", 10000), allow_none=True, logRequests=False) - self.server.register_introspection_functions() - self.parent = None - self.simulation_runnig = False - - self.duration = 0 - self.exit_status = 42 - self.mileage = 0 - - def signal_handler(self,signal,frame): - sys.exit(0) - - def start(self,launch_path,scenario_path,scenario_id,map_path,log_output_dir, log_output_type): - parser = ScenarioParser(scenario_path,generate_mode=False) - arg_scenario_path = "scenario_path:=" + scenario_path - arg_map_path = "map_path:=" + map_path - arg_log_output_path = "log_output_dir:=" + log_output_dir - if os.path.exists(log_output_dir) == False: - os.makedirs(log_output_dir) - arg_scenario_id = "scenario_id:=" + parser.getScenarioId() - arg_log_output = "scenario_runner_output:=" + (log_output_type if log_output_type is not None else "screen") - cli_args = [launch_path, arg_map_path, arg_scenario_path, arg_log_output_path, arg_scenario_id, arg_log_output] - roslaunch_args = cli_args[1:] - roslaunch_file = [(roslaunch.rlutil.resolve_launch_arguments(cli_args)[0], roslaunch_args)] - uuid = roslaunch.rlutil.get_or_generate_uuid(None, False) - self.parent = roslaunch.parent.ROSLaunchParent(uuid, roslaunch_file) - self.parent.start() - self.simulation_runnig = True - - def simulationRunnig(self): - return self.simulation_runnig - - def terminate(self, exit_status): - if self.parent != None: - self.exit_status = exit_status - self.simulation_runnig = False - self.parent.shutdown() - self.parent = None - - # if self.exit_status == 0: - # print(' \x1b[32mSimulation succeeded\x1b[0m') - # elif self.exit_status == 1: - # print(' \x1b[31mSimulation failed\x1b[0m') - # else: - # print(' \x1b[31mSimulation failed (unexpected)\x1b[0m') - - def status(self): - return self.exit_status - - def update_mileage(self, new_mileage): - # print('update mileage from '), - # print(self.mileage), - # print('to'), - # print(new_mileage) - self.mileage = new_mileage - - def current_mileage(self): - return self.mileage - - def update_duration(self, new_duration): - # print('update duration from '), - # print(self.duration), - # print('to'), - # print(new_duration) - self.duration = new_duration - - def current_duration(self): - return self.duration - - def run(self): - self.server.register_function(self.current_duration) - self.server.register_function(self.current_mileage) - self.server.register_function(self.simulationRunnig) - self.server.register_function(self.start) - self.server.register_function(self.status) - self.server.register_function(self.terminate) - self.server.register_function(self.update_duration) - self.server.register_function(self.update_mileage) - self.server.serve_forever() - - -if __name__ == "__main__": - launcher = ScenarioRoslaunchServer() - launcher.run() diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..6de1af2 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,4 @@ +[develop] +script-dir=$base/lib/planning_simulator_launcher +[install] +install-scripts=$base/lib/planning_simulator_launcher \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..1aabcac --- /dev/null +++ b/setup.py @@ -0,0 +1,39 @@ +from glob import glob + +from setuptools import setup + +package_name = 'planning_simulator_launcher' + +setup( + name=package_name, + version='0.0.0', + packages=[package_name], + data_files=[ + ('share/ament_index/resource_index/packages', + ['resource/' + package_name]), + ('share/' + package_name, ['package.xml']), + ('share/' + package_name + '/launch', glob('launch/*')), + ], + install_requires=['setuptools'], + zip_safe=True, + author='Masaya Kataoka, Tomoya Kimura, Tatsuya Yamasaki', + author_email='masaya.kataoka@tier4.jp, tomoya.kimura@tier4.jp, httperror@404-notfound.jp', + maintainer='Masaya Kataoka, Tomoya Kimura, Tatsuya Yamasaki', + maintainer_email='masaya.kataoka@tier4.jp, tomoya.kimura@tier4.jp, httperror@404-notfound.jp', + keywords=['ROS'], + classifiers=[ + 'Intended Audience :: Developers', + 'License :: OSI Approved :: Apache Software License', + 'Programming Language :: Python', + 'Topic :: Software Development', + ], + description='The planning_simulator_launcher package.', + license='Apache License, Version 2.0', + tests_require=['pytest'], + entry_points={ + 'console_scripts': [ + 'launch_main = planning_simulator_launcher.launch:main', + 'show_result_main = planning_simulator_launcher.show_result', + ], + }, +) diff --git a/show_result.py b/show_result.py index f1e9bf0..b310f7a 100644 --- a/show_result.py +++ b/show_result.py @@ -2,8 +2,8 @@ print('\x1B[1;31mThis script has been deprecated.\x1B[0m') print('\x1B[1;31mPlease use the following command.\x1B[0m') -print('\x1B[1;32m> rosrun planning_simulator_launcher show_result.py\x1B[0m') +print('\x1B[1;32m> ros2 run planning_simulator_launcher show_result.py\x1B[0m') import os -os.system('rosrun planning_simulator_launcher show_result.py') +os.system('ros2 run planning_simulator_launcher show_result.py')