Skip to content

Commit 4f7d692

Browse files
Fix/tests (#29)
* update sensor extrinsics lookup to warn after timeout * actually visit fields * add node init to ros tests * fix lookup exit behavior and broadcast test tf * add warning messages if camera info lookup goes on too long * add proper shutdown * fix info subscription * clean up log statements and add ability to print full config * fix shutdown behavior
1 parent 974bcc1 commit 4f7d692

File tree

10 files changed

+183
-75
lines changed

10 files changed

+183
-75
lines changed

hydra_ros/app/hydra_node.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,5 +155,6 @@ int main(int argc, char* argv[]) {
155155
google::RemoveLogSink(ros_sink.get());
156156
}
157157

158+
rclcpp::shutdown();
158159
return 0;
159160
}

hydra_ros/include/hydra_ros/hydra_ros_pipeline.h

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
#include <hydra/backend/backend_module.h>
3939
#include <hydra/common/hydra_pipeline.h>
4040
#include <hydra/frontend/graph_builder.h>
41+
4142
#include <memory>
4243

4344
#include "hydra_ros/input/feature_receiver.h"
@@ -52,15 +53,26 @@ class ExternalLoopClosureSubscriber;
5253
class HydraRosPipeline : public HydraPipeline {
5354
public:
5455
struct Config {
56+
//! @brief Configuration for active window / metric-semantic reconstruction
5557
config::VirtualConfig<ActiveWindowModule> active_window{
5658
ReconstructionModule::Config()};
59+
//! @brief Configuration for frontend module
5760
config::VirtualConfig<GraphBuilder> frontend{GraphBuilder::Config()};
61+
//! @brief Configuration for backend module
5862
config::VirtualConfig<BackendModule> backend{BackendModule::Config()};
63+
//! @brief Publish frontend scene graph in addition to backend
5964
bool enable_frontend_output = true;
60-
bool enable_zmq_interface = true;
65+
//! @brief Turn on zmq-based publishing
66+
bool enable_zmq_interface = false;
67+
//! @brief Configuration for sensor inputs
6168
RosInputModule::Config input;
69+
//! @brief Receiver for language features
6270
config::VirtualConfig<FeatureReceiver> features;
71+
//! @brief Verbosity setting for main pipeline class
6372
int verbosity = 1;
73+
//! @brief Show the config passed to Hydra (before resolving sensor configurations)
74+
bool preprint_config = false;
75+
//! @brief Monitor to report whether or Hydra is running normally
6476
StatusMonitor::Config status_monitor;
6577
} const config;
6678

hydra_ros/include/hydra_ros/input/ros_sensors.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,14 @@ struct RosExtrinsics : SensorExtrinsics {
6161
std::string sensor_frame = "";
6262
//! @brief Override global robot frame ID
6363
std::string robot_frame = "";
64+
//! @brief Amount of time before warning about missing TF (0 disables warning)
65+
double warning_timeout_s = 10.0;
66+
//! @brief Amount of time before forcing Hydra to exit (0 disables exit)
67+
double error_timeout_s = 0.0;
68+
//! @brief Sample period for looking up extrinsics transform
69+
double wait_duration_s = 0.1;
70+
//! @brief TF lookup verbosity
71+
int verbosity = 3;
6472
} const config;
6573

6674
explicit RosExtrinsics(const Config& config);
@@ -72,7 +80,12 @@ struct RosExtrinsics : SensorExtrinsics {
7280

7381
struct RosCamera : InvalidSensor {
7482
struct Config : Sensor::Config {
83+
//! @brief Optional namespace for overriding Hydra's default sensor namespaces
7584
std::string ns = "";
85+
//! @brief Amount of time before warning about missing camera info (0 disables warning)
86+
double warning_timeout_s = 10.0;
87+
//! @brief Amount of time to wait before forcing Hydra to exit (0 disables exit)
88+
double error_timeout_s = 0.0;
7689
} const config;
7790

7891
explicit RosCamera(const Config& config);

hydra_ros/include/hydra_ros/utils/tf_lookup.h

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,27 +46,23 @@
4646

4747
namespace hydra {
4848

49-
PoseStatus lookupTransform(const std::string& target,
50-
const std::string& source,
51-
double wait_duration_s = 0.1,
52-
int verbosity = 10);
53-
5449
PoseStatus lookupTransform(const tf2_ros::Buffer& buffer,
5550
const std::optional<rclcpp::Time>& stamp,
5651
const std::string& target,
5752
const std::string& source,
58-
std::optional<size_t> max_tries = std::nullopt,
53+
size_t max_tries = 0,
5954
double wait_duration_s = 0.1,
60-
int verbosity = 10);
55+
int verbosity = 10,
56+
std::string* message = nullptr);
6157

6258
struct TFLookup {
6359
struct Config {
6460
//! Amount of time to wait between tf lookup attempts
6561
double wait_duration_s = 0.1;
6662
//! Buffer size in second for tf
6763
double buffer_size_s = 30.0;
68-
//! Number of lookup attempts before giving up
69-
int max_tries = 5;
64+
//! Number of lookup attempts before giving up (0 will try forever)
65+
size_t max_tries = 5;
7066
//! Logging verbosity of tf lookup process
7167
int verbosity = 3;
7268
//! Get the buffer size in nanoseconds

hydra_ros/src/hydra_ros_pipeline.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,14 +71,20 @@ void declare_config(HydraRosPipeline::Config& config) {
7171
config.features.setOptional();
7272
field(config.features, "features");
7373
field(config.verbosity, "verbosity");
74+
field(config.preprint_config, "preprint_config");
7475
field(config.status_monitor, "status_monitor");
7576
}
7677

7778
HydraRosPipeline::HydraRosPipeline(int robot_id, int config_verbosity)
7879
: HydraPipeline(config::fromContext<PipelineConfig>(), robot_id, config_verbosity),
7980
config(config::checkValid(config::fromContext<Config>())) {
80-
LOG_IF(INFO, config.verbosity >= 1) << "Starting Hydra-ROS with input configuration\n"
81-
<< config::toString(config.input);
81+
if (config.preprint_config) {
82+
LOG(INFO) << "Using configuration to start Hydra\n" << config::toString(config);
83+
} else {
84+
LOG_IF(INFO, config.verbosity >= 1)
85+
<< "Starting Hydra-ROS with input configuration\n"
86+
<< config::toString(config.input);
87+
}
8288
}
8389

8490
HydraRosPipeline::~HydraRosPipeline() {}

hydra_ros/src/input/ros_sensors.cpp

Lines changed: 72 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -70,30 +70,77 @@ void fillConfigFromInfo(const CameraInfo& msg, Camera::Config& cam_config) {
7070
cam_config.cy = msg.k[5];
7171
}
7272

73-
std::optional<sensor_msgs::msg::CameraInfo> getCameraInfo(const std::string& ns) {
73+
std::optional<sensor_msgs::msg::CameraInfo> getCameraInfo(const RosCamera::Config& c,
74+
const std::string& ns) {
7475
auto nh = getHydraNodeHandle(ns);
7576
const auto resolved_topic = nh.resolve_name("camera_info", false);
7677
LOG(INFO) << "Waiting for CameraInfo on " << resolved_topic
7778
<< " to initialize sensor model";
7879

79-
auto msg = ianvs::getSingleMessage<CameraInfo>(nh, "camera_info", true);
80-
if (!msg) {
81-
LOG(ERROR) << "did not receive message on " << resolved_topic;
82-
return std::nullopt;
80+
const auto start = nh.now();
81+
const auto qos = rclcpp::QoS(1).transient_local();
82+
const size_t timeout = std::floor(c.warning_timeout_s * 1000);
83+
84+
std::optional<sensor_msgs::msg::CameraInfo> msg;
85+
while (!msg && rclcpp::ok()) {
86+
msg = ianvs::getSingleMessage<CameraInfo>(nh, "camera_info", true, qos, timeout);
87+
if (!msg) {
88+
LOG(WARNING) << "Waiting for CameraInfo on topic '" << resolved_topic << "'";
89+
}
90+
91+
const auto diff = nh.now() - start;
92+
if (c.error_timeout_s && (diff.seconds() > c.error_timeout_s)) {
93+
LOG(ERROR) << "Sensor intrinsics lookup timed out on '" << resolved_topic << "'";
94+
break;
95+
}
8396
}
8497

85-
return *msg;
98+
return msg;
8699
}
87100

88-
ParamSensorExtrinsics::Config lookupExtrinsics(const std::string& sensor_frame,
101+
ParamSensorExtrinsics::Config lookupExtrinsics(const RosExtrinsics::Config& config,
102+
const std::string& sensor_frame,
89103
const std::string& robot_frame) {
90-
const auto pose = lookupTransform(robot_frame, sensor_frame);
91-
CHECK(pose.is_valid) << "Could not look up extrinsics from ros!";
104+
LOG(INFO) << "Looking for sensor extrinsics '" << robot_frame << "_T_" << sensor_frame
105+
<< "' via TF";
106+
107+
auto nh = getHydraNodeHandle("");
108+
auto clock = nh.node().get<rclcpp::node_interfaces::NodeClockInterface>();
109+
110+
tf2_ros::Buffer buffer(clock->get_clock());
111+
tf2_ros::TransformListener listener(buffer);
112+
113+
size_t warning_tries = config.warning_timeout_s / config.wait_duration_s;
114+
115+
const auto start = nh.now();
116+
PoseStatus status;
117+
while (!status && rclcpp::ok()) {
118+
std::string message;
119+
status = lookupTransform(buffer,
120+
std::nullopt,
121+
robot_frame,
122+
sensor_frame,
123+
warning_tries,
124+
config.wait_duration_s,
125+
config.verbosity,
126+
&message);
127+
if (!status) {
128+
LOG(WARNING) << "Waiting for sensor extrinsics " << message;
129+
}
130+
131+
const auto diff = nh.now() - start;
132+
if (config.error_timeout_s && (diff.seconds() > config.error_timeout_s)) {
133+
LOG(ERROR) << "Sensor extrinsics lookup timed out for " << message;
134+
break;
135+
}
136+
}
137+
138+
CHECK(status.is_valid) << "Could not look up extrinsics from ros!";
92139

93-
ParamSensorExtrinsics::Config config;
94-
config.body_R_sensor = pose.target_R_source;
95-
config.body_p_sensor = pose.target_p_source;
96-
return config;
140+
ParamSensorExtrinsics::Config params;
141+
params.body_R_sensor = status.target_R_source;
142+
params.body_p_sensor = status.target_p_source;
143+
return params;
97144
}
98145

99146
RosExtrinsics::RosExtrinsics(const Config&) {
@@ -109,13 +156,23 @@ void declare_config(RosExtrinsics::Config& config) {
109156
name("RosExtrinsics::Config");
110157
field(config.sensor_frame, "sensor_frame");
111158
field(config.robot_frame, "robot_frame");
159+
field(config.warning_timeout_s, "warning_timeout_s", "s");
160+
field(config.error_timeout_s, "error_timeout_s", "s");
161+
field(config.wait_duration_s, "wait_duration_s", "s");
162+
field(config.verbosity, "verbosity");
163+
check(config.warning_timeout_s, GE, 0.0, "warning_timeout_s");
164+
check(config.error_timeout_s, GE, 0.0, "error_timeout_s");
112165
}
113166

114167
void declare_config(RosCamera::Config& config) {
115168
using namespace config;
116169
name("RosCamera::Config");
117170
base<Sensor::Config>(config);
118171
field(config.ns, "ns");
172+
field(config.warning_timeout_s, "warning_timeout_s", "s");
173+
field(config.error_timeout_s, "error_timeout_s", "s");
174+
check(config.warning_timeout_s, GE, 0.0, "warning_timeout_s");
175+
check(config.error_timeout_s, GE, 0.0, "error_timeout_s");
119176
}
120177

121178
namespace input {
@@ -145,7 +202,7 @@ VirtualSensor loadExtrinsics(const VirtualSensor& sensor,
145202
return {};
146203
}
147204

148-
const auto info = lookupExtrinsics(frame, parent);
205+
const auto info = lookupExtrinsics(derived, frame, parent);
149206
config::VirtualConfig<SensorExtrinsics> new_config(info);
150207
base_contents["extrinsics"] = config::toYaml(new_config);
151208
return config::fromYaml<VirtualSensor>(base_contents);
@@ -161,7 +218,7 @@ VirtualSensor loadSensor(const VirtualSensor& sensor, const std::string& sensor_
161218

162219
const auto ns =
163220
derived.ns.empty() ? "~/input/" + sensor_name + std::string("/rgb") : derived.ns;
164-
const auto msg = getCameraInfo(ns);
221+
const auto msg = getCameraInfo(derived, ns);
165222
if (!msg) {
166223
return {};
167224
}

hydra_ros/src/utils/tf_lookup.cpp

Lines changed: 24 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -48,34 +48,25 @@
4848

4949
namespace hydra {
5050

51-
rclcpp::Clock::SharedPtr getHydraClock() {
52-
auto nh = getHydraNodeHandle("");
53-
auto clock = nh.node().get<rclcpp::node_interfaces::NodeClockInterface>();
54-
return clock->get_clock();
55-
}
56-
5751
std::chrono::nanoseconds TFLookup::Config::buffer_size_ns() const {
5852
return std::chrono::duration_cast<std::chrono::nanoseconds>(
5953
std::chrono::duration<double>(buffer_size_s));
6054
}
6155

62-
PoseStatus lookupTransform(const std::string& target,
63-
const std::string& source,
64-
double wait_duration_s,
65-
int verbosity) {
66-
tf2_ros::Buffer buffer(getHydraClock());
67-
tf2_ros::TransformListener listener(buffer);
68-
return lookupTransform(
69-
buffer, std::nullopt, target, source, std::nullopt, wait_duration_s, verbosity);
56+
rclcpp::Clock::SharedPtr getHydraClock() {
57+
auto nh = getHydraNodeHandle("");
58+
auto clock = nh.node().get<rclcpp::node_interfaces::NodeClockInterface>();
59+
return clock->get_clock();
7060
}
7161

7262
PoseStatus lookupTransform(const tf2_ros::Buffer& buffer,
7363
const std::optional<rclcpp::Time>& stamp,
7464
const std::string& target,
7565
const std::string& source,
76-
std::optional<size_t> max_tries,
66+
size_t max_tries,
7767
double wait_duration_s,
78-
int verbosity) {
68+
int verbosity,
69+
std::string* message) {
7970
using namespace std::chrono_literals;
8071
rclcpp::WallRate tf_wait_rate(1.0 / wait_duration_s);
8172
std::string stamp_suffix;
@@ -95,8 +86,8 @@ PoseStatus lookupTransform(const tf2_ros::Buffer& buffer,
9586
while (rclcpp::ok()) {
9687
VLOG(verbosity) << "Attempting to lookup tf @ " << lookup_time.nanoseconds()
9788
<< " [ns]: " << attempt_number << " / "
98-
<< (max_tries ? std::to_string(max_tries.value()) : "n/a");
99-
if (max_tries && attempt_number >= *max_tries) {
89+
<< (max_tries ? std::to_string(max_tries) : "n/a");
90+
if (max_tries && attempt_number >= max_tries) {
10091
break;
10192
}
10293

@@ -112,16 +103,27 @@ PoseStatus lookupTransform(const tf2_ros::Buffer& buffer,
112103
}
113104

114105
if (!have_transform) {
115-
LOG(ERROR) << "Failed to find: " << target << "_T_" << source << stamp_suffix
116-
<< ": " << err_str;
106+
std::stringstream ss;
107+
ss << "'" << target << "_T_" << source << stamp_suffix;
108+
if (!err_str.empty()) {
109+
ss << "' (last error: '" << err_str << "')";
110+
}
111+
112+
if (message) {
113+
*message = ss.str();
114+
} else {
115+
LOG(ERROR) << "Failed to find " << ss.str();
116+
}
117+
117118
return {false, {}, {}};
118119
}
119120

120121
geometry_msgs::msg::TransformStamped transform;
121122
try {
122123
transform = buffer.lookupTransform(target, source, lookup_time);
123124
} catch (const tf2::TransformException& ex) {
124-
LOG(ERROR) << "Failed to look up: " << target << "_T_" << source << stamp_suffix;
125+
LOG(ERROR) << "Failed to look up available transform " << target << "_T_" << source
126+
<< stamp_suffix;
125127
return {false, {}, {}};
126128
}
127129

@@ -148,16 +150,12 @@ TFLookup::TFLookup(const Config& config)
148150
listener(buffer) {}
149151

150152
PoseStatus TFLookup::getBodyPose(uint64_t timestamp_ns) const {
151-
// negative or 0 for tf_max_tries means we spin forever if the transform isn't present
152-
const std::optional<size_t> max_tries =
153-
config.max_tries > 0 ? std::optional<size_t>(config.max_tries) : std::nullopt;
154-
155153
rclcpp::Time curr_ros_time(timestamp_ns);
156154
return lookupTransform(buffer,
157155
curr_ros_time,
158156
GlobalInfo::instance().getFrames().odom,
159157
GlobalInfo::instance().getFrames().robot,
160-
max_tries,
158+
config.max_tries,
161159
config.wait_duration_s,
162160
config.verbosity);
163161
}

hydra_ros/tests/hydra_ros.test

Lines changed: 0 additions & 14 deletions
This file was deleted.

0 commit comments

Comments
 (0)