Skip to content

Commit e61b51f

Browse files
authored
feat: added codecov workflow and simulator test (#203)
* feat: added codecov workflow * feat: added simulator test * ci: added workflow * test * fix: correct file names * fix: doesnt run on pull requests * refactor: precommit fixes
1 parent 7745765 commit e61b51f

File tree

8 files changed

+337
-0
lines changed

8 files changed

+337
-0
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
name: Code Coverage
2+
on:
3+
push:
4+
branches:
5+
- main
6+
pull_request:
7+
branches:
8+
- main
9+
jobs:
10+
call_reusable_workflow:
11+
uses: vortexntnu/vortex-ci/.github/workflows/reusable-code-coverage.yml@main
12+
with:
13+
vcs-repo-file-url: './dependencies.repos'
14+
secrets:
15+
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
name: Simulator Test
2+
on:
3+
push:
4+
branches:
5+
- main
6+
workflow_dispatch:
7+
schedule:
8+
- cron: '0 1 * * *' # Runs daily at 01:00 UTC
9+
jobs:
10+
call_reusable_workflow:
11+
strategy:
12+
matrix:
13+
test_script:
14+
- "tests/waypoint_navigation/simulator_test.sh"
15+
uses: vortexntnu/vortex-ci/.github/workflows/reusable-ros2-simulator-test.yml@main
16+
with:
17+
vcs_repos_file: "tests/dependencies.repos"
18+
setup_script: "tests/setup.sh"
19+
test_script: "${{ matrix.test_script }}"

codecov.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
coverage:
2+
precision: 2
3+
round: down
4+
status:
5+
project:
6+
default:
7+
informational: true
8+
flags:
9+
- unittests
10+
patch: off
11+
fixes:
12+
- "ros_ws/src/vortex_asv/::"
13+
comment:
14+
layout: "diff, flags, files"
15+
behavior: default
16+
flags:
17+
unittests:
18+
paths:
19+
- control/hybrydpath_controller
20+
- guidance/d_star_lite
21+
- guidance/hybridpath_guidance
22+
- mission/blackbox
23+
- mission/internal_status
24+
- mission/joystick_interface_asv
25+
- mission/system_monitor
26+
- motion/thrust_allocator_asv
27+
- motion/thruster_interface_asv

tests/dependencies.repos

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
repositories:
2+
vortex-msgs:
3+
type: git
4+
url: https://github.com/vortexntnu/vortex-msgs.git
5+
vortex-utils:
6+
type: git
7+
url: https://github.com/vortexntnu/vortex-utils.git
8+
vortex-asv:
9+
type: git
10+
url: https://github.com/vortexntnu/vortex-asv.git
11+
stonefish_ros2:
12+
type: git
13+
url: https://github.com/patrykcieslak/stonefish_ros2.git
14+
vortex-stonefish-sim:
15+
type: git
16+
url: https://github.com/vortexntnu/vortex-stonefish-sim.git

tests/setup.sh

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
#!/bin/bash
2+
set -e
3+
4+
# ----------------------------- GLOBAL VARIABLES -----------------------------
5+
STONEFISH_DIR="$HOME/opt/stonefish"
6+
ROS_WORKSPACE="$HOME/ros2_ws"
7+
LOG_PREFIX="[$(date +%T)]"
8+
9+
# ----------------------------- HELPER FUNCTIONS -----------------------------
10+
log_info() {
11+
echo -e "$LOG_PREFIX [INFO] $1"
12+
}
13+
14+
log_error() {
15+
echo -e "$LOG_PREFIX [ERROR] $1" >&2
16+
}
17+
18+
# ----------------------------- PYTHON DEPENDENCIES -----------------------------
19+
install_python_dependencies() {
20+
log_info "Installing/upgrading Python dependencies..."
21+
pip3 install --upgrade pip
22+
pip3 install --upgrade 'numpy<1.25' 'scipy<1.12'
23+
log_info "Python dependencies installed."
24+
}
25+
26+
# ----------------------------- C++ DEPENDENCIES -----------------------------
27+
install_cpp_dependencies() {
28+
log_info "Installing required C++ dependencies..."
29+
sudo apt-get update -qq
30+
sudo apt-get install -y \
31+
build-essential \
32+
cmake \
33+
git \
34+
libglm-dev \
35+
libsdl2-dev \
36+
libfreetype6-dev
37+
log_info "C++ dependencies installed."
38+
}
39+
40+
# ----------------------------- STONEFISH INSTALLATION -----------------------------
41+
install_stonefish() {
42+
if [ -d "$STONEFISH_DIR" ]; then
43+
log_info "Stonefish is already installed at $STONEFISH_DIR. Skipping clone."
44+
else
45+
log_info "Cloning Stonefish repository..."
46+
mkdir -p "$STONEFISH_DIR"
47+
git clone https://github.com/patrykcieslak/stonefish.git "$STONEFISH_DIR"
48+
fi
49+
50+
log_info "Building Stonefish..."
51+
mkdir -p "$STONEFISH_DIR/build"
52+
cd "$STONEFISH_DIR/build"
53+
54+
cmake ..
55+
make -j"$(nproc)"
56+
sudo make install
57+
58+
log_info "Stonefish installation complete."
59+
}
60+
61+
# ----------------------------- BUILD ROS 2 PACKAGES -----------------------------
62+
build_ros_workspace() {
63+
log_info "Setting up ROS 2 workspace..."
64+
cd "$ROS_WORKSPACE"
65+
66+
log_info "Sourcing ROS 2 setup..."
67+
. /opt/ros/humble/setup.sh
68+
69+
log_info "Building stonefish_ros2 first (dependency for other packages)..."
70+
colcon build --packages-select stonefish_ros2 --symlink-install
71+
72+
log_info "Sourcing workspace..."
73+
. install/setup.bash
74+
75+
log_info "Building remaining ROS 2 packages..."
76+
colcon build --packages-ignore stonefish_ros2 --symlink-install
77+
78+
log_info "ROS 2 workspace build complete."
79+
}
80+
81+
# ----------------------------- EXECUTE INSTALLATION -----------------------------
82+
log_info "Starting manual installation of extra dependencies..."
83+
install_python_dependencies
84+
install_cpp_dependencies
85+
install_stonefish
86+
build_ros_workspace
87+
88+
log_info "All dependencies installed successfully."
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import math
2+
import time
3+
4+
import rclpy
5+
from nav_msgs.msg import Odometry
6+
from rclpy.node import Node
7+
8+
end_point = (0.0, 0.0)
9+
threshold = 0.25
10+
11+
12+
class FreyaCheckGoal(Node):
13+
def __init__(self):
14+
super().__init__('freya_check_goal')
15+
self.subscription = self.create_subscription(
16+
Odometry, '/seapath/odom/ned', self.odometry_callback, 10
17+
)
18+
19+
self.current_odom: Odometry = None
20+
self.received_odom: bool = False
21+
22+
def odometry_callback(self, msg):
23+
self.current_odom = msg
24+
self.received_odom = True
25+
26+
27+
def main(args=None):
28+
rclpy.init(args=args)
29+
30+
node = FreyaCheckGoal()
31+
32+
print(f"Waiting for the robot to reach the goal at {end_point}...")
33+
34+
start_time = time.time()
35+
timeout = 20 # seconds
36+
37+
while rclpy.ok() and time.time() - start_time < timeout:
38+
rclpy.spin_once(node)
39+
if node.received_odom:
40+
x = node.current_odom.pose.pose.position.x
41+
y = node.current_odom.pose.pose.position.y
42+
distance = math.sqrt((x - end_point[0]) ** 2 + (y - end_point[1]) ** 2)
43+
if distance < threshold:
44+
node.get_logger().info('Goal reached :)')
45+
rclpy.shutdown()
46+
exit(0)
47+
time.sleep(0.1)
48+
49+
print(
50+
f"Timeout reached. The robot did not reach the goal at {end_point}. Current position: ({x}, {y})"
51+
)
52+
rclpy.shutdown()
53+
exit(1)
54+
55+
56+
if __name__ == '__main__':
57+
main()
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import rclpy
2+
from geometry_msgs.msg import Point
3+
from rclpy.action import ActionClient
4+
from rclpy.node import Node
5+
from vortex_msgs.action import HybridpathGuidance
6+
from vortex_msgs.msg import Waypoints
7+
8+
9+
class HybridpathGuidanceClient(Node):
10+
def __init__(self):
11+
super().__init__('hybridpath_guidance_client')
12+
# Create the action client
13+
self._action_client = ActionClient(
14+
self, HybridpathGuidance, '/freya/hybridpath_guidance'
15+
)
16+
self.send_goal()
17+
18+
def send_goal(self):
19+
goal_msg = HybridpathGuidance.Goal()
20+
21+
points = [
22+
Point(x=10.0, y=0.0),
23+
Point(x=10.0, y=10.0),
24+
Point(x=0.0, y=10.0),
25+
Point(x=0.0, y=0.0),
26+
]
27+
goal_msg.waypoints = Waypoints()
28+
goal_msg.waypoints.waypoints = points
29+
30+
self._action_client.wait_for_server()
31+
self.get_logger().info('Sending goal...')
32+
self._send_goal_future = self._action_client.send_goal_async(
33+
goal_msg, feedback_callback=self.feedback_callback
34+
)
35+
self._send_goal_future.add_done_callback(self.goal_response_callback)
36+
37+
def goal_response_callback(self, future):
38+
goal_handle = future.result()
39+
if not goal_handle.accepted:
40+
self.get_logger().info('Goal rejected :(')
41+
return
42+
43+
self.get_logger().info('Goal accepted :)')
44+
self._get_result_future = goal_handle.get_result_async()
45+
self._get_result_future.add_done_callback(self.get_result_callback)
46+
47+
def feedback_callback(self, feedback_msg):
48+
feedback = feedback_msg.feedback.feedback
49+
# self.get_logger().info(f'Received feedback: x={feedback.x}, y={feedback.y}')
50+
51+
def get_result_callback(self, future):
52+
result = future.result().result.success
53+
self.get_logger().info(f'Goal result: {result}')
54+
self.destroy_node()
55+
if rclpy.ok():
56+
rclpy.shutdown()
57+
58+
59+
def main(args=None):
60+
rclpy.init(args=args)
61+
62+
client = HybridpathGuidanceClient()
63+
64+
rclpy.spin(client)
65+
66+
67+
if __name__ == '__main__':
68+
main()
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#!/bin/bash
2+
set -e
3+
4+
# Load ROS 2 environment
5+
echo "Setting up ROS 2 environment..."
6+
. /opt/ros/humble/setup.sh
7+
. ~/ros2_ws/install/setup.bash
8+
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib
9+
10+
# Get the directory of this script dynamically
11+
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
12+
13+
setsid ros2 launch stonefish_sim simulation_nogpu.launch.py task:=freya_no_gpu &
14+
SIM_PID=$!
15+
echo "Launced simulator with PID: $SIM_PID"
16+
17+
echo "Waiting for simulator to start..."
18+
timeout 30s bash -c 'until ros2 topic list | grep -q "/seapath/odom/ned"; do sleep 1; done'
19+
echo "Simulator started"
20+
21+
echo "Waiting for odom data..."
22+
timeout 10s ros2 topic echo /seapath/odom/ned --once
23+
echo "Got odom data"
24+
25+
setsid ros2 launch thrust_allocator_asv thrust_allocator_asv.launch.py &
26+
THRUST_PID=$!
27+
echo "Launced thrust allocator with PID: $THRUST_PID"
28+
29+
setsid ros2 launch hybridpath_controller hybridpath_controller.launch.py &
30+
CONTROLLER_PID=$!
31+
echo "Launced controller with PID: $CONTROLLER_PID"
32+
33+
setsid ros2 launch hybridpath_guidance hybridpath_guidance.launch.py &
34+
GUIDANCE_PID=$!
35+
echo "Launced guidance with PID: $GUIDANCE_PID"
36+
37+
echo "Turning off killswitch and setting operation mode to autonomous mode"
38+
ros2 topic pub /freya/killswitch std_msgs/msg/Bool "{data: false}" -1
39+
ros2 topic pub /freya/operation_mode std_msgs/msg/String "{data: 'autonomous mode'}" -1
40+
41+
echo "Sending goal"
42+
python3 "$SCRIPT_DIR/send_goal.py"
43+
44+
echo "Checking if goal reached"
45+
python3 "$SCRIPT_DIR/check_goal.py"
46+
47+
kill -TERM -"$SIM_PID" -"$CONTROLLER_PID" -"$GUIDANCE_PID" -"$THRUST_PID"

0 commit comments

Comments
 (0)