MRPT  2.0.2
CVelodyneScanner.cpp
Go to the documentation of this file.
1 /* +------------------------------------------------------------------------+
2  | Mobile Robot Programming Toolkit (MRPT) |
3  | https://www.mrpt.org/ |
4  | |
5  | Copyright (c) 2005-2020, Individual contributors, see AUTHORS file |
6  | See: https://www.mrpt.org/Authors - All rights reserved. |
7  | Released under BSD License. See: https://www.mrpt.org/License |
8  +------------------------------------------------------------------------+ */
9 
10 #include "hwdrivers-precomp.h" // Precompiled headers
11 
12 #include <mrpt/comms/net_utils.h>
17 #include <mrpt/system/datetime.h> // timeDifference
18 #include <mrpt/system/filesystem.h>
19 #include <thread>
20 
21 // socket's hdrs:
22 #ifdef _WIN32
23 #define _WINSOCK_DEPRECATED_NO_WARNINGS
24 #if defined(_WIN32_WINNT) && (_WIN32_WINNT < 0x600)
25 #undef _WIN32_WINNT
26 #define _WIN32_WINNT 0x600 // Minimum: Windows Vista (required to pollfd)
27 #endif
28 
29 #include <winsock2.h>
30 using socklen_t = int;
31 
32 #if defined(_MSC_VER)
33 #pragma comment(lib, "WS2_32.LIB")
34 #endif
35 
36 #else
37 #define INVALID_SOCKET (-1)
38 #include <arpa/inet.h>
39 #include <fcntl.h>
40 #include <netdb.h>
41 #include <netinet/in.h>
42 #include <netinet/tcp.h>
43 #include <poll.h>
44 #include <sys/ioctl.h>
45 #include <sys/socket.h>
46 #include <sys/time.h> // gettimeofday()
47 #include <sys/types.h>
48 #include <unistd.h>
49 #include <cerrno>
50 #endif
51 
52 #if MRPT_HAS_LIBPCAP
53 #include <pcap.h>
54 #if !defined(PCAP_NETMASK_UNKNOWN) // for older pcap versions
55 #define PCAP_NETMASK_UNKNOWN 0xffffffff
56 #endif
57 #endif
58 
59 using namespace mrpt;
60 using namespace mrpt::hwdrivers;
61 using namespace mrpt::obs;
62 using namespace std;
63 
65 
68 
71 {
72  static CVelodyneScanner::model_properties_list_t modelProperties;
73  static bool init = false;
74  if (!init)
75  {
76  init = true;
77  modelProperties[CVelodyneScanner::VLP16].maxRange = 120.0;
78  modelProperties[CVelodyneScanner::HDL32].maxRange = 70.0;
79  modelProperties[CVelodyneScanner::HDL64].maxRange = 120.0;
80  }
81  return modelProperties;
82 }
84 {
87  std::string s;
88  for (auto it : lst)
89  s += mrpt::format(
90  "`%s`,",
92  it.first)
93  .c_str());
94  return s;
95 }
96 
98  : m_device_ip(""),
99 
100  m_last_pos_packet_timestamp(INVALID_TIMESTAMP),
101 
102  m_hDataSock(INVALID_SOCKET),
103  m_hPositionSock(INVALID_SOCKET),
104  m_last_gps_rmc_age(INVALID_TIMESTAMP)
105 
106 {
107  m_sensorLabel = "Velodyne";
108 
109 #if MRPT_HAS_LIBPCAP
110  m_pcap_bpf_program = new bpf_program[1];
111 #endif
112 
113 #ifdef _WIN32
114  // Init the WinSock Library:
115  WORD wVersionRequested = MAKEWORD(2, 0);
116  WSADATA wsaData;
117  if (WSAStartup(wVersionRequested, &wsaData))
118  THROW_EXCEPTION("Error calling WSAStartup");
119 #endif
120 }
121 
123 {
124  this->close();
125 #ifdef _WIN32
126  WSACleanup();
127 #endif
128 
129 #if MRPT_HAS_LIBPCAP
130  delete[] reinterpret_cast<bpf_program*>(m_pcap_bpf_program);
131  m_pcap_bpf_program = nullptr;
132 #endif
133 }
134 
136  const std::string& velodyne_xml_calib_file_path)
137 {
138  return m_velodyne_calib.loadFromXMLFile(velodyne_xml_calib_file_path);
139 }
140 
142  const mrpt::config::CConfigFileBase& cfg, const std::string& sect)
143 {
144  MRPT_START
145 
147  MRPT_LOAD_HERE_CONFIG_VAR(device_ip, string, m_device_ip, cfg, sect);
148  MRPT_LOAD_HERE_CONFIG_VAR(pcap_input, string, m_pcap_input_file, cfg, sect);
150  pcap_output, string, m_pcap_output_file, cfg, sect);
152  pcap_read_once, bool, m_pcap_read_once, cfg, sect);
154  pcap_read_fast, bool, m_pcap_read_fast, cfg, sect);
156  pcap_read_full_scan_delay_ms, double, m_pcap_read_full_scan_delay_ms,
157  cfg, sect);
159  pcap_repeat_delay, double, m_pcap_repeat_delay, cfg, sect);
161  pos_packets_timing_timeout, double, m_pos_packets_timing_timeout, cfg,
162  sect);
164  pos_packets_min_period, double, m_pos_packets_min_period, cfg, sect);
165 
166  using mrpt::DEG2RAD;
168  cfg.read_float(sect, "pose_x", 0), cfg.read_float(sect, "pose_y", 0),
169  cfg.read_float(sect, "pose_z", 0),
170  DEG2RAD(cfg.read_float(sect, "pose_yaw", 0)),
171  DEG2RAD(cfg.read_float(sect, "pose_pitch", 0)),
172  DEG2RAD(cfg.read_float(sect, "pose_roll", 0)));
173 
174  std::string calibration_file;
175  MRPT_LOAD_CONFIG_VAR(calibration_file, string, cfg, sect);
176  if (!calibration_file.empty()) this->loadCalibrationFile(calibration_file);
177 
178  // Check validity:
180  if (lstModels.find(m_model) == lstModels.end())
181  {
183  "Unrecognized `model` parameter: `%u` . Known values are: %s",
184  static_cast<unsigned int>(m_model),
186  }
187 
188  // Optional HTTP-based settings:
189  MRPT_LOAD_HERE_CONFIG_VAR(rpm, int, m_lidar_rpm, cfg, sect);
191  cfg.read_enum<return_type_t>(sect, "return_type", m_lidar_return);
192 
193  MRPT_END
194 }
195 
199 {
200  try
201  {
202  ASSERTMSG_(m_initialized, "initialize() has not been called yet!");
203 
204  // Init with empty smart pointers:
207 
208  // Try to get data & pos packets:
211  mrpt::system::TTimeStamp data_pkt_timestamp, pos_pkt_timestamp;
212  bool rx_all_ok = this->receivePackets(
213  data_pkt_timestamp, rx_pkt, pos_pkt_timestamp, rx_pos_pkt);
214 
215  if (!rx_all_ok)
216  {
217  // PCAP EOF:
218  return false;
219  }
220 
221  if (pos_pkt_timestamp != INVALID_TIMESTAMP)
222  {
225  gps_obs->sensorLabel = this->m_sensorLabel + std::string("_GPS");
226  gps_obs->sensorPose = m_sensorPose;
227 
228  gps_obs->originalReceivedTimestamp = pos_pkt_timestamp;
229 
230  bool parsed_ok = CGPSInterface::parse_NMEA(
231  std::string(rx_pos_pkt.NMEA_GPRMC), *gps_obs);
232  const mrpt::obs::gnss::Message_NMEA_RMC* msg_rmc =
233  gps_obs->getMsgByClassPtr<mrpt::obs::gnss::Message_NMEA_RMC>();
234  if (!parsed_ok || !msg_rmc || msg_rmc->fields.validity_char != 'A')
235  {
236  gps_obs->has_satellite_timestamp = false;
237  gps_obs->timestamp = pos_pkt_timestamp;
238  }
239  else
240  {
241  // We have live GPS signal and a recent RMC frame:
242  m_last_gps_rmc_age = pos_pkt_timestamp;
243  m_last_gps_rmc = *msg_rmc;
244  }
245  outGPS = gps_obs; // save in output object
246  }
247 
248  if (data_pkt_timestamp != INVALID_TIMESTAMP)
249  {
250  m_state = ssWorking;
251 
252  // Break into a new observation object when the azimuth passes
253  // 360->0 deg:
254  const uint16_t rx_pkt_start_angle = rx_pkt.blocks[0].rotation();
255  // const uint16_t rx_pkt_end_angle =
256  // rx_pkt.blocks[CObservationVelodyneScan::BLOCKS_PER_PACKET-1].rotation;
257 
258  // Return the observation as done when a complete 360 deg scan is
259  // ready:
260  if (m_rx_scan && !m_rx_scan->scan_packets.empty())
261  {
262  if ((rx_pkt_start_angle <
263  m_rx_scan->scan_packets.rbegin()->blocks[0].rotation()) ||
265  {
266  outScan = m_rx_scan;
267  m_rx_scan.reset();
268 
269  if (m_pcap)
270  {
271  // Keep the reader from blowing through the file.
272  if (!m_pcap_read_fast)
273  std::this_thread::sleep_for(
274  std::chrono::duration<double, std::milli>(
276  }
277  }
278  }
279 
280  // Create smart ptr to new in-progress observation:
281  if (!m_rx_scan)
282  {
284  m_rx_scan->sensorLabel =
285  this->m_sensorLabel + std::string("_SCAN");
286  m_rx_scan->sensorPose = m_sensorPose;
287  m_rx_scan->calibration =
288  m_velodyne_calib; // Embed a copy of the calibration info
289 
290  {
291  const model_properties_list_t& lstModels =
293  auto it = lstModels.find(this->m_model);
294  if (it != lstModels.end())
295  { // Model params:
296  m_rx_scan->maxRange = it->second.maxRange;
297  }
298  else // default params:
299  {
300  m_rx_scan->maxRange = 120.0;
301  }
302  }
303  }
304 
305  // For the first packet, set timestamp:
306  if (m_rx_scan->scan_packets.empty())
307  {
308  m_rx_scan->originalReceivedTimestamp = data_pkt_timestamp;
309  // Using GPS, if available:
310  if (m_last_gps_rmc.fields.validity_char == 'A' &&
312  m_last_gps_rmc_age, data_pkt_timestamp) <
314  {
315  // Each Velodyne data packet has a timestamp field,
316  // with the number of us since the top of the current HOUR:
317  // take the date and time from the GPS, then modify minutes
318  // and seconds from data pkt:
319  const mrpt::system::TTimeStamp gps_tim =
322 
323  mrpt::system::TTimeParts tim_parts;
324  mrpt::system::timestampToParts(gps_tim, tim_parts);
325  tim_parts.minute =
326  rx_pkt.gps_timestamp() /*us from top of hour*/ /
327  60000000ul;
328  tim_parts.second =
329  (rx_pkt.gps_timestamp() /*us from top of hour*/ %
330  60000000ul) *
331  1e-6;
332 
333  const mrpt::system::TTimeStamp data_pkt_tim =
335 
336  m_rx_scan->timestamp = data_pkt_tim;
337  m_rx_scan->has_satellite_timestamp = true;
338  }
339  else
340  {
341  m_rx_scan->has_satellite_timestamp = false;
342  m_rx_scan->timestamp = data_pkt_timestamp;
343  }
344  }
345 
346  // Accumulate pkts in the observation object:
347  m_rx_scan->scan_packets.push_back(rx_pkt);
348  }
349 
350  return true;
351  }
352  catch (exception& e)
353  {
354  cerr << "[CVelodyneScanner::getObservation] Returning false due to "
355  "exception: "
356  << endl;
357  cerr << e.what() << endl;
358  return false;
359  }
360 }
361 
363 {
365  CObservationGPS::Ptr obs_gps;
366 
367  if (getNextObservation(obs, obs_gps))
368  {
369  m_state = ssWorking;
370  if (obs) appendObservation(obs);
371  if (obs_gps) appendObservation(obs_gps);
372  }
373  else
374  {
375  m_state = ssError;
376  cerr << "ERROR receiving data from Velodyne devic!" << endl;
377  }
378 }
379 
380 /** Tries to initialize the sensor, after setting all the parameters with a call
381  * to loadConfig.
382  * \exception This method must throw an exception with a descriptive message if
383  * some critical error is found. */
385 {
386  this->close();
387 
388  // (0) Preparation:
389  // --------------------------------
390  // Make sure we have calibration data:
391  if (m_velodyne_calib.empty())
392  {
393  // Try to load default data:
394  m_velodyne_calib = VelodyneCalibration::LoadDefaultCalibration(
396  m_model));
397  if (m_velodyne_calib.empty())
399  "Could not find default calibration data for the given LIDAR "
400  "`model` name. Please, specify a valid `model` or load a valid "
401  "XML configuration file first.");
402  }
403 
404  // online vs off line operation??
405  // -------------------------------
406  if (m_pcap_input_file.empty())
407  { // Online
408 
409  if (m_lidar_rpm > 0)
410  {
411  if (!setLidarRPM(m_lidar_rpm))
412  THROW_EXCEPTION("Error in setLidarRPM();");
413  }
414  if (m_lidar_return != UNCHANGED)
415  {
417  THROW_EXCEPTION("Error in setLidarReturnType();");
418  }
419 
420  // (1) Create LIDAR DATA socket
421  // --------------------------------
422  if (INVALID_SOCKET == (m_hDataSock = socket(PF_INET, SOCK_DGRAM, 0)))
424  "Error creating UDP socket:\n%s",
426 
427  struct sockaddr_in bindAddr;
428  memset(&bindAddr, 0, sizeof(bindAddr));
429  bindAddr.sin_family = AF_INET;
430  bindAddr.sin_port = htons(VELODYNE_DATA_UDP_PORT);
431  bindAddr.sin_addr.s_addr = INADDR_ANY;
432 
433  if (int(INVALID_SOCKET) ==
434  ::bind(
435  m_hDataSock, (struct sockaddr*)(&bindAddr), sizeof(sockaddr)))
437 
438 #ifdef _WIN32
439  unsigned long non_block_mode = 1;
440  if (ioctlsocket(m_hDataSock, FIONBIO, &non_block_mode))
442  "Error entering non-blocking mode with ioctlsocket().");
443 #else
444  int oldflags = fcntl(m_hDataSock, F_GETFL, 0);
445  if (oldflags == -1)
446  THROW_EXCEPTION("Error retrieving fcntl() of socket.");
447  oldflags |= O_NONBLOCK | FASYNC;
448  if (-1 == fcntl(m_hDataSock, F_SETFL, oldflags))
449  THROW_EXCEPTION("Error entering non-blocking mode with fcntl();");
450 #endif
451 
452  // (2) Create LIDAR POSITION socket
453  // --------------------------------
454  if (INVALID_SOCKET ==
455  (m_hPositionSock = socket(PF_INET, SOCK_DGRAM, 0)))
457  "Error creating UDP socket:\n%s",
459 
460  bindAddr.sin_port = htons(VELODYNE_POSITION_UDP_PORT);
461 
462  if (int(INVALID_SOCKET) == ::bind(
464  (struct sockaddr*)(&bindAddr),
465  sizeof(sockaddr)))
467 
468 #ifdef _WIN32
469  if (ioctlsocket(m_hPositionSock, FIONBIO, &non_block_mode))
471  "Error entering non-blocking mode with ioctlsocket().");
472 #else
473  oldflags = fcntl(m_hPositionSock, F_GETFL, 0);
474  if (oldflags == -1)
475  THROW_EXCEPTION("Error retrieving fcntl() of socket.");
476  oldflags |= O_NONBLOCK | FASYNC;
477  if (-1 == fcntl(m_hPositionSock, F_SETFL, oldflags))
478  THROW_EXCEPTION("Error entering non-blocking mode with fcntl();");
479 #endif
480  }
481  else
482  { // Offline:
483 #if MRPT_HAS_LIBPCAP
484  char errbuf[PCAP_ERRBUF_SIZE];
485 
486  if (m_pcap_verbose)
487  printf(
488  "\n[CVelodyneScanner] Opening PCAP file \"%s\"\n",
489  m_pcap_input_file.c_str());
490  if ((m_pcap = pcap_open_offline(m_pcap_input_file.c_str(), errbuf)) ==
491  nullptr)
492  {
493  THROW_EXCEPTION_FMT("Error opening PCAP file: '%s'", errbuf);
494  }
495 
496  // Build PCAP filter:
497  {
498  std::string filter_str = mrpt::format(
499  "(udp dst port %d || udp dst port %d)",
502  if (!m_device_ip.empty())
503  filter_str += "&& src host " + m_device_ip;
504 
505  static std::string sMsgError =
506  "[CVelodyneScanner] Error calling pcap_compile: "; // This is
507  // to avoid
508  // the
509  // ill-formed
510  // signature
511  // of
512  // pcap_error()
513  // accepting
514  // "char*",
515  // not
516  // "const
517  // char*"...
518  // sigh
519  if (pcap_compile(
520  reinterpret_cast<pcap_t*>(m_pcap),
521  reinterpret_cast<bpf_program*>(m_pcap_bpf_program),
522  filter_str.c_str(), 1, PCAP_NETMASK_UNKNOWN) < 0)
523  pcap_perror(reinterpret_cast<pcap_t*>(m_pcap), &sMsgError[0]);
524  }
525 
526  m_pcap_file_empty = true;
527  m_pcap_read_count = 0;
528 
529 #else
531  "Velodyne: Reading from PCAP requires building MRPT with libpcap "
532  "support!");
533 #endif
534  }
535 
539 
540  m_initialized = true;
541 }
542 
544 {
545  if (m_hDataSock != INVALID_SOCKET)
546  {
547  shutdown(m_hDataSock, 2); // SD_BOTH );
548 #ifdef _WIN32
549  closesocket(m_hDataSock);
550 #else
552 #endif
553  m_hDataSock = INVALID_SOCKET;
554  }
555 
556  if (m_hPositionSock != INVALID_SOCKET)
557  {
558  shutdown(m_hPositionSock, 2); // SD_BOTH );
559 #ifdef _WIN32
560  closesocket(m_hPositionSock);
561 #else
563 #endif
564  m_hPositionSock = INVALID_SOCKET;
565  }
566 #if MRPT_HAS_LIBPCAP
567  if (m_pcap)
568  {
569  pcap_close(reinterpret_cast<pcap_t*>(m_pcap));
570  m_pcap = nullptr;
571  }
572  if (m_pcap_dumper)
573  {
574  pcap_dump_close(reinterpret_cast<pcap_dumper_t*>(m_pcap_dumper));
575  m_pcap_dumper = nullptr;
576  }
577  if (m_pcap_out)
578  {
579  pcap_close(reinterpret_cast<pcap_t*>(m_pcap_out));
580  m_pcap_out = nullptr;
581  }
582 #endif
583  m_initialized = false;
584 }
585 
586 // Fixed Ethernet headers for PCAP capture --------
587 #if MRPT_HAS_LIBPCAP
588 const uint16_t LidarPacketHeader[21] = {0xffff, 0xffff, 0xffff, 0x7660, 0x0088,
589  0x0000, 0x0008, 0x0045, 0xd204, 0x0000,
590  0x0040, 0x11ff, 0xaab4, 0xa8c0, 0xc801,
591  0xffff, // checksum 0xa9b4 //source ip
592  // 0xa8c0, 0xc801 is
593  // 192.168.1.200
594  0xffff, 0x4009, 0x4009, 0xbe04, 0x0000};
595 const uint16_t PositionPacketHeader[21] = {
596  0xffff, 0xffff, 0xffff, 0x7660, 0x0088, 0x0000, 0x0008, 0x0045, 0xd204,
597  0x0000, 0x0040, 0x11ff, 0xaab4, 0xa8c0, 0xc801, 0xffff, // checksum 0xa9b4
598  // //source ip
599  // 0xa8c0, 0xc801
600  // is 192.168.1.200
601  0xffff, 0x7420, 0x7420, 0x0802, 0x0000};
602 #endif
603 
604 #if defined(_WIN32) && MRPT_HAS_LIBPCAP
605 int gettimeofday(struct timeval* tp, void*)
606 {
607  FILETIME ft;
608  ::GetSystemTimeAsFileTime(&ft);
609  long long t =
610  (static_cast<long long>(ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
611  t -= 116444736000000000LL;
612  t /= 10; // microseconds
613  tp->tv_sec = static_cast<long>(t / 1000000UL);
614  tp->tv_usec = static_cast<long>(t % 1000000UL);
615  return 0;
616 }
617 #endif
618 
620  mrpt::system::TTimeStamp& data_pkt_timestamp,
622  mrpt::system::TTimeStamp& pos_pkt_timestamp,
624 {
625  static_assert(
627  CObservationVelodyneScan::POS_PACKET_SIZE,
628  "Velodyne pos packet: wrong size!");
629  static_assert(
631  CObservationVelodyneScan::PACKET_SIZE,
632  "Velodyne raw packet: wrong size!");
633 
634  bool ret = true; // all ok
635  if (m_pcap)
636  {
638  data_pkt_timestamp, (uint8_t*)&out_data_pkt, pos_pkt_timestamp,
639  (uint8_t*)&out_pos_pkt);
640  }
641  else
642  {
643  data_pkt_timestamp = internal_receive_UDP_packet(
644  m_hDataSock, (uint8_t*)&out_data_pkt,
645  CObservationVelodyneScan::PACKET_SIZE, m_device_ip);
646  pos_pkt_timestamp = internal_receive_UDP_packet(
647  m_hPositionSock, (uint8_t*)&out_pos_pkt,
648  CObservationVelodyneScan::POS_PACKET_SIZE, m_device_ip);
649  }
650 
651 // Optional PCAP dump:
652 #if MRPT_HAS_LIBPCAP
653  // Save to PCAP file?
654  if (!m_pcap_output_file.empty() &&
655  !m_pcap_out) // 1st time: create output file
656  {
657 #if MRPT_HAS_LIBPCAP
658  char errbuf[PCAP_ERRBUF_SIZE];
659 
662  string sFilePostfix = "_";
663  sFilePostfix += format(
664  "%04u-%02u-%02u_%02uh%02um%02us", (unsigned int)parts.year,
665  (unsigned int)parts.month, (unsigned int)parts.day,
666  (unsigned int)parts.hour, (unsigned int)parts.minute,
667  (unsigned int)parts.second);
668  const string sFileName =
671  string(".pcap");
672 
673  m_pcap_out = pcap_open_dead(DLT_EN10MB, 65535);
674  ASSERTMSG_(
675  m_pcap_out != nullptr, "Error creating PCAP live capture handle");
676 
677  printf(
678  "\n[CVelodyneScanner] Writing to PCAP file \"%s\"\n",
679  sFileName.c_str());
680  if ((m_pcap_dumper = pcap_dump_open(
681  reinterpret_cast<pcap_t*>(m_pcap_out), sFileName.c_str())) ==
682  nullptr)
683  {
685  "Error creating PCAP live dumper: '%s'", errbuf);
686  }
687 #else
689  "Velodyne: Writing PCAP files requires building MRPT with libpcap "
690  "support!");
691 #endif
692  }
693 
694  if (m_pcap_out && m_pcap_dumper &&
695  (data_pkt_timestamp != INVALID_TIMESTAMP ||
696  pos_pkt_timestamp != INVALID_TIMESTAMP))
697  {
698  struct pcap_pkthdr header;
699  struct timeval currentTime;
700  gettimeofday(&currentTime, nullptr);
701  std::vector<unsigned char> packetBuffer;
702 
703  // Data pkt:
704  if (data_pkt_timestamp != INVALID_TIMESTAMP)
705  {
706  header.caplen = header.len =
707  CObservationVelodyneScan::PACKET_SIZE + 42;
708  packetBuffer.resize(header.caplen);
709  header.ts = currentTime;
710 
711  memcpy(&(packetBuffer[0]), LidarPacketHeader, 42);
712  memcpy(
713  &(packetBuffer[0]) + 42, (uint8_t*)&out_data_pkt,
714  CObservationVelodyneScan::PACKET_SIZE);
715  pcap_dump(
716  (u_char*)this->m_pcap_dumper, &header, &(packetBuffer[0]));
717  }
718  // Pos pkt:
719  if (pos_pkt_timestamp != INVALID_TIMESTAMP)
720  {
721  header.caplen = header.len =
722  CObservationVelodyneScan::POS_PACKET_SIZE + 42;
723  packetBuffer.resize(header.caplen);
724  header.ts = currentTime;
725 
726  memcpy(&(packetBuffer[0]), PositionPacketHeader, 42);
727  memcpy(
728  &(packetBuffer[0]) + 42, (uint8_t*)&out_pos_pkt,
729  CObservationVelodyneScan::POS_PACKET_SIZE);
730  pcap_dump(
731  (u_char*)this->m_pcap_dumper, &header, &(packetBuffer[0]));
732  }
733  }
734 #endif
735 
736  // Convert from Velodyne's standard little-endian ordering to host byte
737  // ordering (done AFTER saving the pckg as is to pcap above).
738  // 2019-NOV: Removed. Don't do this here, since it's problematic to
739  // "remember" whether internal data is already in big or little endian.
740 
741  // Position packet decimation:
742  if (pos_pkt_timestamp != INVALID_TIMESTAMP)
743  {
746  m_last_pos_packet_timestamp, pos_pkt_timestamp) <
748  {
749  // Ignore this packet
750  pos_pkt_timestamp = INVALID_TIMESTAMP;
751  }
752  else
753  {
754  // Reset time watch:
755  m_last_pos_packet_timestamp = pos_pkt_timestamp;
756  }
757  }
758 
759  return ret;
760 }
761 
762 // static method:
764  platform_socket_t hSocket, uint8_t* out_buffer,
765  const size_t expected_packet_size, const std::string& filter_only_from_IP)
766 {
767  if (hSocket == INVALID_SOCKET)
769  "Error: UDP socket is not open yet! Have you called initialize() "
770  "first?");
771 
772  unsigned long devip_addr = 0;
773  if (!filter_only_from_IP.empty())
774  devip_addr = inet_addr(filter_only_from_IP.c_str());
775 
777 
778  struct pollfd fds[1];
779  fds[0].fd = hSocket;
780  fds[0].events = POLLIN;
781  static const int POLL_TIMEOUT = 1; // (ms)
782 
783  sockaddr_in sender_address;
784  socklen_t sender_address_len = sizeof(sender_address);
785 
786  while (true)
787  {
788  // Unfortunately, the Linux kernel recvfrom() implementation
789  // uses a non-interruptible std::this_thread::sleep_for(ms) when waiting
790  // for data,
791  // which would cause this method to hang if the device is not
792  // providing data. We poll() the device first to make sure
793  // the recvfrom() will not block.
794  //
795  // Note, however, that there is a known Linux kernel bug:
796  //
797  // Under Linux, select() may report a socket file descriptor
798  // as "ready for reading", while nevertheless a subsequent
799  // read blocks. This could for example happen when data has
800  // arrived but upon examination has wrong checksum and is
801  // discarded. There may be other circumstances in which a
802  // file descriptor is spuriously reported as ready. Thus it
803  // may be safer to use O_NONBLOCK on sockets that should not
804  // block.
805 
806  // poll() until input available
807  do
808  {
809  int retval =
810 #if !defined(_WIN32)
811  poll
812 #else
813  WSAPoll
814 #endif
815  (fds, 1, POLL_TIMEOUT);
816  if (retval < 0) // poll() error?
817  {
818  if (errno != EINTR)
820  "Error in UDP poll():\n%s",
822  }
823  if (retval == 0) // poll() timeout?
824  {
825  return INVALID_TIMESTAMP;
826  }
827  if ((fds[0].revents & POLLERR) || (fds[0].revents & POLLHUP) ||
828  (fds[0].revents & POLLNVAL)) // device error?
829  {
831  "Error in UDP poll() (seems Velodyne device error?)");
832  }
833  } while ((fds[0].revents & POLLIN) == 0);
834 
835  // Receive packets that should now be available from the
836  // socket using a blocking read.
837  int nbytes = recvfrom(
838  hSocket, (char*)&out_buffer[0], expected_packet_size, 0,
839  (sockaddr*)&sender_address, &sender_address_len);
840 
841  if (nbytes < 0)
842  {
843  if (errno != EWOULDBLOCK) THROW_EXCEPTION("recvfrom() failed!?!");
844  }
845  else if ((size_t)nbytes == expected_packet_size)
846  {
847  // read successful,
848  // if packet is not from the lidar scanner we selected by IP,
849  // continue otherwise we are done
850  if (!filter_only_from_IP.empty() &&
851  sender_address.sin_addr.s_addr != devip_addr)
852  continue;
853  else
854  break; // done
855  }
856 
857  std::cerr
858  << "[CVelodyneScanner] Warning: incomplete Velodyne packet read: "
859  << nbytes << " bytes\n";
860  }
861 
862  // Average the times at which we begin and end reading. Use that to
863  // estimate when the scan occurred.
865 
867  time1.time_since_epoch().count() / 2 +
868  time2.time_since_epoch().count() / 2));
869 }
870 
872  mrpt::system::TTimeStamp& data_pkt_time, uint8_t* out_data_buffer,
873  mrpt::system::TTimeStamp& pos_pkt_time, uint8_t* out_pos_buffer)
874 {
875 #if MRPT_HAS_LIBPCAP
876  ASSERT_(m_pcap);
877 
878  data_pkt_time = INVALID_TIMESTAMP;
879  pos_pkt_time = INVALID_TIMESTAMP;
880 
881  char errbuf[PCAP_ERRBUF_SIZE];
882  struct pcap_pkthdr* header;
883  const u_char* pkt_data;
884 
885  while (true)
886  {
887  int res;
888  if ((res = pcap_next_ex(
889  reinterpret_cast<pcap_t*>(m_pcap), &header, &pkt_data)) >= 0)
890  {
892 
893  // if packet is not from the lidar scanner we selected by IP,
894  // continue
895  if (pcap_offline_filter(
896  reinterpret_cast<bpf_program*>(m_pcap_bpf_program), header,
897  pkt_data) == 0)
898  {
899  if (m_verbose)
900  std::cout
901  << "[CVelodyneScanner] DEBUG: Filtering out packet #"
902  << m_pcap_read_count << " in PCAP file.\n";
903  continue;
904  }
905 
906  // Determine whether it is a DATA or POSITION packet:
907  m_pcap_file_empty = false;
909  const uint16_t udp_dst_port =
910  ntohs(*reinterpret_cast<const uint16_t*>(pkt_data + 0x24));
911 
913  {
914  if (m_verbose)
915  std::cout << "[CVelodyneScanner] DEBUG: Packet #"
917  << " in PCAP file is POSITION pkt.\n";
918  memcpy(
919  out_pos_buffer, pkt_data + 42,
920  CObservationVelodyneScan::POS_PACKET_SIZE);
921  pos_pkt_time = tim; // success
922  return true;
923  }
924  else if (udp_dst_port == CVelodyneScanner::VELODYNE_DATA_UDP_PORT)
925  {
926  if (m_verbose)
927  std::cout << "[CVelodyneScanner] DEBUG: Packet #"
929  << " in PCAP file is DATA pkt.\n";
930  memcpy(
931  out_data_buffer, pkt_data + 42,
932  CObservationVelodyneScan::PACKET_SIZE);
933  data_pkt_time = tim; // success
934  return true;
935  }
936  else
937  {
938  std::cerr << "[CVelodyneScanner] ERROR: Packet "
940  << " in PCAP file passed the filter but does not "
941  "match expected UDP port numbers! Skipping it.\n";
942  }
943  }
944 
945  if (m_pcap_file_empty) // no data in file?
946  {
947  fprintf(
948  stderr,
949  "[CVelodyneScanner] Maybe the PCAP file is empty? Error %d "
950  "reading Velodyne packet: `%s`\n",
951  res, pcap_geterr(reinterpret_cast<pcap_t*>(m_pcap)));
952  return true;
953  }
954 
955  if (m_pcap_read_once)
956  {
957  if (m_pcap_verbose)
958  printf(
959  "[CVelodyneScanner] INFO: end of file reached -- done "
960  "reading.\n");
961  std::this_thread::sleep_for(250ms);
962  return false;
963  }
964 
965  if (m_pcap_repeat_delay > 0.0)
966  {
967  if (m_pcap_verbose)
968  printf(
969  "[CVelodyneScanner] INFO: end of file reached -- delaying "
970  "%.3f seconds.\n",
972  std::this_thread::sleep_for(
973  std::chrono::duration<double, std::milli>(
974  m_pcap_repeat_delay * 1000.0));
975  }
976 
977  if (m_pcap_verbose)
978  printf("[CVelodyneScanner] INFO: replaying Velodyne dump file.\n");
979 
980  // rewind the file
981  pcap_close(reinterpret_cast<pcap_t*>(m_pcap));
982  if ((m_pcap = pcap_open_offline(m_pcap_input_file.c_str(), errbuf)) ==
983  nullptr)
984  {
985  THROW_EXCEPTION_FMT("Error opening PCAP file: '%s'", errbuf);
986  }
987  m_pcap_file_empty = true; // maybe the file disappeared?
988  } // loop back and try again
989 #else
991  "MRPT needs to be built against libpcap to enable this functionality");
992 #endif
993 }
994 
996 {
997  /* HTTP-based config: http://10.0.0.100/tab/config.html
998  <form name='returns' method="post" action="/cgi/setting">
999  <span>Return&nbsp;Type:&nbsp;</span> <select name="returns"
1000  onchange='javascript:this.form.submit()'>
1001  <option>Strongest</option>
1002  <option>Last</option>
1003  <option>Dual</option>
1004  </select>
1005  </form>
1006  */
1007  MRPT_START
1008  std::string strRet;
1009  switch (ret_type)
1010  {
1011  case STRONGEST:
1012  strRet = "Strongest";
1013  break;
1014  case DUAL:
1015  strRet = "Dual";
1016  break;
1017  case LAST:
1018  strRet = "Last";
1019  break;
1020  case UNCHANGED:
1021  return true;
1022  default:
1023  THROW_EXCEPTION("Invalid value for return type!");
1024  };
1025 
1026  const std::string cmd = mrpt::format("returns=%s", strRet.c_str());
1027  return this->internal_send_http_post(cmd);
1028  MRPT_END
1029 }
1030 
1032 {
1033  /* HTTP-based config: http://10.0.0.100/tab/config.html
1034  <form name='rpm' method="post" action="/cgi/setting">
1035  Motor
1036  &nbsp;RPM:&nbsp;<input type="text" name="rpm" size="5"
1037  style="text-align:right" /><input type="button" value="+"
1038  onclick="javascript:this.form.rpm.value++;this.form.submit()" /><input
1039  type="button" value="-"
1040  onclick="javascript:this.form.rpm.value--;this.form.submit()" />
1041  &nbsp;<input type="submit" value="Set" />
1042  </form>
1043  */
1044 
1045  MRPT_START
1046  const std::string cmd = mrpt::format("rpm=%i", rpm);
1047  return this->internal_send_http_post(cmd);
1048  MRPT_END
1049 }
1050 
1052 {
1053  // laser = on|off
1054  MRPT_START
1055  const std::string cmd = mrpt::format("laser=%s", on ? "on" : "off");
1056  return this->internal_send_http_post(cmd);
1057  MRPT_END
1058 }
1059 
1061 {
1062  // frame publishing | data packet publishing = on|off
1063  MRPT_START
1064  m_return_frames = on;
1065  MRPT_END
1066 }
1067 
1068 bool CVelodyneScanner::internal_send_http_post(const std::string& post_data)
1069 {
1070  MRPT_START
1071 
1072  ASSERTMSG_(
1073  !m_device_ip.empty(), "A device IP address must be specified first!");
1074 
1075  using namespace mrpt::comms::net;
1076 
1077  std::vector<uint8_t> post_out;
1078  string post_err_str;
1079 
1080  int http_rep_code;
1081  mrpt::system::TParameters<string> extra_headers, out_headers;
1082 
1083  extra_headers["Origin"] = mrpt::format("http://%s", m_device_ip.c_str());
1084  extra_headers["Referer"] = mrpt::format("http://%s", m_device_ip.c_str());
1085  extra_headers["Upgrade-Insecure-Requests"] = "1";
1086  extra_headers["Content-Type"] = "application/x-www-form-urlencoded";
1087 
1089  "POST", post_data,
1090  mrpt::format("http://%s/cgi/setting", m_device_ip.c_str()), post_out,
1091  post_err_str, 80 /* port */, string(), string(), // user,pass
1092  &http_rep_code, &extra_headers, &out_headers);
1093 
1094  return mrpt::comms::net::erOk == ret &&
1095  (http_rep_code == 200 || http_rep_code == 204); // OK codes
1096 
1097  MRPT_END
1098 }
std::chrono::duration< rep, period > duration
Definition: Clock.h:24
void timestampToParts(TTimeStamp t, TTimeParts &p, bool localTime=false)
Gets the individual parts of a date/time (days, hours, minutes, seconds) - UTC time or local time...
Definition: datetime.cpp:50
content_t fields
Message content, accesible by individual fields.
bool loadFromXMLFile(const std::string &velodyne_calibration_xml_filename)
Loads calibration from file, in the format supplied by the manufacturer.
#define MRPT_START
Definition: exceptions.h:241
A C++ interface to Velodyne laser scanners (HDL-64, HDL-32, VLP-16), working on Linux and Windows...
void appendObservation(const mrpt::serialization::CSerializable::Ptr &obj)
Like appendObservations() but for just one observation.
mrpt::system::TTimeStamp getAsTimestamp(const mrpt::system::TTimeStamp &date) const
Build an MRPT timestamp with the hour/minute/sec of this structure and the date from the given timest...
static short int VELODYNE_POSITION_UDP_PORT
Default: 8308.
std::map< model_t, TModelProperties > model_properties_list_t
std::chrono::time_point< Clock > time_point
Definition: Clock.h:25
#define THROW_EXCEPTION(msg)
Definition: exceptions.h:67
std::string std::string format(std::string_view fmt, ARGS &&... args)
Definition: format.h:26
std::string m_sensorLabel
See CGenericSensor.
bool getNextObservation(mrpt::obs::CObservationVelodyneScan::Ptr &outScan, mrpt::obs::CObservationGPS::Ptr &outGPS)
Polls the UDP port for incoming data packets.
bool internal_read_PCAP_packet(mrpt::system::TTimeStamp &data_pkt_time, uint8_t *out_data_buffer, mrpt::system::TTimeStamp &pos_pkt_time, uint8_t *out_pos_buffer)
mrpt::system::TTimeStamp buildTimestampFromParts(const mrpt::system::TTimeParts &p)
Builds a timestamp from the parts (Parts are in UTC)
Definition: datetime.cpp:74
bool m_return_frames
Default: true Output whole frames and not data packets.
static std::string getListKnownModels()
Return human-readable string: "`VLP16`,`XXX`,...".
std::string getLastSocketErrorStr()
Returns a description of the last Sockets error.
Definition: net_utils.cpp:480
void close()
Close the UDP sockets set-up in initialize().
bool m_pcap_read_once
Default: false.
mrpt::system::TTimeStamp now()
A shortcut for system::getCurrentTime.
Definition: datetime.h:86
Contains classes for various device interfaces.
void * m_pcap_bpf_program
opaque ptr: bpf_program*
float read_float(const std::string &section, const std::string &name, float defaultValue, bool failIfNotFound=false) const
STL namespace.
std::shared_ptr< mrpt::obs ::CObservationGPS > Ptr
std::string fileNameStripInvalidChars(const std::string &filename, const char replacement_to_invalid_chars='_')
Replace invalid filename chars by underscores (&#39;_&#39;) or any other user-given char. ...
Definition: filesystem.cpp:329
int8_t validity_char
This will be: &#39;A&#39;=OK or &#39;V&#39;=void.
bool m_pcap_read_fast
(Default: false) If false, will use m_pcap_read_full_scan_delay_ms
UTC_time UTCTime
The GPS sensor measured timestamp (in UTC time)
mrpt::system::TTimeStamp m_last_pos_packet_timestamp
char NMEA_GPRMC[72+234]
the full $GPRMC message, as received by Velodyne, terminated with "\r\n\0"
ENUMTYPE read_enum(const std::string &section, const std::string &name, const ENUMTYPE &defaultValue, bool failIfNotFound=false) const
Reads an "enum" value, where the value in the config file can be either a numerical value or the symb...
double m_pcap_repeat_delay
Default: 0 (in seconds)
mrpt::obs::VelodyneCalibration m_velodyne_calib
Device calibration file (supplied by vendor in an XML file)
#define ASSERT_(f)
Defines an assertion mechanism.
Definition: exceptions.h:120
mrpt::Clock::time_point TTimeStamp
A system independent time type, it holds the the number of 100-nanosecond intervals since January 1...
Definition: datetime.h:40
This class allows loading and storing values and vectors of different types from a configuration text...
ERRORCODE_HTTP
Possible returns from a HTTP request.
Definition: net_utils.h:31
double m_pos_packets_min_period
Default: 0.5 seconds.
void loadConfig_sensorSpecific(const mrpt::config::CConfigFileBase &configSource, const std::string &section) override
See the class documentation at the top for expected parameters.
bool setLidarOnOff(bool on)
Switches the LASER on/off (saves energy when not measuring) (via HTTP post interface).
A helper class that can convert an enum value into its textual representation, and viceversa...
constexpr double DEG2RAD(const double x)
Degrees to radians.
nv_oem6_header_t header
Novatel frame: NV_OEM6_BESTPOS.
bool receivePackets(mrpt::system::TTimeStamp &data_pkt_timestamp, mrpt::obs::CObservationVelodyneScan::TVelodyneRawPacket &out_data_pkt, mrpt::system::TTimeStamp &pos_pkt_timestamp, mrpt::obs::CObservationVelodyneScan::TVelodynePositionPacket &out_pos_pkt)
Users normally would prefer calling getNextObservation() instead.
The parts of a date/time (it&#39;s like the standard &#39;tm&#39; but with fractions of seconds).
Definition: datetime.h:49
unsigned int m_pcap_read_count
number of pkts read from the file so far (for debugging)
std::string m_pcap_output_file
Default: "" (do not dump to an offline file)
uint8_t day
Month (1-12)
Definition: datetime.h:53
static const model_properties_list_t & get()
Singleton access.
This namespace contains representation of robot actions and observations.
bool m_pcap_verbose
Default: true Output PCAP Info msgs.
constexpr auto sect
#define ASSERTMSG_(f, __ERROR_MSG)
Defines an assertion mechanism.
Definition: exceptions.h:108
bool setLidarReturnType(return_type_t ret_type)
Changes among STRONGEST, LAST, DUAL return types (via HTTP post interface).
std::string m_pcap_input_file
Default: "" (do not operate from an offline file)
#define IMPLEMENTS_GENERIC_SENSOR(class_name, NameSpace)
This must be inserted in all CGenericSensor classes implementation files:
static bool parse_NMEA(const std::string &cmd_line, mrpt::obs::CObservationGPS &out_obs, const bool verbose=false)
Parses one line of NMEA data from a GPS receiver, and writes the recognized fields (if any) into an o...
void setFramePublishing(bool on)
Switches whole frame (points in a single revolution) on/off publication to data packet publication...
int fprintf(FILE *fil, const char *format,...) noexcept MRPT_printf_format_check(2
An OS-independent version of fprintf.
Definition: os.cpp:408
void initialize() override
Tries to initialize the sensor driver, after setting all the parameters with a call to loadConfig...
double second
Minute (0-59)
Definition: datetime.h:56
#define MRPT_LOAD_CONFIG_VAR( variableName, variableType, configFileObject, sectionNameStr)
An useful macro for loading variables stored in a INI-like file under a key with the same name that t...
void * m_pcap_out
opaque ptr: "pcap_t*"
bool internal_send_http_post(const std::string &post_data)
mrpt::system::TTimeStamp getDateAsTimestamp() const
Build an MRPT timestamp with the year/month/day of this observation.
This is the global namespace for all Mobile Robot Programming Toolkit (MRPT) libraries.
double m_pos_packets_timing_timeout
Default: 30 seconds.
uint8_t minute
Hour (0-23)
Definition: datetime.h:55
A class used to store a 3D pose (a 3D translation + a rotation in 3D).
Definition: CPose3D.h:85
One unit of data from the scanner (the payload of one UDP DATA packet)
bool empty() const
Returns true if no calibration has been loaded yet.
#define MRPT_END
Definition: exceptions.h:245
static short int VELODYNE_DATA_UDP_PORT
Default: 2368.
double m_pcap_read_full_scan_delay_ms
(Default:100 ms) delay after each full scan read from a PCAP log
void * m_pcap
opaque ptr: "pcap_t*"
uint8_t month
The year.
Definition: datetime.h:52
ERRORCODE_HTTP http_request(const string &http_method, const string &http_send_content, const string &url, std::vector< uint8_t > &out_content, string &out_errormsg, int port=80, const string &auth_user=string(), const string &auth_pass=string(), int *out_http_responsecode=nullptr, mrpt::system::TParameters< string > *extra_headers=nullptr, mrpt::system::TParameters< string > *out_headers=nullptr, int timeout_ms=1000)
Generic function for HTTP GET & POST methods.
Definition: net_utils.cpp:69
A set of useful routines for networking.
Definition: net_utils.h:23
#define MRPT_LOAD_HERE_CONFIG_VAR( variableName, variableType, targetVariable, configFileObject, sectionNameStr)
uint8_t hour
Day (1-31)
Definition: datetime.h:54
uint32_t platform_socket_t
Handles for the UDP sockets, or INVALID_SOCKET (-1)
double timeDifference(const mrpt::system::TTimeStamp t_first, const mrpt::system::TTimeStamp t_later)
Returns the time difference from t1 to t2 (positive if t2 is posterior to t1), in seconds...
Definition: datetime.h:123
void * m_pcap_dumper
opaque ptr: "pcap_dumper_t *"
std::shared_ptr< mrpt::obs ::CObservationVelodyneScan > Ptr
void doProcess() override
This method will be invoked at a minimum rate of "process_rate" (Hz)
std::string m_device_ip
Default: "" (no IP-based filtering)
#define THROW_EXCEPTION_FMT(_FORMAT_STRING,...)
Definition: exceptions.h:69
bool loadCalibrationFile(const std::string &velodyne_xml_calib_file_path)
Returns false on error.
For usage when passing a dynamic number of (numeric) arguments to a function, by name.
Definition: TParameters.h:54
static Ptr Create(Args &&... args)
#define INVALID_TIMESTAMP
Represents an invalid timestamp, where applicable.
Definition: datetime.h:43
mrpt::system::TTimeStamp m_last_gps_rmc_age
mrpt::obs::CObservationVelodyneScan::Ptr m_rx_scan
In progress RX scan.
static mrpt::system::TTimeStamp internal_receive_UDP_packet(platform_socket_t hSocket, uint8_t *out_buffer, const size_t expected_packet_size, const std::string &filter_only_from_IP)
model_t m_model
Default: "VLP16".
mrpt::obs::gnss::Message_NMEA_RMC m_last_gps_rmc
void memcpy(void *dest, size_t destSize, const void *src, size_t copyCount) noexcept
An OS and compiler independent version of "memcpy".
bool setLidarRPM(int rpm)
Changes Lidar RPM (valid range: 300-600) (via HTTP post interface).



Page generated by Doxygen 1.8.14 for MRPT 2.0.2 Git: 9b4fd2465 Mon May 4 16:59:08 2020 +0200 at lun may 4 17:26:07 CEST 2020