remote_interface.cc
Go to the documentation of this file.
1 /*
2  * This file is part of the rc_dynamics_api package.
3  *
4  * Copyright (c) 2017 Roboception GmbH
5  * All rights reserved
6  *
7  * Author: Christian Emmerich
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions are met:
11  *
12  * 1. Redistributions of source code must retain the above copyright notice,
13  * this list of conditions and the following disclaimer.
14  *
15  * 2. Redistributions in binary form must reproduce the above copyright notice,
16  * this list of conditions and the following disclaimer in the documentation
17  * and/or other materials provided with the distribution.
18  *
19  * 3. Neither the name of the copyright holder nor the names of its contributors
20  * may be used to endorse or promote products derived from this software without
21  * specific prior written permission.
22  *
23  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
24  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
27  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
28  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
29  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
30  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
31  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
32  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
33  * POSSIBILITY OF SUCH DAMAGE.
34  */
35 
36 #include "remote_interface.h"
38 
39 #include "json.hpp"
40 #include <cpr/cpr.h>
41 #include <regex>
42 
43 using namespace std;
45 
46 namespace rc
47 {
48 namespace dynamics
49 {
50 // Definitions of static const members
51 const std::string RemoteInterface::State::IDLE = "IDLE";
52 const std::string RemoteInterface::State::RUNNING = "RUNNING";
53 const std::string RemoteInterface::State::STOPPING = "STOPPING";
54 const std::string RemoteInterface::State::FATAL = "FATAL";
55 const std::string RemoteInterface::State::WAITING_FOR_INS = "WAITING_FOR_INS";
56 const std::string RemoteInterface::State::WAITING_FOR_INS_AND_SLAM = "WAITING_FOR_INS_AND_SLAM";
57 const std::string RemoteInterface::State::WAITING_FOR_SLAM = "WAITING_FOR_SLAM";
58 const std::string RemoteInterface::State::RUNNING_WITH_SLAM = "RUNNING_WITH_SLAM";
59 const std::string RemoteInterface::State::UNKNOWN = "UNKNOWN";
60 
61 string toString(cpr::Response resp)
62 {
63  stringstream s;
64  s << "status code: " << resp.status_code << endl
65  << "url: " << resp.url << endl
66  << "text: " << resp.text << endl
67  << "error: " << resp.error.message;
68  return s.str();
69 }
70 
71 string toString(list<string> list)
72 {
73  stringstream s;
74  s << "[";
75  for (auto it = list.begin(); it != list.end();)
76  {
77  s << *it;
78  if (++it != list.end())
79  {
80  s << ", ";
81  }
82  }
83  s << "]";
84  return s.str();
85 }
86 
87 void handleCPRResponse(cpr::Response r)
88 {
89  switch (r.status_code) {
90  case 200:
91  return;
92  case 429:
94  case 404:
95  throw RemoteInterface::NotAvailable(r.url);
96  default:
97  throw runtime_error(toString(r));
98  }
99 }
100 
101 namespace {
102 
103  vector<int> wait_before_retry = { 50, 200, 500, 1000, 2000};
104 
105  // Wrapper around cpr::Get requests which does retries in case of 429 response
106  cpr::Response cprGetWithRetry(cpr::Url url, cpr::Timeout timeout) {
107  for (int retry : wait_before_retry) {
108  auto response = cpr::Get(url, timeout, cpr::Header{ { "accept", "application/json" }});
109  if (response.status_code == 429) {
110  cout << "WARNING: Got http code 429 (too many requests) on "
111  << url << ". Retrying in " << retry << "ms..." << endl;
112 #ifdef WIN32
113  Sleep(retry);
114 #else
115  usleep(1000 * retry);
116 #endif
117  continue;
118  }
119  return response;
120  }
122  }
123 
124  // Wrapper around cpr::Put requests which does retries in case of 429 response
125  cpr::Response cprPutWithRetry(cpr::Url url, cpr::Timeout timeout, cpr::Body body = cpr::Body{}) {
126 
127  // we need different headers if body is empty or not
128  cpr::Header header;
129  if (body == cpr::Body{}) {
130  header = cpr::Header{ { "accept", "application/json" }};
131  } else {
132  header = cpr::Header{ { "accept", "application/json" }, { "Content-Type", "application/json" }};
133  }
134 
135  for (int retry : wait_before_retry) {
136  auto response = cpr::Put(url, timeout, body, header);
137  if (response.status_code==429) {
138  cout << "WARNING: Got http code 429 (too many requests) on "
139  << url << ". Retrying in " << retry << "ms..." << endl;
140 #ifdef WIN32
141  Sleep(retry);
142 #else
143  usleep(1000 * retry);
144 #endif
145  continue;
146  }
147  return response;
148  }
150  }
151 
152  // Wrapper around cpr::Deletee requests which does retries in case of 429 response
153  cpr::Response cprDeleteWithRetry(cpr::Url url, cpr::Timeout timeout, cpr::Body body = cpr::Body{}) {
154 
155  // we need different headers if body is empty or not
156  cpr::Header header;
157  if (body == cpr::Body{}) {
158  header = cpr::Header{ { "accept", "application/json" }};
159  } else {
160  header = cpr::Header{ { "accept", "application/json" }, { "Content-Type", "application/json" }};
161  }
162 
163  for (int retry : wait_before_retry) {
164  auto response = cpr::Delete(url, timeout, body, header);
165  if (response.status_code==429) {
166  cout << "WARNING: Got http code 429 (too many requests) on "
167  << url << ". Retrying in " << retry << "ms..." << endl;
168 #ifdef WIN32
169  Sleep(retry);
170 #else
171  usleep(1000 * retry);
172 #endif
173  continue;
174  }
175  return response;
176  }
178  }
179 
180 }
181 
188 {
189 public:
190  static shared_ptr<TrackedDataReceiver> create(const string& ip_address, unsigned int& port, const string& stream,
191  shared_ptr<RemoteInterface> creator)
192  {
193  return shared_ptr<TrackedDataReceiver>(new TrackedDataReceiver(ip_address, port, stream, creator));
194  }
195 
197  {
198  try
199  {
200  creator_->deleteDestinationFromStream(stream_, dest_);
201  }
202  catch (exception& e)
203  {
204  cerr << "[TrackedDataReceiver] Could not remove my destination " << dest_ << " for stream type " << stream_
205  << " from rc_visard: " << e.what() << endl;
206  }
207  }
208 
209 protected:
210  TrackedDataReceiver(const string& ip_address, unsigned int& port, const string& stream,
211  shared_ptr<RemoteInterface> creator)
212  : DataReceiver(ip_address, port), dest_(ip_address + ":" + to_string(port)), stream_(stream), creator_(creator)
213  {
214  }
215 
216  string dest_, stream_;
217  shared_ptr<RemoteInterface> creator_;
218 };
219 
220 // map to store already created RemoteInterface objects
221 map<string, RemoteInterface::Ptr> RemoteInterface::remote_interfaces_ = map<string, RemoteInterface::Ptr>();
222 
223 RemoteInterface::Ptr RemoteInterface::create(const string& rc_visard_inet_addrs, unsigned int requests_timeout)
224 {
225  // check if interface is already opened
226  auto found = RemoteInterface::remote_interfaces_.find(rc_visard_inet_addrs);
227  if (found != RemoteInterface::remote_interfaces_.end())
228  {
229  return found->second;
230  }
231 
232  // if not, create it
233  auto new_remote_interface = Ptr(new RemoteInterface(rc_visard_inet_addrs, requests_timeout));
234  RemoteInterface::remote_interfaces_[rc_visard_inet_addrs] = new_remote_interface;
235 
236  return new_remote_interface;
237 }
238 
239 RemoteInterface::RemoteInterface(const string& rc_visard_ip, unsigned int requests_timeout)
240  : visard_addrs_(rc_visard_ip), initialized_(false), visard_version_(0.0),
241  base_url_("http://" + visard_addrs_ + "/api/v1"), timeout_curl_(requests_timeout)
242 {
243  req_streams_.clear();
244  protobuf_map_.clear();
245 
246  // check if given string is a valid IP address
247  if (!isValidIPAddress(rc_visard_ip))
248  {
249  throw invalid_argument("Given IP address is not a valid address: " + rc_visard_ip);
250  }
251 }
252 
254 {
255  try {
257  } catch (exception& e) {
258  cerr << "[RemoteInterface::~RemoteInterface] Could not clean up all previously requested streams: "
259  << e.what() << endl;
260  }
261  for (const auto& s : req_streams_)
262  {
263  if (s.second.size() > 0)
264  {
265  cerr << "[RemoteInterface::~RemoteInterface] Could not stop all previously requested"
266  " streams of type "
267  << s.first << " on rc_visard. Please check "
268  "device manually"
269  " ("
270  << base_url_ << "/datastreams/" << s.first << ")"
271  " for not containing any of the following legacy streams and"
272  " delete them otherwise, e.g. using the swagger UI ("
273  << "http://" + visard_addrs_ + "/api/swagger/)"
274  << ": " << toString(s.second) << endl;
275  }
276  }
277 }
278 
280 {
281  initialized_=false;
282  visard_version_=0.0;
283  req_streams_.clear();
284  protobuf_map_.clear();
285  avail_streams_.clear();
286 
287  cpr::Url url = cpr::Url{ base_url_ + "/system"};
288  auto response = cprGetWithRetry(url, cpr::Timeout{ timeout_curl_ });
289  if (response.status_code == 502) // bad gateway
290  {
291  return false;
292  }
293  handleCPRResponse(response);
294 
295  // initial connection to rc_visard to check if system is ready ...
296  auto get_system = cprGetWithRetry(cpr::Url{ base_url_ + "/system" },
297  cpr::Timeout{ timeout_curl_ });
298  handleCPRResponse(get_system);
299  auto j = json::parse(get_system.text);
300  if (!j["ready"])
301  {
302  return false;
303  }
304 
305  // ... and to get version of rc_visard
306  string version = j["firmware"]["active_image"]["image_version"];
307  std::smatch match;
308  if (std::regex_search(version, match, std::regex("v(\\d+).(\\d+).(\\d+)")))
309  {
310  visard_version_ = stof(match[0].str().substr(1,3));
311  }
312 
313  // ...and to get streams
314  auto get_streams = cprGetWithRetry(cpr::Url{ base_url_ + "/datastreams" },
315  cpr::Timeout{ timeout_curl_ });
316  handleCPRResponse(get_streams);
317 
318  // parse text of response into json object
319  j = json::parse(get_streams.text);
320  for (const auto& stream : j)
321  {
322  avail_streams_.push_back(stream["name"]);
323  protobuf_map_[stream["name"]] = stream["protobuf"];
324  }
325 
326  // return true if system is ready
327  initialized_ = true;
328  return true;
329 }
330 
331 string RemoteInterface::getState(const std::string& node) {
332  cpr::Url url = cpr::Url{ base_url_ + "/nodes/" + node + "/status"};
333  auto response = cprGetWithRetry(url, cpr::Timeout{ timeout_curl_ });
334  handleCPRResponse(response);
335  try
336  {
337  auto j = json::parse(response.text);
338  return j["values"]["state"];
339  } catch (std::domain_error &e) {
340  // in case "state" field does not exist, return UNKNOWN
341  return State::UNKNOWN;
342  }
343 }
344 
346 {
347  return getState("rc_dynamics");
348 }
349 
351 {
352  return getState("rc_slam");
353 }
354 
356 {
357  return getState("rc_stereo_ins");
358 }
359 
360 std::string RemoteInterface::callDynamicsService(std::string service_name)
361 {
362  cpr::Url url = cpr::Url{ base_url_ + "/nodes/rc_dynamics/services/" + service_name };
363  auto response = cprPutWithRetry(url, cpr::Timeout{ timeout_curl_ });
364  handleCPRResponse(response);
365  auto j = json::parse(response.text);
366  std::string entered_state;
367  bool accepted = true;
368 
369  try
370  {
371  const static vector<string> valid_states = {
372  State::IDLE,
375  State::FATAL,
381  };
382  entered_state = j["response"]["current_state"].get<std::string>();
383  if (std::count(valid_states.begin(), valid_states.end(), entered_state) == 0)
384  {
385  // mismatch between rc_slam states and states used in this class?
386  throw InvalidState(entered_state);
387  }
388 
389  accepted = j["response"]["accepted"].get<bool>();
390  }
391  catch (std::logic_error&)
392  {
393  // Maybe old interface version? If so just return the numeric code
394  // as string - it isn't used by the tools using the old interface
395  try
396  {
397  entered_state = std::to_string(j["response"]["enteredState"].get<int>());
398  }
399  catch (std::logic_error&)
400  {
401  // Real problem (may even be unrelated to parsing json. Let the user see what the response is.
402  cerr << "Logic error when parsing the response of a service call to rc_dynamics!\n";
403  cerr << "Service called: " << url << "\n";
404  cerr << "Response:"
405  << "\n";
406  cerr << response.text << "\n";
407  throw;
408  }
409  }
410 
411  if (!accepted)
412  {
413  throw NotAccepted(service_name);
414  }
415 
416  return entered_state;
417 }
418 
420 {
421  return callDynamicsService("restart");
422 }
424 {
425  return callDynamicsService("restart_slam");
426 }
428 {
429  return callDynamicsService("start");
430 }
432 {
433  return callDynamicsService("start_slam");
434 }
436 {
437  return callDynamicsService("stop");
438 }
440 {
441  return callDynamicsService("stop_slam");
442 }
443 
445 {
446  std::string service_name = "reset";
447  cpr::Url url = cpr::Url{ base_url_ + "/nodes/rc_slam/services/" + service_name };
448  auto response = cprPutWithRetry(url, cpr::Timeout{ timeout_curl_ });
449  handleCPRResponse(response);
450  auto j = json::parse(response.text);
451  std::string entered_state;
452  bool accepted = true;
453 
454  try
455  {
456  entered_state = j["response"]["current_state"].get<std::string>();
457  std::vector<std::string> valid_states = { "IDLE", "RUNNING", "FATAL", "WAITING_FOR_DATA",
458  "RESTARTING", "RESETTING", "HALTED" };
459  if (std::count(valid_states.begin(), valid_states.end(), entered_state) == 0)
460  {
461  // mismatch between rc_slam states and states used in this class?
462  throw InvalidState(entered_state);
463  }
464 
465  accepted = j["response"]["accepted"].get<bool>();
466  }
467  catch (std::logic_error& json_exception)
468  {
469  // Maybe old interface version? If so just return the numeric code
470  // as string - it isn't used by the tools using the old interface
471  try
472  {
473  entered_state = std::to_string(j["response"]["enteredState"].get<int>());
474  }
475  catch (std::logic_error& json_exception)
476  {
477  // Real problem (may even be unrelated to parsing json. Let the user see what the response is.
478  cerr << "Logic error when parsing the response of a service call to rc_dynamics!\n";
479  cerr << "Service called: " << url << "\n";
480  cerr << "Response:"
481  << "\n";
482  cerr << response.text << "\n";
483  throw;
484  }
485  }
486 
487  if (!accepted)
488  {
489  throw NotAccepted(service_name);
490  }
491 
492  return entered_state;
493 }
494 
495 RemoteInterface::ReturnCode RemoteInterface::callSlamService(std::string service_name, unsigned int timeout_ms)
496 {
497  cpr::Url url = cpr::Url{ base_url_ + "/nodes/rc_slam/services/" + service_name };
498  auto response = cprPutWithRetry(url, cpr::Timeout{ (int32_t)timeout_ms });
499  handleCPRResponse(response);
500  auto j = json::parse(response.text);
501 
502  ReturnCode return_code;
503 
504  try
505  {
506  return_code.value = j["response"]["return_code"]["value"].get<int>();
507  return_code.message = j["response"]["return_code"]["message"];
508  }
509  catch (std::logic_error& json_exception)
510  {
511  // Real problem (may even be unrelated to parsing json. Let the user see what the response is.
512  cerr << "Logic error when parsing the response of a service call to rc_dynamics!\n";
513  cerr << "Service called: " << url << "\n";
514  cerr << "Response:"
515  << "\n";
516  cerr << response.text << "\n";
517  throw;
518  }
519 
520  return return_code;
521 }
522 
524 {
525  return callSlamService("save_map", timeout_ms);
526 }
528 {
529  return callSlamService("load_map", timeout_ms);
530 }
532 {
533  return callSlamService("remove_map", timeout_ms);
534 }
535 
537 {
538  if (!initialized_ && !checkSystemReady())
539  {
540  throw std::runtime_error("RemoteInterface not properly initialized or rc_visard is not ready. "
541  "Please initialize with method RemoteInterface::checkSystemReady()!");
542  }
543  return avail_streams_;
544 }
545 
546 string RemoteInterface::getPbMsgTypeOfStream(const string& stream)
547 {
548  checkStreamTypeAvailable(stream);
549  return protobuf_map_[stream];
550 }
551 
552 list<string> RemoteInterface::getDestinationsOfStream(const string& stream)
553 {
554  checkStreamTypeAvailable(stream);
555 
556  list<string> destinations;
557 
558  // do get request on respective url (no parameters needed for this simple service call)
559  cpr::Url url = cpr::Url{ base_url_ + "/datastreams/" + stream };
560  auto get = cprGetWithRetry(url, cpr::Timeout{ timeout_curl_ });
561  handleCPRResponse(get);
562 
563  // parse result as json
564  auto j = json::parse(get.text);
565  for (auto dest : j["destinations"])
566  {
567  destinations.push_back(dest.get<string>());
568  }
569  return destinations;
570 }
571 
572 void RemoteInterface::addDestinationToStream(const string& stream, const string& destination)
573 {
574  checkStreamTypeAvailable(stream);
575 
576  // do put request on respective url
577  json js_args;
578  js_args["destination"] = json::array();
579  js_args["destination"].push_back(destination);
580  cpr::Url url = cpr::Url{ base_url_ + "/datastreams/" + stream };
581  auto put = cprPutWithRetry(url, cpr::Timeout{ timeout_curl_ }, cpr::Body{ js_args.dump() });
582  if (put.status_code == 403)
583  {
584  throw TooManyStreamDestinations(json::parse(put.text)["message"].get<string>());
585  }
586  handleCPRResponse(put);
587 
588  // keep track of added destinations
589  req_streams_[stream].push_back(destination);
590 }
591 
592 void RemoteInterface::deleteDestinationFromStream(const string& stream, const string& destination)
593 {
594  checkStreamTypeAvailable(stream);
595 
596  // do delete request on respective url
597  json js_args;
598  js_args["destination"] = json::array();
599  js_args["destination"].push_back(destination);
600  cpr::Url url = cpr::Url{ base_url_ + "/datastreams/" + stream };
601  auto del = cprDeleteWithRetry(url, cpr::Timeout{ timeout_curl_ }, cpr::Body{ js_args.dump() });
602  handleCPRResponse(del);
603 
604  // delete destination also from list of requested streams
605  auto& destinations = req_streams_[stream];
606  auto found = find(destinations.begin(), destinations.end(), destination);
607  if (found != destinations.end())
608  destinations.erase(found);
609 }
610 
611 void RemoteInterface::deleteDestinationsFromStream(const string& stream, const list<string>& destinations)
612 {
613  checkStreamTypeAvailable(stream);
614 
615  // with newer image versions this is the most efficent way, i.e. only one call
616  if (visard_version_ >= 1.600001) {
617 
618  // do delete request on respective url; list of destinationas are given as body
619  json js_destinations = json::array();
620  for (const auto& dest: destinations)
621  {
622  js_destinations.push_back(dest);
623  }
624  json js_args;
625  js_args["destination"] = js_destinations;
626  cpr::Url url = cpr::Url{ base_url_ + "/datastreams/" + stream };
627  auto del = cprDeleteWithRetry(url, cpr::Timeout{ timeout_curl_ }, cpr::Body{ js_args.dump()});
628  handleCPRResponse(del);
629 
630  // with older image versions we have to work around and do several calls
631  } else {
632  for (const auto& dest : destinations)
633  {
634  // do delete request on respective url
635  json js_args;
636  js_args["destination"] = json::array();
637  js_args["destination"].push_back(dest);
638  cpr::Url url = cpr::Url{ base_url_ + "/datastreams/" + stream };
639  auto del = cprDeleteWithRetry(url, cpr::Timeout{ timeout_curl_ }, cpr::Body{ js_args.dump() });
640  handleCPRResponse(del);
641  }
642  }
643 
644  // delete destination also from list of requested streams
645  auto& reqDestinations = req_streams_[stream];
646  for (auto& destination : destinations)
647  {
648  auto found = find(reqDestinations.begin(), reqDestinations.end(), destination);
649  if (found != reqDestinations.end())
650  {
651  reqDestinations.erase(found);
652  }
653  }
654 }
655 
656 namespace
657 {
658 
659 // TODO: find an automatic way to parse Messages from Json
660 // * is possible with protobuf >= 3.0.x
661 // * https://developers.google.com/protocol-buffers/docs/reference/cpp/google.protobuf.util.json_util
662 
663 roboception::msgs::Trajectory toProtobufTrajectory(const json js)
664 {
665  roboception::msgs::Trajectory pb_traj;
666 
667  json::const_iterator js_it;
668  if ((js_it = js.find("parent")) != js.end())
669  {
670  pb_traj.set_parent(js_it.value());
671  }
672  if ((js_it = js.find("name")) != js.end())
673  {
674  pb_traj.set_name(js_it.value());
675  }
676  if ((js_it = js.find("producer")) != js.end())
677  {
678  pb_traj.set_producer(js_it.value());
679  }
680  if ((js_it = js.find("timestamp")) != js.end())
681  {
682  pb_traj.mutable_timestamp()->set_sec(js_it.value()["sec"]); // TODO: sec
683  pb_traj.mutable_timestamp()->set_nsec(js_it.value()["nsec"]); // TODO: nsec
684  }
685  for (const auto& js_pose : js["poses"])
686  {
687  auto pb_pose = pb_traj.add_poses();
688  auto pb_time = pb_pose->mutable_timestamp();
689  pb_time->set_sec(js_pose["timestamp"]["sec"]); // TODO: sec
690  pb_time->set_nsec(js_pose["timestamp"]["nsec"]); // TODO: nsec
691  auto pb_position = pb_pose->mutable_pose()->mutable_position();
692  pb_position->set_x(js_pose["pose"]["position"]["x"]);
693  pb_position->set_y(js_pose["pose"]["position"]["y"]);
694  pb_position->set_z(js_pose["pose"]["position"]["z"]);
695  auto pb_orientation = pb_pose->mutable_pose()->mutable_orientation();
696  pb_orientation->set_x(js_pose["pose"]["orientation"]["x"]);
697  pb_orientation->set_y(js_pose["pose"]["orientation"]["y"]);
698  pb_orientation->set_z(js_pose["pose"]["orientation"]["z"]);
699  pb_orientation->set_w(js_pose["pose"]["orientation"]["w"]);
700  }
701  return pb_traj;
702 }
703 
704 roboception::msgs::Frame toProtobufFrame(const json& js, bool producer_optional)
705 {
706  roboception::msgs::Frame pb_frame;
707 
708  pb_frame.set_parent(js.at("parent").get<string>());
709  pb_frame.set_name(js.at("name").get<string>());
710 
711  // if producer is optional don't throw exception if not found
712  if (!producer_optional || js.find("producer") != js.end())
713  {
714  pb_frame.set_producer(js.at("producer").get<string>());
715  }
716 
717  auto js_pose = js.at("pose");
718  auto pb_pose = pb_frame.mutable_pose();
719  pb_pose->mutable_timestamp()->set_sec(js_pose.at("timestamp").at("sec"));
720  pb_pose->mutable_timestamp()->set_nsec(js_pose.at("timestamp").at("nsec"));
721 
722  auto js_pose_pose = js_pose.at("pose");
723  auto pb_pose_pose = pb_pose->mutable_pose();
724  pb_pose_pose->mutable_position()->set_x(js_pose_pose.at("position").at("x"));
725  pb_pose_pose->mutable_position()->set_y(js_pose_pose.at("position").at("y"));
726  pb_pose_pose->mutable_position()->set_z(js_pose_pose.at("position").at("z"));
727  pb_pose_pose->mutable_orientation()->set_w(js_pose_pose.at("orientation").at("w"));
728  pb_pose_pose->mutable_orientation()->set_x(js_pose_pose.at("orientation").at("x"));
729  pb_pose_pose->mutable_orientation()->set_y(js_pose_pose.at("orientation").at("y"));
730  pb_pose_pose->mutable_orientation()->set_z(js_pose_pose.at("orientation").at("z"));
731 
732  return pb_frame;
733 }
734 
735 } //anonymous namespace
736 
737 roboception::msgs::Trajectory RemoteInterface::getSlamTrajectory(const TrajectoryTime& start, const TrajectoryTime& end, unsigned int timeout_ms)
738 {
739  // convert time specification to json obj
740  json js_args, js_time, js_start_time, js_end_time;
741  js_start_time["sec"] = start.getSec();
742  js_start_time["nsec"] = start.getNsec();
743  js_end_time["sec"] = end.getSec();
744  js_end_time["nsec"] = end.getNsec();
745  js_args["args"]["start_time"] = js_start_time;
746  js_args["args"]["end_time"] = js_end_time;
747  if (start.isRelative())
748  js_args["args"]["start_time_relative"] = true;
749  if (end.isRelative())
750  js_args["args"]["end_time_relative"] = true;
751 
752  // put request on slam module to get the trajectory
753  cpr::Url url = cpr::Url{ base_url_ + "/nodes/rc_slam/services/get_trajectory" };
754  auto get = cprPutWithRetry(url, cpr::Timeout{ (int32_t)timeout_ms }, cpr::Body{ js_args.dump() });
755  handleCPRResponse(get);
756 
757  auto js = json::parse(get.text)["response"]["trajectory"];
758  return toProtobufTrajectory(js);
759 }
760 
761 roboception::msgs::Frame RemoteInterface::getCam2ImuTransform(unsigned int timeout_ms) {
762 
763  // put request on dynamics module to get the cam2imu transfrom
764  cpr::Url url = cpr::Url{ base_url_ + "/nodes/rc_dynamics/services/get_cam2imu_transform" };
765  auto get = cprPutWithRetry(url, cpr::Timeout{ (int32_t)timeout_ms });
766  handleCPRResponse(get);
767 
768  auto js = json::parse(get.text)["response"];
769  return toProtobufFrame(js, true);
770 }
771 
772 DataReceiver::Ptr RemoteInterface::createReceiverForStream(const string& stream, const string& dest_interface,
773  unsigned int dest_port)
774 {
775  checkStreamTypeAvailable(stream);
776 
777  // figure out local inet address for streaming
778  string dest_address;
779  if (!getThisHostsIP(dest_address, visard_addrs_, dest_interface))
780  {
781  stringstream msg;
782  msg << "Could not infer a valid IP address "
783  "for this host as the destination of the stream! "
784  "Given network interface specification was '"
785  << dest_interface << "'.";
786  throw invalid_argument(msg.str());
787  }
788 
789  // create data receiver with port as specified
790  DataReceiver::Ptr receiver = TrackedDataReceiver::create(dest_address, dest_port, stream, shared_from_this());
791 
792  // do REST-API call requesting a UDP stream from rc_visard device
793  string destination = dest_address + ":" + to_string(dest_port);
794  addDestinationToStream(stream, destination);
795 
796  // waiting for first message; we set a long timeout for receiving data
797  unsigned int initial_timeOut = 5000;
798  receiver->setTimeout(initial_timeOut);
799  if (!receiver->receive(protobuf_map_[stream]))
800  {
801  // we did not receive any message; check why, e.g. dynamics not in correct state?
802  string current_state = getDynamicsState();
803  std::vector<std::string> valid_states = { "RUNNING", "RUNNING_WITH_SLAM" };
804  if (std::count(valid_states.begin(), valid_states.end(), current_state) == 0)
805  {
806  throw DynamicsNotRunning(current_state);
807  }
808 
809  // in other cases we cannot tell, what's the reason
810  throw UnexpectedReceiveTimeout(initial_timeOut);
811  }
812 
813  // stream established, prepare everything for normal pose receiving
814  receiver->setTimeout(100);
815  return receiver;
816 }
817 
819 {
820  // for each stream type stop all previously requested streams
821  for (auto const& s : req_streams_)
822  {
823  if (!s.second.empty())
824  {
825  deleteDestinationsFromStream(s.first, s.second);
826  }
827  }
828 }
829 
831 {
832  if (!initialized_ && !checkSystemReady())
833  {
834  throw std::runtime_error("RemoteInterface not properly initialized or rc_visard is not ready. "
835  "Please initialize with method RemoteInterface::checkSystemReady()!");
836  }
837  auto found = find(avail_streams_.begin(), avail_streams_.end(), stream);
838  if (found == avail_streams_.end())
839  {
840  stringstream msg;
841  msg << "Stream of type '" << stream << "' is not available on rc_visard " << visard_addrs_;
842  throw invalid_argument(msg.str());
843  }
844 }
845 }
846 }
Simple remote interface to access the dynamic state estimates of an rc_visard device as data streams...
std::string getState(const std::string &node)
std::map< std::string, std::list< std::string > > req_streams_
roboception::msgs::Trajectory getSlamTrajectory(const TrajectoryTime &start=TrajectoryTime::RelativeToStart(), const TrajectoryTime &end=TrajectoryTime::RelativeToEnd(), unsigned int timeout_ms=0)
Returns the Slam trajectory from the sensor.
roboception::msgs::Frame getCam2ImuTransform(unsigned int timeout_ms=0)
Returns the transformation from camera to IMU coordinate frame.
std::string stop()
Stops rc_dynamics module.
std::shared_ptr< RemoteInterface > Ptr
std::string getPbMsgTypeOfStream(const std::string &stream)
Returns the name of the protobuf message class that corresponds to a given data stream and is require...
float visard_version_
rc_visard&#39;s firmware version as double, i.e. major.minor, e.g. 1.6
void checkStreamTypeAvailable(const std::string &stream)
void deleteDestinationsFromStream(const std::string &stream, const std::list< std::string > &destinations)
Deletes given destinations from a stream, i.e.
long getNsec() const
std::shared_ptr< DataReceiver > Ptr
Definition: data_receiver.h:72
std::list< std::string > getDestinationsOfStream(const std::string &stream)
Returns a list of all destinations registered to the specified rc_dynamics stream.
long getSec() const
Thrown if rc_dynamics is requested to receive dynamics data but component is not running.
static const std::string STOPPING
Intermediate state while transitioning to IDLE (e.g. from RUNNING)
TrackedDataReceiver(const string &ip_address, unsigned int &port, const string &stream, shared_ptr< RemoteInterface > creator)
std::string resetSlam()
Resets the SLAM module The Stereo INS will keep running, if it is.
std::string getStereoInsState()
Returns the current state of rc_stereo_ins module.
static const std::string RUNNING_WITH_SLAM
Stereo INS and SLAM are running.
DataReceiver::Ptr createReceiverForStream(const std::string &stream, const std::string &dest_interface="", unsigned int dest_port=0)
Convenience method that automatically.
bool initialized_
indicates if remote_interface was initialized properly at least once, see checkSystemReady() ...
static const std::string WAITING_FOR_INS_AND_SLAM
Waiting for IMU data, will proceed to WAITING_FOR_SLAM.
Thrown if a service call is not accepted.
std::list< std::string > getAvailableStreams()
Returns a list all available streams on rc_visard.
bool getThisHostsIP(string &this_hosts_ip, const string &other_hosts_ip, const string &network_interface)
Definition: net_utils.cc:236
bool checkSystemReady()
Connects with rc_visard and checks the system state of the rc_visard device.
std::string restart()
Restarts the rc_dynamics module to Stereo INS only mode.
static const std::string FATAL
An error has occured. May be resolvable by stopping.
static shared_ptr< TrackedDataReceiver > create(const string &ip_address, unsigned int &port, const string &stream, shared_ptr< RemoteInterface > creator)
void deleteDestinationFromStream(const std::string &stream, const std::string &destination)
Deletes a destination from a stream, i.e.
A simple receiver object for handling data streamed by rc_visard&#39;s rc_dynamics module.
Definition: data_receiver.h:69
bool isValidIPAddress(const std::string &ip)
Checks if given string is a valid IP address.
Definition: net_utils.cc:279
ReturnCode removeSlamMap(unsigned int timeout_ms=0)
Removes the SLAM map on the sensor.
static const std::string WAITING_FOR_INS
Waiting for IMU data, will proceed to RUNNING.
Thrown if the current_state response of the dynamics service does not correspond to those in the Stat...
std::string restartSlam()
Restarts the rc_dynamics module to SLAM mode.
Exception handling cases where actually everything should be fine and rc_visard&#39;s dynamic state estim...
void addDestinationToStream(const std::string &stream, const std::string &destination)
Adds a destination to a stream, i.e.
std::string stopSlam()
Stops only the SLAM module (via the rc_dynamics module).
shared_ptr< RemoteInterface > creator_
Thrown if too many streams are running already on rc_visard.
std::string getSlamState()
Returns the current state of rc_slam module.
Thrown if a REST API call is rejected because of too many requests.
ReturnCode callSlamService(std::string service_name, unsigned int timeout_ms=0)
call slam services which have a return code with value and message
ReturnCode saveSlamMap(unsigned int timeout_ms=0)
Saves the SLAM map on the sensor.
ReturnCode loadSlamMap(unsigned int timeout_ms=0)
Loads the SLAM map on the sensor.
std::string getDynamicsState()
Returns the current state of rc_dynamics module.
static const std::string IDLE
Not yet started or stopped.
int value
suceess >= 0, failure < 0
std::string callDynamicsService(std::string service_name)
Common functionality for start(), startSlam(), stop(), ...
static const std::string UNKNOWN
State of component is unknown, e.g. not yet reported.
nlohmann::json json
string toString(cpr::Response resp)
void handleCPRResponse(cpr::Response r)
std::string startSlam()
Sets rc_dynamics module to running state.
static const std::string RUNNING
Stereo INS is running.
bool isRelative() const
std::list< std::string > avail_streams_
static const std::string WAITING_FOR_SLAM
Stereo INS is running, waiting for SLAM data, will proceed to RUNNING_WITH_SLAM.
std::map< std::string, std::string > protobuf_map_
Represents a time stamp to query the trajectory of rcvisard&#39;s slam module.
string toString(list< string > list)
std::string start()
Sets rc_dynamics module to running state.
Thrown if a REST API call is rejected because of 404; i.e. URL not found.
Class for data stream receivers that are created by this remote interface in order to keep track of c...


rc_dynamics_api
Author(s): Heiko Hirschmueller , Christian Emmerich , Felix Endres
autogenerated on Sat Mar 6 2021 03:53:19