diff --git a/common/autoware_ad_api_msgs/CMakeLists.txt b/common/autoware_ad_api_msgs/CMakeLists.txt new file mode 100644 index 0000000000000..74f2196f189c0 --- /dev/null +++ b/common/autoware_ad_api_msgs/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 3.5) +project(autoware_ad_api_msgs) + +find_package(ament_cmake_auto REQUIRED) +ament_auto_find_build_dependencies() + +rosidl_generate_interfaces(${PROJECT_NAME} + srv/InterfaceVersion.srv + DEPENDENCIES + builtin_interfaces + std_msgs + geometry_msgs +) + +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + ament_lint_auto_find_test_dependencies() +endif() + +ament_auto_package() diff --git a/common/autoware_ad_api_msgs/README.md b/common/autoware_ad_api_msgs/README.md new file mode 100644 index 0000000000000..d6c01f285425c --- /dev/null +++ b/common/autoware_ad_api_msgs/README.md @@ -0,0 +1,17 @@ +# autoware_ad_api_msgs + +## InterfaceVersion + +This API provides the interface version of the set of AD APIs. +It follows [Semantic Versioning][semver] in order to provide an intuitive understanding of the changes between versions. + +### Use cases + +Considering the product life cycle, there will be multiple vehicles that use different versions of the AD API due to changes in requirements or some improvements. +For example, a vehicle uses `v1` for stability and another vehicle uses `v2` for more functionality. + +In that situation, the AD API users such as developers of a Web service have to switch the application behavior based on the version that each vehicle uses. + + + +[semver]: https://semver.org/ diff --git a/common/autoware_ad_api_msgs/package.xml b/common/autoware_ad_api_msgs/package.xml new file mode 100644 index 0000000000000..6165f788d9b4b --- /dev/null +++ b/common/autoware_ad_api_msgs/package.xml @@ -0,0 +1,29 @@ + + + + autoware_ad_api_msgs + 0.0.0 + The autoware_ad_api_msgs package + Takagi, Isamu + Apache License 2.0 + + ament_cmake_auto + + geometry_msgs + std_msgs + + builtin_interfaces + rosidl_default_generators + + builtin_interfaces + rosidl_default_runtime + + ament_lint_auto + ament_lint_common + + rosidl_interface_packages + + + ament_cmake + + diff --git a/common/autoware_ad_api_msgs/srv/InterfaceVersion.srv b/common/autoware_ad_api_msgs/srv/InterfaceVersion.srv new file mode 100644 index 0000000000000..93ba2680f1859 --- /dev/null +++ b/common/autoware_ad_api_msgs/srv/InterfaceVersion.srv @@ -0,0 +1,4 @@ +--- +uint16 major +uint16 minor +uint16 patch diff --git a/common/component_interface_utils/CMakeLists.txt b/common/component_interface_utils/CMakeLists.txt new file mode 100644 index 0000000000000..ba32ad10851d5 --- /dev/null +++ b/common/component_interface_utils/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 3.8) +project(component_interface_utils) + +if(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 17) + set(CMAKE_CXX_STANDARD_REQUIRED ON) + set(CMAKE_CXX_EXTENSIONS OFF) +endif() + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wall -Wextra -Wpedantic -Werror) +endif() + +find_package(ament_cmake_auto REQUIRED) +ament_auto_find_build_dependencies() + +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + ament_lint_auto_find_test_dependencies() +endif() + +ament_auto_package() diff --git a/common/component_interface_utils/README.md b/common/component_interface_utils/README.md new file mode 100644 index 0000000000000..3bcc195901511 --- /dev/null +++ b/common/component_interface_utils/README.md @@ -0,0 +1,26 @@ +# component_interface_utils + +## Features + +This is a utility package that provides the following features: + +- Logging for service and client + +## Usage + +1. This package requires interface information in this format. + + ```cpp + struct SampleService + { + using Service = sample_msgs::srv::ServiceType; + static constexpr char name[] = "/sample/service"; + }; + ``` + +2. Create a wrapper using the above definition as follows. + + ```cpp + component_interface_utils::Service::SharedPtr srv_; + srv_ = component_interface_utils::create_service(node, ...); + ``` diff --git a/common/component_interface_utils/include/component_interface_utils/rclcpp.hpp b/common/component_interface_utils/include/component_interface_utils/rclcpp.hpp new file mode 100644 index 0000000000000..0101fce383b9f --- /dev/null +++ b/common/component_interface_utils/include/component_interface_utils/rclcpp.hpp @@ -0,0 +1,21 @@ +// Copyright 2022 TIER IV, Inc. +// +// 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. + +#ifndef COMPONENT_INTERFACE_UTILS__RCLCPP_HPP_ +#define COMPONENT_INTERFACE_UTILS__RCLCPP_HPP_ + +#include +#include + +#endif // COMPONENT_INTERFACE_UTILS__RCLCPP_HPP_ diff --git a/common/component_interface_utils/include/component_interface_utils/rclcpp/create_interface.hpp b/common/component_interface_utils/include/component_interface_utils/rclcpp/create_interface.hpp new file mode 100644 index 0000000000000..3a67d40bace70 --- /dev/null +++ b/common/component_interface_utils/include/component_interface_utils/rclcpp/create_interface.hpp @@ -0,0 +1,61 @@ +// Copyright 2022 TIER IV, Inc. +// +// 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. + +#ifndef COMPONENT_INTERFACE_UTILS__RCLCPP__CREATE_INTERFACE_HPP_ +#define COMPONENT_INTERFACE_UTILS__RCLCPP__CREATE_INTERFACE_HPP_ + +#include +#include + +#include + +namespace component_interface_utils +{ + +/// Create a service wrapper for logging. This is a private implementation. +template +typename Service::SharedPtr create_service_impl( + NodeT * node, CallbackT && callback, rclcpp::CallbackGroup::SharedPtr group = nullptr) +{ + // Use a node pointer because shared_from_this cannot be used in constructor. + // This function is a wrapper for the following. + // https://github.com/ros2/rclcpp/blob/48068130edbb43cdd61076dc1851672ff1a80408/rclcpp/include/rclcpp/node.hpp#L267-L281 + auto wrapped = Service::wrap(callback, node->get_logger()); + auto service = node->template create_service( + SpecT::name, wrapped, rmw_qos_profile_services_default, group); + return Service::make_shared(service); +} + +/// Create a service wrapper for logging. This is for lambda or bound function. +template +typename Service::SharedPtr create_service( + NodeT * node, CallbackT && callback, rclcpp::CallbackGroup::SharedPtr group = nullptr) +{ + return create_service_impl(node, std::forward(callback), group); +} + +/// Create a service wrapper for logging. This is for member function of node. +template +typename Service::SharedPtr create_service( + NodeT * node, typename Service::template CallbackType callback, + rclcpp::CallbackGroup::SharedPtr group = nullptr) +{ + using std::placeholders::_1; + using std::placeholders::_2; + return create_service_impl(node, std::bind(callback, node, _1, _2), group); +} + +} // namespace component_interface_utils + +#endif // COMPONENT_INTERFACE_UTILS__RCLCPP__CREATE_INTERFACE_HPP_ diff --git a/common/component_interface_utils/include/component_interface_utils/rclcpp/service_server.hpp b/common/component_interface_utils/include/component_interface_utils/rclcpp/service_server.hpp new file mode 100644 index 0000000000000..221df60e9f81e --- /dev/null +++ b/common/component_interface_utils/include/component_interface_utils/rclcpp/service_server.hpp @@ -0,0 +1,62 @@ +// Copyright 2022 TIER IV, Inc. +// +// 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. + +#ifndef COMPONENT_INTERFACE_UTILS__RCLCPP__SERVICE_SERVER_HPP_ +#define COMPONENT_INTERFACE_UTILS__RCLCPP__SERVICE_SERVER_HPP_ + +#include + +namespace component_interface_utils +{ + +/// The wrapper class of rclcpp::Service for logging. +template +class Service +{ +public: + RCLCPP_SMART_PTR_DEFINITIONS(Service) + + template + using CallbackType = void (NodeT::*)( + typename SpecT::Service::Request::SharedPtr, typename SpecT::Service::Response::SharedPtr); + + /// Constructor. + explicit Service(typename rclcpp::Service::SharedPtr service) + { + service_ = service; // to keep the reference count + } + + /// Create a service callback with logging added. + template + static auto wrap(CallbackT && callback, const rclcpp::Logger & logger) + { + auto wrapped = [logger, callback]( + typename SpecT::Service::Request::SharedPtr request, + typename SpecT::Service::Response::SharedPtr response) { + using rosidl_generator_traits::to_yaml; + RCLCPP_INFO_STREAM(logger, "service call: " << SpecT::name << "\n" << to_yaml(*request)); + callback(request, response); + RCLCPP_INFO_STREAM(logger, "service exit: " << SpecT::name << "\n" << to_yaml(*response)); + }; + return wrapped; + } + +private: + RCLCPP_DISABLE_COPY(Service) + typename rclcpp::Service::SharedPtr service_; +}; + +} // namespace component_interface_utils + +#endif // COMPONENT_INTERFACE_UTILS__RCLCPP__SERVICE_SERVER_HPP_ diff --git a/common/component_interface_utils/package.xml b/common/component_interface_utils/package.xml new file mode 100644 index 0000000000000..3eebdfcd52e55 --- /dev/null +++ b/common/component_interface_utils/package.xml @@ -0,0 +1,22 @@ + + + + component_interface_utils + 0.0.0 + The component_interface_utils package + Takagi, Isamu + Apache License 2.0 + + ament_cmake_auto + + autoware_ad_api_msgs + rclcpp + rclcpp_components + + ament_lint_auto + autoware_lint_common + + + ament_cmake + + diff --git a/system/default_ad_api/CMakeLists.txt b/system/default_ad_api/CMakeLists.txt new file mode 100644 index 0000000000000..cebb265023d81 --- /dev/null +++ b/system/default_ad_api/CMakeLists.txt @@ -0,0 +1,35 @@ +cmake_minimum_required(VERSION 3.8) +project(default_ad_api) + +if(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 17) + set(CMAKE_CXX_STANDARD_REQUIRED ON) + set(CMAKE_CXX_EXTENSIONS OFF) +endif() + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wall -Wextra -Wpedantic -Werror) +endif() + +find_package(ament_cmake_auto REQUIRED) +ament_auto_find_build_dependencies() + +ament_auto_add_library(${PROJECT_NAME} SHARED + src/interface_version.cpp +) + +rclcpp_components_register_nodes(${PROJECT_NAME} "default_ad_api::InterfaceVersionNode") + +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + ament_lint_auto_find_test_dependencies() + find_package(launch_testing_ament_cmake) + add_launch_test(test/main.test.py) +endif() + +install( + PROGRAMS script/web_server.py + DESTINATION lib/${PROJECT_NAME} +) + +ament_auto_package(INSTALL_TO_SHARE launch test) diff --git a/system/default_ad_api/include/default_ad_api/nodes/interface_version.hpp b/system/default_ad_api/include/default_ad_api/nodes/interface_version.hpp new file mode 100644 index 0000000000000..f91d96e2c2a61 --- /dev/null +++ b/system/default_ad_api/include/default_ad_api/nodes/interface_version.hpp @@ -0,0 +1,42 @@ +// Copyright 2022 TIER IV, Inc. +// +// 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. + +#ifndef DEFAULT_AD_API__NODES__INTERFACE_VERSION_HPP_ +#define DEFAULT_AD_API__NODES__INTERFACE_VERSION_HPP_ + +#include "default_ad_api/specs/interface/version.hpp" + +#include +#include + +namespace default_ad_api +{ + +class InterfaceVersionNode : public rclcpp::Node +{ +public: + explicit InterfaceVersionNode(const rclcpp::NodeOptions & options); + +private: + using InterfaceVersion = autoware_ad_api_msgs::srv::InterfaceVersion; + + component_interface_utils::Service::SharedPtr srv_; + void onInterfaceVersion( + const InterfaceVersion::Request::SharedPtr request, + const InterfaceVersion::Response::SharedPtr response); +}; + +} // namespace default_ad_api + +#endif // DEFAULT_AD_API__NODES__INTERFACE_VERSION_HPP_ diff --git a/system/default_ad_api/include/default_ad_api/specs/interface/version.hpp b/system/default_ad_api/include/default_ad_api/specs/interface/version.hpp new file mode 100644 index 0000000000000..7aa46c675f021 --- /dev/null +++ b/system/default_ad_api/include/default_ad_api/specs/interface/version.hpp @@ -0,0 +1,31 @@ +// Copyright 2022 TIER IV, Inc. +// +// 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. + +#ifndef DEFAULT_AD_API__SPECS__INTERFACE__VERSION_HPP_ +#define DEFAULT_AD_API__SPECS__INTERFACE__VERSION_HPP_ + +#include "autoware_ad_api_msgs/srv/interface_version.hpp" + +namespace ad_api::interface::version +{ + +struct T +{ + using Service = autoware_ad_api_msgs::srv::InterfaceVersion; + static constexpr char name[] = "/api/interface/version"; +}; + +} // namespace ad_api::interface::version + +#endif // DEFAULT_AD_API__SPECS__INTERFACE__VERSION_HPP_ diff --git a/system/default_ad_api/launch/default_ad_api.launch.py b/system/default_ad_api/launch/default_ad_api.launch.py new file mode 100644 index 0000000000000..206689f38296c --- /dev/null +++ b/system/default_ad_api/launch/default_ad_api.launch.py @@ -0,0 +1,47 @@ +# Copyright 2022 TIER IV, Inc. +# +# 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. + +import launch +from launch_ros.actions import ComposableNodeContainer +from launch_ros.actions import Node +from launch_ros.descriptions import ComposableNode + + +def _create_api_node(node_name, class_name, **kwargs): + return ComposableNode( + namespace="default_ad_api/node", + name=node_name, + package="default_ad_api", + plugin="default_ad_api::" + class_name, + **kwargs + ) + + +def generate_launch_description(): + components = [ + _create_api_node("interface_version", "InterfaceVersionNode"), + ] + container = ComposableNodeContainer( + namespace="default_ad_api", + name="container", + package="rclcpp_components", + executable="component_container_mt", + composable_node_descriptions=components, + ) + web_server = Node( + package="default_ad_api", + name="web_server", + executable="web_server.py", + ) + return launch.LaunchDescription([container, web_server]) diff --git a/system/default_ad_api/package.xml b/system/default_ad_api/package.xml new file mode 100644 index 0000000000000..3f0fd55896a98 --- /dev/null +++ b/system/default_ad_api/package.xml @@ -0,0 +1,27 @@ + + + + default_ad_api + 0.1.0 + The default_ad_api package + Takagi, Isamu + Apache License 2.0 + + ament_cmake_auto + + autoware_ad_api_msgs + component_interface_utils + rclcpp + rclcpp_components + + python3-flask + + ament_lint_auto + autoware_lint_common + launch_testing + launch_testing_ament_cmake + + + ament_cmake + + diff --git a/system/default_ad_api/script/web_server.py b/system/default_ad_api/script/web_server.py new file mode 100755 index 0000000000000..ac2e4762cd80b --- /dev/null +++ b/system/default_ad_api/script/web_server.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 + +# Copyright 2022 TIER IV, Inc. +# +# 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. + +import sys +from threading import Thread + +from autoware_ad_api_msgs.srv import InterfaceVersion +import flask +import rclpy +from rclpy.node import Node + +app = flask.Flask(__name__) +cli = None + + +@app.route("/interface/version") +def interface_version(): + req = InterfaceVersion.Request() + res = cli.call(req) + return flask.jsonify(convert_dict(res)) + + +def create_service(node, service_type, service_name): + cli = node.create_client(service_type, service_name) + while not cli.wait_for_service(timeout_sec=1.0): + node.get_logger().info(f"service not available, waiting again... ({service_name}") + return cli + + +def convert_dict(msg): + fields = getattr(msg, "get_fields_and_field_types", None) + if not fields: + return msg + return {field: convert_dict(getattr(msg, field)) for field in fields()} + + +def spin_ros_node(): + global cli + rclpy.init(args=sys.argv) + node = Node("ad_api_default_web_server") + cli = create_service(node, InterfaceVersion, "/api/interface/version") + rclpy.spin(node) + + +if __name__ == "__main__": + thread = Thread(target=spin_ros_node) + thread.start() + app.run(host="localhost", port=8888, debug=True) + rclpy.shutdown() + thread.join() diff --git a/system/default_ad_api/src/interface_version.cpp b/system/default_ad_api/src/interface_version.cpp new file mode 100644 index 0000000000000..7286d4006a502 --- /dev/null +++ b/system/default_ad_api/src/interface_version.cpp @@ -0,0 +1,38 @@ +// Copyright 2022 TIER IV, Inc. +// +// 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. + +#include "default_ad_api/nodes/interface_version.hpp" + +namespace default_ad_api +{ + +InterfaceVersionNode::InterfaceVersionNode(const rclcpp::NodeOptions & options) +: Node("interface_version", options) +{ + srv_ = component_interface_utils::create_service( + this, &InterfaceVersionNode::onInterfaceVersion); +} + +void InterfaceVersionNode::onInterfaceVersion( + const InterfaceVersion::Request::SharedPtr, const InterfaceVersion::Response::SharedPtr response) +{ + response->major = 0; + response->minor = 1; + response->patch = 0; +} + +} // namespace default_ad_api + +#include +RCLCPP_COMPONENTS_REGISTER_NODE(default_ad_api::InterfaceVersionNode) diff --git a/system/default_ad_api/test/main.test.py b/system/default_ad_api/test/main.test.py new file mode 100644 index 0000000000000..728f8a3604ada --- /dev/null +++ b/system/default_ad_api/test/main.test.py @@ -0,0 +1,49 @@ +# Copyright 2022 TIER IV, Inc. +# +# 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. + +import importlib.util +import unittest + +from ament_index_python.packages import get_package_share_directory +import launch +import launch_testing.actions +import launch_testing.markers +import launch_testing.tools +import pytest + + +@pytest.mark.launch_test +@launch_testing.markers.keep_alive +def generate_test_description(): + path = get_package_share_directory("default_ad_api") + "/launch/default_ad_api.launch.py" + specification = importlib.util.spec_from_file_location("launch_script", path) + launch_script = importlib.util.module_from_spec(specification) + specification.loader.exec_module(launch_script) + return launch.LaunchDescription( + [ + *launch_script.generate_launch_description().describe_sub_entities(), + launch_testing.actions.ReadyToTest(), + ] + ) + + +class TestMain(unittest.TestCase): + def test_interface_version(self, launch_service, proc_info, proc_output): + prefix = get_package_share_directory("default_ad_api") + target = prefix + "/test/node/interface_version.py" + action = launch.actions.ExecuteProcess(cmd=["python3", target]) + with launch_testing.tools.launch_process(launch_service, action, proc_info, proc_output): + proc_info.assertWaitForStartup(process=action, timeout=1) + proc_info.assertWaitForShutdown(process=action, timeout=3) + launch_testing.asserts.assertExitCodes(proc_info, process=action) diff --git a/system/default_ad_api/test/node/interface_version.py b/system/default_ad_api/test/node/interface_version.py new file mode 100644 index 0000000000000..d5abd2924e24e --- /dev/null +++ b/system/default_ad_api/test/node/interface_version.py @@ -0,0 +1,38 @@ +# Copyright 2022 TIER IV, Inc. +# +# 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. + +from autoware_ad_api_msgs.srv import InterfaceVersion +import rclpy +import rclpy.node + +rclpy.init() +node = rclpy.node.Node("test_client") + +client = node.create_client(InterfaceVersion, "/api/interface/version") +if not client.wait_for_service(timeout_sec=3.0): + exit(1) + +request = InterfaceVersion.Request() +future = client.call_async(request) +rclpy.spin_until_future_complete(node, future) +response = future.result() + +if response.major != 0: + exit(1) +if response.minor != 1: + exit(1) +if response.patch != 0: + exit(1) + +rclpy.shutdown()