Skip to content

Commit 1055020

Browse files
committed
Send and receive service bridge requests (standard mode)
1 parent d8f2c9e commit 1055020

8 files changed

Lines changed: 190 additions & 58 deletions

File tree

src/agnocastlib/include/agnocast/agnocast_client.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
#include "agnocast/agnocast_smart_pointer.hpp"
66
#include "agnocast/agnocast_subscription.hpp"
77
#include "agnocast/agnocast_utils.hpp"
8-
#include "agnocast/bridge/agnocast_bridge_node.hpp"
98
#include "rclcpp/node_interfaces/node_base_interface.hpp"
109
#include "rclcpp/rclcpp.hpp"
1110

@@ -24,6 +23,8 @@
2423
namespace agnocast
2524
{
2625

26+
struct NoBridgeRequestPolicy;
27+
2728
bool service_is_ready_core(const std::string & service_name);
2829
bool wait_for_service_nanoseconds(
2930
const rclcpp::Context::SharedPtr & context, const std::string & service_name,

src/agnocastlib/include/agnocast/agnocast_mq.hpp

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,27 +40,37 @@ struct BridgeFactoryInfo
4040
uintptr_t fn_offset_reverse;
4141
};
4242

43-
struct BridgeTargetInfo
43+
struct PubsubBridgeTargetInfo
4444
{
4545
char topic_name[TOPIC_NAME_BUFFER_SIZE];
4646
topic_local_id_t target_id;
4747
};
4848

49+
struct ServiceBridgeTargetInfo
50+
{
51+
char name[SERVICE_NAME_BUFFER_SIZE];
52+
rmw_qos_profile_t qos;
53+
};
54+
4955
struct MqMsgBridge
5056
{
5157
BridgeFactoryInfo factory;
52-
BridgeTargetInfo target;
58+
union {
59+
PubsubBridgeTargetInfo pubsub_target;
60+
ServiceBridgeTargetInfo srv_target;
61+
};
5362
BridgeDirection direction;
63+
bool is_service;
5464
};
5565

56-
struct PubsubBridgeTargetInfo
66+
struct PubsubBridgeTargetInfoWithType
5767
{
5868
char message_type[MESSAGE_TYPE_BUFFER_SIZE];
5969
char topic_name[TOPIC_NAME_BUFFER_SIZE];
6070
topic_local_id_t target_id;
6171
};
6272

63-
struct ServiceBridgeTargetInfo
73+
struct ServiceBridgeTargetInfoWithType
6474
{
6575
char type[SERVICE_TYPE_BUFFER_SIZE];
6676
char name[SERVICE_NAME_BUFFER_SIZE];
@@ -70,8 +80,8 @@ struct ServiceBridgeTargetInfo
7080
struct MqMsgPerformanceBridge
7181
{
7282
union {
73-
PubsubBridgeTargetInfo pubsub_target;
74-
ServiceBridgeTargetInfo srv_target;
83+
PubsubBridgeTargetInfoWithType pubsub_target;
84+
ServiceBridgeTargetInfoWithType srv_target;
7585
};
7686
BridgeDirection direction;
7787
bool is_service;

src/agnocastlib/include/agnocast/bridge/agnocast_bridge_node.hpp

Lines changed: 94 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#pragma once
22

3+
#include "agnocast/agnocast_client.hpp"
34
#include "agnocast/agnocast_mq.hpp"
45
#include "agnocast/agnocast_publisher.hpp"
56
#include "agnocast/agnocast_subscription.hpp"
@@ -35,6 +36,9 @@ template <typename MessageT>
3536
void send_performance_bridge_request(
3637
const std::string & topic_name, topic_local_id_t id, BridgeDirection direction);
3738
template <typename ServiceT>
39+
void send_service_bridge_request(
40+
const std::string & service_name, const rclcpp::QoS & qos, BridgeDirection direction);
41+
template <typename ServiceT>
3842
void send_performance_service_bridge_request(
3943
const std::string & service_name, const rclcpp::QoS & qos, BridgeDirection direction);
4044

@@ -56,7 +60,7 @@ void request_service_bridge_core(
5660
{
5761
auto bridge_mode = get_bridge_mode();
5862
if (bridge_mode == BridgeMode::Standard) {
59-
throw std::runtime_error("Service bridges are not yet implemented for standard mode");
63+
send_service_bridge_request<ServiceT>(service_name, qos, direction);
6064
} else {
6165
send_performance_service_bridge_request<ServiceT>(service_name, qos, direction);
6266
}
@@ -207,22 +211,74 @@ class AgnocastToRosBridge : public BridgeBase
207211
rclcpp::CallbackGroup::SharedPtr get_callback_group() const override { return agno_cb_group_; }
208212
};
209213

214+
template <typename ServiceT>
215+
class RosToAgnocastServiceBridge : public ServiceBridgeBase
216+
{
217+
typename rclcpp::Service<ServiceT>::SharedPtr ros_srv_;
218+
typename agnocast::Client<ServiceT>::SharedPtr agno_client_;
219+
rclcpp::CallbackGroup::SharedPtr ros_srv_cb_group_;
220+
rclcpp::CallbackGroup::SharedPtr agno_client_cb_group_;
221+
222+
public:
223+
explicit RosToAgnocastServiceBridge(
224+
const rclcpp::Node::SharedPtr & parent_node, const std::string & service_name,
225+
const rmw_qos_profile_t & qos)
226+
{
227+
ros_srv_cb_group_ =
228+
parent_node->create_callback_group(rclcpp::CallbackGroupType::MutuallyExclusive, false);
229+
agno_client_cb_group_ =
230+
parent_node->create_callback_group(rclcpp::CallbackGroupType::MutuallyExclusive, false);
231+
232+
agno_client_ = std::make_shared<agnocast::Client<ServiceT>>(
233+
parent_node.get(), service_name, rclcpp::ServicesQoS(), agno_client_cb_group_);
234+
235+
ros_srv_ = parent_node->create_service<ServiceT>(
236+
service_name,
237+
[this](
238+
const typename ServiceT::Request::SharedPtr ros_req,
239+
typename ServiceT::Response::SharedPtr ros_res) {
240+
auto agno_req = this->agno_client_->borrow_loaned_request();
241+
*agno_req = *ros_req;
242+
243+
auto future = this->agno_client_->async_send_request(std::move(agno_req));
244+
245+
auto agno_res = future.get();
246+
*ros_res = *agno_res;
247+
},
248+
qos, ros_srv_cb_group_);
249+
}
250+
251+
std::pair<rclcpp::CallbackGroup::SharedPtr, rclcpp::CallbackGroup::SharedPtr>
252+
get_callback_groups() const override
253+
{
254+
return {ros_srv_cb_group_, agno_client_cb_group_};
255+
}
256+
};
257+
210258
template <typename MessageT>
211259
std::shared_ptr<void> start_ros_to_agno_node(
212-
rclcpp::Node::SharedPtr node, const BridgeTargetInfo & info, const rclcpp::QoS & qos)
260+
rclcpp::Node::SharedPtr node, const PubsubBridgeTargetInfo & info, const rclcpp::QoS & qos)
213261
{
214262
std::string topic_name(static_cast<const char *>(info.topic_name));
215263
return std::make_shared<RosToAgnocastBridge<MessageT>>(node, topic_name, qos);
216264
}
217265

218266
template <typename MessageT>
219267
std::shared_ptr<void> start_agno_to_ros_node(
220-
rclcpp::Node::SharedPtr node, const BridgeTargetInfo & info, const rclcpp::QoS & qos)
268+
rclcpp::Node::SharedPtr node, const PubsubBridgeTargetInfo & info, const rclcpp::QoS & qos)
221269
{
222270
std::string topic_name(static_cast<const char *>(info.topic_name));
223271
return std::make_shared<AgnocastToRosBridge<MessageT>>(node, topic_name, qos);
224272
}
225273

274+
template <typename ServiceT>
275+
std::shared_ptr<void> start_ros_to_agno_service_node(
276+
rclcpp::Node::SharedPtr node, const ServiceBridgeTargetInfo & info, const rmw_qos_profile_t & qos)
277+
{
278+
std::string service_name(static_cast<const char *>(info.name));
279+
return std::make_shared<RosToAgnocastServiceBridge<ServiceT>>(node, service_name, qos);
280+
}
281+
226282
template <typename MsgStruct>
227283
void send_mq_message(
228284
const std::string & mq_name, const MsgStruct & msg, long msg_size_limit,
@@ -274,55 +330,51 @@ void send_bridge_request(
274330
const std::string & topic_name, topic_local_id_t id, BridgeDirection direction)
275331
{
276332
static const auto logger = rclcpp::get_logger("agnocast_bridge_requester");
333+
277334
// We capture 'fn_reverse' because bridge_manager is responsible for managing both directions
278335
// independently. Storing the reverse factory allows us to instantiate the return path on-demand
279336
// within the same process.
280-
auto fn_current = (direction == BridgeDirection::ROS2_TO_AGNOCAST)
281-
? &start_ros_to_agno_node<MessageT>
282-
: &start_agno_to_ros_node<MessageT>;
283-
auto fn_reverse = (direction == BridgeDirection::ROS2_TO_AGNOCAST)
284-
? &start_agno_to_ros_node<MessageT>
285-
: &start_ros_to_agno_node<MessageT>;
286-
287-
Dl_info info = {};
288-
if (dladdr(reinterpret_cast<void *>(fn_current), &info) == 0 || !info.dli_fname) {
289-
RCLCPP_ERROR(logger, "dladdr failed or filename NULL.");
337+
auto fn_current = reinterpret_cast<uintptr_t>(
338+
(direction == BridgeDirection::ROS2_TO_AGNOCAST) ? &start_ros_to_agno_node<MessageT>
339+
: &start_agno_to_ros_node<MessageT>);
340+
auto fn_reverse = reinterpret_cast<uintptr_t>(
341+
(direction == BridgeDirection::ROS2_TO_AGNOCAST) ? &start_agno_to_ros_node<MessageT>
342+
: &start_ros_to_agno_node<MessageT>);
343+
344+
MqMsgBridge msg = {};
345+
msg.direction = direction;
346+
msg.is_service = false;
347+
msg.pubsub_target.target_id = id;
348+
snprintf(
349+
static_cast<char *>(msg.pubsub_target.topic_name), TOPIC_NAME_BUFFER_SIZE, "%s",
350+
topic_name.c_str());
351+
if (!build_bridge_factory_info(msg.factory, fn_current, fn_reverse, logger)) {
290352
return;
291-
}
353+
};
292354

293-
std::error_code ec;
294-
auto self_path = std::filesystem::read_symlink("/proc/self/exe", ec);
295-
296-
bool is_self_executable = false;
297-
if (!ec) {
298-
std::filesystem::path factory_lib_path(info.dli_fname);
299-
if (std::filesystem::equivalent(factory_lib_path, self_path, ec)) {
300-
is_self_executable = true;
301-
} else if (ec) {
302-
RCLCPP_WARN(
303-
logger, "Filesystem check error for '%s' vs '%s': %s", info.dli_fname, self_path.c_str(),
304-
ec.message().c_str());
305-
}
306-
}
355+
std::string mq_name = create_mq_name_for_bridge(standard_bridge_manager_pid);
356+
send_mq_message(mq_name, msg, BRIDGE_MQ_MESSAGE_SIZE, logger);
357+
}
307358

308-
const char * symbol_to_send = MAIN_EXECUTABLE_SYMBOL;
309-
if (!is_self_executable && info.dli_sname != nullptr) {
310-
symbol_to_send = info.dli_sname;
311-
}
359+
template <typename ServiceT>
360+
void send_service_bridge_request(
361+
const std::string & service_name, const rclcpp::QoS & qos, BridgeDirection direction)
362+
{
363+
static const auto logger = rclcpp::get_logger("agnocast_service_bridge_requester");
364+
365+
// FIXME(bdm-k): Assume the direction is ROS2_TO_AGNOCAST for now.
366+
auto fn_current = reinterpret_cast<uintptr_t>(&start_ros_to_agno_service_node<ServiceT>);
367+
uintptr_t fn_reverse = 0;
312368

313369
MqMsgBridge msg = {};
314370
msg.direction = direction;
315-
msg.target.target_id = id;
316-
snprintf(
317-
static_cast<char *>(msg.target.topic_name), TOPIC_NAME_BUFFER_SIZE, "%s", topic_name.c_str());
318-
snprintf(
319-
static_cast<char *>(msg.factory.shared_lib_path), SHARED_LIB_PATH_BUFFER_SIZE, "%s",
320-
info.dli_fname);
371+
msg.is_service = true;
372+
msg.srv_target.qos = qos.get_rmw_qos_profile();
321373
snprintf(
322-
static_cast<char *>(msg.factory.symbol_name), SYMBOL_NAME_BUFFER_SIZE, "%s", symbol_to_send);
323-
auto base_addr = reinterpret_cast<uintptr_t>(info.dli_fbase);
324-
msg.factory.fn_offset = reinterpret_cast<uintptr_t>(fn_current) - base_addr;
325-
msg.factory.fn_offset_reverse = reinterpret_cast<uintptr_t>(fn_reverse) - base_addr;
374+
static_cast<char *>(msg.srv_target.name), SERVICE_NAME_BUFFER_SIZE, "%s", service_name.c_str());
375+
if (!build_bridge_factory_info(msg.factory, fn_current, fn_reverse, logger)) {
376+
return;
377+
}
326378

327379
std::string mq_name = create_mq_name_for_bridge(standard_bridge_manager_pid);
328380
send_mq_message(mq_name, msg, BRIDGE_MQ_MESSAGE_SIZE, logger);

src/agnocastlib/include/agnocast/bridge/agnocast_bridge_utils.hpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
#pragma once
22

33
#include "agnocast/agnocast_ioctl.hpp"
4+
#include "agnocast/agnocast_mq.hpp"
45

56
#include <rclcpp/rclcpp.hpp>
67

78
#include <string>
9+
#include <utility>
810

911
namespace agnocast
1012
{
@@ -22,6 +24,14 @@ class BridgeBase
2224
virtual rclcpp::CallbackGroup::SharedPtr get_callback_group() const = 0;
2325
};
2426

27+
class ServiceBridgeBase
28+
{
29+
public:
30+
virtual ~ServiceBridgeBase() = default;
31+
virtual std::pair<rclcpp::CallbackGroup::SharedPtr, rclcpp::CallbackGroup::SharedPtr>
32+
get_callback_groups() const = 0;
33+
};
34+
2535
struct SubscriberCountResult
2636
{
2737
int count; // -1 on error
@@ -43,5 +53,9 @@ bool update_ros2_subscriber_num(const rclcpp::Node * node, const std::string & t
4353
bool update_ros2_publisher_num(const rclcpp::Node * node, const std::string & topic_name);
4454
bool has_external_ros2_publisher(const rclcpp::Node * node, const std::string & topic_name);
4555
bool has_external_ros2_subscriber(const rclcpp::Node * node, const std::string & topic_name);
56+
// TODO(bdm-k): Write documentation for this function.
57+
bool build_bridge_factory_info(
58+
BridgeFactoryInfo & factory, uintptr_t fn_current, uintptr_t fn_reverse,
59+
const rclcpp::Logger & logger);
4660

4761
} // namespace agnocast

src/agnocastlib/include/agnocast/bridge/standard/agnocast_standard_bridge_loader.hpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
namespace agnocast
1212
{
1313

14-
using BridgeFn =
15-
std::shared_ptr<void> (*)(rclcpp::Node::SharedPtr, const BridgeTargetInfo &, const rclcpp::QoS &);
14+
using BridgeFn = std::shared_ptr<void> (*)(
15+
rclcpp::Node::SharedPtr, const PubsubBridgeTargetInfo &, const rclcpp::QoS &);
1616

1717
class StandardBridgeLoader
1818
{
@@ -34,7 +34,8 @@ class StandardBridgeLoader
3434

3535
std::shared_ptr<void> create_bridge_instance(
3636
BridgeFn entry_func, const std::shared_ptr<void> & lib_handle,
37-
const rclcpp::Node::SharedPtr & node, const BridgeTargetInfo & target, const rclcpp::QoS & qos);
37+
const rclcpp::Node::SharedPtr & node, const PubsubBridgeTargetInfo & target,
38+
const rclcpp::QoS & qos);
3839
static std::pair<void *, uintptr_t> load_library(const char * lib_path, const char * symbol_name);
3940
std::pair<BridgeFn, std::shared_ptr<void>> resolve_factory_function(
4041
const MqMsgBridge & req, const std::string & topic_name_with_direction);

src/agnocastlib/src/bridge/agnocast_bridge_utils.cpp

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@
44

55
#include <rclcpp/rclcpp.hpp>
66

7+
#include <dlfcn.h>
8+
79
#include <algorithm>
10+
#include <filesystem>
811
#include <stdexcept>
912
#include <string>
1013

@@ -176,4 +179,46 @@ bool has_external_ros2_subscriber(const rclcpp::Node * node, const std::string &
176179
});
177180
}
178181

182+
bool build_bridge_factory_info(
183+
BridgeFactoryInfo & factory, uintptr_t fn_current, uintptr_t fn_reverse,
184+
const rclcpp::Logger & logger)
185+
{
186+
Dl_info info = {};
187+
if (dladdr(reinterpret_cast<void *>(fn_current), &info) == 0 || !info.dli_fname) {
188+
RCLCPP_ERROR(logger, "dladdr failed or filename NULL.");
189+
return false;
190+
}
191+
192+
std::error_code ec;
193+
auto self_path = std::filesystem::read_symlink("/proc/self/exe", ec);
194+
195+
bool is_self_executable = false;
196+
if (!ec) {
197+
std::filesystem::path factory_lib_path(info.dli_fname);
198+
if (std::filesystem::equivalent(factory_lib_path, self_path, ec)) {
199+
is_self_executable = true;
200+
} else if (ec) {
201+
RCLCPP_WARN(
202+
logger, "Filesystem check error for '%s' vs '%s': %s", info.dli_fname, self_path.c_str(),
203+
ec.message().c_str());
204+
}
205+
}
206+
207+
const char * symbol_to_send = MAIN_EXECUTABLE_SYMBOL;
208+
if (!is_self_executable && info.dli_sname != nullptr) {
209+
symbol_to_send = info.dli_sname;
210+
}
211+
212+
snprintf(
213+
static_cast<char *>(factory.shared_lib_path), SHARED_LIB_PATH_BUFFER_SIZE, "%s",
214+
info.dli_fname);
215+
snprintf(static_cast<char *>(factory.symbol_name), SYMBOL_NAME_BUFFER_SIZE, "%s", symbol_to_send);
216+
217+
auto base_addr = reinterpret_cast<uintptr_t>(info.dli_fbase);
218+
factory.fn_offset = fn_current - base_addr;
219+
factory.fn_offset_reverse = fn_reverse - base_addr;
220+
221+
return true;
222+
}
223+
179224
} // namespace agnocast

0 commit comments

Comments
 (0)