diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index bc84d392c..2390e8a74 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -5,8 +5,18 @@ name: Coverage on: push: branches: [master, ros2] + paths-ignore: + - 'docs/**' + - '**/*.md' + - 'LICENSE*' + - '.github/dependabot.yml' pull_request: branches: [master, ros2] + paths-ignore: + - 'docs/**' + - '**/*.md' + - 'LICENSE*' + - '.github/dependabot.yml' permissions: contents: read @@ -40,22 +50,30 @@ jobs: COMPONENT_NAME: ${{ matrix.component.name }} COMPONENT_PACKAGES: ${{ matrix.component.packages }} COMPONENT_FILTER: ${{ matrix.component.source_filter }} + CCACHE_DIR: ${{ github.workspace }}/.ccache # Enable the libmavconn e2e test dep (python3-pymavlink-pip) and allow # rosdep to pip-install it on PEP 668 distros (jazzy+) TEST_ENABLE_E2E: "1" PIP_BREAK_SYSTEM_PACKAGES: "1" steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@v7 # nosemgrep - - uses: ros-tooling/setup-ros@v0.7 + - uses: ros-tooling/setup-ros@649ef6bcd696da05bc27ceb3fab69d810c0daeab with: required-ros-distributions: ${{ env.ROS_DISTRO }} - name: Install Coverage Tools run: | sudo apt-get update - sudo apt-get install -y gcovr + sudo apt-get install -y gcovr ccache + + - uses: actions/cache@v6 # nosemgrep + with: + path: ${{ env.CCACHE_DIR }} + key: "ccache-cov-${{ runner.os }}-${{ matrix.ros_distro }}-${{ matrix.component.name }}-${{ hashFiles('**/package.xml', '**/CMakeLists.txt') }}" + restore-keys: | + ccache-cov-${{ runner.os }}-${{ matrix.ros_distro }}-${{ matrix.component.name }}- - name: Install Dependencies shell: bash @@ -76,7 +94,9 @@ jobs: colcon build --packages-up-to ${COMPONENT_PACKAGES} --cmake-args \ -DCMAKE_BUILD_TYPE=Debug \ -DCMAKE_C_FLAGS=--coverage \ - -DCMAKE_CXX_FLAGS=--coverage + -DCMAKE_CXX_FLAGS=--coverage \ + -DCMAKE_C_COMPILER_LAUNCHER=ccache \ + -DCMAKE_CXX_COMPILER_LAUNCHER=ccache - name: Run Tests shell: bash @@ -100,7 +120,7 @@ jobs: "${GITHUB_WORKSPACE}/build" - name: Upload Coverage Artifact - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@v7 # nosemgrep with: name: coverage-${{ matrix.component.name }}-${{ matrix.ros_distro }} path: coverage/${{ matrix.component.name }} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 67e8ad377..ac3c8194c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -5,8 +5,18 @@ name: CI on: push: branches: [master, ros2] + paths-ignore: + - 'docs/**' + - '**/*.md' + - 'LICENSE*' + - '.github/dependabot.yml' pull_request: branches: [master, ros2] + paths-ignore: + - 'docs/**' + - '**/*.md' + - 'LICENSE*' + - '.github/dependabot.yml' permissions: contents: read @@ -44,8 +54,8 @@ jobs: PIP_BREAK_SYSTEM_PACKAGES: "1" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7 - - uses: actions/cache@v6 + - uses: actions/checkout@v7 # nosemgrep + - uses: actions/cache@v6 # nosemgrep with: path: "${{ env.CCACHE_DIR }}" key: "ccache-${{ runner.os }}-${{ matrix.env.ROS_DISTRO }}-${{ matrix.env.ROS_REPO }}-${{ hashFiles('**/package.xml', '**/CMakeLists.txt') }}" diff --git a/.github/workflows/semgrep-analysis.yml b/.github/workflows/semgrep-analysis.yml index d65c15b2b..806187880 100644 --- a/.github/workflows/semgrep-analysis.yml +++ b/.github/workflows/semgrep-analysis.yml @@ -31,7 +31,7 @@ jobs: if: (github.actor != 'dependabot[bot]') steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@v7 # nosemgrep - run: semgrep ci env: SEMGREP_RULES: p/default diff --git a/mavros/CMakeLists.txt b/mavros/CMakeLists.txt index fd6392973..657f0c561 100644 --- a/mavros/CMakeLists.txt +++ b/mavros/CMakeLists.txt @@ -94,6 +94,13 @@ if(message_filters_VERSION VERSION_LESS 5.0.0) ) endif() +if(rclcpp_VERSION VERSION_GREATER_EQUAL 30.0.0) + # Lyrical and newer: Callback Group Events executor (EventsCBGExecutor) + add_definitions( + -DMAVROS_HAVE_EVENTS_CBG_EXECUTOR + ) +endif() + # [[[cog: # import mavros_cog # ]]] diff --git a/mavros/README.md b/mavros/README.md index 5e2a20d18..3c0dbad3f 100644 --- a/mavros/README.md +++ b/mavros/README.md @@ -110,6 +110,31 @@ Main node. Allow disable GCS proxy by setting empty URL. ros2 run mavros mavros_node --ros-args --params-file params.yaml +Executors +--------- + +MAVROS builds its executors via a factory that honors two environment variables: + + - `MAVROS_EXECUTOR_TYPE` - executor used by both `mavros_node` container and + the UAS plugin executor: + - `mt` (default) - `rclcpp::executors::MultiThreadedExecutor` + - `events` (alias `cbg`) - the Callback Group Events executor + (`rclcpp::executors::EventsCBGExecutor`), available only on Lyrical+ + (rclcpp >= 30.0.0). On older distros the request is ignored with a + warning and `MultiThreadedExecutor` is used. + - `MAVROS_UAS_EXECUTOR_THREADS` - number of threads for the UAS plugin + executor (default: clamped hardware concurrency, min 4 max 16; must be + >= 2 if set). + +Example, running `mavros_node` with the events executor: + + MAVROS_EXECUTOR_TYPE=events ros2 run mavros mavros_node + +When MAVROS is used as composable nodes inside a component container, the +container's executor is chosen with the container's own `--executor-type` +argument instead; see the composable launch below. + + Launch Files ------------ @@ -119,6 +144,29 @@ Launch files are provided for use with common FCUs, in particular [Pixhawk](pixh * [px4.launch](launch/px4.launch) -- for use with the PX4 Autopilot (for VTOL, multicopters and planes) * [apm.launch](launch/apm.launch) -- for use with APM flight stacks (e.g., all versions of ArduPlane, ArduCopter, etc) + * [test_compose.launch.py](launch/test_compose.launch.py) -- loads `mavros::router::Router` and one or two + `mavros::uas::UAS` nodes as composable nodes into a component container. + +`test_compose.launch.py` accepts: + + - `fcu_url` (default `udp://0.0.0.0:14540@`) - FCU connection URL + - `gcs_url` (default `udp://127.0.0.1:14555@`) - GCS connection URL + - `executor` (default `mt`) - container executor: `mt`, `events`, or `auto` + (`events` on Lyrical+, `mt` otherwise). The events (Callback Group + Events) executor support inside a component container is still immature + upstream (ros2/rclcpp#3186), so the reliable `component_container_mt` is + the default. + +Example: + + ros2 launch mavros test_compose.launch.py fcu_url:=udp://@192.168.60.192:15000 executor:=events + +Components can also be loaded/unloaded at runtime against a running +container: + + ros2 component load /mavros_container mavros mavros::router::Router + ros2 component load /mavros_container mavros mavros::uas::UAS + ros2 component unload /mavros_container Examples: diff --git a/mavros/include/mavros/mavros_uas.hpp b/mavros/include/mavros/mavros_uas.hpp index 9e98aa0bb..51a9bafcf 100644 --- a/mavros/include/mavros/mavros_uas.hpp +++ b/mavros/include/mavros/mavros_uas.hpp @@ -262,6 +262,13 @@ class UAS : public rclcpp::Node ~UAS() override; + // UAS is not copyable/movable: it owns an executor thread, TF listeners, + // subscriptions and plugin nodes. + UAS(const UAS &) = delete; + UAS & operator=(const UAS &) = delete; + UAS(UAS &&) = delete; + UAS & operator=(UAS &&) = delete; + /** * @brief Mavros diagnostic updater */ diff --git a/mavros/include/mavros/plugin.hpp b/mavros/include/mavros/plugin.hpp index ad802c379..b8bc66c4e 100644 --- a/mavros/include/mavros/plugin.hpp +++ b/mavros/include/mavros/plugin.hpp @@ -44,7 +44,9 @@ namespace plugin { using mavros::uas::UAS; -using UASPtr = std::shared_ptr; +// NOTE(vooon): non-owning pointer to avoid a reference cycle: the UAS node +// owns the plugins (loaded_plugins), so the plugins must not own the UAS. +using UASPtr = UAS *; using r_unique_lock = std::unique_lock; using s_unique_lock = std::unique_lock; using s_shared_lock = std::shared_lock; @@ -74,18 +76,13 @@ class Plugin : public std::enable_shared_from_this //! Subscriptions vector using Subscriptions = std::vector; - explicit Plugin(UASPtr uas_) - : uas(uas_), node(std::dynamic_pointer_cast(uas_)) - {} + // NOTE(vooon): ctors are implemented in plugin.cpp because UAS is an + // incomplete type at the point plugin.hpp is included (from mavros_uas.hpp). + explicit Plugin(UASPtr uas_); explicit Plugin( UASPtr uas_, const std::string & subnode, - const rclcpp::NodeOptions & options = rclcpp::NodeOptions()) - : uas(uas_), - // node(std::dynamic_pointer_cast(uas_)->create_sub_node(subnode)) // https://github.com/ros2/rclcpp/issues/731 - node(rclcpp::Node::make_shared(subnode, - std::dynamic_pointer_cast(uas_)->get_fully_qualified_name(), options)) - {} + const rclcpp::NodeOptions & options = rclcpp::NodeOptions()); virtual ~Plugin() = default; diff --git a/mavros/include/mavros/plugin_filter.hpp b/mavros/include/mavros/plugin_filter.hpp index 3d3b28a17..b89e0192c 100644 --- a/mavros/include/mavros/plugin_filter.hpp +++ b/mavros/include/mavros/plugin_filter.hpp @@ -32,7 +32,7 @@ namespace filter { using mavros::plugin::Filter; using mavros::uas::UAS; -using UASPtr = UAS::SharedPtr; +using UASPtr = UAS *; using mavconn::Framing; diff --git a/mavros/include/mavros/uas_executor.hpp b/mavros/include/mavros/uas_executor.hpp index 87b8cbedb..5ba95816a 100644 --- a/mavros/include/mavros/uas_executor.hpp +++ b/mavros/include/mavros/uas_executor.hpp @@ -19,14 +19,12 @@ #ifndef MAVROS__UAS_EXECUTOR_HPP_ #define MAVROS__UAS_EXECUTOR_HPP_ -#include +#include #include #include #include "rclcpp/rclcpp.hpp" -#include "mavros/utils.hpp" - namespace mavros { namespace uas @@ -34,26 +32,47 @@ namespace uas /** * Executor for UAS Plugin nodes + * + * Thin wrapper around rclcpp::Executor that lets the user pick the + * underlying executor type at runtime (MAVROS_EXECUTOR_TYPE env var). */ -class UASExecutor : public rclcpp::executors::MultiThreadedExecutor +class UASExecutor { public: explicit UASExecutor(const rclcpp::ExecutorOptions & options = rclcpp::ExecutorOptions()); ~UASExecutor() = default; - void set_ids(uint8_t sysid, uint8_t compid); - -protected: - void run(size_t thread_id); + void spin(); + void cancel(); + void add_node(const std::shared_ptr & node); + size_t get_number_of_threads() const; private: RCLCPP_DISABLE_COPY(UASExecutor) static size_t select_number_of_threads(); - uint8_t source_system, source_component; + std::unique_ptr impl_; + size_t number_of_threads_; }; +/** + * Factory for executors honoring the MAVROS_EXECUTOR_TYPE env var: + * - "events" (or "cbg"): rclcpp::executors::EventsCBGExecutor (Lyrical+ only) + * - otherwise (default): rclcpp::executors::MultiThreadedExecutor + * + * \param options common options for all executors + * \param number_of_threads number of threads to have in the thread pool + * \param yield_before_execute MultiThreadedExecutor option, ignored for + * the events executor + * \param timeout maximum time to wait + */ +std::unique_ptr make_executor( + const rclcpp::ExecutorOptions & options, + size_t number_of_threads, + bool yield_before_execute = false, + std::chrono::nanoseconds timeout = std::chrono::nanoseconds(-1)); + } // namespace uas } // namespace mavros diff --git a/mavros/launch/test_compose.launch.py b/mavros/launch/test_compose.launch.py index 0f06fa533..81629145c 100644 --- a/mavros/launch/test_compose.launch.py +++ b/mavros/launch/test_compose.launch.py @@ -1,24 +1,43 @@ -from launch import LaunchDescription +import os + +from launch import LaunchContext, LaunchDescription +from launch.actions import DeclareLaunchArgument, OpaqueFunction +from launch.substitutions import LaunchConfiguration from launch_ros.actions import ComposableNodeContainer from launch_ros.descriptions import ComposableNode -def generate_launch_description(): - """Generate launch description for MAVROS composable node.""" +def _build_container(context: LaunchContext, fcu_url, gcs_url, executor): + """Build the composable container once launch arguments are resolved.""" + fcu = context.perform_substitution(fcu_url) + gcs = context.perform_substitution(gcs_url) + exec_type = context.launch_configurations["executor"] + + # The Callback Group Events executor is only available on Lyrical+ + # (rclcpp >= 30.0.0); its component-container support is still + # upstream-immature (see ros2/rclcpp#3186), so default to MT. + distro = os.environ.get("ROS_DISTRO", "") + if exec_type == "events" or ( + exec_type == "auto" and distro in ("lyrical", "rolling")): + container_executable = "component_container" + container_arguments = ["--executor-type", "events-cbg"] + else: + container_executable = "component_container_mt" + container_arguments = [] + container = ComposableNodeContainer( name="mavros_container", namespace="", package="rclcpp_components", - executable="component_container_mt", + executable=container_executable, composable_node_descriptions=[ ComposableNode( package="mavros", plugin="mavros::router::Router", name="mavros_router", parameters=[ - # {"fcu_urls": ["tcp://127.0.0.1:5760"]}, - {"fcu_urls": ["udp://0.0.0.0:14540@"]}, - {"gcs_urls": ["udp://127.0.0.1:14555@"]}, + {"fcu_urls": [fcu]}, + {"gcs_urls": [gcs]}, {"uas_urls": ["/uas1", "/uas2"]}, {"fcu_protocol": "v2.0"}, ], @@ -55,8 +74,31 @@ def generate_launch_description(): extra_arguments=[{"use_intra_process_comms": True}], ), ], + arguments=container_arguments + ["--ros-args", "--log-level", "INFO"], output="screen", - arguments=["--ros-args", "--log-level", "DEBUG"], ) - return LaunchDescription([container]) + return [container] + + +def generate_launch_description(): + """Generate launch description for MAVROS composable node.""" + fcu_url = LaunchConfiguration("fcu_url") + gcs_url = LaunchConfiguration("gcs_url") + executor = LaunchConfiguration("executor") + + return LaunchDescription([ + DeclareLaunchArgument( + "fcu_url", default_value="udp://0.0.0.0:14540@", + description="FCU connection URL" + ), + DeclareLaunchArgument( + "gcs_url", default_value="udp://127.0.0.1:14555@", + description="GCS connection URL" + ), + DeclareLaunchArgument( + "executor", default_value="mt", + description="Container executor: mt, events, or auto" + ), + OpaqueFunction(function=_build_container, args=[fcu_url, gcs_url, executor]), + ]) diff --git a/mavros/src/lib/mavros_uas.cpp b/mavros/src/lib/mavros_uas.cpp index 8a8a5131b..695e87159 100644 --- a/mavros/src/lib/mavros_uas.cpp +++ b/mavros/src/lib/mavros_uas.cpp @@ -287,7 +287,7 @@ plugin::Plugin::SharedPtr UAS::create_plugin_instance(const std::string & pl_nam auto plugin_factory = plugin_factory_loader.createSharedInstance(pl_name); return - plugin_factory->create_plugin_instance(std::static_pointer_cast(shared_from_this())); + plugin_factory->create_plugin_instance(this); } void UAS::add_plugin(const std::string & pl_name) diff --git a/mavros/src/lib/plugin.cpp b/mavros/src/lib/plugin.cpp index bb8dd1923..6847a88d4 100644 --- a/mavros/src/lib/plugin.cpp +++ b/mavros/src/lib/plugin.cpp @@ -18,6 +18,25 @@ using mavros::plugin::Plugin; +Plugin::Plugin(UASPtr uas_) +: uas(uas_), node(uas_->shared_from_this()) +{ +} + +Plugin::Plugin( + UASPtr uas_, const std::string & subnode, + const rclcpp::NodeOptions & options) +: uas(uas_) +{ + // Dynamically created plugin nodes must not inherit process-global + // __node/__ns remap rules (a component container remaps itself with + // e.g. -r __node:=), otherwise every plugin node is renamed. + rclcpp::NodeOptions node_options(options); + node_options.use_global_arguments(false); + node_options.use_intra_process_comms(true); + node = rclcpp::Node::make_shared(subnode, uas_->get_fully_qualified_name(), node_options); +} + void Plugin::enable_connection_cb() { uas->add_connection_change_handler( diff --git a/mavros/src/lib/uas_executor.cpp b/mavros/src/lib/uas_executor.cpp index 099041241..fc192b65c 100644 --- a/mavros/src/lib/uas_executor.cpp +++ b/mavros/src/lib/uas_executor.cpp @@ -13,52 +13,102 @@ #include #include +#include +#include #include "mavros/uas_executor.hpp" +#ifdef MAVROS_HAVE_EVENTS_CBG_EXECUTOR +#include "rclcpp/executors/events_cbg_executor/events_cbg_executor.hpp" +#endif + using namespace mavros; // NOLINT using namespace mavros::uas; // NOLINT using namespace std::chrono_literals; // NOLINT UASExecutor::UASExecutor(const rclcpp::ExecutorOptions & options) -: MultiThreadedExecutor(options, select_number_of_threads(), true, 1000ms), - source_system(0), - source_component(0) { + number_of_threads_ = select_number_of_threads(); + impl_ = make_executor(options, number_of_threads_, true, 1000ms); +} + +void UASExecutor::spin() +{ + impl_->spin(); +} + +void UASExecutor::cancel() +{ + impl_->cancel(); +} + +void UASExecutor::add_node(const std::shared_ptr & node) +{ + impl_->add_node(node); +} + +size_t UASExecutor::get_number_of_threads() const +{ + return number_of_threads_; } size_t UASExecutor::select_number_of_threads() { - if (const char *env = std::getenv("MAVROS_UAS_EXECUTOR_THREADS")) { + if (const char * env = std::getenv("MAVROS_UAS_EXECUTOR_THREADS")) { try { size_t n = std::stoul(env); if (n >= 2) { RCLCPP_INFO( - rclcpp::get_logger("uas_executor"), - "UAS executor threads overridden by MAVROS_UAS_EXECUTOR_THREADS: %zu", n); + rclcpp::get_logger("uas_executor"), + "UAS executor threads overridden by MAVROS_UAS_EXECUTOR_THREADS: %zu", n); return n; } RCLCPP_WARN( - rclcpp::get_logger("uas_executor"), - "MAVROS_UAS_EXECUTOR_THREADS must be >= 2, got %zu. Using default.", n); + rclcpp::get_logger("uas_executor"), + "MAVROS_UAS_EXECUTOR_THREADS must be >= 2, got %zu. Using default.", n); } catch (const std::exception & e) { RCLCPP_WARN( - rclcpp::get_logger("uas_executor"), - "Invalid MAVROS_UAS_EXECUTOR_THREADS value '%s': %s. Using default.", env, e.what()); + rclcpp::get_logger("uas_executor"), + "Invalid MAVROS_UAS_EXECUTOR_THREADS value '%s': %s. Using default.", env, e.what()); } } - // return std::max(16, std::min(std::thread::hardware_concurrency(), 4)); return std::clamp(std::thread::hardware_concurrency(), 4, 16); } -void UASExecutor::set_ids(uint8_t sysid, uint8_t compid) +std::unique_ptr mavros::uas::make_executor( + const rclcpp::ExecutorOptions & options, + size_t number_of_threads, + bool yield_before_execute, + std::chrono::nanoseconds timeout) { - source_system = sysid; - source_component = compid; -} + bool use_events = false; + if (const char * env = std::getenv("MAVROS_EXECUTOR_TYPE")) { + if (std::strcmp(env, "events") == 0 || std::strcmp(env, "cbg") == 0) { + use_events = true; + } else if (std::strcmp(env, "mt") != 0 && std::strcmp(env, "multithreaded") != 0) { + RCLCPP_WARN( + rclcpp::get_logger("uas_executor"), + "Invalid MAVROS_EXECUTOR_TYPE value '%s'. Using MultiThreadedExecutor.", env); + } + } -void UASExecutor::run(size_t thread_id) -{ - utils::set_this_thread_name("uas-exec/%d.%d/%zu", source_system, source_component, thread_id); - MultiThreadedExecutor::run(thread_id); +#ifdef MAVROS_HAVE_EVENTS_CBG_EXECUTOR + if (use_events) { + RCLCPP_INFO( + rclcpp::get_logger("uas_executor"), + "Using EventsCBGExecutor, threads: %zu", number_of_threads); + return std::make_unique( + options, number_of_threads, timeout); + } +#else + if (use_events) { + RCLCPP_WARN( + rclcpp::get_logger("uas_executor"), + "EventsCBGExecutor requested but not available in this ROS distro. " + "Using MultiThreadedExecutor."); + } +#endif + + return std::make_unique( + options, number_of_threads, yield_before_execute, timeout); } diff --git a/mavros/src/mavros_node.cpp b/mavros/src/mavros_node.cpp index a39887e31..bd8312475 100644 --- a/mavros/src/mavros_node.cpp +++ b/mavros/src/mavros_node.cpp @@ -17,6 +17,7 @@ #include "mavros/mavros_router.hpp" #include "mavros/mavros_uas.hpp" +#include "mavros/uas_executor.hpp" #include "rclcpp/rclcpp.hpp" /** @@ -26,17 +27,19 @@ int main(int argc, char * argv[]) { rclcpp::init(argc, argv); - rclcpp::executors::MultiThreadedExecutor exec(rclcpp::ExecutorOptions(), 2); + auto exec = mavros::uas::make_executor(rclcpp::ExecutorOptions(), 2); rclcpp::NodeOptions options; - // options.use_intra_process_comms(true); + // NOTE(vooon): when router and uas run in the same process the internal + // MAVLink bus goes zero-copy; cross-process consumers fall back to DDS. + options.use_intra_process_comms(true); std::string fcu_url, gcs_url, uas_url; std::string base_link_frame_id, odom_frame_id, map_frame_id; int tgt_system = 1, tgt_component = 1; auto node = std::make_shared("mavros_node", options); - exec.add_node(node); + exec->add_node(node); node->declare_parameter("fcu_url", fcu_url); node->declare_parameter("gcs_url", gcs_url); @@ -63,7 +66,7 @@ int main(int argc, char * argv[]) RCLCPP_INFO(node->get_logger(), "Starting mavros router node"); auto router_node = std::make_shared(options, "mavros_router"); - exec.add_node(router_node); + exec->add_node(router_node); { std::vector router_params{}; @@ -83,7 +86,7 @@ int main(int argc, char * argv[]) auto uas_node = std::make_shared( options, "mavros", uas_url, tgt_system, tgt_component); - exec.add_node(uas_node); + exec->add_node(uas_node); { std::vector uas_params{}; @@ -102,7 +105,7 @@ int main(int argc, char * argv[]) uas_node->set_parameters(uas_params); } - exec.spin(); + exec->spin(); rclcpp::shutdown(); return 0; } diff --git a/mavros/test/test_uas.cpp b/mavros/test/test_uas.cpp index d1bb11a92..5658b7c94 100644 --- a/mavros/test/test_uas.cpp +++ b/mavros/test/test_uas.cpp @@ -62,7 +62,7 @@ class MockPlugin : public plugin::Plugin using SharedPtr = std::shared_ptr; explicit MockPlugin(UAS::SharedPtr uas_) - : Plugin(uas_) {} + : Plugin(uas_.get()) {} MOCK_METHOD0(get_subscriptions, plugin::Plugin::Subscriptions(void));