1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
//! Implementation of ROS 2 [Services](https://docs.ros.org/en/rolling/Tutorials/Beginner-CLI-Tools/Understanding-ROS2-Services/Understanding-ROS2-Services.html)
use std::marker::PhantomData;

#[allow(unused_imports)]
use log::{debug, error, info, warn};

use crate::message::Message;

pub mod client;
pub mod request_id;
pub mod server;
pub(super) mod wrappers;

pub use request_id::*;
use wrappers::*;
pub use server::*;
pub use client::*;

// --------------------------------------------
// --------------------------------------------

/// Service trait pairs the Request and Response types together.
/// Additionally, it ensures that Response and Request are Messages
/// (Serializable), and we have a means to name the types.
pub trait Service {
  type Request: Message;
  type Response: Message;
  fn request_type_name(&self) -> &str;
  fn response_type_name(&self) -> &str;
}

// --------------------------------------------
// --------------------------------------------

/// AService is a means of constructing a descriptor for a Service on the fly.
/// This allows generic code to construct a Service from the types of
/// request and response.
pub struct AService<Q, S>
where
  Q: Message,
  S: Message,
{
  q: PhantomData<Q>,
  s: PhantomData<S>,
  req_type_name: String,
  resp_type_name: String,
}

impl<Q, S> AService<Q, S>
where
  Q: Message,
  S: Message,
{
  pub fn new(req_type_name: String, resp_type_name: String) -> Self {
    Self {
      req_type_name,
      resp_type_name,
      q: PhantomData,
      s: PhantomData,
    }
  }
}

impl<Q, S> Service for AService<Q, S>
where
  Q: Message,
  S: Message,
{
  type Request = Q;
  type Response = S;

  fn request_type_name(&self) -> &str {
    &self.req_type_name
  }

  fn response_type_name(&self) -> &str {
    &self.resp_type_name
  }
}

// --------------------------------------------
// --------------------------------------------

/// Selects how Service Requests and Responses are to be mapped to DDS.
///
/// There are different and incompatible ways to map Services onto DDS Topics.
/// In order to interoperate with ROS 2, you have to select the same mapping it
/// uses. The mapping used by ROS2 depends on the DDS implementation used and
/// its configuration.
///
/// For details, see OMG Specification
/// [RPC over DDS](https://www.omg.org/spec/DDS-RPC/1.0/About-DDS-RPC/)
/// Section "7.2.4 Basic and Enhanced Service Mapping for RPC over DDS",
/// which defines Service Mappings "Basic" and "Enhanced".
///
/// ServiceMapping::Cyclone represents a third mapping used by RMW for
/// CycloneDDS.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ServiceMapping {
  /// "Basic" service mapping from RPC over DDS specification.
  /// * RTI Connext with `RMW_CONNEXT_REQUEST_REPLY_MAPPING=basic`, but this is
  ///   not tested, so may not work.
  Basic,

  /// "Enhanced" service mapping from RPC over DDS specification.
  /// * ROS2 Foxy with eProsima DDS,
  /// * ROS2 Galactic with RTI Connext (rmw_connextdds, not rmw_connext_cpp) -
  ///   set environment variable `RMW_CONNEXT_REQUEST_REPLY_MAPPING=extended`
  ///   before running ROS2 executable.
  Enhanced,

  /// CycloneDDS-specific service mapping.
  /// Specification for this mapping is unknown, technical details are
  /// reverse-engineered from ROS2 sources.
  /// * ROS2 Galactic with CycloneDDS - Seems to work on the same host only, not
  ///   over actual network.
  Cyclone,
}