Skip to content

Commit 83698df

Browse files
committed
initial demo prototype working!
1 parent 7f6c1e2 commit 83698df

27 files changed

+748
-22
lines changed

README.md

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
This package contains the Complete Coverage Task Server & auxiliary tools utilizing the [Fields2Cover](https://github.com/Fields2Cover/Fields2Cover) complete coverage planning system which includes a great deal of options in headland, swath, route, and final path planning. You can find more information about Fields2Cover (F2C) in its [ReadTheDocs Documentation](https://fields2cover.github.io/index.html). It can accept both GPS and Cartesian coordinates and publishes the field, headland, swaths, and route as separate topics in cartesian coordinates for debugging and visualization.
44

5-
This capability was created by [Open Navigation LLC](https://www.opennav.org/) in partnership with [Bonsai Robotics](https://www.bonsairobotics.ai/). Bonsai Robotics funded the development of this work for their own product and has graciously allowed Open Navigation to open-source it for the community to leverage in their own systems. Please thank Bonsai Robotics for their commendable donation to the ROS community!
5+
This capability was created by [Open Navigation LLC](https://www.opennav.org/) in partnership with [Bonsai Robotics](https://www.bonsairobotics.ai/). Bonsai Robotics builds autonomous software for machines in adverse and GPS degraded conditions utilizing vision. Bonsai Robotics funded the development of this work for their own product and has graciously allowed Open Navigation to open-source it for the community to leverage in their own systems. Please thank Bonsai Robotics for their commendable donation to the ROS community! Bonsai is hiring [here](https://www.bonsairobotics.ai/jobs).
66

77
![BonsaixOpenNavigation](https://github.com/ros-planning/navigation2/assets/14944147/b5c23851-0694-4b87-b5ab-fb7c957413f4)
88

@@ -146,7 +146,3 @@ If you use this work, please make sure to cite both Nav2 and Fields2Cover:
146146
doi={10.1109/LRA.2023.3248439}
147147
}
148148
```
149-
150-
## Current Work
151-
152-
- Use setup with BT nodes / XML / Navigator. Simulator demo altogether. Demo video (in readme).

opennav_coverage/include/opennav_coverage/path_generator.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ class PathGenerator
9494
*/
9595
void setTurnPointDistance(const double & setting) {default_turn_point_distance_ = setting;}
9696

97+
float getTurnPointDistance() {return default_turn_point_distance_;}
98+
9799
protected:
98100
/**
99101
* @brief Creates generator pointer of a requested type

opennav_coverage/include/opennav_coverage/utils.hpp

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,8 @@ inline opennav_coverage_msgs::msg::PathComponents toCoveragePathMsg(
194194

195195
/**
196196
* @brief Converts full path to nav_msgs/path message for action client, visualization
197-
* and use in direct-sending to a controller to replace the planner server
197+
* and use in direct-sending to a controller to replace the planner server. Interpolates
198+
* the F2C path to make it dense for following semantics.
198199
* @param path Full path to convert
199200
* @param Field Field to use for conversion from UTM if necessary
200201
* @param header header
@@ -203,7 +204,8 @@ inline opennav_coverage_msgs::msg::PathComponents toCoveragePathMsg(
203204
*/
204205
inline nav_msgs::msg::Path toNavPathMsg(
205206
const Path & raw_path, const F2CField & field,
206-
const std_msgs::msg::Header & header, const bool is_cartesian)
207+
const std_msgs::msg::Header & header, const bool is_cartesian,
208+
const float & pt_dist)
207209
{
208210
using f2c::types::PathSectionType;
209211
nav_msgs::msg::Path msg;
@@ -218,9 +220,38 @@ inline nav_msgs::msg::Path toNavPathMsg(
218220
path = f2c::Transform::transformToPrevCRS(raw_path, field);
219221
}
220222

221-
msg.poses.resize(path.states.size());
222223
for (unsigned int i = 0; i != path.states.size(); i++) {
223-
msg.poses[i] = toMsg(path.states[i]);
224+
// Swaths come in pairs of start-end sequentially
225+
if (i > 0 && path.states[i].type == PathSectionType::SWATH &&
226+
path.states[i - 1].type == PathSectionType::SWATH)
227+
{
228+
const float & x0 = path.states[i - 1].point.getX();
229+
const float & y0 = path.states[i - 1].point.getY();
230+
const float & x1 = path.states[i].point.getX();
231+
const float & y1 = path.states[i].point.getY();
232+
233+
const float dist = hypotf(x1 - x0, y1 - y0);
234+
const float ux = (x1 - x0) / dist;
235+
const float uy = (y1 - y0) / dist;
236+
float curr_dist = pt_dist;
237+
238+
geometry_msgs::msg::PoseStamped pose;
239+
pose.pose.orientation =
240+
nav2_util::geometry_utils::orientationAroundZAxis(path.states[i].angle);
241+
pose.pose.position.x = x0;
242+
pose.pose.position.y = y0;
243+
pose.pose.position.z = path.states[i].point.getZ();
244+
245+
while (curr_dist < dist) {
246+
pose.pose.position.x += pt_dist * ux;
247+
pose.pose.position.y += pt_dist * uy;
248+
msg.poses.push_back(pose);
249+
curr_dist += pt_dist;
250+
}
251+
} else {
252+
// Turns are already dense paths
253+
msg.poses.push_back(toMsg(path.states[i]));
254+
}
224255
}
225256

226257
return msg;

opennav_coverage/src/coverage_server.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,8 @@ void CoverageServer::computeCoveragePath()
210210
Path path = path_gen_->generatePath(route, goal->path_mode);
211211
result->coverage_path =
212212
util::toCoveragePathMsg(path, master_field, header, cartesian_frame_);
213-
result->nav_path = util::toNavPathMsg(path, master_field, header, cartesian_frame_);
213+
result->nav_path = util::toNavPathMsg(
214+
path, master_field, header, cartesian_frame_, path_gen_->getTurnPointDistance());
214215
} else {
215216
result->coverage_path =
216217
util::toCoveragePathMsg(route, master_field, true, header, cartesian_frame_);
@@ -223,7 +224,7 @@ void CoverageServer::computeCoveragePath()
223224
auto cycle_duration = this->now() - start_time;
224225
result->planning_time = cycle_duration;
225226

226-
// Visualization always uses cartesian coordinates for convenience
227+
// Visualize in native coordinates
227228
visualizer_->visualize(field, field_no_headland, result, header);
228229
action_server_->succeeded_current(result);
229230
} catch (CoverageException & e) {

opennav_coverage/test/test_utils.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ TEST(UtilsTests, TesttoNavPathMsg)
107107
path_in.states.resize(10);
108108
F2CField field;
109109

110-
auto msg = util::toNavPathMsg(path_in, field, header_in, true);
110+
auto msg = util::toNavPathMsg(path_in, field, header_in, true, 0.1);
111111
EXPECT_EQ(msg.header.frame_id, "test");
112112
EXPECT_EQ(msg.poses.size(), 10u);
113113
}

opennav_coverage_bt/behavior_trees/navigate_w_basic_complete_coverage.xml

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,21 @@
44
for changes in operational modes (e.g. raise tool, turn off payload in turns not in field).
55
66
Field filepath is set by the Navigator plugin in the action request, but also may be hardcoded
7-
or set using the polygons input port instead for dynamically computed values instead of from file
7+
or set using the polygons input port instead for dynamically computed values instead of from file.
8+
9+
It might be wise to still have the planner server on hand to connect to the coverage path in realistic uses.
810
-->
911

1012
<root main_tree_to_execute="MainTree">
1113
<BehaviorTree ID="MainTree">
12-
<PipelineSequence name="NavigateWithoutReplanning">
13-
<!-- May also use 'polygons={field_polygon} polygons_frame_id={polygon_frame_id}' if set polygon via NavigateCompleteCoverage instead of filepath -->
14-
<ComputeCoveragePath nav_path="{path}" file_field="{field_filepath}" coords_cartesian="false" error_code_id="{compute_coverage_error_code}"/>
15-
<FollowPath path="{path}" controller_id="FollowPath" error_code_id="{follow_path_error_code}"/>
16-
</PipelineSequence>
14+
<RateController hz="0.0000001"> <!-- once, for demo -->
15+
<Sequence name="NavigateWithoutReplanning">
16+
<!-- May use:
17+
'polygons="{field_polygon}" polygons_frame_id="{polygon_frame_id}"' if set polygon via NavigateCompleteCoverage
18+
or file_field="{field_filepath}" if setting polygon file via NavigateCompleteCoverage -->
19+
<ComputeCoveragePath nav_path="{path}" polygons="{field_polygon}" polygons_frame_id="{polygon_frame_id}" error_code_id="{compute_coverage_error_code}"/>
20+
<FollowPath path="{path}" controller_id="FollowPath" error_code_id="{follow_path_error_code}"/>
21+
</Sequence>
22+
</RateController>
1723
</BehaviorTree>
1824
</root>

opennav_coverage_bt/src/compute_complete_coverage_path.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ void ComputeCoveragePathAction::on_tick()
4949
getInput("polygons", polys);
5050
goal_.polygons.resize(polys.size());
5151
for (unsigned int i = 0; i != polys.size(); i++) {
52-
for (unsigned int j; j != polys[i].points.size(); j++) {
52+
for (unsigned int j = 0; j != polys[i].points.size(); j++) {
5353
opennav_coverage_msgs::msg::Coordinate coord;
5454
coord.axis1 = polys[i].points[j].x;
5555
coord.axis2 = polys[i].points[j].y;

opennav_coverage_demo/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Open Navigation's Nav2 Complete Coverage Demo
2+
3+
This package contains the coverage navigator demo, using the Navigator, BT XML, BT Nodes, and Coverage Server to perform a simple coverage task.
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# Copyright (c) 2023 Open Navigation LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from launch import LaunchDescription
16+
from launch.actions import DeclareLaunchArgument, SetEnvironmentVariable
17+
from launch.substitutions import LaunchConfiguration
18+
from launch_ros.actions import LoadComposableNodes
19+
from launch_ros.actions import Node
20+
from launch_ros.descriptions import ComposableNode, ParameterFile
21+
from nav2_common.launch import RewrittenYaml
22+
23+
24+
def generate_launch_description():
25+
params_file = LaunchConfiguration('params_file')
26+
27+
lifecycle_nodes = ['controller_server',
28+
'bt_navigator',
29+
'velocity_smoother',
30+
'coverage_server']
31+
32+
remappings = [('/tf', 'tf'),
33+
('/tf_static', 'tf_static')]
34+
35+
# Create our own temporary YAML files that include substitutions
36+
autostart = True
37+
use_sim_time = True
38+
param_substitutions = {
39+
'use_sim_time': str(use_sim_time),
40+
'autostart': str(autostart)}
41+
42+
configured_params = ParameterFile(
43+
RewrittenYaml(
44+
source_file=params_file,
45+
root_key='',
46+
param_rewrites=param_substitutions,
47+
convert_types=True),
48+
allow_substs=True)
49+
50+
stdout_linebuf_envvar = SetEnvironmentVariable(
51+
'RCUTILS_LOGGING_BUFFERED_STREAM', '1')
52+
53+
declare_params_file_cmd = DeclareLaunchArgument('params_file')
54+
55+
create_container = Node(
56+
name='nav2_container',
57+
package='rclcpp_components',
58+
executable='component_container_isolated',
59+
parameters=[configured_params, {'autostart': autostart}],
60+
remappings=remappings,
61+
output='screen')
62+
63+
load_composable_nodes = LoadComposableNodes(
64+
target_container='nav2_container',
65+
composable_node_descriptions=[
66+
ComposableNode(
67+
package='nav2_controller',
68+
plugin='nav2_controller::ControllerServer',
69+
name='controller_server',
70+
parameters=[configured_params],
71+
remappings=remappings + [('cmd_vel', 'cmd_vel_nav')]),
72+
ComposableNode(
73+
package='opennav_coverage',
74+
plugin='opennav_coverage::CoverageServer',
75+
name='coverage_server',
76+
parameters=[configured_params],
77+
remappings=remappings),
78+
ComposableNode(
79+
package='nav2_bt_navigator',
80+
plugin='nav2_bt_navigator::BtNavigator',
81+
name='bt_navigator',
82+
parameters=[configured_params],
83+
remappings=remappings),
84+
ComposableNode(
85+
package='nav2_velocity_smoother',
86+
plugin='nav2_velocity_smoother::VelocitySmoother',
87+
name='velocity_smoother',
88+
parameters=[configured_params],
89+
remappings=remappings +
90+
[('cmd_vel', 'cmd_vel_nav'), ('cmd_vel_smoothed', 'cmd_vel')]),
91+
ComposableNode(
92+
package='nav2_lifecycle_manager',
93+
plugin='nav2_lifecycle_manager::LifecycleManager',
94+
name='lifecycle_manager_navigation',
95+
parameters=[{'use_sim_time': use_sim_time,
96+
'autostart': autostart,
97+
'node_names': lifecycle_nodes}]),
98+
],
99+
)
100+
101+
# # Create the launch description and populate
102+
ld = LaunchDescription()
103+
ld.add_action(stdout_linebuf_envvar)
104+
ld.add_action(declare_params_file_cmd)
105+
ld.add_action(create_container)
106+
ld.add_action(load_composable_nodes)
107+
return ld
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# Copyright (c) 2023 Open Navigation LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import os
16+
17+
from ament_index_python.packages import get_package_share_directory
18+
19+
from launch import LaunchDescription
20+
from launch.actions import ExecuteProcess, IncludeLaunchDescription
21+
from launch.launch_description_sources import PythonLaunchDescriptionSource
22+
from launch_ros.actions import Node
23+
24+
25+
def generate_launch_description():
26+
nav2_bringup_dir = get_package_share_directory('nav2_bringup')
27+
coverage_demo_dir = get_package_share_directory('opennav_coverage_demo')
28+
29+
world = os.path.join(coverage_demo_dir, 'blank.world')
30+
param_file_path = os.path.join(coverage_demo_dir, 'demo_params.yaml')
31+
sdf = os.path.join(nav2_bringup_dir, 'worlds', 'waffle.model')
32+
33+
# start the simulation
34+
start_gazebo_server_cmd = ExecuteProcess(
35+
cmd=['gzserver', '-s', 'libgazebo_ros_init.so',
36+
'-s', 'libgazebo_ros_factory.so', world],
37+
cwd=[coverage_demo_dir], output='screen')
38+
39+
# start_gazebo_client_cmd = ExecuteProcess(
40+
# cmd=['gzclient'],
41+
# cwd=[coverage_demo_dir], output='screen')
42+
43+
urdf = os.path.join(nav2_bringup_dir, 'urdf', 'turtlebot3_waffle.urdf')
44+
with open(urdf, 'r') as infp:
45+
robot_description = infp.read()
46+
47+
start_robot_state_publisher_cmd = Node(
48+
package='robot_state_publisher',
49+
executable='robot_state_publisher',
50+
name='robot_state_publisher',
51+
output='screen',
52+
parameters=[{'use_sim_time': True,
53+
'robot_description': robot_description}])
54+
55+
start_gazebo_spawner_cmd = Node(
56+
package='gazebo_ros',
57+
executable='spawn_entity.py',
58+
output='screen',
59+
arguments=[
60+
'-entity', 'tb3',
61+
'-file', sdf,
62+
'-x', '0.0', '-y', '0.0', '-z', '0.10',
63+
'-R', '0.0', '-P', '0.0', '-Y', '0.0'])
64+
65+
# start the visualization
66+
rviz_cmd = IncludeLaunchDescription(
67+
PythonLaunchDescriptionSource(
68+
os.path.join(nav2_bringup_dir, 'launch', 'rviz_launch.py')),
69+
launch_arguments={'namespace': ''}.items())
70+
71+
# start navigation
72+
bringup_cmd = IncludeLaunchDescription(
73+
PythonLaunchDescriptionSource(
74+
os.path.join(coverage_demo_dir, 'bringup_launch.py')),
75+
launch_arguments={'params_file': param_file_path}.items())
76+
77+
# world->odom transform, no localization. Mostly just for visualization.
78+
fake_localization_cmd = Node(
79+
package='tf2_ros',
80+
executable='static_transform_publisher',
81+
output='screen',
82+
arguments=['0', '0', '0', '0', '0', '0', 'map', 'odom'])
83+
84+
# start the demo task
85+
demo_cmd = Node(
86+
package='opennav_coverage_demo',
87+
executable='demo_coverage',
88+
emulate_tty=True,
89+
output='screen')
90+
91+
ld = LaunchDescription()
92+
ld.add_action(start_gazebo_server_cmd)
93+
# ld.add_action(start_gazebo_client_cmd)
94+
ld.add_action(start_robot_state_publisher_cmd)
95+
ld.add_action(start_gazebo_spawner_cmd)
96+
ld.add_action(rviz_cmd)
97+
ld.add_action(bringup_cmd)
98+
ld.add_action(fake_localization_cmd)
99+
ld.add_action(demo_cmd)
100+
return ld

0 commit comments

Comments
 (0)