commit 92f28f37f9748ab6e5163c2869ab35234709fad6 Author: chyoo Date: Wed Jul 29 15:10:12 2026 +0900 feat: add NAV2 perception3D module diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..76b3365 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +build/ +install/ +log/ +.colcon/ +*.log diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..0c1c5bc --- /dev/null +++ b/build.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +set -euo pipefail + +MODULE_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +RUNTIME_ROOT="${AUTONOMY_STUDIO_RUNTIME_ROOT:-$HOME/autonomy-studio/runtime/nav2}" +exec bash "$RUNTIME_ROOT/docker/docker_build_nav2.sh" "$(dirname "$MODULE_PATH")" "$(basename "$MODULE_PATH")" "${NAV2_DOCKER_IMAGE:-future-nav2-gazebo:humble}" "${ROS_DOMAIN_ID:-0}" diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..bec746a --- /dev/null +++ b/run.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -euo pipefail + +MODULE_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +RUNTIME_ROOT="${AUTONOMY_STUDIO_RUNTIME_ROOT:-$HOME/autonomy-studio/runtime/nav2}" + +exec bash "$RUNTIME_ROOT/docker/docker_run_nav2.sh" \ + "lms-nav2-perception3d" \ + "$(dirname "$MODULE_PATH")" \ + "$(basename "$MODULE_PATH")" \ + "${NAV2_DOCKER_IMAGE:-future-nav2:humble}" \ + "${ROS_DOMAIN_ID:-0}" \ + sim_mode:=false robot:=mkmini diff --git a/src/perception3d_pkg/config/3d_fr09.yaml b/src/perception3d_pkg/config/3d_fr09.yaml new file mode 100755 index 0000000..b03087c --- /dev/null +++ b/src/perception3d_pkg/config/3d_fr09.yaml @@ -0,0 +1,20 @@ + + +odometry: + ros__parameters: + odom_topic: "/vehicle_odom" + odom_frame: "odom" + base_frame: "base_link" + +pcd_crop: + ros__parameters: + input_topic: "/raw/lidar" + output_topic: "/lidar_filtered" + min_z: -5.0 + max_z: 0.5 + # min_z: -10.0 + # max_z: 10.0 + min_radius: 1.0 + max_radius: 50.0 + # min_angle: -1.570796 + # max_angle: 2.356194 diff --git a/src/perception3d_pkg/config/3d_mkmini.yaml b/src/perception3d_pkg/config/3d_mkmini.yaml new file mode 100755 index 0000000..fb29cae --- /dev/null +++ b/src/perception3d_pkg/config/3d_mkmini.yaml @@ -0,0 +1,16 @@ + + +odometry: + ros__parameters: + odom_topic: "/vehicle_odom" + odom_frame: "odom" + base_frame: "base_link" + +pcd_crop: + ros__parameters: + input_topic: "/raw/lidar" + output_topic: "/lidar_filtered" + min_z: -2.5 + max_z: 1.5 + min_radius: 2.0 + max_radius: 30.0 diff --git a/src/perception3d_pkg/config/tf_fr09-carla.yaml b/src/perception3d_pkg/config/tf_fr09-carla.yaml new file mode 100755 index 0000000..e8b6219 --- /dev/null +++ b/src/perception3d_pkg/config/tf_fr09-carla.yaml @@ -0,0 +1,31 @@ + +lidar: + x: 0.456 + y: 0.0 + z: 1.135 + roll: 0.0 + pitch: 0.0 + yaw: 0.0 + frame_id: base_link + child_frame_id: os_lidar + +imu: + x: 0.0 + y: 0.0 + z: 0.5 + roll: 0.0 + pitch: 0.0 + yaw: 0.0 + frame_id: base_link + child_frame_id: os_imu + +rgb_front: + x: 0.56331 + y: 0.56343 + z: 0.80703 + roll: -0.025 + pitch: 0.315 + yaw: 1.035 + frame_id: base_link + child_frame_id: hero/rgb_front + diff --git a/src/perception3d_pkg/config/tf_fr09-real.yaml b/src/perception3d_pkg/config/tf_fr09-real.yaml new file mode 100755 index 0000000..3e83097 --- /dev/null +++ b/src/perception3d_pkg/config/tf_fr09-real.yaml @@ -0,0 +1,21 @@ + +lidar: + x: 0.0 + y: 0.0 + z: 0.03618 + roll: 0.0 + pitch: 0.0 + yaw: 2.356194 + frame_id: base_link + child_frame_id: os_lidar + +imu: + x: 0.006253 + y: -0.011775 + z: 0.007645 + roll: 0.0 + pitch: 0.0 + yaw: 0.0 + frame_id: base_link + child_frame_id: os_imu + diff --git a/src/perception3d_pkg/config/tf_fr09.yaml b/src/perception3d_pkg/config/tf_fr09.yaml new file mode 100755 index 0000000..e8b6219 --- /dev/null +++ b/src/perception3d_pkg/config/tf_fr09.yaml @@ -0,0 +1,31 @@ + +lidar: + x: 0.456 + y: 0.0 + z: 1.135 + roll: 0.0 + pitch: 0.0 + yaw: 0.0 + frame_id: base_link + child_frame_id: os_lidar + +imu: + x: 0.0 + y: 0.0 + z: 0.5 + roll: 0.0 + pitch: 0.0 + yaw: 0.0 + frame_id: base_link + child_frame_id: os_imu + +rgb_front: + x: 0.56331 + y: 0.56343 + z: 0.80703 + roll: -0.025 + pitch: 0.315 + yaw: 1.035 + frame_id: base_link + child_frame_id: hero/rgb_front + diff --git a/src/perception3d_pkg/config/tf_mkmini.yaml b/src/perception3d_pkg/config/tf_mkmini.yaml new file mode 100755 index 0000000..1452a9c --- /dev/null +++ b/src/perception3d_pkg/config/tf_mkmini.yaml @@ -0,0 +1,31 @@ + +lidar: + x: 0.1 + y: 0.0 + z: 0.6 + roll: 0.0 + pitch: 0.0 + yaw: 0.0 + frame_id: base_link + child_frame_id: os_lidar + +imu: + x: 0.0 + y: 0.0 + z: 0.5 + roll: 0.0 + pitch: 0.0 + yaw: 0.0 + frame_id: base_link + child_frame_id: os_imu + +rgb_front: + x: 0.10731 + y: 0.56343 + z: -0.27697 + roll: -0.025 + pitch: 0.315 + yaw: 1.035 + frame_id: base_link + child_frame_id: hero/rgb_front + diff --git a/src/perception3d_pkg/launch/perception.launch.py b/src/perception3d_pkg/launch/perception.launch.py new file mode 100755 index 0000000..132c28b --- /dev/null +++ b/src/perception3d_pkg/launch/perception.launch.py @@ -0,0 +1,67 @@ + +from ament_index_python.packages import get_package_share_directory + +from launch import LaunchDescription +from launch.actions import DeclareLaunchArgument +from launch.actions import GroupAction +from launch.substitutions import LaunchConfiguration, PathJoinSubstitution +from launch_ros.actions import Node +from launch_ros.descriptions import ParameterFile +from nav2_common.launch import RewrittenYaml +from launch.actions import OpaqueFunction +from launch.substitutions import PythonExpression +from launch.conditions import IfCondition + +import os +import yaml + + +ARGUMENTS = [ + DeclareLaunchArgument('use_sim_time', default_value='true'), + DeclareLaunchArgument('sim_mode', default_value='true'), + DeclareLaunchArgument('robot', default_value='fr09', choices=['mkmini', 'fr09']), +] + +def generate_launch_description(): + use_sim_time = LaunchConfiguration('use_sim_time') + robot = LaunchConfiguration('robot') + + remappings = [('/tf', 'tf'), + ('/tf_static', 'tf_static')] + + # Directories + pkg_perception = get_package_share_directory( + 'perception3d_pkg') + + params_file = PathJoinSubstitution([ + pkg_perception, + 'config', + PythonExpression(["'3d_' + '", robot, "' + '.yaml'"])]) + param_substitutions = { + 'use_sim_time': use_sim_time, + } + configured_params = ParameterFile( + RewrittenYaml( + source_file=params_file, + param_rewrites=param_substitutions, + convert_types=True), + allow_substs=True) + + load_nodes = GroupAction( + actions=[ + Node( + package='perception3d_pkg', + executable='pcd_crop', + name='pcd_crop', + output='screen', + parameters=[configured_params], + remappings=remappings + ) + ], + ) + + # Create launch description and add actions + ld = LaunchDescription(ARGUMENTS) + ld.add_action(load_nodes) + + return ld diff --git a/src/perception3d_pkg/package.xml b/src/perception3d_pkg/package.xml new file mode 100755 index 0000000..a5f9977 --- /dev/null +++ b/src/perception3d_pkg/package.xml @@ -0,0 +1,18 @@ + + + + perception3d_pkg + 0.0.0 + TODO: Package description + dgist + TODO: License declaration + + ament_copyright + ament_flake8 + ament_pep257 + python3-pytest + + + ament_python + + diff --git a/src/perception3d_pkg/perception3d_pkg/__init__.py b/src/perception3d_pkg/perception3d_pkg/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/src/perception3d_pkg/perception3d_pkg/odometry.py b/src/perception3d_pkg/perception3d_pkg/odometry.py new file mode 100755 index 0000000..2f3b235 --- /dev/null +++ b/src/perception3d_pkg/perception3d_pkg/odometry.py @@ -0,0 +1,212 @@ + +#!/usr/bin/env python3 +import math +from typing import Optional + +import rclpy +from rclpy.node import Node + +from std_msgs.msg import Float32 +from sensor_msgs.msg import Imu +from rosgraph_msgs.msg import Clock +from nav_msgs.msg import Odometry +from geometry_msgs.msg import Quaternion, TransformStamped +from tf2_ros import TransformBroadcaster + + +def quat_to_yaw(x: float, y: float, z: float, w: float) -> float: + siny_cosp = 2.0 * (w * z + x * y) + cosy_cosp = 1.0 - 2.0 * (y * y + z * z) + return math.atan2(siny_cosp, cosy_cosp) + + +def yaw_to_quaternion(yaw: float) -> Quaternion: + q = Quaternion() + q.x = 0.0 + q.y = 0.0 + q.z = math.sin(yaw * 0.5) + q.w = math.cos(yaw * 0.5) + return q + + +def normalize_angle(angle: float) -> float: + while angle > math.pi: + angle -= 2.0 * math.pi + while angle < -math.pi: + angle += 2.0 * math.pi + return angle + + +def stamp_to_sec(stamp) -> float: + return float(stamp.sec) + float(stamp.nanosec) * 1e-9 + + +class SpeedImuOdomNode(Node): + def __init__(self): + super().__init__('speed_imu_odom_node') + + # Parameters + self.declare_parameter('odom_topic', '/vehicle_odom') + self.declare_parameter('odom_frame', 'odom') + self.declare_parameter('base_frame', 'base_link') + + self.odom_topic = self.get_parameter('odom_topic').value + self.odom_frame = self.get_parameter('odom_frame').value + self.base_frame = self.get_parameter('base_frame').value + + self.declare_parameter('pose_cov_x', 0.5) + self.declare_parameter('pose_cov_y', 0.5) + self.declare_parameter('pose_cov_yaw', 0.2) + self.declare_parameter('twist_cov_vx', 0.2) + self.declare_parameter('twist_cov_vy', 0.2) + self.declare_parameter('twist_cov_wz', 0.1) + + self.pose_cov_x = self.get_parameter('pose_cov_x').get_parameter_value().double_value + self.pose_cov_y = self.get_parameter('pose_cov_y').get_parameter_value().double_value + self.pose_cov_yaw = self.get_parameter('pose_cov_yaw').get_parameter_value().double_value + self.twist_cov_vx = self.get_parameter('twist_cov_vx').get_parameter_value().double_value + self.twist_cov_vy = self.get_parameter('twist_cov_vy').get_parameter_value().double_value + self.twist_cov_wz = self.get_parameter('twist_cov_wz').get_parameter_value().double_value + + self.x = 0.0 + self.y = 0.0 + self.yaw = 0.0 + + self.max_dt = 0.1 + + # State + self.speed_mps: float = 0.0 + self.current_yaw: Optional[float] = None + self.last_yaw: Optional[float] = None + self.last_pub_time: Optional[float] = None + + self.x = 0.0 + self.y = 0.0 + + self.has_speed = False + self.has_imu = False + + self.ground_truth_odom: Optional[Odometry] = None + + # Pub/Sub + self.odom_pub = self.create_publisher(Odometry, self.odom_topic, 50) + self.tf_broadcaster = TransformBroadcaster(self) + + self.create_subscription(Float32, '/raw/speed', self.speed_cb, 50) + self.create_subscription(Imu, '/raw/imu', self.imu_cb, 50) + + self.get_logger().info('SpeedImuOdomNode started') + self.get_logger().info(f'odom_topic: {self.odom_topic}') + + def speed_cb(self, msg: Float32): + self.speed_mps = float(msg.data) + self.has_speed = True + + def imu_cb(self, msg: Imu): + self.has_imu = True + + # speed가 아직 없으면 선택적으로 skip + if not self.has_speed: + return + + # IMU timestamp 우선 사용 + stamp = msg.header.stamp + now_sec = stamp_to_sec(stamp) + + omega = float(msg.angular_velocity.z) + + if self.last_pub_time is None: + self.last_pub_time = now_sec + self.current_yaw = self.yaw + self.last_yaw = self.yaw + # 첫 샘플은 dt가 없으므로 현재 yaw로 publish + self.publish_odom(stamp, self.yaw, self.speed_mps, omega) + return + + dt = now_sec - self.last_pub_time + if dt <= 0.0: + # 시간 역전/동일시간이면 적분하지 않고 현재 yaw만 publish + self.publish_odom(stamp, self.yaw, self.speed_mps, omega) + return + + # 긴 공백은 과적분 방지 + if dt > self.max_dt: + dt = self.max_dt + + # angular velocity integration + self.yaw += omega * dt + self.yaw = normalize_angle(self.yaw) + + # position integration + self.x += self.speed_mps * math.cos(self.yaw) * dt + self.y += self.speed_mps * math.sin(self.yaw) * dt + + self.current_yaw = self.yaw + self.last_yaw = self.yaw + self.last_pub_time = now_sec + + self.publish_odom(stamp, self.yaw, self.speed_mps, omega) + + def publish_odom(self, stamp, yaw: float, v: float, omega: float): + odom = Odometry() + odom.header.stamp = stamp + odom.header.frame_id = self.odom_frame + odom.child_frame_id = self.base_frame + + odom.pose.pose.position.x = self.x + odom.pose.pose.position.y = self.y + odom.pose.pose.position.z = 0.0 + odom.pose.pose.orientation = yaw_to_quaternion(yaw) + + odom.twist.twist.linear.x = v + odom.twist.twist.linear.y = 0.0 + odom.twist.twist.linear.z = 0.0 + odom.twist.twist.angular.x = 0.0 + odom.twist.twist.angular.y = 0.0 + odom.twist.twist.angular.z = omega + + odom.pose.covariance = [ + self.pose_cov_x, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, self.pose_cov_y, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 999.0,0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 999.0,0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 999.0,0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, self.pose_cov_yaw + ] + + odom.twist.covariance = [ + self.twist_cov_vx, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, self.twist_cov_vy, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 999.0,0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 999.0,0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 999.0,0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, self.twist_cov_wz + ] + + self.odom_pub.publish(odom) + + tf_msg = TransformStamped() + tf_msg.header.stamp = stamp + tf_msg.header.frame_id = self.odom_frame + tf_msg.child_frame_id = self.base_frame + tf_msg.transform.translation.x = self.x + tf_msg.transform.translation.y = self.y + tf_msg.transform.translation.z = 0.0 + tf_msg.transform.rotation = yaw_to_quaternion(yaw) + self.tf_broadcaster.sendTransform(tf_msg) + + +def main(args=None): + rclpy.init(args=args) + node = SpeedImuOdomNode() + try: + rclpy.spin(node) + except KeyboardInterrupt: + pass + finally: + node.destroy_node() + rclpy.shutdown() + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/src/perception3d_pkg/perception3d_pkg/pcd_crop.py b/src/perception3d_pkg/perception3d_pkg/pcd_crop.py new file mode 100755 index 0000000..ef91a87 --- /dev/null +++ b/src/perception3d_pkg/perception3d_pkg/pcd_crop.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 + +import rclpy +from rclpy.node import Node + +from sensor_msgs.msg import PointCloud2 +import sensor_msgs_py.point_cloud2 as pc2 + +import numpy as np + +class PointCloudCrop(Node): + def __init__(self): + super().__init__('pcd_crop') + + self.declare_parameter('min_z', -2.5) + self.declare_parameter('max_z', -0.5) + self.declare_parameter('min_radius', 5.0) # 너무 가까운 점 제거 + self.declare_parameter('max_radius', 30.0) # 너무 먼 점 제거 + self.declare_parameter('input_topic', '/raw/lidar') + self.declare_parameter('output_topic', '/lidar_filtered') + + self.min_z = self.get_parameter('min_z').value + self.max_z = self.get_parameter('max_z').value + self.min_radius = self.get_parameter('min_radius').value + self.max_radius = self.get_parameter('max_radius').value + + input_topic = self.get_parameter('input_topic').value + output_topic = self.get_parameter('output_topic').value + + self.declare_parameter('min_angle', (-np.pi)) # rad + self.declare_parameter('max_angle', (np.pi)) # rad + + self.min_angle = self.get_parameter('min_angle').value + self.max_angle = self.get_parameter('max_angle').value + + self.sub = self.create_subscription( + PointCloud2, + input_topic, + self.callback, + 10 + ) + + self.pub = self.create_publisher( + PointCloud2, + output_topic, + 10 + ) + + self.get_logger().info( + f"Crop started: Z[{self.min_z}, {self.max_z}] " + f"Radius[{self.min_radius}, {self.max_radius}]" + f"Angle[{self.min_angle}, {self.max_angle}]" + ) + + def callback(self, msg: PointCloud2): + pts = pc2.read_points( + msg, + field_names=("x", "y", "z"), + skip_nans=True + ) + + if pts.shape[0] == 0: + return + + # structured array -> 일반 (N,3) float32 배열 + points = np.stack( + (pts["x"], pts["y"], pts["z"]), axis=-1).astype(np.float32, copy=False) + + x = points[:, 0] + y = points[:, 1] + z = points[:, 2] + + min_r2 = self.min_radius * self.min_radius + max_r2 = self.max_radius * self.max_radius + r2 = x * x + y * y + + angles = np.arctan2(-y, -x) + + if self.min_angle < self.max_angle: + angle_mask = (angles >= self.min_angle) & (angles <= self.max_angle) + else: + angle_mask = (angles >= self.min_angle) | (angles <= self.max_angle) + + mask = ( + (z >= self.min_z) & + (z <= self.max_z) & + (r2 >= min_r2) & + (r2 <= max_r2) & + angle_mask + ) + + filtered = points[mask] + + if filtered.shape[0] == 0: + return + + filtered_msg = pc2.create_cloud_xyz32(msg.header, filtered.tolist()) + self.pub.publish(filtered_msg) + + +def main(args=None): + rclpy.init(args=args) + node = PointCloudCrop() + rclpy.spin(node) + node.destroy_node() + rclpy.shutdown() + + +if __name__ == '__main__': + main() + \ No newline at end of file diff --git a/src/perception3d_pkg/perception3d_pkg/test.py b/src/perception3d_pkg/perception3d_pkg/test.py new file mode 100755 index 0000000..e2e3661 --- /dev/null +++ b/src/perception3d_pkg/perception3d_pkg/test.py @@ -0,0 +1,6 @@ +def main(): + print('Hi from perception3d_pkg.') + + +if __name__ == '__main__': + main() diff --git a/src/perception3d_pkg/resource/perception3d_pkg b/src/perception3d_pkg/resource/perception3d_pkg new file mode 100755 index 0000000..e69de29 diff --git a/src/perception3d_pkg/setup.cfg b/src/perception3d_pkg/setup.cfg new file mode 100755 index 0000000..4dd6cb1 --- /dev/null +++ b/src/perception3d_pkg/setup.cfg @@ -0,0 +1,4 @@ +[develop] +script_dir=$base/lib/perception3d_pkg +[install] +install_scripts=$base/lib/perception3d_pkg diff --git a/src/perception3d_pkg/setup.py b/src/perception3d_pkg/setup.py new file mode 100755 index 0000000..02e8e8e --- /dev/null +++ b/src/perception3d_pkg/setup.py @@ -0,0 +1,36 @@ + +import os +from glob import glob +from setuptools import find_packages, setup + +package_name = 'perception3d_pkg' + +setup( + name=package_name, + version='0.0.0', + packages=find_packages(exclude=['test']), + data_files=[ + ('share/ament_index/resource_index/packages', + ['resource/' + package_name]), + ('share/' + package_name, ['package.xml']), + (os.path.join('share', package_name, 'launch'), glob('launch/*.launch.py')), + (os.path.join('share', package_name, 'config'), glob('config/*.yaml')), + ], + install_requires=['setuptools'], + zip_safe=True, + maintainer='dgist', + maintainer_email='jang1future@gmail.com', + description='TODO: Package description', + license='TODO: License declaration', + extras_require={ + 'test': [ + 'pytest', + ], + }, + entry_points={ + 'console_scripts': [ + 'test = perception3d_pkg.test:main', + 'pcd_crop = perception3d_pkg.pcd_crop:main' + ], + }, +) diff --git a/src/perception3d_pkg/test/test_copyright.py b/src/perception3d_pkg/test/test_copyright.py new file mode 100755 index 0000000..97a3919 --- /dev/null +++ b/src/perception3d_pkg/test/test_copyright.py @@ -0,0 +1,25 @@ +# 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. + +from ament_copyright.main import main +import pytest + + +# 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/src/perception3d_pkg/test/test_flake8.py b/src/perception3d_pkg/test/test_flake8.py new file mode 100755 index 0000000..27ee107 --- /dev/null +++ b/src/perception3d_pkg/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. + +from ament_flake8.main import main_with_errors +import pytest + + +@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/src/perception3d_pkg/test/test_pep257.py b/src/perception3d_pkg/test/test_pep257.py new file mode 100755 index 0000000..b234a38 --- /dev/null +++ b/src/perception3d_pkg/test/test_pep257.py @@ -0,0 +1,23 @@ +# 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. + +from ament_pep257.main import main +import pytest + + +@pytest.mark.linter +@pytest.mark.pep257 +def test_pep257(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found code style errors / warnings'