1use heapless::Vec;
4use nros_core::RosMessage;
5use nros_rmw::{QoSProfile, TopicInfo};
6
7use crate::{publisher::PublisherHandle, subscriber::SubscriptionHandle};
8
9#[derive(Debug, Clone)]
11pub struct NodeConfig<'a> {
12 pub name: &'a str,
14 pub namespace: &'a str,
16 pub domain_id: u32,
18}
19
20impl<'a> NodeConfig<'a> {
21 pub const fn new(name: &'a str, namespace: &'a str) -> Self {
23 Self {
24 name,
25 namespace,
26 domain_id: 0,
27 }
28 }
29
30 pub const fn with_domain(mut self, domain_id: u32) -> Self {
32 self.domain_id = domain_id;
33 self
34 }
35}
36
37impl Default for NodeConfig<'_> {
38 fn default() -> Self {
39 Self::new("nros_node", "/")
40 }
41}
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub enum NodeError {
46 MaxPublishersReached,
48 MaxSubscribersReached,
50 InvalidPublisherHandle,
52 InvalidSubscriberHandle,
54 SerializationFailed,
56 DeserializationFailed,
58 BufferTooSmall,
60 TransportError,
62 NotConnected,
64 TopicNameTooLong,
67 NameTooLong,
69 NamespaceTooLong,
71}
72
73pub const NODE_TX_BUF_LEN: usize = 1024;
77pub const NODE_RX_BUF_LEN: usize = 1024;
79
80#[derive(Debug, Clone)]
82#[allow(dead_code)] struct PublisherInfo {
84 topic_name: heapless::String<64>,
86 type_name: &'static str,
88 type_hash: &'static str,
90 qos: QoSProfile,
92 active: bool,
94}
95
96#[derive(Debug, Clone)]
98#[allow(dead_code)] struct SubscriberInfo {
100 topic_name: heapless::String<64>,
102 type_name: &'static str,
104 type_hash: &'static str,
106 qos: QoSProfile,
108 active: bool,
110}
111
112pub struct Node<const MAX_PUBS: usize = 8, const MAX_SUBS: usize = 8> {
123 name: heapless::String<64>,
125 namespace: heapless::String<64>,
127 domain_id: u32,
129 publishers: Vec<PublisherInfo, MAX_PUBS>,
131 subscribers: Vec<SubscriberInfo, MAX_SUBS>,
133 tx_buffer: [u8; NODE_TX_BUF_LEN],
135 #[allow(dead_code)] rx_buffer: [u8; NODE_RX_BUF_LEN],
138}
139
140#[derive(Debug, Clone)]
142pub struct PublisherOptions<'a> {
143 pub topic: &'a str,
145 pub qos: QoSProfile,
147}
148
149impl<'a> PublisherOptions<'a> {
150 pub fn new(topic: &'a str) -> Self {
152 Self {
153 topic,
154 qos: QoSProfile::default(),
155 }
156 }
157
158 pub fn qos(mut self, qos: QoSProfile) -> Self {
160 self.qos = qos;
161 self
162 }
163}
164
165#[derive(Debug, Clone)]
167pub struct SubscriptionOptions<'a> {
168 pub topic: &'a str,
170 pub qos: QoSProfile,
172}
173
174impl<'a> SubscriptionOptions<'a> {
175 pub fn new(topic: &'a str) -> Self {
177 Self {
178 topic,
179 qos: QoSProfile::default(),
180 }
181 }
182
183 pub fn qos(mut self, qos: QoSProfile) -> Self {
185 self.qos = qos;
186 self
187 }
188}
189
190impl<const MAX_PUBS: usize, const MAX_SUBS: usize> Node<MAX_PUBS, MAX_SUBS> {
191 pub fn new(config: NodeConfig) -> Result<Self, NodeError> {
193 let mut name = heapless::String::new();
196 name.push_str(config.name)
197 .map_err(|_| NodeError::NameTooLong)?;
198
199 let mut namespace = heapless::String::new();
200 namespace
201 .push_str(config.namespace)
202 .map_err(|_| NodeError::NamespaceTooLong)?;
203
204 Ok(Self {
205 name,
206 namespace,
207 domain_id: config.domain_id,
208 publishers: Vec::new(),
209 subscribers: Vec::new(),
210 tx_buffer: [0u8; NODE_TX_BUF_LEN],
211 rx_buffer: [0u8; NODE_RX_BUF_LEN],
212 })
213 }
214
215 pub fn name(&self) -> &str {
217 &self.name
218 }
219
220 pub fn namespace(&self) -> &str {
222 &self.namespace
223 }
224
225 pub fn domain_id(&self) -> u32 {
227 self.domain_id
228 }
229
230 pub fn fully_qualified_name(&self) -> Result<heapless::String<128>, NodeError> {
236 crate::names::fully_qualified_name(&self.name, &self.namespace)
245 .map_err(|()| NodeError::NameTooLong)
246 }
247
248 pub fn create_publisher<M: RosMessage>(
250 &mut self,
251 options: PublisherOptions,
252 ) -> Result<PublisherHandle<M>, NodeError> {
253 if self.publishers.len() >= MAX_PUBS {
254 return Err(NodeError::MaxPublishersReached);
255 }
256
257 let mut topic_name = heapless::String::new();
258 topic_name
261 .push_str(options.topic)
262 .map_err(|_| NodeError::TopicNameTooLong)?;
263
264 let info = PublisherInfo {
265 topic_name,
266 type_name: M::TYPE_NAME,
267 type_hash: M::TYPE_HASH,
268 qos: options.qos,
269 active: true,
270 };
271
272 let index = self.publishers.len();
273 self.publishers
274 .push(info)
275 .map_err(|_| NodeError::MaxPublishersReached)?;
276
277 Ok(PublisherHandle::new(index))
278 }
279
280 pub fn create_subscription<M: RosMessage>(
282 &mut self,
283 options: SubscriptionOptions,
284 ) -> Result<SubscriptionHandle<M>, NodeError> {
285 if self.subscribers.len() >= MAX_SUBS {
286 return Err(NodeError::MaxSubscribersReached);
287 }
288
289 let mut topic_name = heapless::String::new();
290 topic_name
292 .push_str(options.topic)
293 .map_err(|_| NodeError::TopicNameTooLong)?;
294
295 let info = SubscriberInfo {
296 topic_name,
297 type_name: M::TYPE_NAME,
298 type_hash: M::TYPE_HASH,
299 qos: options.qos,
300 active: true,
301 };
302
303 let index = self.subscribers.len();
304 self.subscribers
305 .push(info)
306 .map_err(|_| NodeError::MaxSubscribersReached)?;
307
308 Ok(SubscriptionHandle::new(index))
309 }
310
311 pub fn publisher_topic_info(&self, handle: PublisherHandle<()>) -> Option<TopicInfo<'_>> {
313 self.publishers.get(handle.index()).map(|info| {
314 TopicInfo::new(&info.topic_name, info.type_name, info.type_hash)
315 .with_domain(self.domain_id)
316 })
317 }
318
319 pub fn subscription_topic_info(&self, handle: SubscriptionHandle<()>) -> Option<TopicInfo<'_>> {
321 self.subscribers.get(handle.index()).map(|info| {
322 TopicInfo::new(&info.topic_name, info.type_name, info.type_hash)
323 .with_domain(self.domain_id)
324 })
325 }
326
327 pub fn serialize_message<M: RosMessage>(
332 &mut self,
333 _handle: &PublisherHandle<M>,
334 msg: &M,
335 ) -> Result<&[u8], NodeError> {
336 let mut writer =
337 crate::tx_writer(&mut self.tx_buffer).map_err(|_| NodeError::BufferTooSmall)?;
338 msg.serialize(&mut writer)
339 .map_err(|_| NodeError::SerializationFailed)?;
340 let len = writer.position();
341
342 Ok(&self.tx_buffer[..len])
343 }
344
345 pub fn deserialize_message<M: RosMessage>(
349 &self,
350 _handle: &SubscriptionHandle<M>,
351 data: &[u8],
352 ) -> Result<M, NodeError> {
353 use nros_core::CdrReader;
354
355 let mut reader =
356 CdrReader::new_with_header(data).map_err(|_| NodeError::DeserializationFailed)?;
357 M::deserialize(&mut reader).map_err(|_| NodeError::DeserializationFailed)
358 }
359
360 pub fn publisher_count(&self) -> usize {
362 self.publishers.iter().filter(|p| p.active).count()
363 }
364
365 pub fn subscription_count(&self) -> usize {
367 self.subscribers.iter().filter(|s| s.active).count()
368 }
369}
370
371impl<const MAX_PUBS: usize, const MAX_SUBS: usize> Default for Node<MAX_PUBS, MAX_SUBS> {
372 fn default() -> Self {
373 Self::new(NodeConfig::default())
376 .expect("default NodeConfig fits the bounded name/namespace")
377 }
378}
379
380#[cfg(test)]
381mod tests {
382 use super::*;
383
384 #[derive(Debug, Clone, Default)]
386 struct TestMessage {
387 data: i32,
388 }
389
390 impl RosMessage for TestMessage {
391 const TYPE_NAME: &'static str = "test_msgs::msg::TestMessage";
392 const TYPE_HASH: &'static str = "abc123";
393 }
394
395 impl nros_core::Serialize for TestMessage {
396 fn serialize(&self, writer: &mut nros_core::CdrWriter) -> Result<(), nros_core::SerError> {
397 self.data.serialize(writer)
398 }
399 }
400
401 impl nros_core::Deserialize for TestMessage {
402 fn deserialize(reader: &mut nros_core::CdrReader) -> Result<Self, nros_core::DeserError> {
403 Ok(Self {
404 data: i32::deserialize(reader)?,
405 })
406 }
407 }
408
409 #[test]
410 fn test_node_creation() {
411 let config = NodeConfig::new("test_node", "/test");
412 let node = Node::<4, 4>::new(config).unwrap();
413
414 assert_eq!(node.name(), "test_node");
415 assert_eq!(node.namespace(), "/test");
416 assert_eq!(node.domain_id(), 0);
417 }
418
419 #[test]
420 fn test_fully_qualified_name() {
421 let config = NodeConfig::new("my_node", "/my_ns");
422 let node = Node::<4, 4>::new(config).unwrap();
423
424 assert_eq!(
425 node.fully_qualified_name().unwrap().as_str(),
426 "/my_ns/my_node"
427 );
428 }
429
430 #[test]
434 fn fully_qualified_name_collapses_root_and_absolutises() {
435 for (ns, want) in [
436 ("/", "/my_node"),
438 ("/my_ns", "/my_ns/my_node"),
439 ("my_ns", "/my_ns/my_node"),
442 ] {
443 let node = Node::<4, 4>::new(NodeConfig::new("my_node", ns)).unwrap();
444 assert_eq!(
445 node.fully_qualified_name().unwrap().as_str(),
446 want,
447 "namespace {ns:?}"
448 );
449 }
450 }
451
452 #[test]
453 fn test_create_publisher() {
454 let mut node = Node::<4, 4>::default();
455 let handle = node.create_publisher::<TestMessage>(PublisherOptions::new("/test_topic"));
456
457 assert!(handle.is_ok());
458 assert_eq!(node.publisher_count(), 1);
459 }
460
461 #[test]
462 fn test_create_subscriber() {
463 let mut node = Node::<4, 4>::default();
464 let handle =
465 node.create_subscription::<TestMessage>(SubscriptionOptions::new("/test_topic"));
466
467 assert!(handle.is_ok());
468 assert_eq!(node.subscription_count(), 1);
469 }
470
471 #[test]
472 fn test_max_publishers() {
473 let mut node = Node::<2, 2>::default();
474
475 let _ = node.create_publisher::<TestMessage>(PublisherOptions::new("/topic1"));
476 let _ = node.create_publisher::<TestMessage>(PublisherOptions::new("/topic2"));
477 let result = node.create_publisher::<TestMessage>(PublisherOptions::new("/topic3"));
478
479 assert_eq!(result, Err(NodeError::MaxPublishersReached));
480 }
481
482 #[test]
483 fn test_serialize_deserialize() {
484 let mut node = Node::<4, 4>::default();
485 let pub_handle = node
486 .create_publisher::<TestMessage>(PublisherOptions::new("/test"))
487 .unwrap();
488 let sub_handle = node
489 .create_subscription::<TestMessage>(SubscriptionOptions::new("/test"))
490 .unwrap();
491
492 let msg = TestMessage { data: 42 };
493
494 let mut buf = [0u8; 128];
496 let bytes = node.serialize_message(&pub_handle, &msg).unwrap();
497 let len = bytes.len();
498 buf[..len].copy_from_slice(bytes);
499
500 let received: TestMessage = node.deserialize_message(&sub_handle, &buf[..len]).unwrap();
501 assert_eq!(received.data, 42);
502 }
503}