CreOS SDK
The CreOS SDK allows you to interact with Avular robots
 
Loading...
Searching...
No Matches
Command Line Interface (C++)

Command line interface using the CreOS SDK client library.

Introduction

The Command Line Interface (CLI) is a tool that uses the CreOS SDK client library to enable interactions with an Avular robot directly from a terminal. You can install the CLI by installing the creos-cli package and its dependencies as follows, replacing <version> with the version number of the CreOS SDK you want to install, and <architecture> with the architecture of your system (e.g., amd64, arm64):

sudo apt install ./creos-cli_<version>_<architecture>.deb ./creos-client_<version>_<architecture>.deb ./creos-utils_<version>_<architecture>.deb

Once installed, the CLI can be invoked using the creos-cli command. The CLI provides a set of commands for subscribing or pulling to/from Data Sources, calling Callables, and publishing to Data Sinks. It also provides a command for listing all available data sources, callables, and data sinks.

A complete list of commands can be found by running:

creos-cli --help

Source code

main.cpp

1// Copyright (C) 2024 Avular Holding B.V. - All Rights Reserved
2// You may use this code under the terms of the Avular
3// Software End-User License Agreement.
4//
5// You should have received a copy of the Avular
6// Software End-User License Agreement license with
7// this file, or download it from: avular.com/eula
8//
15#include <avular_version.h>
16#include <fmt/chrono.h>
17#include <spdlog/sinks/stdout_color_sinks.h>
18#include <spdlog/spdlog.h>
19#include <creos/client.hpp>
20#include <cxxopts.hpp>
21#include <exception>
22#include <functional>
23#include <nlohmann/json.hpp>
24#include <ranges>
25#include <string>
26#include <thread>
27#include <unordered_set>
28#include <vector>
29
30static void receiveData(const std::string src, const auto& data, bool ndjson_format = true) {
31 nlohmann::json data_json = data;
32 nlohmann::json json{{"source", src}, {"data", data_json}};
33 if (ndjson_format) {
34 std::cout << json.dump() << std::endl;
35 } else {
36 std::cout << json.dump(4) << std::endl;
37 }
38}
39
40void callEnterLowPowerMode(creos::IPowerManagementInterface* interface, std::vector<std::string> request_data) {
41 if (request_data.size() == 0) {
42 spdlog::debug("Entering low power mode");
43 interface->enterLowPowerMode();
44 } else if (request_data.size() == 1) {
45 time_t wake_time = 0;
46 try {
47 // Try to parse as the ISO8601 time
48 struct tm tm{};
49
50 if (strptime(request_data[0].c_str(), "%Y-%m-%dT%H:%M:%S", &tm) != nullptr) {
51 wake_time = mktime(&tm);
52 } else if (strptime(request_data[0].c_str(), "%Y-%m-%dT%H:%M:%S%z", &tm) != nullptr) {
53 wake_time = mktime(&tm);
54 } else {
55 // Try to parse as a unix timestamp
56 wake_time = std::stoll(request_data[0]);
57 }
58 } catch (std::exception& e) {
59 spdlog::error("Failed to parse wake_time, It must be a UNIX timestamp or an ISO8601 date time string");
60 return;
61 }
62 spdlog::debug("Entering low power mode with wake_time: {}", wake_time);
64 } else {
65 spdlog::error("Invalid number of arguments, expected 0 or 1");
66 return;
67 }
68 std::cout << "{}" << std::endl;
69}
70
71void getSource(auto interface, auto method, bool ndjson_format = true, int timeout_ms = 1500) {
72 auto res = (interface->*method)(timeout_ms);
73 nlohmann::json json = res;
74 if (ndjson_format) {
75 std::cout << json.dump() << std::endl;
76 } else {
77 std::cout << json.dump(4) << std::endl;
78 }
79}
80
81template <typename MessageType, typename PublishFunction>
82void publishMessage(const std::string& sink, const std::optional<std::string>& data,
83 const std::string& message_type_name, PublishFunction publishFunc,
84 const std::optional<MessageType>& default_message = std::nullopt) {
85 MessageType message;
86 try {
87 if (data.has_value()) {
88 nlohmann::json data_json = nlohmann::json::parse(data.value());
89 nlohmann::from_json(data_json, message);
90 } else {
91 spdlog::info("No data provided, using default message data");
92 if (default_message.has_value()) {
93 message = default_message.value();
94 }
95 }
96 } catch (const std::exception& e) {
97 spdlog::error("Invalid JSON format, run using: --publish {} --publish {} --publish'{}'", sink,
98 message_type_name, nlohmann::json(message).dump());
99 exit(1);
100 }
101
102 try {
103 spdlog::info("Publishing {} message to: {}", message_type_name, sink);
104 publishFunc(message);
105 } catch (const std::exception& e) {
106 spdlog::error("Failed to publish message: {}", e.what());
107 exit(1);
108 }
109}
110
111// The data is passed as a JSON string which contains ',' separated values.
112// Which are split into individual arguments by the cxxopts library.
113// Therefore we need to join the remaining vector to get the full JSON string
114std::string argumentsToJsonStr(const std::vector<std::string>& args) {
115 if (args.size() == 0) {
116 return "{}";
117 }
118
119 std::string json_str = "";
120 for (size_t i = 0; i < args.size(); ++i) {
121 if (i > 0) {
122 json_str += ',';
123 }
124 json_str += args[i];
125 }
126 return json_str;
127}
128
129void handleList(creos::ISensorsInterface* sensors, creos::ITransformsInterface* transforms,
131 creos::IPowerManagementInterface* power_management, creos::IDiagnosticsInterface* diagnostics);
132void handleSubscribe(cxxopts::ParseResult& arguments, creos::ISensorsInterface* sensors,
135 bool ndjson_format, bool statistics);
136void handleCall(cxxopts::ParseResult& arguments, creos::IPowerManagementInterface* power_management,
137 creos::ISetpointControlInterface* setpoint_control);
138void handleGet(cxxopts::ParseResult& arguments, creos::ISystemInfoInterface* system_info,
140 bool ndjson_format);
141void handlePublish(cxxopts::ParseResult& arguments, creos::ISetpointControlInterface* setpoint_control);
142
143int main(int argc, char** argv) {
144 // Setup the spdlog output to output to stderr, this will allow other tools to parse the stdout which will only contain data.
145 auto console_sink = std::make_shared<spdlog::sinks::stderr_color_sink_st>();
146 spdlog::set_default_logger(std::make_shared<spdlog::logger>("console", console_sink));
147
148 cxxopts::Options options(argv[0], "CLI tool to interact with avular robots");
149 // clang-format off
150 options.add_options()
151 ("h,host", "Host address of the robot", cxxopts::value<std::string>())
152 ("version", "Print version")
153 ("v,verbose", "Enable verbose output")
154 ("help", "Print help")
155 ("echo", "Echo data received from the selected sources (to echo multiple sources at the same time, add "
156 "multiple --echo arguments: --echo source_1 --echo source_2)",
157 cxxopts::value<std::vector<std::string>>())
158 ("statistics", "Print statistics of the received data (can be used with multiple sources at the same time: "
159 "--statistics source_1 --statistics source_2)",
160 cxxopts::value<std::vector<std::string>>())
161 ("window", "Window size for statistics in messages", cxxopts::value<unsigned>()->default_value("1000"))
162 ("list", "List the available sources, sinks and callables")
163 ("call", "Call a callable", cxxopts::value<std::vector<std::string>>())
164 ("get", "Get data from a source", cxxopts::value<std::string>())
165 ("publish", "Publish data to a sink", cxxopts::value<std::vector<std::string>>())
166 ("ndjson", "Output JSON in NDJSON format (one line per JSON object) instead of pretty printed format", cxxopts::value<bool>()->default_value("false"))
167 ;
168 // clang-format on
169
170 cxxopts::ParseResult arguments;
171 try {
172 arguments = options.parse(argc, argv);
173 } catch (cxxopts::OptionException& e) {
174 spdlog::critical("Argument error: {}", e.what());
175 return 1;
176 }
177
178 if (arguments.count("help") || argc == 1) {
179 std::cout << options.help() << std::endl;
180 return 0;
181 }
182
183 if (arguments.count("version")) {
184 PrintAvularVersion();
185 return 0;
186 }
187
188 if (arguments.count("verbose")) {
189 spdlog::set_level(spdlog::level::debug);
190 }
191
192 bool ndjson_format = arguments["ndjson"].as<bool>();
193
194 std::unique_ptr<creos::Client> client;
195 if (arguments.count("host")) {
196 client = std::make_unique<creos::Client>(arguments["host"].as<std::string>());
197 } else {
198 client = std::make_unique<creos::Client>();
199 }
200
201 auto sensors = client->sensors();
202 auto power_management = client->power_management();
203 auto transforms = client->transforms();
204 auto system_info = client->system_info();
205 auto setpoint_control = client->setpoint_control();
206 auto diagnostics = client->diagnostics();
207
208 if (arguments.count("list")) {
209 handleList(sensors, transforms, system_info, setpoint_control, power_management, diagnostics);
210 }
211 if (arguments.count("echo")) {
212 handleSubscribe(arguments, sensors, transforms, system_info, setpoint_control, diagnostics, ndjson_format,
213 false);
214 }
215 if (arguments.count("statistics")) {
216 handleSubscribe(arguments, sensors, transforms, system_info, setpoint_control, diagnostics, ndjson_format,
217 true);
218 }
219 if (arguments.count("call")) {
220 handleCall(arguments, power_management, setpoint_control);
221 }
222 if (arguments.count("get")) {
223 handleGet(arguments, system_info, setpoint_control, diagnostics, ndjson_format);
224 }
225 if (arguments.count("publish")) {
226 handlePublish(arguments, setpoint_control);
227 }
228}
229
230void handleList(creos::ISensorsInterface* sensors, creos::ITransformsInterface* transforms,
232 creos::IPowerManagementInterface* power_management, creos::IDiagnosticsInterface* diagnostics) {
233 // List the available sources and callables
234 std::cout << "Available data sources:" << std::endl;
235 if (sensors) {
236 std::cout << " - sensors.lidar_points.front" << std::endl;
237 std::cout << " - sensors.odometry" << std::endl;
238 std::cout << " - sensors.pose" << std::endl;
239 std::cout << " - sensors.gnss_fix" << std::endl;
240 std::cout << " - sensors.remote_control" << std::endl;
241 std::cout << " - sensors.wheel_states" << std::endl;
242 std::cout << " - sensors.magnetic_field.body" << std::endl;
243 std::cout << " - sensors.barometer" << std::endl;
244 std::cout << " - sensors.range.downwards_lidar" << std::endl;
245 std::cout << " - sensors.imu.body" << std::endl;
246 }
247 if (transforms) {
248 std::cout << " - transforms.dynamic_transforms" << std::endl;
249 std::cout << " - transforms.static_transforms" << std::endl;
250 }
251 if (system_info) {
252 std::cout << " - system_info.system_info" << std::endl;
253 std::cout << " - system_info.battery_status" << std::endl;
254 }
255 if (setpoint_control) {
256 std::cout << " - setpoint_control.current_control_source" << std::endl;
257 }
258 if (diagnostics) {
259 std::cout << " - diagnostics.user_events" << std::endl;
260 std::cout << " - diagnostics.state" << std::endl;
261 }
262
263 std::cout << "Available data sinks:" << std::endl;
264 if (setpoint_control) {
265 std::cout << " - setpoint_control.cmd_vel_user" << std::endl;
266 std::cout << " - setpoint_control.state_reference" << std::endl;
267 }
268
269 std::cout << "Available callables:" << std::endl;
270 if (power_management) {
271 std::cout << " - power_management.enter_low_power_mode[,wake_time]" << std::endl;
272 std::cout << " - power_management.exit_low_power_mode" << std::endl;
273 std::cout << " - power_management.shutdown" << std::endl;
274 std::cout << " - power_management.reboot" << std::endl;
275 }
276 if (setpoint_control) {
277 std::cout << " - setpoint_control.set_control_source" << std::endl;
278 std::cout << " - setpoint_control.command" << std::endl;
279 }
280
281 std::cout << "Available getters:" << std::endl;
282 if (system_info) {
283 std::cout << " - system_info.system_info" << std::endl;
284 std::cout << " - system_info.battery_status" << std::endl;
285 }
286 if (setpoint_control) {
287 std::cout << " - setpoint_control.current_control_source" << std::endl;
288 }
289 if (diagnostics) {
290 std::cout << " - diagnostics.state" << std::endl;
291 }
292}
293
294using float_millis = std::chrono::duration<float, std::milli>;
295
296void handleSubscribe(cxxopts::ParseResult& arguments, creos::ISensorsInterface* sensors,
299 bool ndjson_format, bool statistics) {
300 auto sources = std::vector<std::string>();
301 if (statistics) {
302 sources = arguments["statistics"].as<std::vector<std::string>>();
303 } else {
304 sources = arguments["echo"].as<std::vector<std::string>>();
305 }
306 auto source_statistics = std::map<std::string, std::deque<float_millis>>();
307 for (const auto& source : sources) {
308 source_statistics[source] = std::deque<float_millis>();
309 }
310 auto statistics_window = arguments["window"].as<unsigned>();
311 std::unordered_set<std::string> registered_sources;
312
313 auto isSpecified = [&](const std::string& expr, const std::string& interfaceName, const std::string& sourceName) {
314 // Accept the wildcard match
315 if (expr == "*") {
316 return true;
317 }
318 auto separator_it = expr.find('.');
319 if (separator_it == std::string::npos) {
320 return false;
321 }
322 auto interface_part = expr.substr(0, separator_it);
323 auto source_part = expr.substr(separator_it + 1);
324 auto match_interface = interface_part == "*" || interface_part == interfaceName;
325 auto match_source = source_part == "*" || source_part == sourceName;
326 return match_interface && match_source;
327 };
328
329 auto subscribeIfSpecified = [&](const std::string& expr, const std::string& interfaceName,
330 const std::string& sourceName, auto interface, auto subscribeFunc,
331 bool& subscription_registered) {
332 if (!isSpecified(expr, interfaceName, sourceName)) {
333 return;
334 }
335 if (registered_sources.find(expr) != registered_sources.end()) {
336 return;
337 }
338 try {
339 spdlog::info("Subscribing to: {}.{}", interfaceName, sourceName);
340
341 std::string key = interfaceName + "." + sourceName;
342 registered_sources.insert(key);
343
344 // Call the subscribe function of the interface
345 (interface->*subscribeFunc)(
346 [key, ndjson_format, statistics, &source_statistics, &statistics_window,
347 last_time = std::chrono::system_clock::time_point()](const auto& data) mutable {
348 if (statistics) {
349 if (last_time.time_since_epoch().count() > 0) {
350 source_statistics[key].push_back(
351 std::chrono::duration_cast<float_millis>(std::chrono::system_clock::now() - last_time));
352 if (source_statistics[key].size() > statistics_window) {
353 source_statistics[key].pop_front();
354 }
355 }
356 last_time = std::chrono::system_clock::now();
357 } else {
358 receiveData(key, data, ndjson_format);
359 }
360 });
361 subscription_registered = true;
362 } catch (const creos::Exception& e) {
363 spdlog::warn("Failed to subscribe to {}.{}: {}", interfaceName, sourceName, e.what());
364 }
365 };
366
367 auto subscribeNamedIfSpecified = [&](const std::string& expr, const std::string& interfaceName,
368 const std::string& sourceName, auto sourceId, auto interface,
369 auto subscribeFunc, bool& subscription_registered) {
370 if (!isSpecified(expr, interfaceName, sourceName)) {
371 return;
372 }
373 if (registered_sources.find(expr) != registered_sources.end()) {
374 return;
375 }
376 try {
377 spdlog::info("Subscribing to: {}.{}", interfaceName, sourceName);
378
379 std::string key = interfaceName + "." + sourceName;
380 registered_sources.insert(key);
381 (interface->*subscribeFunc)(
382 sourceId, [key, ndjson_format, statistics, &source_statistics, &statistics_window,
383 last_time = std::chrono::system_clock::time_point()](const auto& data) mutable {
384 if (statistics) {
385 if (last_time.time_since_epoch().count() > 0) {
386 source_statistics[key].push_back(
387 std::chrono::duration_cast<float_millis>(std::chrono::system_clock::now() - last_time));
388 if (source_statistics[key].size() > statistics_window) {
389 source_statistics[key].pop_front();
390 }
391 }
392 last_time = std::chrono::system_clock::now();
393 } else {
394 receiveData(key, data, ndjson_format);
395 }
396 });
397 subscription_registered = true;
398 } catch (const creos::Exception& e) {
399 spdlog::warn("Failed to subscribe to {}.{}: {}", interfaceName, sourceName, e.what());
400 }
401 };
402
403 for (const auto& source : sources) {
404 bool subscription_registered = false;
405
406 subscribeNamedIfSpecified(source, "sensors", "lidar_points.front", creos_messages::Lidar3dId::kFront, sensors,
407 &creos::ISensorsInterface::subscribeTo3dLidar, subscription_registered);
408 subscribeIfSpecified(source, "sensors", "odometry", sensors, &creos::ISensorsInterface::subscribeToOdometry,
409 subscription_registered);
410 subscribeIfSpecified(source, "sensors", "pose", sensors, &creos::ISensorsInterface::subscribeToPose,
411 subscription_registered);
412 subscribeIfSpecified(source, "sensors", "gnss_fix", sensors, &creos::ISensorsInterface::subscribeToGnss,
413 subscription_registered);
414 subscribeIfSpecified(source, "sensors", "remote_control", sensors,
416 subscribeIfSpecified(source, "sensors", "wheel_states", sensors,
417 &creos::ISensorsInterface::subscribeToWheelState, subscription_registered);
418 subscribeNamedIfSpecified(source, "sensors", "magnetic_field.body", creos_messages::MagneticFieldId::kBody,
420 subscription_registered);
421 subscribeIfSpecified(source, "sensors", "barometer", sensors, &creos::ISensorsInterface::subscribeToBarometer,
422 subscription_registered);
423 subscribeNamedIfSpecified(source, "sensors", "range.downwards_lidar",
425 &creos::ISensorsInterface::subscribeToRangeSensor, subscription_registered);
426 subscribeNamedIfSpecified(source, "sensors", "imu.body", creos_messages::ImuId::kBody, sensors,
427 &creos::ISensorsInterface::subscribeToImu, subscription_registered);
428 subscribeIfSpecified(source, "transforms", "dynamic_transforms", transforms,
430 subscribeIfSpecified(source, "transforms", "static_transforms", transforms,
432 subscribeIfSpecified(source, "system_info", "system_info", system_info,
434 subscribeIfSpecified(source, "system_info", "battery_status", system_info,
436 subscribeIfSpecified(source, creos::ISetpointControlInterface::name, "current_control_source", setpoint_control,
438 subscription_registered);
439 subscribeIfSpecified(source, "diagnostics", "user_events", diagnostics,
441 subscribeIfSpecified(source, "diagnostics", "state", diagnostics,
442 &creos::IDiagnosticsInterface::subscribeToState, subscription_registered);
443
444 if (!subscription_registered) {
445 spdlog::error("Unknown source: {}", source);
446 }
447 }
448
449 while (true) {
450 std::this_thread::sleep_for(std::chrono::seconds(1));
451 if (statistics) {
452 if (!ndjson_format) {
453 fmt::print("Statistics at {}:\n", std::chrono::system_clock::now());
454 }
455 for (const auto& [source, durations] : source_statistics) {
456 if (durations.size() < 2) {
457 if (!ndjson_format) {
458 std::cout << "- " << source << ": <not enough data>" << std::endl;
459 }
460 continue;
461 }
462
463 // Drop first duration, as the first sample always comes in immediately after subscribing due to
464 // caching behavior.
465 auto durations_view = std::views::drop(durations, 1);
466 auto total_duration = std::accumulate(durations_view.begin(), durations_view.end(), float_millis(0));
467 auto avg_duration = total_duration / durations_view.size();
468 auto frequency = avg_duration.count() > 0
469 ? std::chrono::duration<double>(1) /
470 std::chrono::duration_cast<std::chrono::duration<double>>(avg_duration)
471 : 0;
472 auto min_duration = *std::min_element(durations_view.begin(), durations_view.end());
473 auto max_duration = *std::max_element(durations_view.begin(), durations_view.end());
474 auto variance = std::accumulate(durations_view.begin(), durations_view.end(), float_millis(0),
475 [avg_duration](auto acc, auto duration) {
476 return acc + float_millis((duration - avg_duration).count() *
477 (duration - avg_duration).count());
478 }) /
479 durations_view.size();
480 auto std_dev = float_millis(std::sqrt(variance.count()));
481 if (ndjson_format) {
482 std::cout << nlohmann::json::object({
483 {"timestamp", std::chrono::duration_cast<std::chrono::seconds>(
484 std::chrono::system_clock::now().time_since_epoch())
485 .count()},
486 {"source", source},
487 {"total", durations.size()},
488 {"freq", frequency},
489 {"avg", avg_duration.count()},
490 {"min", min_duration.count()},
491 {"max", max_duration.count()},
492 {"sd", std_dev.count()},
493 })
494 << std::endl;
495 } else {
496 std::cout << fmt::format(
497 "- {}: \n\t\ttotal={} \n\t\tfreq={:.2f}hz \n\t\tavg={}, min={}, max={}, sd={}",
498 source, durations.size(), frequency, avg_duration, min_duration, max_duration,
499 std_dev)
500 << std::endl;
501 }
502 }
503 }
504 }
505}
506
507void handleCall(cxxopts::ParseResult& arguments, creos::IPowerManagementInterface* power_management,
508 creos::ISetpointControlInterface* setpoint_control) {
509 {
510 auto call_arguments = arguments["call"].as<std::vector<std::string>>();
511 try {
512 auto callable = call_arguments.at(0);
513 auto callable_args = std::vector<std::string>(call_arguments.begin() + 1, call_arguments.end());
514
515 auto interface_name = callable.substr(0, callable.find('.'));
516
517 // Power management interface
518 if (interface_name == "power_management") {
519 if (!power_management) {
520 spdlog::error("Power management interface not available");
521 exit(1);
522 }
523 if (callable == "power_management.enter_low_power_mode") {
524 callEnterLowPowerMode(power_management, callable_args);
525 } else if (callable == "power_management.exit_low_power_mode") {
526 power_management->exitLowPowerMode();
527 std::cout << "Success" << std::endl;
528 } else if (callable == "power_management.shutdown") {
529 power_management->shutdown();
530 std::cout << "Success" << std::endl;
531 } else if (callable == "power_management.reboot") {
532 power_management->reboot();
533 std::cout << "Success" << std::endl;
534 } else {
535 spdlog::error("unknown callable: {}", callable);
536 }
537 }
538 // Setpoint control interface
539 else if (interface_name == "setpoint_control") {
540 if (!setpoint_control) {
541 spdlog::error("Setpoint control interface not available");
542 exit(1);
543 }
544
545 if (callable == "setpoint_control.set_control_source") {
546 if (callable_args.size() != 1) {
547 spdlog::error("Invalid number of arguments, expected 1");
548 exit(1);
549 }
550
551 creos_messages::ControlSource control_source =
552 creos_messages::ControlSource(std::stoi(callable_args.at(0)));
553 setpoint_control->setControlSource(control_source);
554 std::cout << "Success" << std::endl;
555 } else if (callable == "setpoint_control.command") {
556 if (callable_args.size() != 1) {
557 spdlog::error("Invalid number of arguments, expected 1");
558 exit(1);
559 }
560
562 command.action = creos_messages::Command::Action(std::stoi(callable_args.at(0)));
563 setpoint_control->sendCommand(command);
564 std::cout << "Success" << std::endl;
565 } else {
566 spdlog::error("unknown callable: {}", callable);
567 }
568 } else {
569 spdlog::error("unknown interface: {}", interface_name);
570 exit(1);
571 }
572 } catch (const creos::ServiceCallError& e) {
573 std::cout << "Call failed[" << static_cast<int>(e.code()) << "]: " << e.what() << std::endl;
574 } catch (const creos::TimeoutError& e) {
575 std::cout << "Call timed out: " << e.what() << std::endl;
576 } catch (const creos::UnsupportedError& e) {
577 std::cout << "Call unsupported: " << e.what() << std::endl;
578 } catch (const std::exception& e) {
579 std::cout << "Call failed: " << e.what() << std::endl;
580 exit(1);
581 }
582 }
583}
584
585void handleGet(cxxopts::ParseResult& arguments, creos::ISystemInfoInterface* system_info,
587 bool ndjson_format) {
588 auto source = arguments["get"].as<std::string>();
589
590 if (source == "system_info.system_info") {
591 getSource(system_info, &creos::ISystemInfoInterface::getSystemInfo, ndjson_format);
592 } else if (source == "system_info.battery_status") {
593 getSource(system_info, &creos::ISystemInfoInterface::getBatteryStatus, ndjson_format);
594 } else if (source == "setpoint_control.current_control_source") {
595 getSource(setpoint_control, &creos::ISetpointControlInterface::getCurrentControlSource, ndjson_format);
596 } else if (source == "diagnostics.state") {
597 getSource(diagnostics, &creos::IDiagnosticsInterface::getState, ndjson_format);
598 } else {
599 spdlog::error("unknown source: {}", source);
600 exit(1);
601 }
602}
603
604void handlePublish(cxxopts::ParseResult& arguments, creos::ISetpointControlInterface* setpoint_control) {
605 auto pub_arguments = arguments["publish"].as<std::vector<std::string>>();
606 try {
607 if (pub_arguments.size() < 2) {
608 spdlog::error(
609 "Invalid number of arguments, usage: --publish sink,message_type or --publish "
610 "sink,message_type,'json_data'");
611 exit(1);
612 }
613
614 auto pub_sink = pub_arguments.at(0);
615 auto pub_message_type = pub_arguments.at(1);
616
617 std::optional<std::string> pub_data = std::nullopt;
618 if (pub_arguments.size() >= 3) {
619 pub_data = argumentsToJsonStr(std::vector<std::string>(pub_arguments.begin() + 2, pub_arguments.end()));
620 }
621
622 auto interface_name = pub_sink.substr(0, pub_sink.find('.'));
623
624 // Setpoint control interface
625 if (interface_name == "setpoint_control") {
626
627 if (!setpoint_control) {
628 spdlog::error("Setpoint control interface not available");
629 exit(1);
630 }
631
632 if (pub_sink == "setpoint_control.cmd_vel_user") {
633 if (pub_message_type == "Twist") {
634 publishMessage<creos_messages::Twist>(
635 pub_sink, pub_data, pub_message_type,
636 [&](const auto& message) { setpoint_control->publishVelocityCommand(message); });
637 } else {
638 spdlog::error("Invalid message type: {}", pub_message_type);
639 exit(1);
640 }
641 } else if (pub_sink == "setpoint_control.state_reference") {
642 if (pub_message_type == "StateReference") {
643 creos_messages::StateReference default_message;
644 default_message.orientation_mode =
645 creos_messages::StateReference::OrientationMode::kAngularVelocity;
646 default_message.translation_mode = creos_messages::StateReference::TranslationMode::kPosition;
647 publishMessage<creos_messages::StateReference>(
648 pub_sink, pub_data, pub_message_type,
649 [&](const auto& message) { setpoint_control->publishStateReference(message); },
650 default_message);
651 } else {
652 spdlog::error("Invalid message type: {}", pub_message_type);
653 exit(1);
654 }
655 } else {
656 spdlog::error("unknown sink: {}", pub_sink);
657 exit(1);
658 }
659 } else {
660 spdlog::error("unknown interface: {}", interface_name);
661 exit(1);
662 }
663 } catch (const std::exception& e) {
664 std::cerr << e.what() << '\n';
665 }
666}
Exception class for the CreOS client library.
Definition errors.hpp:32
const char * what() const noexcept override
Returns the error message.
Definition errors.hpp:43
The interface for retrieving diagnostic information from an Avular robot.
Definition diagnostics_interface.hpp:28
virtual creos_messages::State getState(int timeout_ms=1500)=0
Get the state of the robot.
virtual SubscriptionId subscribeToUserEvents(const std::function< void(const creos_messages::UserEvent &)> &callback)=0
Subscribe to user events generated by the robot.
virtual SubscriptionId subscribeToState(const std::function< void(const creos_messages::State &)> &callback)=0
Subscribe to state information of the robot.
The interface for querying and managing the power state of an Avular robot.
Definition power_management_interface.hpp:27
virtual void exitLowPowerMode(int timeout_ms=5000)=0
Exit low power mode.
virtual void reboot(int timeout_ms=10000)=0
Reboot the robot.
virtual void enterLowPowerMode(int timeout_ms=5000)=0
Enter low power mode.
virtual void shutdown(int timeout_ms=5000)=0
Shutdown the robot.
The interface for retrieving sensor data from an Avular robot.
Definition sensors_interface.hpp:36
virtual SubscriptionId subscribeToRemoteController(const std::function< void(const creos_messages::ControllerState &)> &callback)=0
Subscribe to the remote controller state data.
virtual SubscriptionId subscribeToGnss(const std::function< void(const creos_messages::Gnss &)> &callback)=0
Subscribe to the GNSS data.
virtual SubscriptionId subscribeToOdometry(const std::function< void(const creos_messages::Odometry &)> &callback)=0
Subscribe to the odometry data.
virtual SubscriptionId subscribeToRangeSensor(creos_messages::RangeSensorId range_sensor_id, const std::function< void(const creos_messages::Range &)> &callback)=0
Subscribe to range sensor data.
virtual SubscriptionId subscribeToWheelState(const std::function< void(const creos_messages::WheelStates &)> &callback)=0
Subscribe to the wheel state data.
virtual SubscriptionId subscribeToImu(const creos_messages::ImuId imu_id, const std::function< void(const creos_messages::Imu &)> &callback)=0
Subscribe to the IMU sensor data.
virtual SubscriptionId subscribeToMagneticField(creos_messages::MagneticFieldId magnetic_field_id, const std::function< void(const creos_messages::MagneticField &)> &callback)=0
Subscribe to the magnetic field data.
virtual SubscriptionId subscribeToBarometer(const std::function< void(const creos_messages::Barometer &)> &callback)=0
Subscribe to the barometer data.
virtual SubscriptionId subscribeToPose(const std::function< void(const creos_messages::PoseWithCovarianceStamped &)> &callback)=0
Subscribe to the global pose data.
virtual SubscriptionId subscribeTo3dLidar(const creos_messages::Lidar3dId lidar_id, const std::function< void(const creos_messages::PointCloud2 &)> &callback)=0
Subscribe to the lidar point cloud data.
The interface for sending control setpoints to an Avular robot.
Definition setpoint_control_interface.hpp:30
virtual creos_messages::ControlSource getCurrentControlSource(int timeout_ms=1500)=0
Get the current control source of the robot.
virtual void publishStateReference(const creos_messages::StateReference &state_reference)=0
Publish a state reference to the robot.
virtual void publishVelocityCommand(const creos_messages::Twist &cmd_vel)=0
Publish a velocity command to the robot.
virtual void setControlSource(const creos_messages::ControlSource &source, int timeout_ms=5000)=0
Set the control source of the robot.
virtual void sendCommand(const creos_messages::Command command, int timeout_ms=5000)=0
Send a command to the robot to trigger a one-time action.
virtual SubscriptionId subscribeToCurrentControlSource(const std::function< void(const creos_messages::ControlSource &)> &callback)=0
Subscribe to the current control source of the robot.
The interface for retrieving general system information of an Avular robot.
Definition system_info_interface.hpp:28
virtual creos_messages::SystemInfo getSystemInfo(int timeout_ms=1500)=0
Get the system info.
virtual creos_messages::BatteryStatus getBatteryStatus(int timeout_ms=1500)=0
Get the battery status.
virtual SubscriptionId subscribeToSystemInfo(const std::function< void(const creos_messages::SystemInfo &)> &callback)=0
Subscribe to system info messages.
virtual SubscriptionId subscribeToBatteryStatus(const std::function< void(const creos_messages::BatteryStatus &)> &callback)=0
Subscribe to battery status updates.
The interface for retrieving information about the relationships between different coordinate frames ...
Definition transforms_interface.hpp:27
virtual SubscriptionId subscribeToDynamicTransforms(const std::function< void(const creos_messages::TransformStampedList &)> &callback)=0
Subscribe to transformation frame messages for dynamic transformations.
virtual SubscriptionId subscribeToStaticTransforms(const std::function< void(const creos_messages::TransformStampedList &)> &callback)=0
Subscribe to transformation frame messages for static transformations.
static time_point from_time_t(std::time_t t) noexcept
Construct a time_point from a time_t.
Definition robot_clock.cpp:20
Service call failure.
Definition errors.hpp:105
Timeout error.
Definition errors.hpp:88
A part of the API is not supported by the connected robot.
Definition errors.hpp:120
@ kBody
IMU sensor on the body of the robot.
@ kDownwardsLidar
Downwards facing lidar.
@ kFront
The front lidar 3D sensor.
ControlSource
Control source message containing the control source of the robot.
Definition control_source.hpp:28
@ kBody
Magnetic field sensor on the body of the robot.
Command message containing the command to send to the robot.
Definition command.hpp:26
Action action
Construct action.
Definition command.hpp:41
State reference message containing the pose, velocity and acceleration of the robot.
Definition state_reference.hpp:31
TranslationMode translation_mode
Translation mode of the state reference.
Definition state_reference.hpp:74
OrientationMode orientation_mode
Orientation mode of the state reference.
Definition state_reference.hpp:79