11 #include <quanergy/client/version.h>
13 #if (QUANERGY_CLIENT_VERSION/100000 != 5)
14 #error Incompatible Quanergy Client Version; looking for v5.x.x
18 #include <boost/program_options.hpp>
21 #include <quanergy/client/sensor_client.h>
24 #include <quanergy/pipelines/sensor_pipeline.h>
27 #include <quanergy/pipelines/async.h>
37 std::string
topic =
"points";
43 void load(
const quanergy::pipeline::SettingsFileLoader& settings);
46 int main(
int argc,
char** argv)
48 ros::init(argc, argv,
"quanergy_client_ros");
51 namespace po = boost::program_options;
53 po::options_description description(
"Quanergy Client ROS Node");
54 const po::positional_options_description p;
56 quanergy::pipeline::SensorPipelineSettings pipeline_settings;
58 std::string return_string;
59 std::vector<float> correct_params;
62 std::string port =
"4141";
64 description.add_options()
65 (
"help,h",
"Display this help message.")
66 (
"settings-file,s", po::value<std::string>(),
67 "Settings file. Setting file values override defaults and command line arguments override the settings file.")
68 (
"topic,t", po::value<std::string>(&ros_node_settings.
topic)->
70 "ROS topic for publishing the point cloud. If 'all_separate_topics' is chosen for --return, "
71 "a topic will be created for each return number with the return number appended to the topic name.")
72 (
"use-ros-time", po::bool_switch(&ros_node_settings.
use_ros_time),
73 "flag determining whether to use ROS time in message; uses sensor time otherwise,")
74 (
"host", po::value<std::string>(&pipeline_settings.host),
75 "Host name or IP of the sensor.")
76 (
"frame,f", po::value<std::string>(&pipeline_settings.frame)->
78 "Frame name inserted in the point cloud.")
79 (
"return,r", po::value<std::string>(&return_string),
80 "Return selection (M-series only) - "
81 "Options are 0, 1, 2, all, or all_separate_topics. For 3 return packets, 'all' creates an unorganized point cloud "
82 "and 'all_separate_topics' creates 3 separate organized point clouds on their own topics. "
83 "For single return, explicitly setting a value produces an error if the selection doesn't match the packet.")
84 (
"calibrate", po::bool_switch(&pipeline_settings.calibrate),
85 "Flag indicating encoder calibration should be performed and applied to outgoing points; M-series only.")
86 (
"frame-rate", po::value<double>(&pipeline_settings.frame_rate)->
88 "Frame rate used when peforming encoder calibration; M-series only.")
89 (
"manual-correct", po::value<std::vector<float>>(&correct_params)->multitoken()->value_name(
"amplitude phase"),
90 "Correct encoder error with user defined values. Both amplitude and phase are in radians; M-series only.")
91 (
"min-distance", po::value<float>(&pipeline_settings.min_distance)->
93 "minimum distance (inclusive) for distance filtering.")
94 (
"max-distance", po::value<float>(&pipeline_settings.max_distance)->
96 "maximum distance (inclusive) for distance filtering.")
97 (
"min-cloud-size", po::value<std::int32_t>(&pipeline_settings.min_cloud_size)->
99 "minimum cloud size; produces an error and ignores clouds smaller than this.")
100 (
"max-cloud-size", po::value<std::int32_t>(&pipeline_settings.max_cloud_size)->
102 "maximum cloud size; produces an error and ignores clouds larger than this.");
107 po::variables_map vm;
108 po::store(po::command_line_parser(argc, argv).options(description).positional(p).
run(), vm);
110 if (vm.count(
"help"))
112 std::cout << description << std::endl;
117 if (vm.count(
"settings-file"))
119 std::string settings_file = vm[
"settings-file"].as<std::string>();
120 quanergy::pipeline::SettingsFileLoader file_loader;
121 file_loader.loadXML(settings_file);
122 ros_node_settings.
load(file_loader);
128 file_loader.put(
"Settings.return", pipeline_settings.stringFromReturn(0));
131 pipeline_settings.load(file_loader);
138 if (pipeline_settings.host.empty())
140 std::cout <<
"No host provided" << std::endl;
141 std::cout << description << std::endl;
146 if (!return_string.empty())
148 pipeline_settings.return_selection_set =
true;
149 if (return_string ==
"all_separate_topics")
152 pipeline_settings.return_selection = 0;
156 pipeline_settings.return_selection = pipeline_settings.returnFromString(return_string);
161 if (!correct_params.empty())
163 if (correct_params.size() == 2)
165 pipeline_settings.override_encoder_params =
true;
166 pipeline_settings.amplitude = correct_params[0];
167 pipeline_settings.phase = correct_params[1];
171 std::cout <<
"Manual encoder correction expects exactly 2 paramters: amplitude and phase" << std::endl;
172 std::cout << description << std::endl;
179 std::cout <<
"Boost Program Options Error: " << e.what() << std::endl << std::endl;
180 std::cout << description << std::endl;
183 catch (std::exception& e)
185 std::cout <<
"Error: " << e.what() << std::endl;
191 quanergy::client::SensorClient client(pipeline_settings.host, port, 100);
194 using Async = quanergy::pipeline::AsyncModule<std::shared_ptr<std::vector<char>>>;
195 std::list<Async> asyncs;
199 std::list<quanergy::pipeline::SensorPipeline> pipelines;
203 std::list<SimplePublisher<quanergy::PointXYZIR>> publishers;
207 std::vector<std::thread> publisher_threads;
210 std::vector<boost::signals2::connection> connections;
213 std::mutex client_mutex;
214 std::atomic<int> publishers_count = {0};
215 std::condition_variable publisher_cv;
218 int pipeline_count = 1;
221 pipeline_count = quanergy::client::M_SERIES_NUM_RETURNS;
224 for (
int i = 0; i < pipeline_count; ++i)
227 std::string topic = ros_node_settings.
topic;
230 pipeline_settings.return_selection = i;
231 topic += std::to_string(i);
235 pipelines.emplace_back(pipeline_settings);
236 auto& pipeline = pipelines.back();
239 publishers.emplace_back(nh, topic, ros_node_settings.
use_ros_time);
240 auto& publisher = publishers.back();
246 asyncs.emplace_back(100);
247 auto& async = asyncs.back();
250 connections.push_back(client.connect(
251 [&async](
const std::shared_ptr<std::vector<char>>& packet){ async.slot(packet); }
255 connections.push_back(async.connect(
256 [&pipeline](
const std::shared_ptr<std::vector<char>>& packet){ pipeline.slot(packet); }
262 connections.push_back(client.connect(
263 [&pipeline](
const std::shared_ptr<std::vector<char>>& packet){ pipeline.slot(packet); }
268 connections.push_back(pipeline.connect(
269 [&publisher](
const boost::shared_ptr<pcl::PointCloud<quanergy::PointXYZIR>>& pc){ publisher.slot(pc); }
273 publisher_threads.emplace_back(
274 [&publisher, &client_mutex, &client, &publishers_count, &publisher_cv]
276 std::unique_lock<std::mutex> lock(client_mutex);
279 publisher_cv.notify_one();
291 std::unique_lock<std::mutex> lock(client_mutex);
292 publisher_cv.wait(lock, [&publishers_count, pipeline_count]{
return publishers_count == pipeline_count; });
300 catch (std::exception& e)
302 std::cerr <<
"Terminating after catching exception: "
313 for (
auto &thread : publisher_threads)
318 publisher_threads.clear();
325 auto r = settings.get_optional<std::string>(
"Settings.return");
326 if (r && *r ==
"all_separate_topics")
331 topic = settings.get(
"Settings.RosNode.topic",
topic);