1use std::{
2 collections::HashMap,
3 sync::{Arc, Mutex},
4};
5#[cfg(feature = "security")]
7use std::path::{Path, PathBuf};
8
9#[allow(unused_imports)]
10use log::{debug, error, info, trace, warn};
11use serde::Serialize;
13use rustdds::{
14 dds::CreateResult,
15 no_key::{DeserializerAdapter, SerializerAdapter},
16 policy::*,
17 *,
18};
19
20use crate::{
21 builtin_topics,
22 entities_info::{NodeEntitiesInfo, ParticipantEntitiesInfo},
23 gid::Gid,
24 names::*,
25 node::{Node, NodeOptions},
26 pubsub::{Publisher, Subscription},
27 NodeCreateError,
28};
29
30lazy_static! {
31pub static ref DEFAULT_SUBSCRIPTION_QOS: QosPolicies = QosPolicyBuilder::new()
36 .durability(Durability::Volatile) .deadline(Deadline(Duration::INFINITE)) .ownership(Ownership::Shared) .reliability(Reliability::BestEffort) .history(History::KeepLast { depth: 1 }) .lifespan(Lifespan {
42 duration: Duration::INFINITE
44 })
45 .build();
46}
47
48lazy_static! {
49pub static ref DEFAULT_PUBLISHER_QOS: QosPolicies = QosPolicyBuilder::new()
51 .durability(Durability::Volatile)
52 .deadline(Deadline(Duration::INFINITE))
53 .ownership(Ownership::Shared)
54 .reliability(Reliability::Reliable{max_blocking_time: Duration::from_millis(100)})
55 .history(History::KeepLast { depth: 1 })
57 .lifespan(Lifespan {
58 duration: Duration::INFINITE
59 })
60 .build();
61}
62
63#[cfg(feature = "security")]
64struct SecurityConfig {
65 security_config_dir: PathBuf,
67 private_key_password: String,
69}
70
71pub struct ContextOptions {
73 domain_id: u16,
74 #[cfg(feature = "security")]
75 security_config: Option<SecurityConfig>,
76}
77
78impl ContextOptions {
79 pub fn new() -> Self {
80 Self {
81 domain_id: 0,
82 #[cfg(feature = "security")]
83 security_config: None,
84 }
85 }
86
87 pub fn domain_id(mut self, domain_id: u16) -> Self {
93 self.domain_id = domain_id;
94 self
95 }
96
97 #[cfg(feature = "security")]
103 pub fn enable_security(
104 mut self,
105 security_config_dir: impl AsRef<Path>,
106 private_key_password: String,
107 ) -> Self {
108 self.security_config = Some(SecurityConfig {
109 security_config_dir: security_config_dir.as_ref().to_path_buf(),
110 private_key_password,
111 });
112 self
113 }
114}
115
116impl Default for ContextOptions {
117 fn default() -> Self {
118 Self::new()
119 }
120}
121
122#[derive(Clone)]
129pub struct Context {
130 inner: Arc<Mutex<ContextInner>>,
131}
132
133impl Context {
134 pub fn new() -> CreateResult<Context> {
136 Self::from_domain_participant(DomainParticipant::new(0)?)
137 }
138
139 pub fn with_options(opt: ContextOptions) -> CreateResult<Context> {
141 #[allow(unused_mut)] let mut dpb = DomainParticipantBuilder::new(opt.domain_id);
143
144 #[cfg(feature = "security")]
145 {
146 if let Some(sc) = opt.security_config {
147 dpb = dpb.builtin_security(
148 DomainParticipantSecurityConfigFiles::with_ros_default_names(
149 sc.security_config_dir,
150 sc.private_key_password,
151 ),
152 );
153 }
154 }
155
156 Self::from_domain_participant(dpb.build()?)
157 }
158
159 pub fn from_domain_participant(domain_participant: DomainParticipant) -> CreateResult<Context> {
161 static ROS_DISTRO_CHECK: std::sync::Once = std::sync::Once::new();
164 ROS_DISTRO_CHECK.call_once(crate::distributions::verify_ros_distro_env);
165
166 let i = ContextInner::from_domain_participant(domain_participant)?;
167 Ok(Context {
168 inner: Arc::new(Mutex::new(i)),
169 })
170 }
171
172 pub fn new_node(
174 &self,
175 node_name: NodeName,
176 options: NodeOptions,
177 ) -> Result<Node, NodeCreateError> {
178 Node::new(node_name, options, self.clone())
179 }
180
181 pub fn domain_id(&self) -> u16 {
183 self.inner.lock().unwrap().domain_participant.domain_id()
184 }
185
186 pub fn discovered_topics(&self) -> Vec<rustdds::discovery::DiscoveredTopicData> {
188 self.domain_participant().discovered_topics()
189 }
190
191 pub fn participant_entities_info(&self) -> ParticipantEntitiesInfo {
194 self.inner.lock().unwrap().participant_entities_info()
195 }
196
197 pub fn get_parameter_events_topic(&self) -> Topic {
199 self
200 .inner
201 .lock()
202 .unwrap()
203 .ros_parameter_events_topic
204 .clone()
205 }
206
207 pub fn get_rosout_topic(&self) -> Topic {
212 self.inner.lock().unwrap().ros_rosout_topic.clone()
213 }
214
215 pub fn domain_participant(&self) -> DomainParticipant {
219 self.inner.lock().unwrap().domain_participant.clone()
220 }
221
222 pub fn create_topic(
230 &self,
231 topic_dds_name: String,
232 type_name: MessageTypeName,
233 qos: &QosPolicies,
234 ) -> CreateResult<Topic> {
235 info!("Creating topic, DDS name: {topic_dds_name}");
236 let topic = self.domain_participant().create_topic(
237 topic_dds_name,
238 type_name.dds_msg_type(),
239 qos,
240 TopicKind::NoKey,
241 )?;
242 info!("Created topic");
244 Ok(topic)
245 }
246
247 pub(crate) fn create_publisher<M>(
248 &self,
249 topic: &Topic,
250 qos: Option<QosPolicies>,
251 ) -> dds::CreateResult<Publisher<M>>
252 where
253 M: Serialize,
254 {
255 let datawriter = self
256 .get_ros_default_publisher()
257 .create_datawriter_no_key(topic, qos)?;
258
259 Ok(Publisher::new(datawriter))
260 }
261
262 pub(crate) fn create_subscription<M>(
263 &self,
264 topic: &Topic,
265 qos: Option<QosPolicies>,
266 ) -> dds::CreateResult<Subscription<M>>
267 where
268 M: 'static,
269 {
270 let datareader = self
271 .get_ros_default_subscriber()
272 .create_simple_datareader_no_key(topic, qos)?;
273 Ok(Subscription::new(datareader))
274 }
275
276 pub(crate) fn create_datawriter<M, SA>(
277 &self,
278 topic: &Topic,
279 qos: Option<QosPolicies>,
280 ) -> dds::CreateResult<no_key::DataWriter<M, SA>>
281 where
282 SA: SerializerAdapter<M>,
283 {
284 self
285 .get_ros_default_publisher()
286 .create_datawriter_no_key(topic, qos)
287 }
288
289 pub(crate) fn create_simpledatareader<M, DA>(
290 &self,
291 topic: &Topic,
292 qos: Option<QosPolicies>,
293 ) -> dds::CreateResult<no_key::SimpleDataReader<M, DA>>
294 where
295 M: 'static,
296 DA: 'static + DeserializerAdapter<M>,
297 {
298 self
299 .get_ros_default_subscriber()
300 .create_simple_datareader_no_key(topic, qos)
301 }
302
303 pub(crate) fn update_node(&mut self, node_info: NodeEntitiesInfo) {
304 self.inner.lock().unwrap().update_node(node_info);
305 }
306
307 pub(crate) fn remove_node(&mut self, node_name: &str) {
308 self.inner.lock().unwrap().remove_node(node_name);
309 }
310
311 fn get_ros_default_publisher(&self) -> rustdds::Publisher {
312 self.inner.lock().unwrap().ros_default_publisher.clone()
313 }
314
315 fn get_ros_default_subscriber(&self) -> rustdds::Subscriber {
316 self.inner.lock().unwrap().ros_default_subscriber.clone()
317 }
318
319 pub(crate) fn ros_discovery_topic(&self) -> Topic {
320 self.inner.lock().unwrap().ros_discovery_topic.clone()
321 }
322}
323
324struct ContextInner {
325 local_nodes: HashMap<String, NodeEntitiesInfo>,
326
327 ros_discovery_topic: Topic,
329 node_writer: Publisher<ParticipantEntitiesInfo>,
330 domain_participant: DomainParticipant,
335 ros_default_publisher: rustdds::Publisher,
338 ros_default_subscriber: rustdds::Subscriber,
339
340 ros_parameter_events_topic: Topic,
341 ros_rosout_topic: Topic,
342}
343
344impl ContextInner {
345 pub fn from_domain_participant(
347 domain_participant: DomainParticipant,
348 ) -> CreateResult<ContextInner> {
349 let ros_default_publisher = domain_participant.create_publisher(&DEFAULT_PUBLISHER_QOS)?;
350 let ros_default_subscriber = domain_participant.create_subscriber(&DEFAULT_SUBSCRIPTION_QOS)?;
351
352 let ros_discovery_topic = domain_participant.create_topic(
354 builtin_topics::ros_discovery::TOPIC_NAME.to_string(),
355 builtin_topics::ros_discovery::TYPE_NAME.to_string(),
356 &builtin_topics::ros_discovery::QOS_PUB,
357 TopicKind::NoKey,
358 )?;
359
360 let ros_parameter_events_topic = domain_participant.create_topic(
361 builtin_topics::parameter_events::TOPIC_NAME.to_string(),
362 builtin_topics::parameter_events::TYPE_NAME.to_string(),
363 &builtin_topics::parameter_events::QOS,
364 TopicKind::NoKey,
365 )?;
366
367 let ros_rosout_topic = domain_participant.create_topic(
368 builtin_topics::rosout::TOPIC_NAME.to_string(),
369 builtin_topics::rosout::TYPE_NAME.to_string(),
370 &builtin_topics::rosout::QOS,
371 TopicKind::NoKey,
372 )?;
373
374 let node_writer =
375 Publisher::new(ros_default_publisher.create_datawriter_no_key(&ros_discovery_topic, None)?);
376
377 Ok(ContextInner {
378 local_nodes: HashMap::new(),
379 node_writer,
380
381 domain_participant,
382 ros_discovery_topic,
383 ros_default_publisher,
384 ros_default_subscriber,
385 ros_parameter_events_topic,
386 ros_rosout_topic,
387 })
388 }
389
390 pub fn participant_entities_info(&self) -> ParticipantEntitiesInfo {
392 ParticipantEntitiesInfo::new(
393 Gid::from(self.domain_participant.guid()),
394 self.local_nodes.values().cloned().collect(),
395 )
396 }
397
398 fn update_node(&mut self, mut node_info: NodeEntitiesInfo) {
400 node_info.add_writer(Gid::from(self.node_writer.guid()));
402
403 self
404 .local_nodes
405 .insert(node_info.fully_qualified_name(), node_info);
406 self.broadcast_node_infos();
407 }
408
409 fn remove_node(&mut self, node_fqn: &str) {
411 self.local_nodes.remove(node_fqn);
412 self.broadcast_node_infos();
413 }
414
415 fn broadcast_node_infos(&self) {
416 let pei = self.participant_entities_info();
417 debug!("ROS discovery publish: {pei:?}");
418 self
419 .node_writer
420 .publish(pei)
421 .unwrap_or_else(|e| error!("Failed to write into node_writer {e:?}"));
422 }
423} impl Drop for ContextInner {
426 fn drop(&mut self) {
427 self.local_nodes.clear();
429 self.broadcast_node_infos();
430 }
431}
432
433#[test]
437fn test_node_create() {
438 use crate::ParameterValue;
439
440 let context = Context::new().unwrap();
441 let _node = context
442 .new_node(
443 NodeName::new("/rustdds", "test_node").unwrap(),
444 NodeOptions::new().declare_parameter("foo", ParameterValue::Boolean(true)),
445 )
446 .is_ok();
447}