Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
155 changes: 155 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
name: CI

on:
push:
branches: [ main, dev ]
pull_request:
branches: [ main, dev ]

jobs:
lint:
name: Lint
runs-on: ubuntu-latest
container:
image: osrf/ros:humble-desktop
steps:
- uses: actions/checkout@v4

- name: Install dependencies
run: |
apt-get update && apt-get install -y \
python3-pip \
python3-pytest \
python3-pytest-mock \
python3-opencv \
gstreamer1.0-tools \
gstreamer1.0-plugins-base \
gstreamer1.0-plugins-good \
gstreamer1.0-plugins-bad \
gstreamer1.0-plugins-ugly \
v4l-utils \
build-essential \
cmake \
git \
&& rm -rf /var/lib/apt/lists/*

- name: Setup workspace structure
run: |
mkdir -p ws/src
cp -r camera_ros lucy_bringup ws/src/

- name: Build workspace
working-directory: /__w/lucy_ros_packages/lucy_ros_packages/ws
shell: bash
run: |
source /opt/ros/humble/setup.bash
colcon build --packages-select camera_ros lucy_bringup --cmake-args -DBUILD_TESTING=ON

- name: Run linters
working-directory: /__w/lucy_ros_packages/lucy_ros_packages/ws
shell: bash
run: |
source /opt/ros/humble/setup.bash
source install/setup.bash
colcon test --packages-select camera_ros lucy_bringup --event-handlers console_direct+ || true

- name: Check lint results
working-directory: /__w/lucy_ros_packages/lucy_ros_packages/ws
shell: bash
run: |
source /opt/ros/humble/setup.bash
source install/setup.bash
colcon test-result --verbose || true

test:
name: Unit Tests
runs-on: ubuntu-latest
container:
image: osrf/ros:humble-desktop
steps:
- uses: actions/checkout@v4

- name: Install dependencies
run: |
apt-get update && apt-get install -y \
python3-pip \
python3-pytest \
python3-pytest-mock \
python3-opencv \
gstreamer1.0-tools \
gstreamer1.0-plugins-base \
gstreamer1.0-plugins-good \
gstreamer1.0-plugins-bad \
gstreamer1.0-plugins-ugly \
v4l-utils \
build-essential \
cmake \
git \
&& rm -rf /var/lib/apt/lists/*

- name: Install Python test dependencies
run: |
pip3 install --no-cache-dir pytest pytest-mock pytest-cov

- name: Setup workspace structure
run: |
mkdir -p ws/src
cp -r camera_ros lucy_bringup ws/src/

- name: Build workspace with tests
working-directory: /__w/lucy_ros_packages/lucy_ros_packages/ws
shell: bash
run: |
source /opt/ros/humble/setup.bash
colcon build --packages-select camera_ros lucy_bringup --cmake-args -DBUILD_TESTING=ON

- name: Run unit tests
working-directory: /__w/lucy_ros_packages/lucy_ros_packages/ws
shell: bash
run: |
source /opt/ros/humble/setup.bash
source install/setup.bash
colcon test --packages-select camera_ros lucy_bringup --event-handlers console_direct+

- name: Check test results
working-directory: /__w/lucy_ros_packages/lucy_ros_packages/ws
shell: bash
run: |
source /opt/ros/humble/setup.bash
source install/setup.bash
colcon test-result --verbose
# Fail if any tests failed
if colcon test-result --verbose | grep -q "failed"; then
echo "Some tests failed!"
exit 1
fi

- name: Generate coverage report
working-directory: /__w/lucy_ros_packages/lucy_ros_packages/ws
shell: bash
run: |
source /opt/ros/humble/setup.bash
source install/setup.bash
cd build/camera_ros
python3 -m pytest ../../src/camera_ros/test/ \
--cov=../../src/camera_ros/scripts \
--cov-report=xml:coverage.xml \
--cov-report=html:htmlcov \
--cov-report=term-missing \
--cov-branch || true

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
file: ${{ github.workspace }}/ws/build/camera_ros/coverage.xml
flags: unittests
name: codecov-umbrella
fail_ci_if_error: false

- name: Upload coverage HTML report
uses: actions/upload-artifact@v4
if: always()
with:
name: coverage-report
path: ${{ github.workspace }}/ws/build/camera_ros/htmlcov/
retention-days: 30
56 changes: 56 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,57 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
*.egg-info/
dist/
build/
.eggs/

# Virtual environments
.venv
venv/
ENV/
env/

# Coverage reports
htmlcov/
.coverage
.coverage.*
coverage.xml
*.cover
.hypothesis/

# Test artifacts
.pytest_cache/
test_results/
*.xunit.xml
.tox/
.nox/

# ROS2 build artifacts
install/
log/
build/

# IDE
.vscode/
.idea/
*.swp
*.swo
*~
.DS_Store

# OS
Thumbs.db
.DS_Store

# ROS2 specific
.ros/

# Temporary files
*.tmp
*.log
*.bak
*.orig
26 changes: 26 additions & 0 deletions camera_ros/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ rosidl_generate_interfaces(${PROJECT_NAME}
# Install Python scripts
install(PROGRAMS
scripts/camera_publisher.py
scripts/camera_stream_controller.py
DESTINATION lib/${PROJECT_NAME}
)

Expand Down Expand Up @@ -54,7 +55,32 @@ install(FILES

if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
find_package(ament_cmake_pytest REQUIRED)
ament_lint_auto_find_test_dependencies()

# Install test files
install(DIRECTORY test
DESTINATION share/${PROJECT_NAME}
)

# Install pytest.ini for coverage configuration
install(FILES
pytest.ini
DESTINATION share/${PROJECT_NAME}
)

# Add pytest tests with coverage
ament_add_pytest_test(test_camera_publisher test/test_camera_publisher.py
TIMEOUT 60
PYTHON_EXECUTABLE "${PYTHON3_EXECUTABLE}"
APPEND_ENV PYTHONPATH="${CMAKE_CURRENT_SOURCE_DIR}/scripts:${CMAKE_CURRENT_SOURCE_DIR}"
)

ament_add_pytest_test(test_camera_stream_controller test/test_camera_stream_controller.py
TIMEOUT 60
PYTHON_EXECUTABLE "${PYTHON3_EXECUTABLE}"
APPEND_ENV PYTHONPATH="${CMAKE_CURRENT_SOURCE_DIR}/scripts:${CMAKE_CURRENT_SOURCE_DIR}"
)
endif()

# Export the generated interfaces
Expand Down
3 changes: 2 additions & 1 deletion camera_ros/camera_ros/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"""
Camera ROS Package.

Zero-copy MJPEG camera publisher optimized for NVIDIA Jetson AGX Orin."""
Zero-copy MJPEG camera publisher optimized for NVIDIA Jetson AGX Orin.
"""

__version__ = "1.0.0"
45 changes: 42 additions & 3 deletions camera_ros/launch/camera.launch.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,21 @@
"""
Launch file for zero-copy MJPEG camera publisher.

This launch file starts the camera node with optimized settings for NVIDIA Jetson AGX Orin."""
This launch file starts the camera node with optimized settings for
NVIDIA Jetson AGX Orin.
"""

from launch import LaunchDescription
from launch_ros.actions import Node
from launch.actions import DeclareLaunchArgument, LogInfo
from launch.substitutions import LaunchConfiguration


def generate_launch_description():
# Launch arguments
fps_arg = DeclareLaunchArgument(
'fps',
default_value='15.0',
default_value='10.0',
description='Camera frame rate (1.0-30.0 FPS)'
)

Expand All @@ -38,6 +41,24 @@ def generate_launch_description():
description='Camera device path'
)

vendor_id_arg = DeclareLaunchArgument(
'vendor_id',
default_value='',
description='USB vendor ID for camera identification (e.g., 0x046d)'
)

product_id_arg = DeclareLaunchArgument(
'product_id',
default_value='',
description='USB product ID for camera identification'
)

serial_number_arg = DeclareLaunchArgument(
'serial_number',
default_value='',
description='Camera serial number for identification'
)

# Camera node
camera_node = Node(
package='camera_ros',
Expand All @@ -47,12 +68,30 @@ def generate_launch_description():
parameters=[{
'fps': LaunchConfiguration('fps'),
'device': LaunchConfiguration('device'),
'vendor_id': LaunchConfiguration('vendor_id'),
'product_id': LaunchConfiguration('product_id'),
'serial_number': LaunchConfiguration('serial_number'),
}],
)

# Camera stream controller node
camera_controller_node = Node(
package='camera_ros',
executable='camera_stream_controller.py',
name='camera_stream_controller',
output='screen',
)

return LaunchDescription([
fps_arg,
device_arg,
LogInfo(msg='Starting zero-copy MJPEG camera publisher with automatic client-based activation'),
vendor_id_arg,
product_id_arg,
serial_number_arg,
LogInfo(
msg='Starting zero-copy MJPEG camera publisher with '
'service-based streaming control'
),
camera_node,
camera_controller_node,
])
4 changes: 4 additions & 0 deletions camera_ros/package.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@

<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
<test_depend>ament_cmake_pytest</test_depend>
<test_depend>python3-pytest</test_depend>
<test_depend>python3-pytest-mock</test_depend>
<test_depend>python3-pytest-cov</test_depend>

<member_of_group>rosidl_interface_packages</member_of_group>

Expand Down
17 changes: 17 additions & 0 deletions camera_ros/pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[pytest]
# Pytest configuration for camera_ros package
testpaths = test
python_files = test_*.py
python_classes = Test*
python_functions = test_*
addopts =
-v
--strict-markers
--tb=short
--cov=scripts
--cov-report=term-missing
--cov-report=html:htmlcov
--cov-report=xml:coverage.xml
--cov-branch
--no-cov-on-fail

Loading