diff --git a/.clang-tidy b/.clang-tidy index 22487524..a6bfad84 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -25,8 +25,7 @@ Checks: > -bugprone-narrowing-conversions, -bugprone-easily-swappable-parameters, -bugprone-implicit-widening-of-multiplication-result, - -modernize-avoid-bind, - -modernize-use-nodiscard + -modernize-avoid-bind WarningsAsErrors: "*" CheckOptions: - key: readability-braces-around-statements.ShortStatementLines diff --git a/.devcontainer.json b/.devcontainer.json index 8dd28b60..461b19f5 100644 --- a/.devcontainer.json +++ b/.devcontainer.json @@ -36,6 +36,7 @@ "files.insertFinalNewline": true, "files.trimTrailingWhitespace": true, "editor.formatOnSave": true, + "editor.tabSize": 2, "xml.format.maxLineWidth": 100, "json.format.enable": true, "python.linting.enabled": true, @@ -45,14 +46,20 @@ "python.linting.mypyEnabled": true, "python.formatting.provider": "black", "autoDocstring.startOnNewLine": false, - "autoDocstring.docstringFormat": "google", + "autoDocstring.docstringFormat": "google-notypes", "isort.args": ["--profile", "black"], "isort.check": true, "python.autoComplete.extraPaths": [ - "/opt/ros/humble/lib/python3.10/site-packages/" + "/opt/ros/humble/lib/python3.10/site-packages/", + "/opt/ros/humble/local/lib/python3.10/dist-packages/", + "/opt/ros/rolling/lib/python3.10/site-packages/", + "/opt/ros/rolling/local/lib/python3.10/dist-packages/" ], "python.analysis.extraPaths": [ - "/opt/ros/humble/lib/python3.10/site-packages/" + "/opt/ros/humble/lib/python3.10/site-packages/", + "/opt/ros/humble/local/lib/python3.10/dist-packages/", + "/opt/ros/rolling/lib/python3.10/site-packages/", + "/opt/ros/rolling/local/lib/python3.10/dist-packages/" ], "C_Cpp.default.intelliSenseMode": "linux-gcc-x86", "C_Cpp.clang_format_fallbackStyle": "Google", diff --git a/.docker/Dockerfile b/.docker/Dockerfile index 6372a20b..2dd764dd 100644 --- a/.docker/Dockerfile +++ b/.docker/Dockerfile @@ -4,12 +4,15 @@ FROM ros:$ROS_DISTRO-ros-base as ci LABEL maintainer="Evan Palmer" LABEL maintainer-email="evanp922@gmail.com" +ENV DEBIAN_FRONTEND=noninteractive WORKDIR /root/ws_blue COPY . src/blue -# Install the core apt packages -RUN apt-get update && apt-get upgrade -y && apt-get install -y --no-install-recommends \ +# Install apt packages +RUN apt-get -q update \ + && apt-get -q -y upgrade \ + && apt-get -q install --no-install-recommends -y \ git \ wget \ curl \ @@ -30,10 +33,10 @@ RUN [ "/bin/bash" , "-c" , "\ && sudo ./install_geographiclib_datasets.sh" ] # Install all ROS dependencies -RUN apt-get update && apt-get upgrade -y \ +RUN apt-get -q update \ + && apt-get -q -y upgrade \ && rosdep update \ - && DEBIAN_FRONTEND=noninteractive \ - rosdep install -y --from-paths src --ignore-src --rosdistro ${ROS_DISTRO} --as-root=apt:false \ + && rosdep install -y --from-paths src --ignore-src --rosdistro ${ROS_DISTRO} --as-root=apt:false \ && rm -rf src \ && apt-get autoremove -y \ && apt-get clean -y \ @@ -42,14 +45,15 @@ RUN apt-get update && apt-get upgrade -y \ FROM ci as source ENV ROS_UNDERLAY /root/ws_blue/install +ENV DEBIAN_FRONTEND=noninteractive WORKDIR $ROS_UNDERLAY/.. COPY . src/blue -RUN apt-get update && apt-get upgrade -y \ +RUN apt-get -q update \ + && apt-get -q -y upgrade \ && rosdep update \ - && DEBIAN_FRONTEND=noninteractive \ - rosdep install -y --from-paths src --ignore-src --rosdistro ${ROS_DISTRO} --as-root=apt:false \ + && rosdep install -y --from-paths src --ignore-src --rosdistro ${ROS_DISTRO} --as-root=apt:false \ && apt-get autoremove -y \ && apt-get clean -y \ && rm -rf /var/lib/apt/lists/* @@ -65,21 +69,24 @@ RUN . "/opt/ros/${ROS_DISTRO}/setup.sh" \ FROM ci as develop ENV ROS_UNDERLAY /root/ws_blue/install +ENV DEBIAN_FRONTEND=noninteractive WORKDIR $ROS_UNDERLAY/.. COPY . src/blue -RUN apt-get update && apt-get upgrade -y \ +RUN apt-get -q update \ + && apt-get -q -y upgrade \ && rosdep update \ - && DEBIAN_FRONTEND=noninteractive \ - rosdep install -y --from-paths src --ignore-src --rosdistro ${ROS_DISTRO} --as-root=apt:false \ + && rosdep install -y --from-paths src --ignore-src --rosdistro ${ROS_DISTRO} --as-root=apt:false \ && rm -rf src \ && apt-get autoremove -y \ && apt-get clean -y \ && rm -rf /var/lib/apt/lists/* # Install development tools -RUN apt-get update && apt-get upgrade -y && apt-get install -y --no-install-recommends \ +RUN apt-get -q update \ + && apt-get -q -y upgrade \ + && apt-get -q install --no-install-recommends -y \ python3-dev \ python3-pip \ iputils-ping \ @@ -95,7 +102,8 @@ RUN pip3 install \ mypy \ isort \ flake8 \ - black + black \ + setuptools==58.2.0 # Configure a new non-root user ARG USERNAME=dev diff --git a/.dockerignore b/.dockerignore index 55c94c45..9efc3ebb 100644 --- a/.dockerignore +++ b/.dockerignore @@ -2,4 +2,4 @@ * # Except the following -!blue_bridge +!blue_manager diff --git a/blue_bridge/CMakeLists.txt b/blue_bridge/CMakeLists.txt deleted file mode 100644 index 7e9ad530..00000000 --- a/blue_bridge/CMakeLists.txt +++ /dev/null @@ -1,54 +0,0 @@ -cmake_minimum_required(VERSION 3.8) -project(blue_bridge) - -# Default to C99 -if(NOT CMAKE_C_STANDARD) - set(CMAKE_C_STANDARD 99) -endif() - -# Default to C++ 17 -if(NOT CMAKE_CXX_STANDARD) - set(CMAKE_CXX_STANDARD 17) -endif() - -if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") - add_compile_options(-Wall -Wextra -Wpedantic) -endif() - -set(THIS_PACKAGE_INCLUDE_DEPENDS - rclcpp - ament_cmake - mavros_msgs - std_msgs - std_srvs -) - -foreach(Dependency IN ITEMS ${THIS_PACKAGE_INCLUDE_DEPENDS}) - find_package(${Dependency} REQUIRED) -endforeach() - -include_directories(include) - -add_executable(${PROJECT_NAME} src/bridge.cpp) -ament_target_dependencies(${PROJECT_NAME} ${THIS_PACKAGE_INCLUDE_DEPENDS}) - -install( - TARGETS ${PROJECT_NAME} - DESTINATION lib/${PROJECT_NAME} -) - -install( - DIRECTORY include/ - DESTINATION include -) - -if(BUILD_TESTING) - find_package(ament_lint_auto REQUIRED) - - # Run linters found in package.xml except those below - set(ament_cmake_uncrustify_FOUND TRUE) - - ament_lint_auto_find_test_dependencies() -endif() - -ament_package() diff --git a/blue_bridge/include/blue_bridge/bridge.hpp b/blue_bridge/include/blue_bridge/bridge.hpp deleted file mode 100644 index 51e7082b..00000000 --- a/blue_bridge/include/blue_bridge/bridge.hpp +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2023, Evan Palmer -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: - -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. - -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -#include "mavros_msgs/msg/override_rc_in.hpp" -#include "rclcpp/rclcpp.hpp" -#include "std_srvs/srv/set_bool.hpp" - -namespace blue_bridge -{ - -class Bridge : public rclcpp::Node -{ -public: - Bridge(); - - bool active() const; - -private: - void publishOverrideRCIn() const; - void updateCurrentPwmValues(const mavros_msgs::msg::OverrideRCIn & desired_pwm_values_msg); - void enableOverride( - std::shared_ptr request, - std::shared_ptr response); - - bool bridge_running_; - - mavros_msgs::msg::OverrideRCIn current_pwm_values_; - rclcpp::Subscription::SharedPtr rc_override_subscription_; - rclcpp::Publisher::SharedPtr rc_override_publisher_; - rclcpp::Service::SharedPtr enable_override_service_; - rclcpp::TimerBase::SharedPtr timer_; -}; - -} // namespace blue_bridge diff --git a/blue_bridge/src/bridge.cpp b/blue_bridge/src/bridge.cpp deleted file mode 100644 index 15d34521..00000000 --- a/blue_bridge/src/bridge.cpp +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright 2023, Evan Palmer -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: - -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. - -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -#include "blue_bridge/bridge.hpp" - -#include -#include - -#include "rclcpp/rclcpp.hpp" - -using namespace std::chrono_literals; - -namespace blue_bridge -{ - -Bridge::Bridge() -: Node("blue_bridge"), - bridge_running_(false) -{ - // Create a subscription to get the desired PWM values - rc_override_subscription_ = this->create_subscription( - "/blue_bridge/rc/override", 1, - std::bind(&Bridge::updateCurrentPwmValues, this, std::placeholders::_1)); - - // Create a publisher to publish the current desired RC values - rc_override_publisher_ = - this->create_publisher("/mavros/rc/override", 1); - - // Start a timer to publish the current desired PWM values at a frequency of 50hz - timer_ = create_wall_timer(20ms, std::bind(&Bridge::publishOverrideRCIn, this)); - - // Set up a service to manage control - enable_override_service_ = this->create_service( - "/blue_bridge/rc/override/enable", - std::bind(&Bridge::enableOverride, this, std::placeholders::_1, std::placeholders::_2)); -} - -bool Bridge::active() const { return bridge_running_; } - -void Bridge::publishOverrideRCIn() const -{ - // Only publish the override values if the bridge has been enabled - if (active()) { - rc_override_publisher_->publish(current_pwm_values_); - } -} - -void Bridge::updateCurrentPwmValues(const mavros_msgs::msg::OverrideRCIn & desired_pwm_values_msg) -{ - // Update the desired RC override values - current_pwm_values_ = desired_pwm_values_msg; -} - -void Bridge::enableOverride( - const std::shared_ptr request, // NOLINT - std::shared_ptr response) // NOLINT -{ - // Enable/disable publishing the override messages - bridge_running_ = request->data; - - // Set the response according to whether or not the update was done properly - response->success = (bridge_running_ == request->data); -} - -} // namespace blue_bridge - -int main(int argc, char ** argv) -{ - rclcpp::init(argc, argv); - rclcpp::spin(std::make_shared()); - rclcpp::shutdown(); - return 0; -} diff --git a/blue_bridge/LICENSE b/blue_manager/LICENSE similarity index 100% rename from blue_bridge/LICENSE rename to blue_manager/LICENSE diff --git a/blue_manager/blue_manager/__init__.py b/blue_manager/blue_manager/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/blue_manager/blue_manager/manager.py b/blue_manager/blue_manager/manager.py new file mode 100644 index 00000000..580dcbfd --- /dev/null +++ b/blue_manager/blue_manager/manager.py @@ -0,0 +1,295 @@ +# Copyright 2023, Evan Palmer +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +from copy import deepcopy + +import rclpy +from mavros_msgs.msg import OverrideRCIn, ParamEvent +from rcl_interfaces.msg import Parameter +from rcl_interfaces.srv import SetParameters +from rclpy.callback_groups import ReentrantCallbackGroup +from rclpy.executors import MultiThreadedExecutor +from rclpy.node import Node +from std_srvs.srv import SetBool + + +class Manager(Node): + """Provides an interface between custom controllers and the BlueROV2.""" + + STOPPED_PWM = 1500 + + def __init__(self) -> None: + """Create a new control manager.""" + super().__init__("blue_manager") + + self.passthrough_enabled = False + self.num_thrusters = 8 + self.timeout = 1.0 + self.retries = 3 + + # Get the ROS parameters from the parameter server + self._get_ros_params() + + # We need a reentrant callback group to get synchronous calls to services from + # within service callbacks. + reentrant_callback_group = ReentrantCallbackGroup() + + self.thruster_params_backup: dict[str, Parameter | None] = { + f"SERVO{i}_FUNCTION": None for i in range(1, self.num_thrusters + 1) + } + + # Publishers + self.override_rc_in_pub = self.create_publisher( + OverrideRCIn, "/mavros/rc/override", 1 + ) + + # Subscribers + self.param_event_sub = self.create_subscription( + ParamEvent, "/mavros/param/event", self.backup_thruster_params_cb, 50 + ) + + # Services + self.set_pwm_passthrough_srv = self.create_service( + SetBool, + "/blue/manager/enable_passthrough", + self.set_rc_passthrough_mode_cb, + callback_group=reentrant_callback_group, + ) + + # Service clients + self.set_param_srv_client = self.create_client( + SetParameters, + "/mavros/param/set_parameters", + callback_group=reentrant_callback_group, + ) + + while not self.set_param_srv_client.wait_for_service(timeout_sec=1.0): + ... + + def _get_ros_params(self) -> None: + """Get the ROS parameters from the parameter server.""" + self.declare_parameters( + "", + [ + ("num_thrusters", self.num_thrusters), + ("mode_change_timeout", self.timeout), + ("mode_change_retries", self.retries), + ], + ) + + self.num_thrusters = ( + self.get_parameter("num_thrusters").get_parameter_value().integer_value + ) + self.timeout = ( + self.get_parameter("mode_change_timeout").get_parameter_value().double_value + ) + self.retries = ( + self.get_parameter("mode_change_retries") + .get_parameter_value() + .integer_value + ) + + @property + def params_successfully_backed_up(self) -> bool: + """Whether or not the thruster parameters are backed up. + + Returns: + Whether or not the parameters are backed up. + """ + return None not in self.thruster_params_backup.values() + + def backup_thruster_params_cb(self, event: ParamEvent) -> None: + """Backup the default thruster parameter values. + + MAVROS publishes the parameter values of all ArduSub parameters when + populating the parameter server. We subscribe to this topic to receive the + messages at the same time as MAVROS so that we can avoid duplicating requests + to the FCU. + + Args: + event: The default parameter loading event triggered by MAVROS. + """ + if ( + event.param_id in self.thruster_params_backup + and self.thruster_params_backup[event.param_id] is None + ): + self.thruster_params_backup[event.param_id] = Parameter( + name=event.param_id, value=event.value + ) + + if self.params_successfully_backed_up: + self.get_logger().info( + "Successfully backed up the thruster parameters." + ) + + def set_rc(self, pwm: list[int]) -> None: + """Set the PWM values of the thrusters. + + Args: + pwm: The PWM values to set the thruster to. The length of the provided list + must be equal to the number of thrusters. + + Returns: + The result of the PWM setting call. + """ + if len(pwm) != self.num_thrusters: + raise ValueError( + "The length of the PWM input must equal the number of thrusters." + ) + + # Change the values of only the thruster channels + channels = pwm + [OverrideRCIn.CHAN_NOCHANGE] * (18 - self.num_thrusters) + + self.override_rc_in_pub.publish(OverrideRCIn(channels=channels)) + + def stop_thrusters(self) -> None: + """Stop all thrusters.""" + self.get_logger().warning("Stopping all BlueROV2 thrusters.") + self.set_rc([self.STOPPED_PWM] * self.num_thrusters) + + def set_thruster_params(self, params: list[Parameter]) -> bool: + """Set the thruster parameters. + + Args: + params: The ArduSub parameters to set. + + Returns: + True if the parameters were successfully set, False otherwise. + """ + # We would actually prefer to set all of the parameters atomically, but + # this functionality is not currently supported by MAVROS + response: SetParameters.Response = self.set_param_srv_client.call( + SetParameters.Request(parameters=params) + ) + return all([result.successful for result in response.results]) + + def set_rc_passthrough_mode_cb( + self, request: SetBool.Request, response: SetBool.Response + ) -> SetBool.Response: + """Set the RC Passthrough mode. + + RC Passthrough mode enables users to control the BlueROV2 thrusters directly + using the RC channels. It is important that users disable their RC transmitter + prior to enabling RC Passthrough mode to avoid sending conflicting commands to + the thrusters. + + Args: + request: The request to enable/disable RC passthrough mode. + response: The result of the request. + + Returns: + The result of the request. + """ + if request.data: + if self.passthrough_enabled: + response.success = True + response.message = "The system is already in RC Passthrough mode." + return response + + if not self.thruster_params_backup: + response.success = False + response.message = ( + "The thrusters cannot be set to RC Passthrough mode without first" + " being successfully backed up." + ) + return response + + self.get_logger().warning( + "Attempting to switch to the RC Passthrough flight mode. All ArduSub" + " arming and failsafe procedures will be disabled upon success." + ) + + passthrough_params = deepcopy(self.thruster_params_backup) + + # Set the servo mode to "RC Passthrough" + # This disables the arming and failsafe features, but now lets us send PWM + # values to the thrusters without any mixing + for param in passthrough_params.values(): + param.value.integer_value = 1 # type: ignore + + for _ in range(self.retries): + self.passthrough_enabled = self.set_thruster_params( + list(passthrough_params.values()) + ) + response.success = self.passthrough_enabled + + if response.success: + break + + if response.success: + response.message = "Successfully switched to RC Passthrough mode." + + self.stop_thrusters() + else: + response.message = "Failed to switch to RC Passthrough mode." + else: + if not self.thruster_params_backup: + response.success = False + response.message = ( + "The thruster backup parameters have not yet been stored." + ) + + if not self.passthrough_enabled: + response.success = True + response.message = ( + "The system was not in the RC Passthrough mode to start with." + ) + return response + + self.stop_thrusters() + + self.get_logger().warning("Attempting to disable RC Passthrough mode.") + + for _ in range(self.retries): + self.passthrough_enabled = not self.set_thruster_params( + list(self.thruster_params_backup.values()) + ) + response.success = not self.passthrough_enabled + + if response.success: + break + + if response.success: + response.message = "Successfully left RC Passthrough mode." + else: + self.get_logger().warning( + "Failed to leave the RC Passthrough mode. If failure persists," + " the following backup parameters may be restored manually using" + f" QGC: {self.thruster_params_backup}" + ) + response.message = ( + "Failed to leave RC Passthrough mode. Good luck soldier." + ) + + self.get_logger().info(response.message) + + return response + + +def main(args: list[str] | None = None): + """Run the ROV manager.""" + rclpy.init(args=args) + + node = Manager() + executor = MultiThreadedExecutor() + rclpy.spin(node, executor) + + node.destroy_node() + rclpy.shutdown() diff --git a/blue_bridge/package.xml b/blue_manager/package.xml similarity index 51% rename from blue_bridge/package.xml rename to blue_manager/package.xml index 75f7a231..40717cf8 100644 --- a/blue_bridge/package.xml +++ b/blue_manager/package.xml @@ -1,22 +1,21 @@ - blue_bridge + blue_manager 0.0.1 - mavros interface between the BlueROV2 hardware and the driver. + An interface for enabling individual thruster control on the BlueROV2. Evan Palmer MIT - ament_cmake - - mavros - mavros_extras - std_msgs - std_srvs mavros_msgs - geographic_msgs + std_srvs + + ament_copyright + ament_flake8 + ament_pep257 + python3-pytest - ament_cmake + ament_python diff --git a/blue_manager/resource/blue_manager b/blue_manager/resource/blue_manager new file mode 100644 index 00000000..e69de29b diff --git a/blue_manager/setup.cfg b/blue_manager/setup.cfg new file mode 100644 index 00000000..036fce1c --- /dev/null +++ b/blue_manager/setup.cfg @@ -0,0 +1,4 @@ +[develop] +script_dir=$base/lib/blue_manager +[install] +install_scripts=$base/lib/blue_manager diff --git a/blue_manager/setup.py b/blue_manager/setup.py new file mode 100644 index 00000000..372d6539 --- /dev/null +++ b/blue_manager/setup.py @@ -0,0 +1,27 @@ +from setuptools import setup + +package_name = "blue_manager" + +setup( + name=package_name, + version="0.0.1", + packages=[package_name], + data_files=[ + ("share/ament_index/resource_index/packages", ["resource/" + package_name]), + ("share/" + package_name, ["package.xml"]), + ], + install_requires=["setuptools"], + zip_safe=True, + maintainer="Evan Palmer", + maintainer_email="evanp922@gmail.com", + description=( + "An interface for enabling individual thruster control on the BlueROV2." + ), + license="MIT", + tests_require=["pytest"], + entry_points={ + "console_scripts": [ + "blue_manager = blue_manager.manager:main", + ], + }, +) diff --git a/blue_manager/test/test_copyright.py b/blue_manager/test/test_copyright.py new file mode 100644 index 00000000..8f18fa4b --- /dev/null +++ b/blue_manager/test/test_copyright.py @@ -0,0 +1,27 @@ +# Copyright 2015 Open Source Robotics Foundation, 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 pytest +from ament_copyright.main import main + + +# Remove the `skip` decorator once the source file(s) have a copyright header +@pytest.mark.skip( + reason="No copyright header has been placed in the generated source file." +) +@pytest.mark.copyright +@pytest.mark.linter +def test_copyright(): + rc = main(argv=[".", "test"]) + assert rc == 0, "Found errors" diff --git a/blue_manager/test/test_flake8.py b/blue_manager/test/test_flake8.py new file mode 100644 index 00000000..f494570f --- /dev/null +++ b/blue_manager/test/test_flake8.py @@ -0,0 +1,25 @@ +# Copyright 2017 Open Source Robotics Foundation, 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 pytest +from ament_flake8.main import main_with_errors + + +@pytest.mark.flake8 +@pytest.mark.linter +def test_flake8(): + rc, errors = main_with_errors(argv=[]) + assert rc == 0, "Found %d code style errors / warnings:\n" % len( + errors + ) + "\n".join(errors) diff --git a/cspell.json b/cspell.json new file mode 100644 index 00000000..f73932ab --- /dev/null +++ b/cspell.json @@ -0,0 +1,15 @@ +{ + "version": "0.2", + "ignorePaths": [], + "dictionaryDefinitions": [], + "dictionaries": [], + "words": [ + "ArduSub", + "mavros", + "NOCHANGE", + "rclpy", + "srvs" + ], + "ignoreWords": [], + "import": [] +} diff --git a/setup.cfg b/setup.cfg index 33e87d65..424125b8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -12,5 +12,4 @@ ignore = profile=black [pydocstyle] -ignore = D100 -convention = google +ignore = D203,D213,D215,D406,D407,D408,D409,D413,D100,D104