Skip to content

Commit d30398f

Browse files
committed
Add a very rudimentary way of loading maps
Signed-off-by: Arjo Chakravarty <arjoc@intrinsic.ai>
1 parent efa125b commit d30398f

3 files changed

Lines changed: 168 additions & 5 deletions

File tree

path_server/rmf_path_server/src/lib.rs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,16 @@
1515
use mapf_post::{na::Isometry2, MapfResult, SemanticPlan, SemanticWaypoint};
1616
use rclrs::{IntoPrimitiveOptions, Node};
1717
use ros_env::builtin_interfaces;
18-
use ros_env::nav_msgs::msg::Odometry;
19-
use ros_env::rmf_prototype_msgs;
20-
use ros_env::rmf_prototype_msgs::msg::{Destination, Plan, PlanId, TrafficDependency, Waypoint};
18+
use ros_env::nav_msgs::msg::{OccupancyGrid, Odometry};
19+
use ros_env::rmf_prototype_msgs::{
20+
self,
21+
msg::{Destination, Plan, PlanId, TrafficDependency, Waypoint},
22+
};
2123
use std::collections::{hash_map::Entry, HashMap};
2224
use std::sync::Arc;
2325

2426
pub mod planner;
25-
pub use planner::{MapfPlanner, MockPlanner, PibtPlanner};
27+
pub use planner::{Map, MapfPlanner, MockPlanner, PibtPlanner};
2628

2729
pub struct PlanSuccess {
2830
pub session_id: u64,
@@ -53,6 +55,7 @@ pub struct PlanServer<P: MapfPlanner> {
5355
pub current_planning_session: Option<u64>,
5456
pub footprints: Arc<std::sync::Mutex<HashMap<String, f32>>>,
5557
pub active_plan_ids: HashMap<String, PlanId>,
58+
pub map: Arc<Map>,
5659
}
5760

5861
impl<P: MapfPlanner> PlanServer<P> {
@@ -77,6 +80,7 @@ impl<P: MapfPlanner> PlanServer<P> {
7780
current_planning_session: None,
7881
footprints,
7982
active_plan_ids: HashMap::new(),
83+
map: Arc::new(Map::default()),
8084
}
8185
}
8286

@@ -322,6 +326,7 @@ impl<P: MapfPlanner> PlanServer<P> {
322326
let planner_clone = Arc::clone(&self.planner);
323327
let footprints_clone = Arc::clone(&self.footprints);
324328
let sender_clone = self.plan_sender.clone();
329+
let map_clone = self.map.clone();
325330

326331
std::thread::spawn(move || {
327332
if cancellation.load(std::sync::atomic::Ordering::Relaxed) {
@@ -351,6 +356,7 @@ impl<P: MapfPlanner> PlanServer<P> {
351356
&goals,
352357
&footprints_map,
353358
&robot_ids,
359+
map_clone.as_ref(),
354360
Arc::clone(&cancellation),
355361
) {
356362
Ok(plan) => plan,
@@ -489,6 +495,7 @@ pub struct PathServerRunning<P: MapfPlanner> {
489495
rclrs::WorkerSubscription<rmf_prototype_msgs::msg::ParticipantList, DiscoveryServer<P>>,
490496
pub discovery_subscription:
491497
rclrs::WorkerSubscription<rmf_prototype_msgs::msg::ParticipantList, DiscoveryServer<P>>,
498+
pub map_subscription: rclrs::WorkerSubscription<OccupancyGrid, PlanServer<P>>,
492499
}
493500

494501
pub fn start_path_server<P: MapfPlanner + 'static>(
@@ -502,6 +509,14 @@ pub fn start_path_server<P: MapfPlanner + 'static>(
502509
let destinations_worker =
503510
node.create_worker(PlanServer::new(node.clone(), planner, footprints));
504511

512+
let map_subscription = destinations_worker.create_subscription::<OccupancyGrid, _>(
513+
"/map".transient_local().reliable(),
514+
move |server: &mut PlanServer<P>, msg: OccupancyGrid| {
515+
rclrs::log!(server.node.logger(), "Received map message");
516+
server.map = Arc::new(Map { grid: msg });
517+
},
518+
)?;
519+
505520
// Create a periodic timer on the Destinations worker to trigger replans asynchronously.
506521
let replan_timer = destinations_worker.create_timer_repeating(
507522
std::time::Duration::from_millis(100),
@@ -611,5 +626,6 @@ pub fn start_path_server<P: MapfPlanner + 'static>(
611626
replan_timer: Box::new(replan_timer),
612627
list_subscription,
613628
discovery_subscription,
629+
map_subscription,
614630
})
615631
}

path_server/rmf_path_server/src/planner.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,18 @@
1414

1515
use hetpibt::external_tracks_pibt::PiBTWithExternalTracks;
1616
use mapf_post::na::Isometry2;
17-
use ros_env::nav_msgs::msg::Odometry;
17+
use ros_env::nav_msgs::msg::{OccupancyGrid, Odometry};
1818
use ros_env::rmf_prototype_msgs::msg::Destination;
1919
use std::collections::HashMap;
2020

2121
use std::sync::atomic::AtomicBool;
2222
use std::sync::Arc;
2323

24+
#[derive(Clone, Debug, Default)]
25+
pub struct Map {
26+
pub grid: OccupancyGrid,
27+
}
28+
2429
/// Implement this trait to use your own custom MAPF
2530
/// planner. The planner in this scenario will take in
2631
/// starts and goals and assign a trajectory to the agents.
@@ -31,6 +36,7 @@ pub trait MapfPlanner: Send + Sync + 'static {
3136
goals: &HashMap<String, Destination>,
3237
footprints: &HashMap<String, Arc<dyn mapf_post::shape::Shape>>,
3338
robot_ids: &[String],
39+
map: &Map,
3440
cancellation: Arc<AtomicBool>,
3541
) -> Result<Vec<Vec<Isometry2<f32>>>, Box<dyn std::error::Error>>;
3642
}
@@ -45,6 +51,7 @@ impl MapfPlanner for MockPlanner {
4551
_goals: &HashMap<String, Destination>,
4652
_footprints: &HashMap<String, Arc<dyn mapf_post::shape::Shape>>,
4753
robot_ids: &[String],
54+
_map: &Map,
4855
_cancellation: Arc<AtomicBool>,
4956
) -> Result<Vec<Vec<Isometry2<f32>>>, Box<dyn std::error::Error>> {
5057
let mut plan = Vec::new();
@@ -79,6 +86,7 @@ impl MapfPlanner for PibtPlanner {
7986
goals: &HashMap<String, Destination>,
8087
_footprints: &HashMap<String, Arc<dyn mapf_post::shape::Shape>>,
8188
robot_ids: &[String],
89+
_map: &Map,
8290
_cancellation: Arc<AtomicBool>,
8391
) -> Result<Vec<Vec<Isometry2<f32>>>, Box<dyn std::error::Error>> {
8492
if robot_ids.is_empty() {
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
// Copyright 2026 Open Source Robotics Foundation
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+
use mapf_post::na::Isometry2;
16+
use rclrs::{Context, CreateBasicExecutor, IntoPrimitiveOptions, SpinOptions};
17+
use rmf_path_server::{start_path_server, Map, MapfPlanner};
18+
use ros_env::nav_msgs::msg::{OccupancyGrid, Odometry};
19+
use ros_env::rmf_prototype_msgs::{
20+
self,
21+
msg::{Destination, Participant, ParticipantList},
22+
};
23+
use std::collections::HashMap;
24+
use std::sync::atomic::AtomicBool;
25+
use std::sync::{Arc, Mutex};
26+
27+
struct TestPlanner {
28+
received_map: Arc<Mutex<Option<Map>>>,
29+
}
30+
31+
impl MapfPlanner for TestPlanner {
32+
fn plan(
33+
&self,
34+
_starts: &HashMap<String, Odometry>,
35+
_goals: &HashMap<String, Destination>,
36+
_footprints: &HashMap<String, Arc<dyn mapf_post::shape::Shape>>,
37+
robot_ids: &[String],
38+
map: &Map,
39+
_cancellation: Arc<AtomicBool>,
40+
) -> Result<Vec<Vec<Isometry2<f32>>>, Box<dyn std::error::Error>> {
41+
if let Ok(mut guard) = self.received_map.lock() {
42+
*guard = Some(map.clone());
43+
}
44+
let mut plan = Vec::new();
45+
for _ in 0..robot_ids.len() {
46+
plan.push(vec![Isometry2::identity()]);
47+
}
48+
Ok(plan)
49+
}
50+
}
51+
52+
#[test]
53+
fn test_map_subscription() -> Result<(), Box<dyn std::error::Error>> {
54+
let context = Context::default_from_env().unwrap();
55+
let mut executor = context.create_basic_executor();
56+
let test_node = Arc::new(executor.create_node("test_node")?);
57+
let server_node = Arc::new(executor.create_node("path_server")?);
58+
59+
let received_map = Arc::new(Mutex::new(None));
60+
let planner = TestPlanner {
61+
received_map: Arc::clone(&received_map),
62+
};
63+
64+
let _path_server_guard = start_path_server(Arc::clone(&server_node), planner)?;
65+
66+
// Publishers
67+
let discovery_pub = test_node.create_publisher::<ParticipantList>(
68+
"/destination/discovery".transient_local().reliable(),
69+
)?;
70+
let map_pub =
71+
test_node.create_publisher::<OccupancyGrid>("/map".transient_local().reliable())?;
72+
73+
let robot_id = "robot1";
74+
let odom_topic = format!("{}/odom", robot_id);
75+
let odom_pub = test_node.create_publisher::<Odometry>(odom_topic.as_str())?;
76+
let dest_topic = format!("{}/destination", robot_id);
77+
let dest_pub = test_node
78+
.create_publisher::<Destination>(dest_topic.as_str().transient_local().reliable())?;
79+
80+
// Publish map
81+
let mut map_msg = OccupancyGrid::default();
82+
map_msg.info.resolution = 1.0;
83+
map_msg.info.width = 10;
84+
map_msg.info.height = 10;
85+
map_msg.data = vec![0; 100];
86+
map_pub.publish(&map_msg)?;
87+
println!("Published map");
88+
89+
// Publish discovery
90+
let mut discovery_msg = ParticipantList::default();
91+
discovery_msg.participants.push(Participant {
92+
name: robot_id.to_string(),
93+
components: vec![],
94+
});
95+
discovery_pub.publish(&discovery_msg)?;
96+
println!("Published discovery");
97+
98+
// Publish odom
99+
let mut odom_msg = Odometry::default();
100+
odom_msg.pose.pose.position.x = 1.0;
101+
odom_msg.pose.pose.position.y = 1.0;
102+
odom_pub.publish(&odom_msg)?;
103+
println!("Published odom");
104+
105+
// Publish destination
106+
let mut dest_msg = Destination::default();
107+
dest_msg
108+
.constraints
109+
.regions
110+
.push(rmf_prototype_msgs::msg::TargetRegion {
111+
region: rmf_prototype_msgs::msg::Region {
112+
points: vec![5.0, 5.0],
113+
..Default::default()
114+
},
115+
..Default::default()
116+
});
117+
dest_pub.publish(&dest_msg)?;
118+
println!("Published destination");
119+
120+
// Wait for the planner to receive the map.
121+
let start = std::time::Instant::now();
122+
let timeout = std::time::Duration::from_secs(5);
123+
while start.elapsed() < timeout {
124+
odom_pub.publish(&odom_msg)?;
125+
executor.spin(SpinOptions::spin_once().timeout(std::time::Duration::from_millis(100)));
126+
127+
if let Ok(guard) = received_map.lock() {
128+
if let Some(map) = guard.as_ref() {
129+
assert_eq!(map.grid.info.resolution, 1.0);
130+
assert_eq!(map.grid.info.width, 10);
131+
assert_eq!(map.grid.info.height, 10);
132+
assert_eq!(map.grid.data, vec![0; 100]);
133+
return Ok(());
134+
}
135+
}
136+
}
137+
138+
Err("Timeout waiting for map in planner".into())
139+
}

0 commit comments

Comments
 (0)