1use core::marker::PhantomData;
4
5use nros_core::{RosAction, RosMessage, RosService};
6use nros_rmw::{ActionInfo, QoSProfile, ServiceInfo, Session as _, TopicInfo, TransportError};
7
8use crate::{
9 rmw_type_registry::{MessageForRmw, register_type},
10 session,
11};
12
13use super::{
14 handles::{
15 ActionClient, ActionClientCallback, ActionServer, EmbeddedPublisher, EmbeddedServiceClient,
16 EmbeddedServiceServer, ServiceClientCallback, Subscription,
17 },
18 types::NodeError,
19};
20
21pub(crate) fn validate_qos_or_report(
42 qos: &QoSProfile,
43 supported: nros_rmw::QoSPolicyMask,
44 kind: &str,
45 name: &str,
46) -> Result<(), NodeError> {
47 match qos.validate_against(supported) {
48 Ok(()) => Ok(()),
49 Err(err) => {
50 nros_log::log_error!(
51 nros_log::get_logger("nros_node"),
52 "qos: {} '{}' refused — the backend does not honour {}",
53 kind,
54 name,
55 err.qos_policy_name()
60 .unwrap_or("a QoS policy it did not name")
61 );
62 Err(NodeError::Transport(err))
63 }
64 }
65}
66
67pub struct NodeHandle<'a> {
73 name: heapless::String<64>,
74 namespace: heapless::String<64>,
75 session: &'a mut session::ConcreteSession,
76 remaps: &'a [Option<super::spin::RemapRule>],
79 domain_id: u32,
80 qos_overrides: &'static [nros_rmw::QoSOverride],
87 monitors: &'static [crate::executor::monitor::MonitorSpec],
91 age_monitors: &'static [crate::executor::monitor::AgeMonitorSpec],
94 epoch_us_fn: Option<fn() -> u64>,
95}
96
97impl<'a> NodeHandle<'a> {
98 pub(crate) fn new(
100 name: heapless::String<64>,
101 namespace: heapless::String<64>,
102 session: &'a mut session::ConcreteSession,
103 remaps: &'a [Option<super::spin::RemapRule>],
104 domain_id: u32,
105 ) -> Self {
106 Self {
107 name,
108 namespace,
109 session,
110 remaps,
111 domain_id,
112 qos_overrides: &[],
113 monitors: &[],
114 age_monitors: &[],
115 epoch_us_fn: None,
116 }
117 }
118
119 pub fn set_qos_overrides(&mut self, overrides: &'static [nros_rmw::QoSOverride]) {
127 self.qos_overrides = overrides;
128 }
129
130 pub fn set_monitors(&mut self, monitors: &'static [crate::executor::monitor::MonitorSpec]) {
133 self.monitors = monitors;
134 }
135
136 pub fn set_age_monitors(
139 &mut self,
140 table: &'static [crate::executor::monitor::AgeMonitorSpec],
141 epoch_us: Option<fn() -> u64>,
142 ) {
143 self.age_monitors = table;
144 self.epoch_us_fn = epoch_us;
145 }
146
147 #[must_use]
149 pub fn qos_overrides(&self) -> &'static [nros_rmw::QoSOverride] {
150 self.qos_overrides
151 }
152
153 pub fn name(&self) -> &str {
155 &self.name
156 }
157
158 #[must_use]
175 pub fn serialization_format(&self) -> Option<&'static str> {
176 nros_rmw::Session::serialization_format(&*self.session)
177 }
178
179 #[must_use]
210 pub fn logger(&self) -> &'static nros_log::Logger {
211 nros_log::resolve_node_logger(self.name())
212 }
213
214 pub fn domain_id(&self) -> u32 {
216 self.domain_id
217 }
218
219 pub fn set_domain_id(&mut self, domain_id: u32) {
221 self.domain_id = domain_id;
222 }
223
224 pub fn session_mut(&mut self) -> &mut session::ConcreteSession {
226 self.session
227 }
228
229 fn topic_info<'b>(
249 domain_id: u32,
250 node_name: &'b str,
251 namespace: &'b str,
252 topic_name: &'b str,
253 type_name: &'b str,
254 type_hash: &'b str,
255 ) -> TopicInfo<'b> {
256 TopicInfo::new(topic_name, type_name, type_hash)
257 .with_domain(domain_id)
258 .with_node_name(node_name)
259 .with_namespace(namespace)
260 }
261
262 fn service_info<'b>(
263 domain_id: u32,
264 node_name: &'b str,
265 namespace: &'b str,
266 service_name: &'b str,
267 type_name: &'b str,
268 type_hash: &'b str,
269 ) -> ServiceInfo<'b> {
270 ServiceInfo::new(service_name, type_name, type_hash)
271 .with_domain(domain_id)
272 .with_node_name(node_name)
273 .with_namespace(namespace)
274 }
275
276 fn action_info<'b>(
277 domain_id: u32,
278 action_name: &'b str,
279 type_name: &'b str,
280 type_hash: &'b str,
281 ) -> ActionInfo<'b> {
282 ActionInfo::new(action_name, type_name, type_hash).with_domain(domain_id)
286 }
287
288 fn remapped(&self, source: &str) -> Option<crate::names::ResolvedName> {
308 use super::spin::{RemapTier, tier_rules};
309 crate::names::remap_layered(
310 source,
311 &self.name,
312 &self.namespace,
313 tier_rules(self.remaps, RemapTier::Launch, &self.name, &self.namespace),
314 tier_rules(self.remaps, RemapTier::Argv, &self.name, &self.namespace),
315 )
316 }
317
318 pub fn create_publisher<M: MessageForRmw>(
322 &mut self,
323 topic_name: &str,
324 ) -> Result<EmbeddedPublisher<M>, NodeError> {
325 self.create_publisher_with_qos::<M>(topic_name, QoSProfile::default())
326 }
327
328 pub fn create_publisher_with_qos<M: MessageForRmw>(
330 &mut self,
331 topic_name: &str,
332 qos: QoSProfile,
333 ) -> Result<EmbeddedPublisher<M>, NodeError> {
334 crate::format_check::assert_message_format::<M>();
338 register_type::<M>()?;
342 let qos = qos.apply_overrides(
346 topic_name,
347 nros_rmw::QoSOverrideRole::Publisher,
348 self.qos_overrides,
349 );
350 let remapped = self.remapped(topic_name);
353 let topic = Self::topic_info(
354 self.domain_id,
355 &self.name,
356 &self.namespace,
357 remapped.as_deref().unwrap_or(topic_name),
358 <M as RosMessage>::TYPE_NAME,
359 <M as RosMessage>::TYPE_HASH,
360 );
361 let qos = crate::declared_qos::honour_publisher(topic.type_name, topic.name, qos)?;
365 validate_qos_or_report(
366 &qos,
367 nros_rmw::Session::supported_qos_policies(self.session),
368 "publisher",
369 topic_name,
370 )?;
371 let handle = self
372 .session
373 .create_publisher(&topic, qos)
374 .map_err(|_| NodeError::Transport(TransportError::PublisherCreationFailed))?;
375 let monitor = self
378 .monitors
379 .iter()
380 .find(|m| m.topic == topic_name)
381 .map(|m| m.cell);
382 Ok(EmbeddedPublisher {
383 handle,
384 event_regs: crate::executor::handles::empty_event_regs(),
385 monitor,
386 epoch: self.epoch_us_fn,
387 _phantom: PhantomData,
388 })
389 }
390
391 pub fn create_publisher_raw(
397 &mut self,
398 topic_name: &str,
399 type_name: &str,
400 type_hash: &str,
401 ) -> Result<crate::executor::handles::EmbeddedRawPublisher, NodeError> {
402 self.create_publisher_raw_with_qos(topic_name, type_name, type_hash, QoSProfile::default())
403 }
404
405 pub fn create_publisher_raw_with_qos(
407 &mut self,
408 topic_name: &str,
409 type_name: &str,
410 type_hash: &str,
411 qos: QoSProfile,
412 ) -> Result<crate::executor::handles::EmbeddedRawPublisher, NodeError> {
413 let qos = qos.apply_overrides(
415 topic_name,
416 nros_rmw::QoSOverrideRole::Publisher,
417 self.qos_overrides,
418 );
419 let remapped = self.remapped(topic_name);
420 let topic = Self::topic_info(
421 self.domain_id,
422 &self.name,
423 &self.namespace,
424 remapped.as_deref().unwrap_or(topic_name),
425 type_name,
426 type_hash,
427 );
428 let qos = crate::declared_qos::honour_publisher(topic.type_name, topic.name, qos)?;
430 validate_qos_or_report(
431 &qos,
432 nros_rmw::Session::supported_qos_policies(self.session),
433 "publisher",
434 topic_name,
435 )?;
436 let handle = self
437 .session
438 .create_publisher(&topic, qos)
439 .map_err(|_| NodeError::Transport(TransportError::PublisherCreationFailed))?;
440 Ok(crate::executor::handles::EmbeddedRawPublisher {
441 handle,
442 arena: crate::executor::handles::TxArena::new(),
443 event_regs: crate::executor::handles::empty_event_regs(),
444 monitor: self
446 .monitors
447 .iter()
448 .find(|m| m.topic == topic_name)
449 .map(|m| m.cell),
450 })
451 }
452
453 pub fn publisher<'t>(&mut self, topic: &'t str) -> PublisherBuilder<'_, 'a, 't> {
459 PublisherBuilder {
460 node: self,
461 topic,
462 qos: QoSProfile::default(),
463 }
464 }
465
466 pub fn create_subscription<M: MessageForRmw>(
470 &mut self,
471 topic_name: &str,
472 ) -> Result<Subscription<M>, NodeError> {
473 self.create_subscription_sized::<M, { crate::config::DEFAULT_RX_BUF_SIZE }>(topic_name)
474 }
475
476 pub fn create_subscription_sized<M: MessageForRmw, const RX_BUF: usize>(
478 &mut self,
479 topic_name: &str,
480 ) -> Result<Subscription<M, RX_BUF>, NodeError> {
481 self.create_subscription_with_qos::<M, RX_BUF>(topic_name, QoSProfile::default())
482 }
483
484 pub fn create_subscription_with_qos<M: MessageForRmw, const RX_BUF: usize>(
486 &mut self,
487 topic_name: &str,
488 qos: QoSProfile,
489 ) -> Result<Subscription<M, RX_BUF>, NodeError> {
490 crate::format_check::assert_message_format::<M>();
494 register_type::<M>()?;
496 let qos = qos.apply_overrides(
498 topic_name,
499 nros_rmw::QoSOverrideRole::Subscription,
500 self.qos_overrides,
501 );
502 validate_qos_or_report(
503 &qos,
504 nros_rmw::Session::supported_qos_policies(self.session),
505 "subscription",
506 topic_name,
507 )?;
508 let remapped = self.remapped(topic_name);
509 let topic = Self::topic_info(
510 self.domain_id,
511 &self.name,
512 &self.namespace,
513 remapped.as_deref().unwrap_or(topic_name),
514 <M as RosMessage>::TYPE_NAME,
515 <M as RosMessage>::TYPE_HASH,
516 );
517 let qos = crate::declared_qos::honour(topic.type_name, topic.name, qos)?;
519 let handle = self
520 .session
521 .create_subscription(&topic, qos)
522 .map_err(NodeError::Transport)?;
523 let age_mon = match (<M as RosMessage>::STAMP_OFFSET, self.epoch_us_fn) {
526 (Some(_), Some(epoch)) => self
527 .age_monitors
528 .iter()
529 .find(|a| a.topic == topic_name)
530 .map(|a| (a.cell, epoch)),
531 _ => None,
532 };
533 Ok(Subscription {
534 handle,
535 buffer: [0u8; RX_BUF],
536 event_regs: crate::executor::handles::empty_event_regs(),
537 age_mon,
538 _phantom: PhantomData,
539 })
540 }
541
542 pub fn create_subscription_raw(
544 &mut self,
545 topic_name: &str,
546 type_name: &str,
547 type_hash: &str,
548 ) -> Result<crate::executor::handles::RawSubscription, NodeError> {
549 self.create_subscription_raw_sized::<{ crate::config::DEFAULT_RX_BUF_SIZE }>(
550 topic_name, type_name, type_hash,
551 )
552 }
553
554 pub fn create_subscription_raw_sized<const RX_BUF: usize>(
556 &mut self,
557 topic_name: &str,
558 type_name: &str,
559 type_hash: &str,
560 ) -> Result<crate::executor::handles::RawSubscription<RX_BUF>, NodeError> {
561 let qos = QoSProfile::default().apply_overrides(
566 topic_name,
567 nros_rmw::QoSOverrideRole::Subscription,
568 self.qos_overrides,
569 );
570 validate_qos_or_report(
571 &qos,
572 nros_rmw::Session::supported_qos_policies(self.session),
573 "subscription",
574 topic_name,
575 )?;
576 let remapped = self.remapped(topic_name);
577 let topic = Self::topic_info(
578 self.domain_id,
579 &self.name,
580 &self.namespace,
581 remapped.as_deref().unwrap_or(topic_name),
582 type_name,
583 type_hash,
584 );
585 let qos = crate::declared_qos::honour(topic.type_name, topic.name, qos)?;
590 let handle = self
591 .session
592 .create_subscription(&topic, qos)
593 .map_err(NodeError::Transport)?;
594 Ok(crate::executor::handles::RawSubscription {
595 handle,
596 buffer: [0u8; RX_BUF],
597 event_regs: crate::executor::handles::empty_event_regs(),
598 })
599 }
600
601 pub fn create_service<Svc: RosService>(
605 &mut self,
606 service_name: &str,
607 ) -> Result<EmbeddedServiceServer<Svc>, NodeError>
608 where
609 Svc::Request: MessageForRmw,
610 Svc::Reply: MessageForRmw,
611 {
612 self.create_service_sized::<Svc, { crate::config::DEFAULT_RX_BUF_SIZE }, { crate::config::DEFAULT_RX_BUF_SIZE }>(service_name, QoSProfile::services_default())
613 }
614
615 pub fn create_service_with_qos<Svc: RosService>(
618 &mut self,
619 service_name: &str,
620 qos: QoSProfile,
621 ) -> Result<EmbeddedServiceServer<Svc>, NodeError>
622 where
623 Svc::Request: MessageForRmw,
624 Svc::Reply: MessageForRmw,
625 {
626 self.create_service_sized::<Svc, { crate::config::DEFAULT_RX_BUF_SIZE }, { crate::config::DEFAULT_RX_BUF_SIZE }>(service_name, qos)
627 }
628
629 pub fn create_service_sized<Svc: RosService, const REQ_BUF: usize, const REPLY_BUF: usize>(
631 &mut self,
632 service_name: &str,
633 qos: QoSProfile,
634 ) -> Result<EmbeddedServiceServer<Svc, REQ_BUF, REPLY_BUF>, NodeError>
635 where
636 Svc::Request: MessageForRmw,
637 Svc::Reply: MessageForRmw,
638 {
639 register_type::<Svc::Request>()?;
642 register_type::<Svc::Reply>()?;
643 validate_qos_or_report(
648 &qos,
649 nros_rmw::Session::supported_qos_policies(self.session),
650 "service",
651 service_name,
652 )?;
653 let remapped = self.remapped(service_name);
654 let info = Self::service_info(
655 self.domain_id,
656 &self.name,
657 &self.namespace,
658 remapped.as_deref().unwrap_or(service_name),
659 Svc::SERVICE_NAME,
660 Svc::SERVICE_HASH,
661 );
662 let handle = self
663 .session
664 .create_service(&info, qos)
665 .map_err(NodeError::Transport)?;
666 Ok(EmbeddedServiceServer {
667 handle,
668 req_buffer: [0u8; REQ_BUF],
669 reply_buffer: [0u8; REPLY_BUF],
670 _phantom: PhantomData,
671 })
672 }
673
674 pub fn create_client<Svc: RosService>(
676 &mut self,
677 service_name: &str,
678 ) -> Result<EmbeddedServiceClient<Svc>, NodeError>
679 where
680 Svc::Request: MessageForRmw,
681 Svc::Reply: MessageForRmw,
682 {
683 self.create_client_sized::<Svc, { crate::config::DEFAULT_RX_BUF_SIZE }, { crate::config::DEFAULT_RX_BUF_SIZE }>(service_name, QoSProfile::services_default())
684 }
685
686 pub fn create_client_with_qos<Svc: RosService>(
688 &mut self,
689 service_name: &str,
690 qos: QoSProfile,
691 ) -> Result<EmbeddedServiceClient<Svc>, NodeError>
692 where
693 Svc::Request: MessageForRmw,
694 Svc::Reply: MessageForRmw,
695 {
696 self.create_client_sized::<Svc, { crate::config::DEFAULT_RX_BUF_SIZE }, { crate::config::DEFAULT_RX_BUF_SIZE }>(service_name, qos)
697 }
698
699 pub fn create_client_sized<Svc: RosService, const REQ_BUF: usize, const REPLY_BUF: usize>(
701 &mut self,
702 service_name: &str,
703 qos: QoSProfile,
704 ) -> Result<EmbeddedServiceClient<Svc, REQ_BUF, REPLY_BUF>, NodeError>
705 where
706 Svc::Request: MessageForRmw,
707 Svc::Reply: MessageForRmw,
708 {
709 register_type::<Svc::Request>()?;
711 register_type::<Svc::Reply>()?;
712 validate_qos_or_report(
715 &qos,
716 nros_rmw::Session::supported_qos_policies(self.session),
717 "client",
718 service_name,
719 )?;
720 let remapped = self.remapped(service_name);
721 let info = Self::service_info(
722 self.domain_id,
723 &self.name,
724 &self.namespace,
725 remapped.as_deref().unwrap_or(service_name),
726 Svc::SERVICE_NAME,
727 Svc::SERVICE_HASH,
728 );
729 let handle = self
730 .session
731 .create_client(&info, qos)
732 .map_err(|_| NodeError::Transport(TransportError::ServiceClientCreationFailed))?;
733 Ok(EmbeddedServiceClient {
734 handle,
735 req_buffer: [0u8; REQ_BUF],
736 reply_buffer: [0u8; REPLY_BUF],
737 in_flight: false,
738 _phantom: PhantomData,
739 })
740 }
741
742 pub fn create_service_raw(
747 &mut self,
748 service_name: &str,
749 type_name: &str,
750 type_hash: &str,
751 ) -> Result<crate::executor::handles::RawServiceServer, NodeError> {
752 self.create_service_raw_sized::<
753 { crate::config::DEFAULT_RX_BUF_SIZE },
754 { crate::config::DEFAULT_RX_BUF_SIZE },
755 >(service_name, type_name, type_hash)
756 }
757
758 pub fn create_service_raw_sized<const REQ_BUF: usize, const RESP_BUF: usize>(
760 &mut self,
761 service_name: &str,
762 type_name: &str,
763 type_hash: &str,
764 ) -> Result<crate::executor::handles::RawServiceServer<REQ_BUF, RESP_BUF>, NodeError> {
765 let remapped = self.remapped(service_name);
766 let info = Self::service_info(
767 self.domain_id,
768 &self.name,
769 &self.namespace,
770 remapped.as_deref().unwrap_or(service_name),
771 type_name,
772 type_hash,
773 );
774 let handle = self
775 .session
776 .create_service(&info, QoSProfile::services_default())
777 .map_err(NodeError::Transport)?;
778 Ok(crate::executor::handles::RawServiceServer::new(handle))
779 }
780
781 pub fn create_client_raw(
783 &mut self,
784 service_name: &str,
785 type_name: &str,
786 type_hash: &str,
787 ) -> Result<crate::executor::handles::RawServiceClient, NodeError> {
788 self.create_client_raw_sized::<
789 { crate::config::DEFAULT_RX_BUF_SIZE },
790 { crate::config::DEFAULT_RX_BUF_SIZE },
791 >(service_name, type_name, type_hash)
792 }
793
794 pub fn create_client_raw_sized<const REQ_BUF: usize, const REPLY_BUF: usize>(
796 &mut self,
797 service_name: &str,
798 type_name: &str,
799 type_hash: &str,
800 ) -> Result<crate::executor::handles::RawServiceClient<REQ_BUF, REPLY_BUF>, NodeError> {
801 let remapped = self.remapped(service_name);
802 let info = Self::service_info(
803 self.domain_id,
804 &self.name,
805 &self.namespace,
806 remapped.as_deref().unwrap_or(service_name),
807 type_name,
808 type_hash,
809 );
810 let handle = self
811 .session
812 .create_client(&info, QoSProfile::services_default())
813 .map_err(|_| NodeError::Transport(TransportError::ServiceClientCreationFailed))?;
814 Ok(crate::executor::handles::RawServiceClient::new(handle))
815 }
816
817 pub fn create_action_server_raw(
827 &mut self,
828 action_name: &str,
829 type_name: &str,
830 type_hash: &str,
831 ) -> Result<
832 super::action_core::ActionServerCore<
833 { crate::config::DEFAULT_RX_BUF_SIZE },
834 { crate::config::DEFAULT_RX_BUF_SIZE },
835 { crate::config::DEFAULT_RX_BUF_SIZE },
836 4,
837 >,
838 NodeError,
839 > {
840 self.create_action_server_raw_sized::<
841 { crate::config::DEFAULT_RX_BUF_SIZE },
842 { crate::config::DEFAULT_RX_BUF_SIZE },
843 { crate::config::DEFAULT_RX_BUF_SIZE },
844 4,
845 >(action_name, type_name, type_hash)
846 }
847
848 pub fn create_action_server_raw_sized<
850 const GOAL_BUF: usize,
851 const RESULT_BUF: usize,
852 const FEEDBACK_BUF: usize,
853 const MAX_GOALS: usize,
854 >(
855 &mut self,
856 action_name: &str,
857 type_name: &str,
858 type_hash: &str,
859 ) -> Result<
860 super::action_core::ActionServerCore<GOAL_BUF, RESULT_BUF, FEEDBACK_BUF, MAX_GOALS>,
861 NodeError,
862 > {
863 let remapped = self.remapped(action_name);
864 let action_name = remapped.as_deref().unwrap_or(action_name);
865 let action_info = Self::action_info(self.domain_id, action_name, type_name, type_hash);
866
867 let send_goal_type: heapless::String<256> =
873 super::action_core::action_channel_type(type_name, "SendGoal");
874 let send_goal_keyexpr: heapless::String<256> = action_info.send_goal_key();
875 let send_goal_info = Self::service_info(
876 self.domain_id,
877 &self.name,
878 &self.namespace,
879 &send_goal_keyexpr,
880 &send_goal_type,
881 type_hash,
882 );
883 let send_goal_server = self
884 .session
885 .create_service(&send_goal_info, QoSProfile::services_default())
886 .map_err(|_| NodeError::ActionCreationFailed)?;
887
888 let cancel_goal_keyexpr: heapless::String<256> = action_info.cancel_goal_key();
889 let cancel_goal_info = Self::service_info(
890 self.domain_id,
891 &self.name,
892 &self.namespace,
893 &cancel_goal_keyexpr,
894 "action_msgs::srv::dds_::CancelGoal_",
895 type_hash,
896 );
897 let cancel_goal_server = self
898 .session
899 .create_service(&cancel_goal_info, QoSProfile::services_default())
900 .map_err(|_| NodeError::ActionCreationFailed)?;
901
902 let get_result_type: heapless::String<256> =
903 super::action_core::action_channel_type(type_name, "GetResult");
904 let get_result_keyexpr: heapless::String<256> = action_info.get_result_key();
905 let get_result_info = Self::service_info(
906 self.domain_id,
907 &self.name,
908 &self.namespace,
909 &get_result_keyexpr,
910 &get_result_type,
911 type_hash,
912 );
913 let get_result_server = self
914 .session
915 .create_service(&get_result_info, QoSProfile::services_default())
916 .map_err(|_| NodeError::ActionCreationFailed)?;
917
918 let feedback_type: heapless::String<256> =
919 super::action_core::action_channel_type(type_name, "FeedbackMessage");
920 let feedback_keyexpr: heapless::String<256> = action_info.feedback_key();
921 let feedback_topic = Self::topic_info(
922 self.domain_id,
923 &self.name,
924 &self.namespace,
925 &feedback_keyexpr,
926 &feedback_type,
927 type_hash,
928 );
929 let feedback_publisher = self
930 .session
931 .create_publisher(&feedback_topic, QoSProfile::QOS_PROFILE_DEFAULT)
932 .map_err(|_| NodeError::ActionCreationFailed)?;
933
934 let status_keyexpr: heapless::String<256> = action_info.status_key();
935 let status_topic = Self::topic_info(
936 self.domain_id,
937 &self.name,
938 &self.namespace,
939 &status_keyexpr,
940 "action_msgs::msg::dds_::GoalStatusArray_",
941 type_hash,
942 );
943 let status_publisher = self
944 .session
945 .create_publisher(&status_topic, QoSProfile::QOS_PROFILE_ACTION_STATUS_DEFAULT)
946 .map_err(|_| NodeError::ActionCreationFailed)?;
947
948 Ok(super::action_core::ActionServerCore {
949 send_goal_server,
950 cancel_goal_server,
951 get_result_server,
952 feedback_publisher,
953 status_publisher,
954 active_goals: heapless::Vec::new(),
955 completed_results: heapless::Vec::new(),
956 pending_get_results: heapless::Vec::new(),
957 result_slab: [0u8; RESULT_BUF],
958 result_slab_used: 0,
959 goal_buffer: [0u8; GOAL_BUF],
960 feedback_buffer: [0u8; FEEDBACK_BUF],
961 cancel_buffer: [0u8; 256],
962 })
963 }
964
965 pub fn create_action_client_raw(
969 &mut self,
970 action_name: &str,
971 type_name: &str,
972 type_hash: &str,
973 ) -> Result<
974 super::action_core::ActionClientCore<
975 { crate::config::DEFAULT_RX_BUF_SIZE },
976 { crate::config::DEFAULT_RX_BUF_SIZE },
977 { crate::config::DEFAULT_RX_BUF_SIZE },
978 >,
979 NodeError,
980 > {
981 self.create_action_client_raw_sized::<
982 { crate::config::DEFAULT_RX_BUF_SIZE },
983 { crate::config::DEFAULT_RX_BUF_SIZE },
984 { crate::config::DEFAULT_RX_BUF_SIZE },
985 >(action_name, type_name, type_hash)
986 }
987
988 pub fn create_action_client_raw_sized<
990 const GOAL_BUF: usize,
991 const RESULT_BUF: usize,
992 const FEEDBACK_BUF: usize,
993 >(
994 &mut self,
995 action_name: &str,
996 type_name: &str,
997 type_hash: &str,
998 ) -> Result<super::action_core::ActionClientCore<GOAL_BUF, RESULT_BUF, FEEDBACK_BUF>, NodeError>
999 {
1000 let remapped = self.remapped(action_name);
1001 let action_name = remapped.as_deref().unwrap_or(action_name);
1002 let action_info = Self::action_info(self.domain_id, action_name, type_name, type_hash);
1003
1004 let send_goal_type: heapless::String<256> =
1010 super::action_core::action_channel_type(type_name, "SendGoal");
1011 let send_goal_keyexpr: heapless::String<256> = action_info.send_goal_key();
1012 let send_goal_info = Self::service_info(
1013 self.domain_id,
1014 &self.name,
1015 &self.namespace,
1016 &send_goal_keyexpr,
1017 &send_goal_type,
1018 type_hash,
1019 );
1020 let send_goal_client = self
1021 .session
1022 .create_client(&send_goal_info, QoSProfile::services_default())
1023 .map_err(|_| NodeError::ActionCreationFailed)?;
1024
1025 let cancel_goal_keyexpr: heapless::String<256> = action_info.cancel_goal_key();
1026 let cancel_goal_info = Self::service_info(
1027 self.domain_id,
1028 &self.name,
1029 &self.namespace,
1030 &cancel_goal_keyexpr,
1031 "action_msgs::srv::dds_::CancelGoal_",
1032 type_hash,
1033 );
1034 let cancel_goal_client = self
1035 .session
1036 .create_client(&cancel_goal_info, QoSProfile::services_default())
1037 .map_err(|_| NodeError::ActionCreationFailed)?;
1038
1039 let get_result_type: heapless::String<256> =
1040 super::action_core::action_channel_type(type_name, "GetResult");
1041 let get_result_keyexpr: heapless::String<256> = action_info.get_result_key();
1042 let get_result_info = Self::service_info(
1043 self.domain_id,
1044 &self.name,
1045 &self.namespace,
1046 &get_result_keyexpr,
1047 &get_result_type,
1048 type_hash,
1049 );
1050 let get_result_client = self
1051 .session
1052 .create_client(&get_result_info, QoSProfile::services_default())
1053 .map_err(|_| NodeError::ActionCreationFailed)?;
1054
1055 let feedback_type: heapless::String<256> =
1056 super::action_core::action_channel_type(type_name, "FeedbackMessage");
1057 let feedback_keyexpr: heapless::String<256> = action_info.feedback_key();
1058 let feedback_topic = Self::topic_info(
1059 self.domain_id,
1060 &self.name,
1061 &self.namespace,
1062 &feedback_keyexpr,
1063 &feedback_type,
1064 type_hash,
1065 );
1066 let feedback_subscriber = self
1067 .session
1068 .create_subscription(&feedback_topic, QoSProfile::BEST_EFFORT)
1069 .map_err(|_| NodeError::ActionCreationFailed)?;
1070
1071 Ok(super::action_core::ActionClientCore::new(
1072 send_goal_client,
1073 cancel_goal_client,
1074 get_result_client,
1075 feedback_subscriber,
1076 ))
1077 }
1078
1079 pub fn create_action_server<A: RosAction>(
1081 &mut self,
1082 action_name: &str,
1083 ) -> Result<ActionServer<A>, NodeError>
1084 where
1085 A::Goal: MessageForRmw,
1086 A::Result: MessageForRmw,
1087 A::Feedback: MessageForRmw,
1088 A::SendGoalRequest: MessageForRmw,
1089 A::SendGoalResponse: MessageForRmw,
1090 A::GetResultRequest: MessageForRmw,
1091 A::GetResultResponse: MessageForRmw,
1092 A::FeedbackMessage: MessageForRmw,
1093 {
1094 self.create_action_server_sized::<A, { crate::config::DEFAULT_RX_BUF_SIZE }, { crate::config::DEFAULT_RX_BUF_SIZE }, { crate::config::DEFAULT_RX_BUF_SIZE }, 4>(action_name)
1095 }
1096
1097 pub fn create_action_server_sized<
1099 A: RosAction,
1100 const GOAL_BUF: usize,
1101 const RESULT_BUF: usize,
1102 const FEEDBACK_BUF: usize,
1103 const MAX_GOALS: usize,
1104 >(
1105 &mut self,
1106 action_name: &str,
1107 ) -> Result<ActionServer<A, GOAL_BUF, RESULT_BUF, FEEDBACK_BUF, MAX_GOALS>, NodeError>
1108 where
1109 A::Goal: MessageForRmw,
1110 A::Result: MessageForRmw,
1111 A::Feedback: MessageForRmw,
1112 A::SendGoalRequest: MessageForRmw,
1113 A::SendGoalResponse: MessageForRmw,
1114 A::GetResultRequest: MessageForRmw,
1115 A::GetResultResponse: MessageForRmw,
1116 A::FeedbackMessage: MessageForRmw,
1117 {
1118 register_type::<A::Goal>()?;
1126 register_type::<A::Result>()?;
1127 register_type::<A::Feedback>()?;
1128 register_type::<A::SendGoalRequest>()?;
1129 register_type::<A::SendGoalResponse>()?;
1130 register_type::<A::GetResultRequest>()?;
1131 register_type::<A::GetResultResponse>()?;
1132 register_type::<A::FeedbackMessage>()?;
1133 A::register_protocol_types().map_err(|()| NodeError::ActionCreationFailed)?;
1144 let remapped = self.remapped(action_name);
1145 let action_name = remapped.as_deref().unwrap_or(action_name);
1146 let action_info =
1147 Self::action_info(self.domain_id, action_name, A::ACTION_NAME, A::ACTION_HASH);
1148
1149 let send_goal_type = super::action_core::action_service_base_type(
1158 <A::SendGoalRequest as RosMessage>::TYPE_NAME,
1159 A::ACTION_NAME,
1160 );
1161 let get_result_type = super::action_core::action_service_base_type(
1162 <A::GetResultRequest as RosMessage>::TYPE_NAME,
1163 A::ACTION_NAME,
1164 );
1165 let feedback_type = <A::FeedbackMessage as RosMessage>::TYPE_NAME;
1166
1167 let send_goal_keyexpr: heapless::String<256> = action_info.send_goal_key();
1168 let send_goal_info = Self::service_info(
1169 self.domain_id,
1170 &self.name,
1171 &self.namespace,
1172 &send_goal_keyexpr,
1173 send_goal_type,
1174 A::SEND_GOAL_SERVICE_HASH,
1175 );
1176 let send_goal_server = self
1177 .session
1178 .create_service(&send_goal_info, QoSProfile::services_default())
1179 .map_err(|_| NodeError::ActionCreationFailed)?;
1180
1181 let cancel_goal_keyexpr: heapless::String<256> = action_info.cancel_goal_key();
1182 let cancel_goal_info = Self::service_info(
1183 self.domain_id,
1184 &self.name,
1185 &self.namespace,
1186 &cancel_goal_keyexpr,
1187 "action_msgs::srv::dds_::CancelGoal_",
1188 A::CANCEL_GOAL_SERVICE_HASH,
1189 );
1190 let cancel_goal_server = self
1191 .session
1192 .create_service(&cancel_goal_info, QoSProfile::services_default())
1193 .map_err(|_| NodeError::ActionCreationFailed)?;
1194
1195 let get_result_keyexpr: heapless::String<256> = action_info.get_result_key();
1196 let get_result_info = Self::service_info(
1197 self.domain_id,
1198 &self.name,
1199 &self.namespace,
1200 &get_result_keyexpr,
1201 get_result_type,
1202 A::GET_RESULT_SERVICE_HASH,
1203 );
1204 let get_result_server = self
1205 .session
1206 .create_service(&get_result_info, QoSProfile::services_default())
1207 .map_err(|_| NodeError::ActionCreationFailed)?;
1208
1209 let feedback_keyexpr: heapless::String<256> = action_info.feedback_key();
1210 let feedback_topic = Self::topic_info(
1211 self.domain_id,
1212 &self.name,
1213 &self.namespace,
1214 &feedback_keyexpr,
1215 feedback_type,
1216 <A::FeedbackMessage as RosMessage>::TYPE_HASH,
1217 );
1218 let feedback_publisher = self
1219 .session
1220 .create_publisher(&feedback_topic, QoSProfile::QOS_PROFILE_DEFAULT)
1221 .map_err(|_| NodeError::ActionCreationFailed)?;
1222
1223 let status_keyexpr: heapless::String<256> = action_info.status_key();
1224 let status_topic = Self::topic_info(
1225 self.domain_id,
1226 &self.name,
1227 &self.namespace,
1228 &status_keyexpr,
1229 "action_msgs::msg::dds_::GoalStatusArray_",
1230 A::STATUS_MESSAGE_HASH,
1231 );
1232 let status_publisher = self
1233 .session
1234 .create_publisher(&status_topic, QoSProfile::QOS_PROFILE_ACTION_STATUS_DEFAULT)
1235 .map_err(|_| NodeError::ActionCreationFailed)?;
1236
1237 Ok(ActionServer {
1238 core: super::action_core::ActionServerCore {
1239 send_goal_server,
1240 cancel_goal_server,
1241 get_result_server,
1242 feedback_publisher,
1243 status_publisher,
1244 active_goals: heapless::Vec::new(),
1245 completed_results: heapless::Vec::new(),
1246 pending_get_results: heapless::Vec::new(),
1247 result_slab: [0u8; RESULT_BUF],
1248 result_slab_used: 0,
1249 goal_buffer: [0u8; GOAL_BUF],
1250 feedback_buffer: [0u8; FEEDBACK_BUF],
1251 cancel_buffer: [0u8; 256],
1252 },
1253 typed_goals: heapless::Vec::new(),
1254 completed_goals: heapless::Vec::new(),
1255 })
1256 }
1257
1258 pub fn create_action_client<A: RosAction>(
1260 &mut self,
1261 action_name: &str,
1262 ) -> Result<ActionClient<A>, NodeError>
1263 where
1264 A::Goal: MessageForRmw,
1265 A::Result: MessageForRmw,
1266 A::Feedback: MessageForRmw,
1267 A::SendGoalRequest: MessageForRmw,
1268 A::SendGoalResponse: MessageForRmw,
1269 A::GetResultRequest: MessageForRmw,
1270 A::GetResultResponse: MessageForRmw,
1271 A::FeedbackMessage: MessageForRmw,
1272 {
1273 self.create_action_client_sized::<A, { crate::config::DEFAULT_RX_BUF_SIZE }, { crate::config::DEFAULT_RX_BUF_SIZE }, { crate::config::DEFAULT_RX_BUF_SIZE }>(action_name)
1274 }
1275
1276 pub fn create_action_client_sized<
1278 A: RosAction,
1279 const GOAL_BUF: usize,
1280 const RESULT_BUF: usize,
1281 const FEEDBACK_BUF: usize,
1282 >(
1283 &mut self,
1284 action_name: &str,
1285 ) -> Result<ActionClient<A, GOAL_BUF, RESULT_BUF, FEEDBACK_BUF>, NodeError>
1286 where
1287 A::Goal: MessageForRmw,
1288 A::Result: MessageForRmw,
1289 A::Feedback: MessageForRmw,
1290 A::SendGoalRequest: MessageForRmw,
1291 A::SendGoalResponse: MessageForRmw,
1292 A::GetResultRequest: MessageForRmw,
1293 A::GetResultResponse: MessageForRmw,
1294 A::FeedbackMessage: MessageForRmw,
1295 {
1296 register_type::<A::Goal>()?;
1298 register_type::<A::Result>()?;
1299 register_type::<A::Feedback>()?;
1300 register_type::<A::SendGoalRequest>()?;
1301 register_type::<A::SendGoalResponse>()?;
1302 register_type::<A::GetResultRequest>()?;
1303 register_type::<A::GetResultResponse>()?;
1304 register_type::<A::FeedbackMessage>()?;
1305 A::register_protocol_types().map_err(|()| NodeError::ActionCreationFailed)?;
1311 let remapped = self.remapped(action_name);
1312 let action_name = remapped.as_deref().unwrap_or(action_name);
1313 let action_info =
1314 Self::action_info(self.domain_id, action_name, A::ACTION_NAME, A::ACTION_HASH);
1315
1316 let send_goal_type = super::action_core::action_service_base_type(
1324 <A::SendGoalRequest as RosMessage>::TYPE_NAME,
1325 A::ACTION_NAME,
1326 );
1327 let get_result_type = super::action_core::action_service_base_type(
1328 <A::GetResultRequest as RosMessage>::TYPE_NAME,
1329 A::ACTION_NAME,
1330 );
1331 let feedback_type = <A::FeedbackMessage as RosMessage>::TYPE_NAME;
1332
1333 let send_goal_keyexpr: heapless::String<256> = action_info.send_goal_key();
1334 let send_goal_info = Self::service_info(
1335 self.domain_id,
1336 &self.name,
1337 &self.namespace,
1338 &send_goal_keyexpr,
1339 send_goal_type,
1340 A::SEND_GOAL_SERVICE_HASH,
1341 );
1342 let send_goal_client = self
1343 .session
1344 .create_client(&send_goal_info, QoSProfile::services_default())
1345 .map_err(|_| NodeError::ActionCreationFailed)?;
1346
1347 let cancel_goal_keyexpr: heapless::String<256> = action_info.cancel_goal_key();
1348 let cancel_goal_info = Self::service_info(
1349 self.domain_id,
1350 &self.name,
1351 &self.namespace,
1352 &cancel_goal_keyexpr,
1353 "action_msgs::srv::dds_::CancelGoal_",
1354 A::CANCEL_GOAL_SERVICE_HASH,
1355 );
1356 let cancel_goal_client = self
1357 .session
1358 .create_client(&cancel_goal_info, QoSProfile::services_default())
1359 .map_err(|_| NodeError::ActionCreationFailed)?;
1360
1361 let get_result_keyexpr: heapless::String<256> = action_info.get_result_key();
1362 let get_result_info = Self::service_info(
1363 self.domain_id,
1364 &self.name,
1365 &self.namespace,
1366 &get_result_keyexpr,
1367 get_result_type,
1368 A::GET_RESULT_SERVICE_HASH,
1369 );
1370 let get_result_client = self
1371 .session
1372 .create_client(&get_result_info, QoSProfile::services_default())
1373 .map_err(|_| NodeError::ActionCreationFailed)?;
1374
1375 let feedback_keyexpr: heapless::String<256> = action_info.feedback_key();
1376 let feedback_topic = Self::topic_info(
1377 self.domain_id,
1378 &self.name,
1379 &self.namespace,
1380 &feedback_keyexpr,
1381 feedback_type,
1382 <A::FeedbackMessage as RosMessage>::TYPE_HASH,
1383 );
1384 let feedback_subscriber = self
1385 .session
1386 .create_subscription(&feedback_topic, QoSProfile::BEST_EFFORT)
1387 .map_err(|_| NodeError::ActionCreationFailed)?;
1388
1389 Ok(ActionClient {
1390 core: super::action_core::ActionClientCore {
1391 send_goal_client,
1392 cancel_goal_client,
1393 get_result_client,
1394 feedback_subscriber,
1395 goal_buffer: [0u8; GOAL_BUF],
1396 result_buffer: [0u8; RESULT_BUF],
1397 feedback_buffer: [0u8; FEEDBACK_BUF],
1398 goal_counter: 0,
1399 in_flight_send_goal: false,
1400 in_flight_cancel: false,
1401 in_flight_get_result: false,
1402 },
1403 _phantom: PhantomData,
1404 })
1405 }
1406}
1407
1408pub struct PublisherBuilder<'n, 'a, 't> {
1415 node: &'n mut NodeHandle<'a>,
1416 topic: &'t str,
1417 qos: QoSProfile,
1418}
1419
1420impl<'n, 'a, 't> PublisherBuilder<'n, 'a, 't> {
1421 pub fn qos(mut self, qos: QoSProfile) -> Self {
1423 self.qos = qos;
1424 self
1425 }
1426
1427 pub fn tx_express(mut self, express: bool) -> Self {
1431 self.qos.tx_express = express;
1432 self
1433 }
1434
1435 pub fn typed<M: MessageForRmw>(self) -> TypedPublisherBuilder<'n, 'a, 't, M> {
1437 TypedPublisherBuilder {
1438 node: self.node,
1439 topic: self.topic,
1440 qos: self.qos,
1441 _phantom: PhantomData,
1442 }
1443 }
1444
1445 pub fn generic(
1448 self,
1449 type_name: &'t str,
1450 type_hash: &'t str,
1451 ) -> GenericPublisherBuilder<'n, 'a, 't> {
1452 GenericPublisherBuilder {
1453 node: self.node,
1454 topic: self.topic,
1455 type_name,
1456 type_hash,
1457 qos: self.qos,
1458 }
1459 }
1460}
1461
1462pub struct TypedPublisherBuilder<'n, 'a, 't, M> {
1464 node: &'n mut NodeHandle<'a>,
1465 topic: &'t str,
1466 qos: QoSProfile,
1467 _phantom: PhantomData<M>,
1468}
1469
1470impl<'n, 'a, 't, M: MessageForRmw> TypedPublisherBuilder<'n, 'a, 't, M> {
1471 pub fn qos(mut self, qos: QoSProfile) -> Self {
1472 self.qos = qos;
1473 self
1474 }
1475
1476 pub fn tx_express(mut self, express: bool) -> Self {
1478 self.qos.tx_express = express;
1479 self
1480 }
1481
1482 pub fn build(self) -> Result<EmbeddedPublisher<M>, NodeError> {
1483 self.node
1484 .create_publisher_with_qos::<M>(self.topic, self.qos)
1485 }
1486}
1487
1488pub struct GenericPublisherBuilder<'n, 'a, 't> {
1490 node: &'n mut NodeHandle<'a>,
1491 topic: &'t str,
1492 type_name: &'t str,
1493 type_hash: &'t str,
1494 qos: QoSProfile,
1495}
1496
1497impl<'n, 'a, 't> GenericPublisherBuilder<'n, 'a, 't> {
1498 pub fn qos(mut self, qos: QoSProfile) -> Self {
1499 self.qos = qos;
1500 self
1501 }
1502
1503 pub fn tx_express(mut self, express: bool) -> Self {
1505 self.qos.tx_express = express;
1506 self
1507 }
1508
1509 pub fn build(self) -> Result<crate::executor::handles::EmbeddedRawPublisher, NodeError> {
1510 self.node.create_publisher_raw_with_qos(
1511 self.topic,
1512 self.type_name,
1513 self.type_hash,
1514 self.qos,
1515 )
1516 }
1517}
1518
1519pub struct CallbackGroup {
1535 name: heapless::String<32>,
1536}
1537
1538impl CallbackGroup {
1539 pub fn name(&self) -> &str {
1541 &self.name
1542 }
1543}
1544
1545pub struct Node<'e, 's> {
1560 executor: &'e mut super::spin::Executor<'s>,
1561 node_id: super::node_record::NodeId,
1562}
1563
1564impl<'e, 's> Node<'e, 's> {
1565 pub(crate) fn new(
1566 executor: &'e mut super::spin::Executor<'s>,
1567 node_id: super::node_record::NodeId,
1568 ) -> Self {
1569 Self { executor, node_id }
1570 }
1571
1572 #[must_use]
1577 pub fn name(&self) -> &str {
1578 self.executor
1579 .node(self.node_id)
1580 .map(|r| r.name.as_str())
1581 .unwrap_or("")
1582 }
1583
1584 #[must_use]
1586 pub fn namespace(&self) -> &str {
1587 self.executor
1588 .node(self.node_id)
1589 .map(|r| r.namespace.as_str())
1590 .unwrap_or("")
1591 }
1592
1593 pub fn fully_qualified_name(&self) -> Result<crate::names::ResolvedName, NodeError> {
1598 crate::names::fully_qualified_name(self.name(), self.namespace())
1599 .map_err(|()| NodeError::NameTooLong)
1600 }
1601
1602 #[must_use]
1605 pub fn domain_id(&self) -> u32 {
1606 self.executor.domain_id
1607 }
1608
1609 #[must_use]
1612 pub fn logger(&self) -> &'static nros_log::Logger {
1613 nros_log::resolve_node_logger(self.name())
1614 }
1615
1616 #[doc(hidden)]
1619 pub fn __executor(&mut self) -> &mut super::spin::Executor<'s> {
1620 self.executor
1621 }
1622
1623 #[must_use]
1627 pub fn id(&self) -> super::node_record::NodeId {
1628 self.node_id
1629 }
1630
1631 pub fn get_clock(&self) -> nros_core::clock::Clock {
1656 nros_core::clock::Clock::ros_time()
1657 }
1658
1659 pub fn subscription<'t>(&mut self, topic: &'t str) -> SubscriptionBuilder<'_, 'e, 't, 's> {
1662 SubscriptionBuilder {
1663 ctx: self,
1664 topic,
1665 qos: QoSProfile::default(),
1666 }
1667 }
1668
1669 pub fn publisher<'t>(&mut self, topic: &'t str) -> CtxPublisherBuilder<'_, 'e, 't, 's> {
1676 CtxPublisherBuilder {
1677 ctx: self,
1678 topic,
1679 qos: QoSProfile::default(),
1680 }
1681 }
1682
1683 pub fn create_publisher<M: MessageForRmw>(
1685 &mut self,
1686 topic: &str,
1687 ) -> Result<EmbeddedPublisher<M>, NodeError> {
1688 self.create_publisher_with_qos::<M>(topic, QoSProfile::default())
1689 }
1690
1691 pub fn create_generic_publisher(
1693 &mut self,
1694 topic: &str,
1695 type_name: &str,
1696 type_hash: &str,
1697 ) -> Result<crate::executor::handles::EmbeddedRawPublisher, NodeError> {
1698 self.create_generic_publisher_with_qos(topic, type_name, type_hash, QoSProfile::default())
1699 }
1700
1701 pub fn create_generic_publisher_with_qos(
1707 &mut self,
1708 topic: &str,
1709 type_name: &str,
1710 type_hash: &str,
1711 qos: QoSProfile,
1712 ) -> Result<crate::executor::handles::EmbeddedRawPublisher, NodeError> {
1713 self.executor
1714 .create_publisher_raw_on(self.node_id, topic, type_name, type_hash, qos)
1715 }
1716
1717 pub fn create_subscription<M, F>(
1720 &mut self,
1721 topic: &str,
1722 callback: F,
1723 ) -> Result<super::types::HandleId, NodeError>
1724 where
1725 M: MessageForRmw + 'static,
1726 F: FnMut(&M) + 'static,
1727 {
1728 self.executor
1729 .register_subscription_buffered_on::<M, F, { crate::config::DEFAULT_RX_BUF_SIZE }>(
1730 self.node_id,
1731 topic,
1732 QoSProfile::default(),
1733 callback,
1734 None, None, )
1737 }
1738
1739 #[cfg(all(feature = "sim-time", any(has_rmw, test)))]
1761 pub fn install_ros_time_source(&mut self) -> Result<super::types::HandleId, NodeError> {
1762 self.install_ros_time_source_on(crate::time_source::CLOCK_TOPIC)
1763 }
1764
1765 #[cfg(all(feature = "sim-time", any(has_rmw, test)))]
1769 pub fn install_ros_time_source_on(
1770 &mut self,
1771 topic: &str,
1772 ) -> Result<super::types::HandleId, NodeError> {
1773 self.executor.install_ros_time_source(self.node_id, topic)
1777 }
1778
1779 pub fn create_callback_group(&self, name: &str) -> CallbackGroup {
1798 let mut s = heapless::String::<32>::new();
1799 for ch in name.chars() {
1802 if s.push(ch).is_err() {
1803 break;
1804 }
1805 }
1806 CallbackGroup { name: s }
1807 }
1808
1809 pub fn create_timer_in_group<F>(
1816 &mut self,
1817 group: &CallbackGroup,
1818 period: crate::timer::TimerDuration,
1819 callback: F,
1820 ) -> Result<super::types::HandleId, NodeError>
1821 where
1822 F: FnMut() + 'static,
1823 {
1824 self.executor
1825 .register_timer_on(Some(self.node_id), period, callback, Some(group.name()))
1826 }
1827
1828 pub fn create_guard_condition<F>(
1856 &mut self,
1857 callback: F,
1858 ) -> Result<(super::types::HandleId, super::types::GuardCondition), NodeError>
1859 where
1860 F: FnMut() + 'static,
1861 {
1862 self.executor
1863 .register_guard_condition_on(Some(self.node_id), callback)
1864 }
1865
1866 pub fn create_timer_on_clock<F>(
1891 &mut self,
1892 period: crate::timer::TimerDuration,
1893 source: crate::executor::TimerClockSource,
1894 callback: F,
1895 ) -> Result<super::types::HandleId, NodeError>
1896 where
1897 F: FnMut() + 'static,
1898 {
1899 self.executor.register_timer_entry(
1900 Some(self.node_id),
1901 period,
1902 source,
1903 false,
1904 None,
1905 callback,
1906 )
1907 }
1908
1909 pub fn create_timer_on_clock_in_group<F>(
1915 &mut self,
1916 group: &CallbackGroup,
1917 period: crate::timer::TimerDuration,
1918 source: crate::executor::TimerClockSource,
1919 callback: F,
1920 ) -> Result<super::types::HandleId, NodeError>
1921 where
1922 F: FnMut() + 'static,
1923 {
1924 self.executor.register_timer_entry(
1925 Some(self.node_id),
1926 period,
1927 source,
1928 false,
1929 Some(group.name()),
1930 callback,
1931 )
1932 }
1933
1934 pub fn create_subscription_in_group<M, F>(
1940 &mut self,
1941 group: &CallbackGroup,
1942 topic: &str,
1943 callback: F,
1944 ) -> Result<super::types::HandleId, NodeError>
1945 where
1946 M: MessageForRmw + 'static,
1947 F: FnMut(&M) + 'static,
1948 {
1949 self.executor
1950 .register_subscription_buffered_on::<M, F, { crate::config::DEFAULT_RX_BUF_SIZE }>(
1951 self.node_id,
1952 topic,
1953 QoSProfile::default(),
1954 callback,
1955 Some(group.name()),
1956 None, )
1958 }
1959
1960 pub fn create_publisher_in_group<M: MessageForRmw>(
1968 &mut self,
1969 _group: &CallbackGroup,
1970 topic: &str,
1971 ) -> Result<EmbeddedPublisher<M>, NodeError> {
1972 self.create_publisher::<M>(topic)
1974 }
1975
1976 pub fn create_client_with_callback<Svc, F>(
1982 &mut self,
1983 service_name: &str,
1984 callback: F,
1985 ) -> Result<ServiceClientCallback<Svc>, NodeError>
1986 where
1987 Svc: RosService + 'static,
1988 Svc::Request: MessageForRmw,
1989 Svc::Reply: MessageForRmw,
1990 F: FnMut(&Svc::Reply) + 'static,
1991 {
1992 self.create_client_with_callback_sized::<
1993 Svc,
1994 F,
1995 { crate::config::DEFAULT_RX_BUF_SIZE },
1996 { crate::config::DEFAULT_RX_BUF_SIZE },
1997 >(service_name, callback)
1998 }
1999
2000 pub fn create_client_with_callback_sized<Svc, F, const REQ_BUF: usize, const REPLY_BUF: usize>(
2002 &mut self,
2003 service_name: &str,
2004 callback: F,
2005 ) -> Result<ServiceClientCallback<Svc, REQ_BUF, REPLY_BUF>, NodeError>
2006 where
2007 Svc: RosService + 'static,
2008 Svc::Request: MessageForRmw,
2009 Svc::Reply: MessageForRmw,
2010 F: FnMut(&Svc::Reply) + 'static,
2011 {
2012 register_type::<Svc::Request>()?;
2013 register_type::<Svc::Reply>()?;
2014 let (_id, hdr) = self
2015 .executor
2016 .register_service_client_callback::<Svc, F, REPLY_BUF>(
2017 Some(self.node_id),
2018 service_name,
2019 Svc::SERVICE_NAME,
2020 Svc::SERVICE_HASH,
2021 QoSProfile::services_default(),
2022 callback,
2023 )?;
2024 Ok(ServiceClientCallback::new(hdr))
2025 }
2026
2027 #[allow(clippy::type_complexity)]
2035 pub fn create_action_client_with_callbacks<A, GRespF, FbF, ResF>(
2036 &mut self,
2037 action_name: &str,
2038 on_goal_response: GRespF,
2039 on_feedback: FbF,
2040 on_result: ResF,
2041 ) -> Result<ActionClientCallback<A>, NodeError>
2042 where
2043 A: RosAction + 'static,
2044 A::Goal: MessageForRmw,
2045 A::Result: MessageForRmw,
2046 A::Feedback: MessageForRmw,
2047 GRespF: FnMut(&nros_core::GoalId, bool) + 'static,
2048 FbF: FnMut(&nros_core::GoalId, &A::Feedback) + 'static,
2049 ResF: FnMut(&nros_core::GoalId, nros_core::GoalStatus, &A::Result) + 'static,
2050 {
2051 self.create_action_client_with_callbacks_sized::<
2052 A,
2053 GRespF,
2054 FbF,
2055 ResF,
2056 { crate::config::DEFAULT_RX_BUF_SIZE },
2057 { crate::config::DEFAULT_RX_BUF_SIZE },
2058 { crate::config::DEFAULT_RX_BUF_SIZE },
2059 >(action_name, on_goal_response, on_feedback, on_result)
2060 }
2061
2062 #[allow(clippy::type_complexity)]
2064 pub fn create_action_client_with_callbacks_sized<
2065 A,
2066 GRespF,
2067 FbF,
2068 ResF,
2069 const GOAL_BUF: usize,
2070 const RESULT_BUF: usize,
2071 const FEEDBACK_BUF: usize,
2072 >(
2073 &mut self,
2074 action_name: &str,
2075 on_goal_response: GRespF,
2076 on_feedback: FbF,
2077 on_result: ResF,
2078 ) -> Result<ActionClientCallback<A, GOAL_BUF, RESULT_BUF, FEEDBACK_BUF>, NodeError>
2079 where
2080 A: RosAction + 'static,
2081 A::Goal: MessageForRmw,
2082 A::Result: MessageForRmw,
2083 A::Feedback: MessageForRmw,
2084 GRespF: FnMut(&nros_core::GoalId, bool) + 'static,
2085 FbF: FnMut(&nros_core::GoalId, &A::Feedback) + 'static,
2086 ResF: FnMut(&nros_core::GoalId, nros_core::GoalStatus, &A::Result) + 'static,
2087 {
2088 register_type::<A::Goal>()?;
2089 register_type::<A::Result>()?;
2090 register_type::<A::Feedback>()?;
2091 let (_id, core) = self
2092 .executor
2093 .register_action_client_callback::<A, GRespF, FbF, ResF, GOAL_BUF, RESULT_BUF, FEEDBACK_BUF>(
2094 Some(self.node_id),
2095 action_name,
2096 A::ACTION_NAME,
2097 A::ACTION_HASH,
2098 super::arena::DEFAULT_ACTION_FEEDBACK_DEPTH,
2101 on_goal_response,
2102 on_feedback,
2103 on_result,
2104 )?;
2105 Ok(ActionClientCallback::new(core))
2106 }
2107
2108 pub fn create_generic_subscription<F>(
2110 &mut self,
2111 topic: &str,
2112 type_name: &str,
2113 type_hash: &str,
2114 callback: F,
2115 ) -> Result<super::types::HandleId, NodeError>
2116 where
2117 F: FnMut(&[u8]) + 'static,
2118 {
2119 self.create_generic_subscription_with_qos(
2120 topic,
2121 type_name,
2122 type_hash,
2123 QoSProfile::default(),
2124 callback,
2125 )
2126 }
2127
2128 pub fn create_generic_subscription_with_qos<F>(
2131 &mut self,
2132 topic: &str,
2133 type_name: &str,
2134 type_hash: &str,
2135 qos: QoSProfile,
2136 callback: F,
2137 ) -> Result<super::types::HandleId, NodeError>
2138 where
2139 F: FnMut(&[u8]) + 'static,
2140 {
2141 self.executor
2142 .register_subscription_buffered_raw_on::<F, { crate::config::DEFAULT_RX_BUF_SIZE }>(
2143 self.node_id,
2144 topic,
2145 type_name,
2146 type_hash,
2147 qos,
2148 callback,
2149 )
2150 }
2151
2152 #[cfg(feature = "safety-e2e")]
2159 pub fn create_generic_subscription_with_integrity<F>(
2160 &mut self,
2161 topic: &str,
2162 type_name: &str,
2163 type_hash: &str,
2164 callback: F,
2165 ) -> Result<super::types::HandleId, NodeError>
2166 where
2167 F: FnMut(&[u8], &nros_rmw::IntegrityStatus) + 'static,
2168 {
2169 self.executor
2170 .register_subscription_buffered_raw_safety_on::<F, { crate::config::DEFAULT_RX_BUF_SIZE }>(
2171 self.node_id,
2172 topic,
2173 type_name,
2174 type_hash,
2175 QoSProfile::default(),
2176 callback,
2177 )
2178 }
2179
2180 pub fn create_subscription_viewable<B, F>(
2200 &mut self,
2201 topic: &str,
2202 callback: F,
2203 ) -> Result<super::types::HandleId, NodeError>
2204 where
2205 B: nros_core::ViewableMessage + 'static,
2206 F: for<'a> FnMut(&B::View<'a>) + 'static,
2207 {
2208 self.executor
2209 .register_subscription_buffered_borrowed_on::<B, F, { crate::config::DEFAULT_RX_BUF_SIZE }>(
2210 self.node_id,
2211 topic,
2212 QoSProfile::default().keep_last(1),
2213 callback,
2214 )
2215 }
2216
2217 pub fn service<'t>(&mut self, name: &'t str) -> CtxServiceBuilder<'_, 'e, 't, 's> {
2221 CtxServiceBuilder {
2222 ctx: self,
2223 name,
2224 qos: QoSProfile::services_default(),
2225 }
2226 }
2227
2228 pub fn create_service<Svc, F>(
2231 &mut self,
2232 name: &str,
2233 callback: F,
2234 ) -> Result<super::types::HandleId, NodeError>
2235 where
2236 Svc: RosService + 'static,
2237 Svc::Request: crate::rmw_type_registry::MessageForRmw,
2238 Svc::Reply: crate::rmw_type_registry::MessageForRmw,
2239 F: FnMut(&Svc::Request) -> Svc::Reply + 'static,
2240 {
2241 self.executor.register_service_sized_on::<
2242 Svc,
2243 F,
2244 { crate::config::DEFAULT_RX_BUF_SIZE },
2245 { crate::config::DEFAULT_RX_BUF_SIZE },
2246 >(self.node_id, name, QoSProfile::services_default(), callback)
2247 }
2248
2249 pub fn get_node_names(
2296 &mut self,
2297 visit: &mut dyn FnMut(&str, &str) -> bool,
2298 ) -> Result<(), NodeError> {
2299 self.executor
2300 .get_node_names(&mut |name, ns, _enclave| visit(name, ns))
2301 }
2302
2303 pub fn get_node_names_with_enclaves(
2311 &mut self,
2312 visit: &mut dyn FnMut(&str, &str, Option<&str>) -> bool,
2313 ) -> Result<(), NodeError> {
2314 self.executor.get_node_names(visit)
2315 }
2316
2317 pub fn get_topic_names_and_types(
2325 &mut self,
2326 visit: &mut dyn FnMut(&str, &[&str]) -> bool,
2327 ) -> Result<(), NodeError> {
2328 self.executor.get_topic_names_and_types(visit)
2329 }
2330
2331 pub fn get_service_names_and_types(
2336 &mut self,
2337 visit: &mut dyn FnMut(&str, &[&str]) -> bool,
2338 ) -> Result<(), NodeError> {
2339 self.executor.get_service_names_and_types(visit)
2340 }
2341
2342 pub fn count_publishers(&mut self, topic_name: &str) -> Result<usize, NodeError> {
2354 self.executor.count_publishers(topic_name)
2355 }
2356
2357 pub fn count_subscriptions(&mut self, topic_name: &str) -> Result<usize, NodeError> {
2371 self.executor.count_subscribers(topic_name)
2372 }
2373
2374 pub fn get_publisher_names_and_types_by_node(
2381 &mut self,
2382 node_name: &str,
2383 node_namespace: &str,
2384 visit: &mut dyn FnMut(&str, &[&str]) -> bool,
2385 ) -> Result<(), NodeError> {
2386 self.executor
2387 .get_publisher_names_and_types_by_node(node_name, node_namespace, visit)
2388 }
2389
2390 pub fn get_subscription_names_and_types_by_node(
2394 &mut self,
2395 node_name: &str,
2396 node_namespace: &str,
2397 visit: &mut dyn FnMut(&str, &[&str]) -> bool,
2398 ) -> Result<(), NodeError> {
2399 self.executor
2400 .get_subscription_names_and_types_by_node(node_name, node_namespace, visit)
2401 }
2402
2403 pub fn get_service_names_and_types_by_node(
2406 &mut self,
2407 node_name: &str,
2408 node_namespace: &str,
2409 visit: &mut dyn FnMut(&str, &[&str]) -> bool,
2410 ) -> Result<(), NodeError> {
2411 self.executor
2412 .get_service_names_and_types_by_node(node_name, node_namespace, visit)
2413 }
2414
2415 pub fn get_client_names_and_types_by_node(
2418 &mut self,
2419 node_name: &str,
2420 node_namespace: &str,
2421 visit: &mut dyn FnMut(&str, &[&str]) -> bool,
2422 ) -> Result<(), NodeError> {
2423 self.executor
2424 .get_client_names_and_types_by_node(node_name, node_namespace, visit)
2425 }
2426
2427 pub fn get_publishers_info_by_topic(
2448 &mut self,
2449 topic_name: &str,
2450 visit: &mut dyn FnMut(&nros_rmw::GraphEndpointInfo<'_>) -> bool,
2451 ) -> Result<(), NodeError> {
2452 self.executor
2453 .get_publishers_info_by_topic(topic_name, visit)
2454 }
2455
2456 pub fn get_subscriptions_info_by_topic(
2464 &mut self,
2465 topic_name: &str,
2466 visit: &mut dyn FnMut(&nros_rmw::GraphEndpointInfo<'_>) -> bool,
2467 ) -> Result<(), NodeError> {
2468 self.executor
2469 .get_subscriptions_info_by_topic(topic_name, visit)
2470 }
2471
2472 pub fn create_publisher_with_qos<M: MessageForRmw>(
2491 &mut self,
2492 topic_name: &str,
2493 qos: QoSProfile,
2494 ) -> Result<EmbeddedPublisher<M>, NodeError> {
2495 self.executor.with_node_try(self.node_id, |h| {
2496 h.create_publisher_with_qos::<M>(topic_name, qos)
2497 })
2498 }
2499
2500 pub fn create_publisher_raw(
2502 &mut self,
2503 topic_name: &str,
2504 type_name: &str,
2505 type_hash: &str,
2506 ) -> Result<crate::executor::handles::EmbeddedRawPublisher, NodeError> {
2507 self.executor.with_node_try(self.node_id, |h| {
2508 h.create_publisher_raw(topic_name, type_name, type_hash)
2509 })
2510 }
2511
2512 pub fn create_publisher_raw_with_qos(
2514 &mut self,
2515 topic_name: &str,
2516 type_name: &str,
2517 type_hash: &str,
2518 qos: QoSProfile,
2519 ) -> Result<crate::executor::handles::EmbeddedRawPublisher, NodeError> {
2520 self.executor.with_node_try(self.node_id, |h| {
2521 h.create_publisher_raw_with_qos(topic_name, type_name, type_hash, qos)
2522 })
2523 }
2524
2525 pub fn create_polling_subscription<M: MessageForRmw>(
2528 &mut self,
2529 topic_name: &str,
2530 ) -> Result<Subscription<M>, NodeError> {
2531 self.executor
2532 .with_node_try(self.node_id, |h| h.create_subscription::<M>(topic_name))
2533 }
2534
2535 pub fn create_polling_subscription_sized<M: MessageForRmw, const RX_BUF: usize>(
2538 &mut self,
2539 topic_name: &str,
2540 ) -> Result<Subscription<M, RX_BUF>, NodeError> {
2541 self.executor.with_node_try(self.node_id, |h| {
2542 h.create_subscription_sized::<M, RX_BUF>(topic_name)
2543 })
2544 }
2545
2546 pub fn create_polling_subscription_with_qos<M: MessageForRmw, const RX_BUF: usize>(
2549 &mut self,
2550 topic_name: &str,
2551 qos: QoSProfile,
2552 ) -> Result<Subscription<M, RX_BUF>, NodeError> {
2553 self.executor.with_node_try(self.node_id, |h| {
2554 h.create_subscription_with_qos::<M, RX_BUF>(topic_name, qos)
2555 })
2556 }
2557
2558 pub fn create_polling_subscription_raw(
2561 &mut self,
2562 topic_name: &str,
2563 type_name: &str,
2564 type_hash: &str,
2565 ) -> Result<crate::executor::handles::RawSubscription, NodeError> {
2566 self.executor.with_node_try(self.node_id, |h| {
2567 h.create_subscription_raw(topic_name, type_name, type_hash)
2568 })
2569 }
2570
2571 pub fn create_polling_subscription_raw_sized<const RX_BUF: usize>(
2574 &mut self,
2575 topic_name: &str,
2576 type_name: &str,
2577 type_hash: &str,
2578 ) -> Result<crate::executor::handles::RawSubscription<RX_BUF>, NodeError> {
2579 self.executor.with_node_try(self.node_id, |h| {
2580 h.create_subscription_raw_sized::<RX_BUF>(topic_name, type_name, type_hash)
2581 })
2582 }
2583
2584 pub fn create_polling_service<Svc: RosService>(
2587 &mut self,
2588 service_name: &str,
2589 ) -> Result<EmbeddedServiceServer<Svc>, NodeError>
2590 where
2591 Svc::Request: MessageForRmw,
2592 Svc::Reply: MessageForRmw,
2593 {
2594 self.executor
2595 .with_node_try(self.node_id, |h| h.create_service::<Svc>(service_name))
2596 }
2597
2598 pub fn create_polling_service_with_qos<Svc: RosService>(
2601 &mut self,
2602 service_name: &str,
2603 qos: QoSProfile,
2604 ) -> Result<EmbeddedServiceServer<Svc>, NodeError>
2605 where
2606 Svc::Request: MessageForRmw,
2607 Svc::Reply: MessageForRmw,
2608 {
2609 self.executor.with_node_try(self.node_id, |h| {
2610 h.create_service_with_qos::<Svc>(service_name, qos)
2611 })
2612 }
2613
2614 pub fn create_polling_service_sized<
2617 Svc: RosService,
2618 const REQ_BUF: usize,
2619 const REPLY_BUF: usize,
2620 >(
2621 &mut self,
2622 service_name: &str,
2623 qos: QoSProfile,
2624 ) -> Result<EmbeddedServiceServer<Svc, REQ_BUF, REPLY_BUF>, NodeError>
2625 where
2626 Svc::Request: MessageForRmw,
2627 Svc::Reply: MessageForRmw,
2628 {
2629 self.executor.with_node_try(self.node_id, |h| {
2630 h.create_service_sized::<Svc, REQ_BUF, REPLY_BUF>(service_name, qos)
2631 })
2632 }
2633
2634 pub fn create_client<Svc: RosService>(
2636 &mut self,
2637 service_name: &str,
2638 ) -> Result<EmbeddedServiceClient<Svc>, NodeError>
2639 where
2640 Svc::Request: MessageForRmw,
2641 Svc::Reply: MessageForRmw,
2642 {
2643 self.executor
2644 .with_node_try(self.node_id, |h| h.create_client::<Svc>(service_name))
2645 }
2646
2647 pub fn create_client_with_qos<Svc: RosService>(
2649 &mut self,
2650 service_name: &str,
2651 qos: QoSProfile,
2652 ) -> Result<EmbeddedServiceClient<Svc>, NodeError>
2653 where
2654 Svc::Request: MessageForRmw,
2655 Svc::Reply: MessageForRmw,
2656 {
2657 self.executor.with_node_try(self.node_id, |h| {
2658 h.create_client_with_qos::<Svc>(service_name, qos)
2659 })
2660 }
2661
2662 pub fn create_client_sized<Svc: RosService, const REQ_BUF: usize, const REPLY_BUF: usize>(
2664 &mut self,
2665 service_name: &str,
2666 qos: QoSProfile,
2667 ) -> Result<EmbeddedServiceClient<Svc, REQ_BUF, REPLY_BUF>, NodeError>
2668 where
2669 Svc::Request: MessageForRmw,
2670 Svc::Reply: MessageForRmw,
2671 {
2672 self.executor.with_node_try(self.node_id, |h| {
2673 h.create_client_sized::<Svc, REQ_BUF, REPLY_BUF>(service_name, qos)
2674 })
2675 }
2676
2677 pub fn create_polling_service_raw(
2680 &mut self,
2681 service_name: &str,
2682 type_name: &str,
2683 type_hash: &str,
2684 ) -> Result<crate::executor::handles::RawServiceServer, NodeError> {
2685 self.executor.with_node_try(self.node_id, |h| {
2686 h.create_service_raw(service_name, type_name, type_hash)
2687 })
2688 }
2689
2690 pub fn create_polling_service_raw_sized<const REQ_BUF: usize, const RESP_BUF: usize>(
2693 &mut self,
2694 service_name: &str,
2695 type_name: &str,
2696 type_hash: &str,
2697 ) -> Result<crate::executor::handles::RawServiceServer<REQ_BUF, RESP_BUF>, NodeError> {
2698 self.executor.with_node_try(self.node_id, |h| {
2699 h.create_service_raw_sized::<REQ_BUF, RESP_BUF>(service_name, type_name, type_hash)
2700 })
2701 }
2702
2703 pub fn create_client_raw(
2705 &mut self,
2706 service_name: &str,
2707 type_name: &str,
2708 type_hash: &str,
2709 ) -> Result<crate::executor::handles::RawServiceClient, NodeError> {
2710 self.executor.with_node_try(self.node_id, |h| {
2711 h.create_client_raw(service_name, type_name, type_hash)
2712 })
2713 }
2714
2715 pub fn create_client_raw_sized<const REQ_BUF: usize, const REPLY_BUF: usize>(
2717 &mut self,
2718 service_name: &str,
2719 type_name: &str,
2720 type_hash: &str,
2721 ) -> Result<crate::executor::handles::RawServiceClient<REQ_BUF, REPLY_BUF>, NodeError> {
2722 self.executor.with_node_try(self.node_id, |h| {
2723 h.create_client_raw_sized::<REQ_BUF, REPLY_BUF>(service_name, type_name, type_hash)
2724 })
2725 }
2726
2727 pub fn create_action_server_raw(
2729 &mut self,
2730 action_name: &str,
2731 type_name: &str,
2732 type_hash: &str,
2733 ) -> Result<
2734 super::action_core::ActionServerCore<
2735 { crate::config::DEFAULT_RX_BUF_SIZE },
2736 { crate::config::DEFAULT_RX_BUF_SIZE },
2737 { crate::config::DEFAULT_RX_BUF_SIZE },
2738 4,
2739 >,
2740 NodeError,
2741 > {
2742 self.executor.with_node_try(self.node_id, |h| {
2743 h.create_action_server_raw(action_name, type_name, type_hash)
2744 })
2745 }
2746
2747 pub fn create_action_server_raw_sized<
2749 const GOAL_BUF: usize,
2750 const RESULT_BUF: usize,
2751 const FEEDBACK_BUF: usize,
2752 const MAX_GOALS: usize,
2753 >(
2754 &mut self,
2755 action_name: &str,
2756 type_name: &str,
2757 type_hash: &str,
2758 ) -> Result<
2759 super::action_core::ActionServerCore<GOAL_BUF, RESULT_BUF, FEEDBACK_BUF, MAX_GOALS>,
2760 NodeError,
2761 > {
2762 self.executor.with_node_try(self.node_id, |h| {
2763 h.create_action_server_raw_sized::<GOAL_BUF, RESULT_BUF, FEEDBACK_BUF, MAX_GOALS>(
2764 action_name,
2765 type_name,
2766 type_hash,
2767 )
2768 })
2769 }
2770
2771 pub fn create_action_client_raw(
2773 &mut self,
2774 action_name: &str,
2775 type_name: &str,
2776 type_hash: &str,
2777 ) -> Result<
2778 super::action_core::ActionClientCore<
2779 { crate::config::DEFAULT_RX_BUF_SIZE },
2780 { crate::config::DEFAULT_RX_BUF_SIZE },
2781 { crate::config::DEFAULT_RX_BUF_SIZE },
2782 >,
2783 NodeError,
2784 > {
2785 self.executor.with_node_try(self.node_id, |h| {
2786 h.create_action_client_raw(action_name, type_name, type_hash)
2787 })
2788 }
2789
2790 pub fn create_action_client_raw_sized<
2792 const GOAL_BUF: usize,
2793 const RESULT_BUF: usize,
2794 const FEEDBACK_BUF: usize,
2795 >(
2796 &mut self,
2797 action_name: &str,
2798 type_name: &str,
2799 type_hash: &str,
2800 ) -> Result<super::action_core::ActionClientCore<GOAL_BUF, RESULT_BUF, FEEDBACK_BUF>, NodeError>
2801 {
2802 self.executor.with_node_try(self.node_id, |h| {
2803 h.create_action_client_raw_sized::<GOAL_BUF, RESULT_BUF, FEEDBACK_BUF>(
2804 action_name,
2805 type_name,
2806 type_hash,
2807 )
2808 })
2809 }
2810
2811 pub fn create_action_server<A: RosAction>(
2813 &mut self,
2814 action_name: &str,
2815 ) -> Result<ActionServer<A>, NodeError>
2816 where
2817 A::Goal: MessageForRmw,
2818 A::Result: MessageForRmw,
2819 A::Feedback: MessageForRmw,
2820 A::SendGoalRequest: MessageForRmw,
2821 A::SendGoalResponse: MessageForRmw,
2822 A::GetResultRequest: MessageForRmw,
2823 A::GetResultResponse: MessageForRmw,
2824 A::FeedbackMessage: MessageForRmw,
2825 {
2826 self.executor
2827 .with_node_try(self.node_id, |h| h.create_action_server::<A>(action_name))
2828 }
2829
2830 pub fn create_action_server_sized<
2832 A: RosAction,
2833 const GOAL_BUF: usize,
2834 const RESULT_BUF: usize,
2835 const FEEDBACK_BUF: usize,
2836 const MAX_GOALS: usize,
2837 >(
2838 &mut self,
2839 action_name: &str,
2840 ) -> Result<ActionServer<A, GOAL_BUF, RESULT_BUF, FEEDBACK_BUF, MAX_GOALS>, NodeError>
2841 where
2842 A::Goal: MessageForRmw,
2843 A::Result: MessageForRmw,
2844 A::Feedback: MessageForRmw,
2845 A::SendGoalRequest: MessageForRmw,
2846 A::SendGoalResponse: MessageForRmw,
2847 A::GetResultRequest: MessageForRmw,
2848 A::GetResultResponse: MessageForRmw,
2849 A::FeedbackMessage: MessageForRmw,
2850 {
2851 self.executor.with_node_try(self.node_id, |h| {
2852 h.create_action_server_sized::<A, GOAL_BUF, RESULT_BUF, FEEDBACK_BUF, MAX_GOALS>(
2853 action_name,
2854 )
2855 })
2856 }
2857
2858 pub fn create_action_client<A: RosAction>(
2860 &mut self,
2861 action_name: &str,
2862 ) -> Result<ActionClient<A>, NodeError>
2863 where
2864 A::Goal: MessageForRmw,
2865 A::Result: MessageForRmw,
2866 A::Feedback: MessageForRmw,
2867 A::SendGoalRequest: MessageForRmw,
2868 A::SendGoalResponse: MessageForRmw,
2869 A::GetResultRequest: MessageForRmw,
2870 A::GetResultResponse: MessageForRmw,
2871 A::FeedbackMessage: MessageForRmw,
2872 {
2873 self.executor
2874 .with_node_try(self.node_id, |h| h.create_action_client::<A>(action_name))
2875 }
2876
2877 pub fn create_action_client_sized<
2879 A: RosAction,
2880 const GOAL_BUF: usize,
2881 const RESULT_BUF: usize,
2882 const FEEDBACK_BUF: usize,
2883 >(
2884 &mut self,
2885 action_name: &str,
2886 ) -> Result<ActionClient<A, GOAL_BUF, RESULT_BUF, FEEDBACK_BUF>, NodeError>
2887 where
2888 A::Goal: MessageForRmw,
2889 A::Result: MessageForRmw,
2890 A::Feedback: MessageForRmw,
2891 A::SendGoalRequest: MessageForRmw,
2892 A::SendGoalResponse: MessageForRmw,
2893 A::GetResultRequest: MessageForRmw,
2894 A::GetResultResponse: MessageForRmw,
2895 A::FeedbackMessage: MessageForRmw,
2896 {
2897 self.executor.with_node_try(self.node_id, |h| {
2898 h.create_action_client_sized::<A, GOAL_BUF, RESULT_BUF, FEEDBACK_BUF>(action_name)
2899 })
2900 }
2901}
2902
2903pub struct CtxServiceBuilder<'c, 'e, 't, 's> {
2905 ctx: &'c mut Node<'e, 's>,
2906 name: &'t str,
2907 qos: QoSProfile,
2908}
2909
2910impl<'c, 'e, 't, 's> CtxServiceBuilder<'c, 'e, 't, 's> {
2911 pub fn qos(mut self, qos: QoSProfile) -> Self {
2914 self.qos = qos;
2915 self
2916 }
2917
2918 pub fn build<Svc, F>(self, callback: F) -> Result<super::types::HandleId, NodeError>
2919 where
2920 Svc: RosService + 'static,
2921 Svc::Request: crate::rmw_type_registry::MessageForRmw,
2922 Svc::Reply: crate::rmw_type_registry::MessageForRmw,
2923 F: FnMut(&Svc::Request) -> Svc::Reply + 'static,
2924 {
2925 self.ctx.executor.register_service_sized_on::<
2926 Svc,
2927 F,
2928 { crate::config::DEFAULT_RX_BUF_SIZE },
2929 { crate::config::DEFAULT_RX_BUF_SIZE },
2930 >(self.ctx.node_id, self.name, self.qos, callback)
2931 }
2932}
2933
2934pub struct CtxPublisherBuilder<'c, 'e, 't, 's> {
2936 ctx: &'c mut Node<'e, 's>,
2937 topic: &'t str,
2938 qos: QoSProfile,
2939}
2940
2941impl<'c, 'e, 't, 's> CtxPublisherBuilder<'c, 'e, 't, 's> {
2942 pub fn qos(mut self, qos: QoSProfile) -> Self {
2943 self.qos = qos;
2944 self
2945 }
2946
2947 pub fn typed<M: MessageForRmw>(self) -> CtxTypedPublisherBuilder<'c, 'e, 't, 's, M> {
2949 CtxTypedPublisherBuilder {
2950 ctx: self.ctx,
2951 topic: self.topic,
2952 qos: self.qos,
2953 _phantom: PhantomData,
2954 }
2955 }
2956
2957 pub fn generic(
2959 self,
2960 type_name: &'t str,
2961 type_hash: &'t str,
2962 ) -> CtxGenericPublisherBuilder<'c, 'e, 't, 's> {
2963 CtxGenericPublisherBuilder {
2964 ctx: self.ctx,
2965 topic: self.topic,
2966 type_name,
2967 type_hash,
2968 qos: self.qos,
2969 }
2970 }
2971}
2972
2973pub struct CtxTypedPublisherBuilder<'c, 'e, 't, 's, M> {
2975 ctx: &'c mut Node<'e, 's>,
2976 topic: &'t str,
2977 qos: QoSProfile,
2978 _phantom: PhantomData<M>,
2979}
2980
2981impl<'c, 'e, 't, 's, M: MessageForRmw> CtxTypedPublisherBuilder<'c, 'e, 't, 's, M> {
2982 pub fn qos(mut self, qos: QoSProfile) -> Self {
2983 self.qos = qos;
2984 self
2985 }
2986
2987 pub fn build(self) -> Result<EmbeddedPublisher<M>, NodeError> {
2988 self.ctx
2989 .executor
2990 .create_publisher_on::<M>(self.ctx.node_id, self.topic, self.qos)
2991 }
2992}
2993
2994pub struct CtxGenericPublisherBuilder<'c, 'e, 't, 's> {
2996 ctx: &'c mut Node<'e, 's>,
2997 topic: &'t str,
2998 type_name: &'t str,
2999 type_hash: &'t str,
3000 qos: QoSProfile,
3001}
3002
3003impl<'c, 'e, 't, 's> CtxGenericPublisherBuilder<'c, 'e, 't, 's> {
3004 pub fn qos(mut self, qos: QoSProfile) -> Self {
3005 self.qos = qos;
3006 self
3007 }
3008
3009 pub fn build(self) -> Result<crate::executor::handles::EmbeddedRawPublisher, NodeError> {
3010 self.ctx.executor.create_publisher_raw_on(
3011 self.ctx.node_id,
3012 self.topic,
3013 self.type_name,
3014 self.type_hash,
3015 self.qos,
3016 )
3017 }
3018}
3019
3020pub struct SubscriptionBuilder<'c, 'e, 't, 's> {
3022 ctx: &'c mut Node<'e, 's>,
3023 topic: &'t str,
3024 qos: QoSProfile,
3025}
3026
3027impl<'c, 'e, 't, 's> SubscriptionBuilder<'c, 'e, 't, 's> {
3028 pub fn qos(mut self, qos: QoSProfile) -> Self {
3029 self.qos = qos;
3030 self
3031 }
3032
3033 pub fn typed<M: MessageForRmw + 'static>(self) -> TypedSubscriptionBuilder<'c, 'e, 't, 's, M> {
3035 TypedSubscriptionBuilder {
3036 ctx: self.ctx,
3037 topic: self.topic,
3038 qos: self.qos,
3039 sched: None,
3040 rx_explicit: false,
3041 _phantom: PhantomData,
3042 }
3043 }
3044
3045 pub fn generic(
3047 self,
3048 type_name: &'t str,
3049 type_hash: &'t str,
3050 ) -> GenericSubscriptionBuilder<'c, 'e, 't, 's> {
3051 GenericSubscriptionBuilder {
3052 ctx: self.ctx,
3053 topic: self.topic,
3054 type_name,
3055 type_hash,
3056 qos: self.qos,
3057 sched: None,
3058 }
3059 }
3060}
3061
3062pub struct TypedSubscriptionBuilder<
3065 'c,
3066 'e,
3067 't,
3068 's,
3069 M,
3070 const RX: usize = { crate::config::DEFAULT_RX_BUF_SIZE },
3071> {
3072 ctx: &'c mut Node<'e, 's>,
3073 topic: &'t str,
3074 qos: QoSProfile,
3075 sched: Option<super::sched_context::SchedContextId>,
3076 rx_explicit: bool,
3083 _phantom: PhantomData<M>,
3084}
3085
3086impl<'c, 'e, 't, 's, M: MessageForRmw + 'static, const RX: usize>
3087 TypedSubscriptionBuilder<'c, 'e, 't, 's, M, RX>
3088{
3089 pub fn qos(mut self, qos: QoSProfile) -> Self {
3090 self.qos = qos;
3091 self
3092 }
3093
3094 pub fn sched_context(mut self, sc: super::sched_context::SchedContextId) -> Self {
3096 self.sched = Some(sc);
3097 self
3098 }
3099
3100 pub fn rx_buffer<const N: usize>(self) -> TypedSubscriptionBuilder<'c, 'e, 't, 's, M, N> {
3113 TypedSubscriptionBuilder {
3114 ctx: self.ctx,
3115 topic: self.topic,
3116 qos: self.qos,
3117 sched: self.sched,
3118 rx_explicit: true,
3119 _phantom: PhantomData,
3120 }
3121 }
3122
3123 pub fn rx_buffer_from_type(self) -> TypedSubBoundBuilder<'c, 'e, 't, 's, M, RX>
3182 where
3183 M: nros_serdes::schema::Message,
3184 {
3185 const {
3189 assert!(
3190 crate::rmw_type_registry::subscription_rx_bytes::<M>(RX).is_some(),
3191 "this message type has NO maximum serialized size, so its receive \
3192 buffer cannot be sized from it. Every message type must carry a \
3193 bound: give the unbounded member one in the `.msg` \
3194 (`string<=64`, `int32[<=8]`) or a `cap` in `nros-codegen.toml`. \
3195 The generated C header for this type names the member that costs \
3196 it the bound (`NROS_UNBOUNDED__<type>__field_<member>`)."
3197 )
3198 }
3199 TypedSubBoundBuilder {
3200 ctx: self.ctx,
3201 topic: self.topic,
3202 qos: self.qos,
3203 sched: self.sched,
3204 _phantom: PhantomData,
3205 }
3206 }
3207
3208 pub fn message_info(self) -> TypedSubInfoBuilder<'c, 'e, 't, 's, M, RX> {
3213 TypedSubInfoBuilder {
3214 ctx: self.ctx,
3215 topic: self.topic,
3216 qos: self.qos,
3217 sched: self.sched,
3218 rx_explicit: self.rx_explicit,
3219 _phantom: PhantomData,
3220 }
3221 }
3222
3223 #[cfg(feature = "safety-e2e")]
3226 pub fn safety(self) -> TypedSubSafetyBuilder<'c, 'e, 't, 's, M, RX> {
3227 TypedSubSafetyBuilder {
3228 ctx: self.ctx,
3229 topic: self.topic,
3230 qos: self.qos,
3231 sched: self.sched,
3232 rx_explicit: self.rx_explicit,
3233 _phantom: PhantomData,
3234 }
3235 }
3236
3237 pub fn build<F: FnMut(&M) + 'static>(
3238 self,
3239 callback: F,
3240 ) -> Result<super::types::HandleId, NodeError> {
3241 let handle = self
3242 .ctx
3243 .executor
3244 .register_subscription_buffered_on::<M, F, RX>(
3245 self.ctx.node_id,
3246 self.topic,
3247 self.qos,
3248 callback,
3249 None, if self.rx_explicit { Some(RX) } else { None },
3254 )?;
3255 if let Some(sc) = self.sched {
3256 self.ctx.executor.bind_handle_to_sched_context(handle, sc)?;
3257 }
3258 Ok(handle)
3259 }
3260}
3261
3262pub struct TypedSubBoundBuilder<
3270 'c,
3271 'e,
3272 't,
3273 's,
3274 M,
3275 const RX: usize = { crate::config::DEFAULT_RX_BUF_SIZE },
3276> {
3277 ctx: &'c mut Node<'e, 's>,
3278 topic: &'t str,
3279 qos: QoSProfile,
3280 sched: Option<super::sched_context::SchedContextId>,
3281 _phantom: PhantomData<M>,
3282}
3283
3284impl<'c, 'e, 't, 's, M: MessageForRmw + nros_serdes::schema::Message + 'static, const RX: usize>
3285 TypedSubBoundBuilder<'c, 'e, 't, 's, M, RX>
3286{
3287 pub fn qos(mut self, qos: QoSProfile) -> Self {
3288 self.qos = qos;
3289 self
3290 }
3291
3292 pub fn sched_context(mut self, sc: super::sched_context::SchedContextId) -> Self {
3294 self.sched = Some(sc);
3295 self
3296 }
3297
3298 pub fn build<F: FnMut(&M) + 'static>(
3299 self,
3300 callback: F,
3301 ) -> Result<super::types::HandleId, NodeError> {
3302 let rx_bytes = crate::rmw_type_registry::subscription_rx_bytes::<M>(RX)
3305 .expect("rx_buffer_from_type asserts the bound exists at build time");
3306 let handle = self
3307 .ctx
3308 .executor
3309 .register_subscription_buffered_on::<M, F, RX>(
3310 self.ctx.node_id,
3311 self.topic,
3312 self.qos,
3313 callback,
3314 None,
3315 Some(rx_bytes),
3316 )?;
3317 if let Some(sc) = self.sched {
3318 self.ctx.executor.bind_handle_to_sched_context(handle, sc)?;
3319 }
3320 Ok(handle)
3321 }
3322}
3323
3324pub struct TypedSubInfoBuilder<
3327 'c,
3328 'e,
3329 't,
3330 's,
3331 M,
3332 const RX: usize = { crate::config::DEFAULT_RX_BUF_SIZE },
3333> {
3334 ctx: &'c mut Node<'e, 's>,
3335 topic: &'t str,
3336 qos: QoSProfile,
3337 sched: Option<super::sched_context::SchedContextId>,
3338 rx_explicit: bool,
3341 _phantom: PhantomData<M>,
3342}
3343
3344impl<'c, 'e, 't, 's, M: MessageForRmw + 'static, const RX: usize>
3345 TypedSubInfoBuilder<'c, 'e, 't, 's, M, RX>
3346{
3347 pub fn qos(mut self, qos: QoSProfile) -> Self {
3348 self.qos = qos;
3349 self
3350 }
3351
3352 pub fn sched_context(mut self, sc: super::sched_context::SchedContextId) -> Self {
3353 self.sched = Some(sc);
3354 self
3355 }
3356
3357 pub fn rx_buffer<const N: usize>(self) -> TypedSubInfoBuilder<'c, 'e, 't, 's, M, N> {
3358 TypedSubInfoBuilder {
3359 ctx: self.ctx,
3360 topic: self.topic,
3361 qos: self.qos,
3362 sched: self.sched,
3363 rx_explicit: true,
3364 _phantom: PhantomData,
3365 }
3366 }
3367
3368 pub fn build<F: FnMut(&M, Option<&nros_core::MessageInfo>) + 'static>(
3369 self,
3370 callback: F,
3371 ) -> Result<super::types::HandleId, NodeError> {
3372 let handle = self
3373 .ctx
3374 .executor
3375 .register_subscription_with_info_sized_inner::<M, F, RX>(
3376 Some(self.ctx.node_id),
3377 self.topic,
3378 self.qos,
3379 callback,
3380 if self.rx_explicit { Some(RX) } else { None },
3381 )?;
3382 if let Some(sc) = self.sched {
3383 self.ctx.executor.bind_handle_to_sched_context(handle, sc)?;
3384 }
3385 Ok(handle)
3386 }
3387}
3388
3389#[cfg(feature = "safety-e2e")]
3392pub struct TypedSubSafetyBuilder<
3393 'c,
3394 'e,
3395 't,
3396 's,
3397 M,
3398 const RX: usize = { crate::config::DEFAULT_RX_BUF_SIZE },
3399> {
3400 ctx: &'c mut Node<'e, 's>,
3401 topic: &'t str,
3402 qos: QoSProfile,
3403 sched: Option<super::sched_context::SchedContextId>,
3404 rx_explicit: bool,
3407 _phantom: PhantomData<M>,
3408}
3409
3410#[cfg(feature = "safety-e2e")]
3411impl<'c, 'e, 't, 's, M: MessageForRmw + 'static, const RX: usize>
3412 TypedSubSafetyBuilder<'c, 'e, 't, 's, M, RX>
3413{
3414 pub fn qos(mut self, qos: QoSProfile) -> Self {
3415 self.qos = qos;
3416 self
3417 }
3418
3419 pub fn sched_context(mut self, sc: super::sched_context::SchedContextId) -> Self {
3420 self.sched = Some(sc);
3421 self
3422 }
3423
3424 pub fn rx_buffer<const N: usize>(self) -> TypedSubSafetyBuilder<'c, 'e, 't, 's, M, N> {
3425 TypedSubSafetyBuilder {
3426 ctx: self.ctx,
3427 topic: self.topic,
3428 qos: self.qos,
3429 sched: self.sched,
3430 rx_explicit: true,
3431 _phantom: PhantomData,
3432 }
3433 }
3434
3435 pub fn build<F: FnMut(&M, &nros_rmw::IntegrityStatus) + 'static>(
3436 self,
3437 callback: F,
3438 ) -> Result<super::types::HandleId, NodeError> {
3439 let handle = self
3440 .ctx
3441 .executor
3442 .register_subscription_with_safety_sized_inner::<M, F, RX>(
3443 Some(self.ctx.node_id),
3444 self.topic,
3445 self.qos,
3446 callback,
3447 if self.rx_explicit { Some(RX) } else { None },
3448 )?;
3449 if let Some(sc) = self.sched {
3450 self.ctx.executor.bind_handle_to_sched_context(handle, sc)?;
3451 }
3452 Ok(handle)
3453 }
3454}
3455
3456pub struct GenericSubscriptionBuilder<
3458 'c,
3459 'e,
3460 't,
3461 's,
3462 const RX: usize = { crate::config::DEFAULT_RX_BUF_SIZE },
3463> {
3464 ctx: &'c mut Node<'e, 's>,
3465 topic: &'t str,
3466 type_name: &'t str,
3467 type_hash: &'t str,
3468 qos: QoSProfile,
3469 sched: Option<super::sched_context::SchedContextId>,
3470}
3471
3472impl<'c, 'e, 't, 's, const RX: usize> GenericSubscriptionBuilder<'c, 'e, 't, 's, RX> {
3473 pub fn qos(mut self, qos: QoSProfile) -> Self {
3474 self.qos = qos;
3475 self
3476 }
3477
3478 pub fn sched_context(mut self, sc: super::sched_context::SchedContextId) -> Self {
3479 self.sched = Some(sc);
3480 self
3481 }
3482
3483 pub fn rx_buffer<const N: usize>(self) -> GenericSubscriptionBuilder<'c, 'e, 't, 's, N> {
3484 GenericSubscriptionBuilder {
3485 ctx: self.ctx,
3486 topic: self.topic,
3487 type_name: self.type_name,
3488 type_hash: self.type_hash,
3489 qos: self.qos,
3490 sched: self.sched,
3491 }
3492 }
3493
3494 pub fn message_info(self) -> GenericSubInfoBuilder<'c, 'e, 't, 's, RX> {
3498 GenericSubInfoBuilder {
3499 ctx: self.ctx,
3500 topic: self.topic,
3501 type_name: self.type_name,
3502 type_hash: self.type_hash,
3503 qos: self.qos,
3504 sched: self.sched,
3505 }
3506 }
3507
3508 pub fn build<F: FnMut(&[u8]) + 'static>(
3509 self,
3510 callback: F,
3511 ) -> Result<super::types::HandleId, NodeError> {
3512 let handle = self
3513 .ctx
3514 .executor
3515 .register_subscription_buffered_raw_on::<F, RX>(
3516 self.ctx.node_id,
3517 self.topic,
3518 self.type_name,
3519 self.type_hash,
3520 self.qos,
3521 callback,
3522 )?;
3523 if let Some(sc) = self.sched {
3524 self.ctx.executor.bind_handle_to_sched_context(handle, sc)?;
3525 }
3526 Ok(handle)
3527 }
3528}
3529
3530pub struct GenericSubInfoBuilder<
3533 'c,
3534 'e,
3535 't,
3536 's,
3537 const RX: usize = { crate::config::DEFAULT_RX_BUF_SIZE },
3538> {
3539 ctx: &'c mut Node<'e, 's>,
3540 topic: &'t str,
3541 type_name: &'t str,
3542 type_hash: &'t str,
3543 qos: QoSProfile,
3544 sched: Option<super::sched_context::SchedContextId>,
3545}
3546
3547impl<'c, 'e, 't, 's, const RX: usize> GenericSubInfoBuilder<'c, 'e, 't, 's, RX> {
3548 pub fn qos(mut self, qos: QoSProfile) -> Self {
3549 self.qos = qos;
3550 self
3551 }
3552
3553 pub fn sched_context(mut self, sc: super::sched_context::SchedContextId) -> Self {
3554 self.sched = Some(sc);
3555 self
3556 }
3557
3558 pub fn rx_buffer<const N: usize>(self) -> GenericSubInfoBuilder<'c, 'e, 't, 's, N> {
3559 GenericSubInfoBuilder {
3560 ctx: self.ctx,
3561 topic: self.topic,
3562 type_name: self.type_name,
3563 type_hash: self.type_hash,
3564 qos: self.qos,
3565 sched: self.sched,
3566 }
3567 }
3568
3569 pub fn build<F: FnMut(&[u8], &nros_core::RawMessageInfo) + 'static>(
3570 self,
3571 callback: F,
3572 ) -> Result<super::types::HandleId, NodeError> {
3573 let handle = self
3574 .ctx
3575 .executor
3576 .register_subscription_buffered_raw_info_on::<F, RX>(
3577 self.ctx.node_id,
3578 self.topic,
3579 self.type_name,
3580 self.type_hash,
3581 self.qos,
3582 callback,
3583 )?;
3584 if let Some(sc) = self.sched {
3585 self.ctx.executor.bind_handle_to_sched_context(handle, sc)?;
3586 }
3587 Ok(handle)
3588 }
3589}
3590
3591#[cfg(all(test, feature = "std", not(feature = "rmw-cffi")))]
3597mod builder_tests {
3598 use super::*;
3599 use crate::{executor::Executor, mock::MockSession};
3600 use nros_core::{CdrReader, CdrWriter, DeserError, Deserialize, SerError, Serialize};
3601
3602 struct TestMsg;
3603 impl RosMessage for TestMsg {
3604 const TYPE_NAME: &'static str = "test/msg/TestMsg";
3605 const TYPE_HASH: &'static str = "test_hash";
3606 }
3607 impl Serialize for TestMsg {
3608 fn serialize(&self, _w: &mut CdrWriter) -> Result<(), SerError> {
3609 Ok(())
3610 }
3611 }
3612 impl Deserialize for TestMsg {
3613 fn deserialize(_r: &mut CdrReader) -> Result<Self, DeserError> {
3614 Ok(Self)
3615 }
3616 }
3617 impl nros_serdes::schema::Message for TestMsg {
3626 const TYPE_NAME: &'static str = "test/msg/TestMsg";
3627 const FIELDS: &'static [nros_serdes::schema::Field] = &[nros_serdes::schema::Field {
3628 name: "data",
3629 ty: nros_serdes::schema::FieldType::Uint8,
3630 offset: 0,
3631 }];
3632 }
3633
3634 fn s(v: &str) -> heapless::String<64> {
3635 heapless::String::try_from(v).unwrap()
3636 }
3637
3638 #[test]
3639 fn publisher_builder_typed_and_generic() {
3640 let mut session = MockSession::new();
3641 let mut node = NodeHandle::new(s("n"), s("/"), &mut session, &[], 0);
3642
3643 let _typed = node
3645 .publisher("/chatter")
3646 .typed::<TestMsg>()
3647 .qos(QoSProfile::default().keep_last(5))
3648 .build()
3649 .expect("typed publisher builds");
3650
3651 let _generic = node
3653 .publisher("/chatter")
3654 .qos(QoSProfile::default())
3655 .generic("std_msgs/msg/Int32", "hash")
3656 .build()
3657 .expect("generic publisher builds");
3658 }
3659
3660 fn argv_rule(from: &'static str, to: &'static str) -> crate::ros_args::RemapArg<'static> {
3665 crate::ros_args::RemapArg {
3666 node: None,
3667 from,
3668 to,
3669 }
3670 }
3671
3672 #[test]
3673 fn create_node_handle_applies_an_argv_remap() {
3674 let mut exec: Executor = Executor::from_session(MockSession::new());
3675 crate::ros_args::install_argv_remaps(&mut exec, [argv_rule("chatter", "/from_argv")])
3676 .unwrap();
3677 let mut node = exec.create_node("talker").expect("node");
3678 let p = node
3679 .create_publisher::<TestMsg>("chatter")
3680 .expect("publisher");
3681 assert_eq!(p.handle.topic_name(), "/from_argv");
3682 }
3683
3684 #[test]
3685 fn create_node_handle_prefers_a_launch_rule_over_argv() {
3686 let mut exec: Executor = Executor::from_session(MockSession::new());
3687 crate::ros_args::install_argv_remaps(&mut exec, [argv_rule("chatter", "/from_argv")])
3688 .unwrap();
3689 exec.declare_remap("talker", "/", "chatter", "/from_launch")
3690 .unwrap();
3691 let mut node = exec.create_node("talker").expect("node");
3692 let p = node
3693 .create_publisher::<TestMsg>("chatter")
3694 .expect("publisher");
3695 assert_eq!(p.handle.topic_name(), "/from_launch");
3696 }
3697
3698 #[test]
3699 fn create_node_handle_passes_an_unmatched_name_through_unchanged() {
3700 let mut exec: Executor = Executor::from_session(MockSession::new());
3704 crate::ros_args::install_argv_remaps(&mut exec, [argv_rule("other", "/x")]).unwrap();
3705 let mut node = exec.create_node("talker").expect("node");
3706 let p = node
3707 .create_publisher::<TestMsg>("chatter")
3708 .expect("publisher");
3709 assert_eq!(p.handle.topic_name(), "chatter");
3710 }
3711
3712 #[test]
3713 fn subscription_builder_and_convenient() {
3714 let mut exec: Executor = Executor::from_session(MockSession::new());
3715 let id = exec.node_builder("n").build().expect("node");
3716
3717 let _h = exec
3719 .node_mut(id)
3720 .subscription("/chatter")
3721 .typed::<TestMsg>()
3722 .qos(QoSProfile::default().keep_last(5))
3723 .build(|_m: &TestMsg| {})
3724 .expect("typed subscription builds");
3725
3726 let _g = exec
3728 .node_mut(id)
3729 .subscription("/raw")
3730 .generic("std_msgs/msg/Int32", "hash")
3731 .build(|_b: &[u8]| {})
3732 .expect("generic subscription builds");
3733
3734 let sc = exec.default_sched_context_id();
3736 let _s = exec
3737 .node_mut(id)
3738 .subscription("/sized")
3739 .typed::<TestMsg>()
3740 .rx_buffer::<64>()
3741 .sched_context(sc)
3742 .build(|_m: &TestMsg| {})
3743 .expect("sized + sched subscription builds");
3744
3745 let _c = exec
3747 .node_mut(id)
3748 .create_subscription::<TestMsg, _>("/conv", |_m: &TestMsg| {})
3749 .expect("convenient typed subscription builds");
3750 }
3751
3752 #[test]
3753 fn generic_message_info_builder() {
3754 let mut exec: Executor = Executor::from_session(MockSession::new());
3757 let id = exec.node_builder("n").build().expect("node");
3758
3759 let _i = exec
3760 .node_mut(id)
3761 .subscription("/info")
3762 .generic("std_msgs/msg/Int32", "hash")
3763 .message_info()
3764 .rx_buffer::<256>()
3765 .build(|_payload: &[u8], info: &nros_core::RawMessageInfo| {
3766 let _ = info.attachment();
3767 })
3768 .expect("generic + message_info subscription builds");
3769 }
3770
3771 #[test]
3772 fn typed_message_info_builder() {
3773 let mut exec: Executor = Executor::from_session(MockSession::new());
3776 let id = exec.node_builder("n").build().expect("node");
3777 let _h = exec
3778 .node_mut(id)
3779 .subscription("/chatter")
3780 .typed::<TestMsg>()
3781 .qos(QoSProfile::default().keep_last(5))
3782 .message_info()
3783 .build(|_m: &TestMsg, _info: Option<&nros_core::MessageInfo>| {})
3784 .expect("typed + message_info subscription builds");
3785 }
3786
3787 #[cfg(feature = "safety-e2e")]
3788 #[test]
3789 fn typed_safety_builder() {
3790 let mut exec: Executor = Executor::from_session(MockSession::new());
3792 let id = exec.node_builder("n").build().expect("node");
3793 let _h = exec
3794 .node_mut(id)
3795 .subscription("/chatter")
3796 .typed::<TestMsg>()
3797 .safety()
3798 .build(|_m: &TestMsg, _status: &nros_rmw::IntegrityStatus| {})
3799 .expect("typed + safety subscription builds");
3800 }
3801
3802 #[test]
3803 fn generator_emitted_chain_compiles() {
3804 let mut exec: Executor = Executor::from_session(MockSession::new());
3807 let id = exec.node_builder("n").build().expect("node");
3808 let _h = exec
3809 .node_mut(id)
3810 .subscription("/topic")
3811 .generic("std_msgs/msg/Int32", "hash")
3812 .qos(QoSProfile::default().keep_last(1))
3813 .rx_buffer::<1024>()
3814 .build(|_data: &[u8]| {})
3815 .expect("generator-shape subscription builds");
3816 }
3817
3818 #[test]
3819 fn nodectx_publisher_and_bridge_shape() {
3820 let mut exec: Executor = Executor::from_session(MockSession::new());
3824 let id = exec.node_builder("n").build().expect("node");
3825
3826 let _p = exec
3828 .node_mut(id)
3829 .create_publisher::<TestMsg>("/p")
3830 .expect("ctx convenient publisher");
3831 let dest_pub = exec
3832 .node_mut(id)
3833 .publisher("/fwd")
3834 .generic("std_msgs/msg/Int32", "hash")
3835 .build()
3836 .expect("ctx generic publisher builds"); let _s = exec
3840 .node_mut(id)
3841 .subscription("/src")
3842 .generic("std_msgs/msg/Int32", "hash")
3843 .message_info()
3844 .build(move |payload: &[u8], _info: &nros_core::RawMessageInfo| {
3845 let _ = dest_pub.publish_raw(payload);
3846 })
3847 .expect("bridge-shape source subscription builds");
3848 }
3849}