rplidar_driver.cpp
Go to the documentation of this file.
00001 /*
00002  * Copyright (c) 2014, RoboPeak
00003  * All rights reserved.
00004  *
00005  * Redistribution and use in source and binary forms, with or without 
00006  * modification, are permitted provided that the following conditions are met:
00007  *
00008  * 1. Redistributions of source code must retain the above copyright notice, 
00009  *    this list of conditions and the following disclaimer.
00010  *
00011  * 2. Redistributions in binary form must reproduce the above copyright notice, 
00012  *    this list of conditions and the following disclaimer in the documentation 
00013  *    and/or other materials provided with the distribution.
00014  *
00015  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
00016  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 
00017  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
00018  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 
00019  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
00020  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
00021  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 
00022  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
00023  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
00024  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 
00025  * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
00026  *
00027  */
00028 /*
00029  *  RoboPeak LIDAR System
00030  *  Serial based RPlidar Driver
00031  *
00032  *  Copyright 2009 - 2014 RoboPeak Team
00033  *  http://www.robopeak.com
00034  * 
00035  */
00036 
00037 #include "sdkcommon.h"
00038 #include "hal/abs_rxtx.h"
00039 #include "hal/thread.h"
00040 #include "hal/locker.h"
00041 #include "hal/event.h"
00042 #include "rplidar_driver_serial.h"
00043 
00044 #ifndef min
00045 #define min(a,b)            (((a) < (b)) ? (a) : (b))
00046 #endif
00047 
00048 namespace rp { namespace standalone{ namespace rplidar {
00049 
00050 
00051 // Factory Impl
00052 RPlidarDriver * RPlidarDriver::CreateDriver(_u32 drivertype)
00053 {
00054     switch (drivertype) {
00055     case DRIVER_TYPE_SERIALPORT:
00056         return new RPlidarDriverSerialImpl();
00057     default:
00058         return NULL;
00059     }
00060 }
00061 
00062 
00063 void RPlidarDriver::DisposeDriver(RPlidarDriver * drv)
00064 {
00065     delete drv;
00066 }
00067 
00068 
00069 
00070 // Serial Driver Impl
00071 
00072 RPlidarDriverSerialImpl::RPlidarDriverSerialImpl() 
00073     : _isConnected(false)
00074     , _isScanning(false)
00075 {
00076     _rxtx = rp::hal::serial_rxtx::CreateRxTx();
00077     _cached_scan_node_count = 0;
00078 }
00079 
00080 RPlidarDriverSerialImpl::~RPlidarDriverSerialImpl()
00081 {
00082     // force disconnection
00083     disconnect();
00084 
00085     rp::hal::serial_rxtx::ReleaseRxTx(_rxtx);
00086 }
00087 
00088 u_result RPlidarDriverSerialImpl::connect(const char * port_path, _u32 baudrate, _u32 flag)
00089 {
00090     rp::hal::AutoLocker l(_lock);
00091     if (isConnected()) return RESULT_ALREADY_DONE;
00092 
00093     if (!_rxtx) return RESULT_INSUFFICIENT_MEMORY;
00094 
00095     // establish the serial connection...
00096     if (!_rxtx->bind(port_path, baudrate)  ||  !_rxtx->open()) {
00097         return RESULT_INVALID_DATA;
00098     }
00099 
00100     _rxtx->flush(0);
00101     _isConnected = true;
00102 
00103     return RESULT_OK;
00104 }
00105 
00106 void RPlidarDriverSerialImpl::disconnect()
00107 {
00108     if (!_isConnected) return ;
00109     stop();
00110     _rxtx->close();
00111 }
00112 
00113 bool RPlidarDriverSerialImpl::isConnected()
00114 {
00115     return _isConnected;
00116 }
00117 
00118 
00119 u_result RPlidarDriverSerialImpl::reset(_u32 timeout)
00120 {
00121     u_result ans;
00122     if (IS_FAIL(ans = _sendCommand(RPLIDAR_CMD_RESET))) {
00123         return ans;
00124     }
00125     return RESULT_OK;
00126 }
00127 
00128 u_result RPlidarDriverSerialImpl::getHealth(rplidar_response_device_health_t & healthinfo, _u32 timeout)
00129 {
00130     u_result  ans;
00131     
00132     if (!isConnected()) return RESULT_OPERATION_FAIL;
00133     
00134     _disableDataGrabbing();
00135 
00136     {
00137         rp::hal::AutoLocker l(_lock);
00138         if (IS_FAIL(ans = _sendCommand(RPLIDAR_CMD_GET_DEVICE_HEALTH))) {
00139             return ans;
00140         }
00141 
00142         rplidar_ans_header_t response_header;
00143         if (IS_FAIL(ans = _waitResponseHeader(&response_header, timeout))) {
00144             return ans;
00145         }
00146 
00147         // verify whether we got a correct header
00148         if (response_header.type != RPLIDAR_ANS_TYPE_DEVHEALTH) {
00149             return RESULT_INVALID_DATA;
00150         }
00151 
00152         if (response_header.size < sizeof(rplidar_response_device_health_t)) {
00153             return RESULT_INVALID_DATA;
00154         }
00155 
00156         if (_rxtx->waitfordata(response_header.size, timeout) != rp::hal::serial_rxtx::ANS_OK) {
00157             return RESULT_OPERATION_TIMEOUT;
00158         }
00159         _rxtx->recvdata(reinterpret_cast<_u8 *>(&healthinfo), sizeof(healthinfo));
00160     }
00161     return RESULT_OK;
00162 }
00163 
00164 u_result RPlidarDriverSerialImpl::getDeviceInfo(rplidar_response_device_info_t & info, _u32 timeout)
00165 {
00166     u_result  ans;
00167     
00168     if (!isConnected()) return RESULT_OPERATION_FAIL;
00169 
00170     _disableDataGrabbing();
00171 
00172     {
00173         rp::hal::AutoLocker l(_lock);
00174         if (IS_FAIL(ans = _sendCommand(RPLIDAR_CMD_GET_DEVICE_INFO))) {
00175             return ans;
00176         }
00177 
00178         rplidar_ans_header_t response_header;
00179         if (IS_FAIL(ans = _waitResponseHeader(&response_header, timeout))) {
00180             return ans;
00181         }
00182 
00183         // verify whether we got a correct header
00184         if (response_header.type != RPLIDAR_ANS_TYPE_DEVINFO) {
00185             return RESULT_INVALID_DATA;
00186         }
00187 
00188         if (response_header.size < sizeof(rplidar_response_device_info_t)) {
00189             return RESULT_INVALID_DATA;
00190         }
00191 
00192         if (_rxtx->waitfordata(response_header.size, timeout) != rp::hal::serial_rxtx::ANS_OK) {
00193             return RESULT_OPERATION_TIMEOUT;
00194         }
00195 
00196         _rxtx->recvdata(reinterpret_cast<_u8 *>(&info), sizeof(info));
00197     }
00198     return RESULT_OK;
00199 }
00200 
00201 u_result RPlidarDriverSerialImpl::getFrequency(rplidar_response_measurement_node_t * nodebuffer, size_t count, float & frequency)
00202 {
00203     const float RPLIDAR_SAMPLE_DURATION = 490.3; //ms
00204         if (count == 0)
00205         {
00206                 frequency = 0.0f;
00207         }
00208         else
00209         {
00210                 frequency = (float)(1000000/(count * RPLIDAR_SAMPLE_DURATION));
00211         }
00212         return RESULT_OK;
00213 }
00214 
00215 u_result RPlidarDriverSerialImpl::startScan(bool force, _u32 timeout)
00216 {
00217     u_result ans;
00218     if (!isConnected()) return RESULT_OPERATION_FAIL;
00219     if (_isScanning) return RESULT_ALREADY_DONE;
00220 
00221     stop(); //force the previous operation to stop
00222 
00223     {
00224         rp::hal::AutoLocker l(_lock);
00225 
00226         if (IS_FAIL(ans = _sendCommand(force?RPLIDAR_CMD_FORCE_SCAN:RPLIDAR_CMD_SCAN))) {
00227             return ans;
00228         }
00229 
00230         // waiting for confirmation
00231         rplidar_ans_header_t response_header;
00232         if (IS_FAIL(ans = _waitResponseHeader(&response_header, timeout))) {
00233             return ans;
00234         }
00235 
00236         // verify whether we got a correct header
00237         if (response_header.type != RPLIDAR_ANS_TYPE_MEASUREMENT) {
00238             return RESULT_INVALID_DATA;
00239         }
00240 
00241         if (response_header.size < sizeof(rplidar_response_measurement_node_t)) {
00242             return RESULT_INVALID_DATA;
00243         }
00244 
00245         _isScanning = true;
00246         _cachethread = CLASS_THREAD(RPlidarDriverSerialImpl, _cacheScanData);
00247     }
00248     return RESULT_OK;
00249 }
00250 
00251 u_result RPlidarDriverSerialImpl::stop(_u32 timeout)
00252 {
00253     _disableDataGrabbing();
00254     u_result ans = _sendCommand(RPLIDAR_CMD_STOP);
00255     return ans;
00256 }
00257 
00258 u_result RPlidarDriverSerialImpl::_cacheScanData()
00259 {
00260     rplidar_response_measurement_node_t      local_buf[128];
00261     size_t                                   count = 128;
00262     rplidar_response_measurement_node_t      local_scan[MAX_SCAN_NODES];
00263     size_t                                   scan_count = 0;
00264     u_result                                 ans;
00265     memset(local_scan, 0, sizeof(local_scan));
00266 
00267     _waitScanData(local_buf, count); // // always discard the first data since it may be incomplete
00268 
00269     while(_isScanning)
00270     {
00271         if (IS_FAIL(ans=_waitScanData(local_buf, count))) {
00272             if (ans != RESULT_OPERATION_TIMEOUT) {
00273                 _isScanning = false;
00274                 return RESULT_OPERATION_FAIL;
00275             }
00276         }
00277 
00278         for (size_t pos = 0; pos < count; ++pos)
00279         {
00280             if (local_buf[pos].sync_quality & RPLIDAR_RESP_MEASUREMENT_SYNCBIT)
00281             {
00282                 // only publish the data when it contains a full 360 degree scan 
00283                 
00284                 if ((local_scan[0].sync_quality & RPLIDAR_RESP_MEASUREMENT_SYNCBIT)) {
00285                     _lock.lock();
00286                     memcpy(_cached_scan_node_buf, local_scan, scan_count*sizeof(rplidar_response_measurement_node_t));
00287                     _cached_scan_node_count = scan_count;
00288                     _dataEvt.set();
00289                     _lock.unlock();
00290                 }
00291                 scan_count = 0;
00292             }
00293             local_scan[scan_count++] = local_buf[pos];
00294             if (scan_count == _countof(local_scan)) scan_count-=1; // prevent overflow
00295         }
00296     }
00297     _isScanning = false;
00298     return RESULT_OK;
00299 }
00300 
00301 u_result RPlidarDriverSerialImpl::grabScanData(rplidar_response_measurement_node_t * nodebuffer, size_t & count, _u32 timeout)
00302 {
00303     switch (_dataEvt.wait(timeout))
00304     {
00305     case rp::hal::Event::EVENT_TIMEOUT:
00306         count = 0;
00307         return RESULT_OPERATION_TIMEOUT;
00308     case rp::hal::Event::EVENT_OK:
00309         {
00310             if(_cached_scan_node_count == 0) return RESULT_OPERATION_TIMEOUT; //consider as timeout
00311 
00312             rp::hal::AutoLocker l(_lock);
00313 
00314             size_t size_to_copy = min(count, _cached_scan_node_count);
00315 
00316             memcpy(nodebuffer, _cached_scan_node_buf, size_to_copy*sizeof(rplidar_response_measurement_node_t));
00317             count = size_to_copy;
00318             _cached_scan_node_count = 0;
00319         }
00320         return RESULT_OK;
00321 
00322     default:
00323         count = 0;
00324         return RESULT_OPERATION_FAIL;
00325     }
00326 }
00327 
00328 u_result RPlidarDriverSerialImpl::ascendScanData(rplidar_response_measurement_node_t * nodebuffer, size_t count)
00329 {
00330     float inc_origin_angle = 360.0/count;
00331     rplidar_response_measurement_node_t *tmpbuffer = new rplidar_response_measurement_node_t[count];
00332     int i = 0;
00333 
00334     //Tune head
00335     for (i = 0; i < count; i++) {
00336         if(nodebuffer[i].distance_q2 == 0) {
00337             continue;
00338         } else {
00339             while(i != 0) {
00340                 i--;
00341                 float expect_angle = (nodebuffer[i+1].angle_q6_checkbit >> RPLIDAR_RESP_MEASUREMENT_ANGLE_SHIFT)/64.0f - inc_origin_angle;
00342                 if (expect_angle < 0.0f) expect_angle = 0.0f;
00343                 _u16 checkbit = nodebuffer[i].angle_q6_checkbit & RPLIDAR_RESP_MEASUREMENT_CHECKBIT;
00344                 nodebuffer[i].angle_q6_checkbit = (((_u16)(expect_angle * 64.0f)) << RPLIDAR_RESP_MEASUREMENT_ANGLE_SHIFT) + checkbit;
00345             }
00346             break;
00347         }
00348     }
00349 
00350     // all the data is invalid
00351     if (i == count){
00352         delete[] tmpbuffer;
00353         return RESULT_OPERATION_FAIL;
00354     }
00355 
00356     //Tune tail
00357     for (i = count - 1; i >= 0; i--) {
00358         if(nodebuffer[i].distance_q2 == 0) {
00359             continue;
00360         } else {
00361             while(i != (count - 1)) {
00362                 i++;
00363                 float expect_angle = (nodebuffer[i-1].angle_q6_checkbit >> RPLIDAR_RESP_MEASUREMENT_ANGLE_SHIFT)/64.0f + inc_origin_angle;
00364                 if (expect_angle > 360.0f) expect_angle -= 360.0f;
00365                 _u16 checkbit = nodebuffer[i].angle_q6_checkbit & RPLIDAR_RESP_MEASUREMENT_CHECKBIT;
00366                 nodebuffer[i].angle_q6_checkbit = (((_u16)(expect_angle * 64.0f)) << RPLIDAR_RESP_MEASUREMENT_ANGLE_SHIFT) + checkbit;
00367             }
00368             break;
00369         }
00370     }
00371 
00372     //Fill invalid angle in the scan
00373     float frontAngle = (nodebuffer[0].angle_q6_checkbit >> RPLIDAR_RESP_MEASUREMENT_ANGLE_SHIFT)/64.0f;
00374     for (i = 0; i < count; i++) {
00375         if(nodebuffer[i].distance_q2 == 0) {
00376             float expect_angle =  frontAngle + i * inc_origin_angle;
00377             if (expect_angle > 360.0f) expect_angle -= 360.0f;
00378             _u16 checkbit = nodebuffer[i].angle_q6_checkbit & RPLIDAR_RESP_MEASUREMENT_CHECKBIT;
00379             nodebuffer[i].angle_q6_checkbit = (((_u16)(expect_angle * 64.0f)) << RPLIDAR_RESP_MEASUREMENT_ANGLE_SHIFT) + checkbit;
00380         }
00381     }
00382             
00383     // find zero_position in the full scan
00384     size_t zero_pos = 0;
00385     float pre_degree = (nodebuffer[0].angle_q6_checkbit >> RPLIDAR_RESP_MEASUREMENT_ANGLE_SHIFT)/64.0f;
00386 
00387     for (i = 1; i < count ; ++i) {
00388         float degree = (nodebuffer[i].angle_q6_checkbit >> RPLIDAR_RESP_MEASUREMENT_ANGLE_SHIFT)/64.0f;
00389         if (zero_pos == 0 && (pre_degree - degree > 180)) {
00390             zero_pos = i;
00391             break;
00392         }
00393         pre_degree = degree;
00394     }
00395 
00396     // reorder the scan
00397     for (i = zero_pos; i < count; i++) {
00398         tmpbuffer[i-zero_pos] = nodebuffer[i];
00399     }
00400     for (i = 0; i < zero_pos; i++) {
00401         tmpbuffer[i+count-zero_pos] = nodebuffer[i];
00402     }
00403 
00404     memcpy(nodebuffer, tmpbuffer, count*sizeof(rplidar_response_measurement_node_t));
00405     delete[] tmpbuffer;
00406 
00407     return RESULT_OK;
00408 }
00409 
00410 u_result RPlidarDriverSerialImpl::stopMotor()
00411 {
00412   //stop();
00413   _rxtx->setDTR();
00414   return RESULT_OK;
00415 }
00416 
00417 u_result RPlidarDriverSerialImpl::startMotor()
00418 {
00419   //startScan();
00420   _rxtx->clearDTR();
00421   return RESULT_OK;
00422 }
00423 
00424 
00425 u_result RPlidarDriverSerialImpl::_waitNode(rplidar_response_measurement_node_t * node, _u32 timeout)
00426 {
00427     int  recvPos = 0;
00428     _u32 startTs = getms();
00429     _u8  recvBuffer[sizeof(rplidar_response_measurement_node_t)];
00430     _u8 *nodeBuffer = (_u8*)node;
00431     _u32 waitTime;
00432 
00433    while ((waitTime=getms() - startTs) <= timeout) {
00434         size_t remainSize = sizeof(rplidar_response_measurement_node_t) - recvPos;
00435         size_t recvSize;
00436 
00437         int ans = _rxtx->waitfordata(remainSize, timeout-waitTime, &recvSize);
00438         if (ans == rp::hal::serial_rxtx::ANS_DEV_ERR) 
00439             return RESULT_OPERATION_FAIL;
00440         else if (ans == rp::hal::serial_rxtx::ANS_TIMEOUT)
00441             return RESULT_OPERATION_TIMEOUT;
00442 
00443         if (recvSize > remainSize) recvSize = remainSize;
00444         
00445         _rxtx->recvdata(recvBuffer, recvSize);
00446 
00447         for (size_t pos = 0; pos < recvSize; ++pos) {
00448             _u8 currentByte = recvBuffer[pos];
00449             switch (recvPos) {
00450             case 0: // expect the sync bit and its reverse in this byte
00451                 {
00452                     _u8 tmp = (currentByte>>1);
00453                     if ( (tmp ^ currentByte) & 0x1 ) {
00454                         // pass
00455                     } else {
00456                         continue;
00457                     }
00458 
00459                 }
00460                 break;
00461             case 1: // expect the highest bit to be 1
00462                 {
00463                     if (currentByte & RPLIDAR_RESP_MEASUREMENT_CHECKBIT) {
00464                         // pass
00465                     } else {
00466                         recvPos = 0;
00467                         continue;
00468                     }
00469                 }
00470                 break;
00471             }
00472             nodeBuffer[recvPos++] = currentByte;
00473 
00474             if (recvPos == sizeof(rplidar_response_measurement_node_t)) {
00475                 return RESULT_OK;
00476             }
00477         }
00478     }
00479 
00480     return RESULT_OPERATION_TIMEOUT;
00481 }
00482 
00483 u_result RPlidarDriverSerialImpl::_waitScanData(rplidar_response_measurement_node_t * nodebuffer, size_t & count, _u32 timeout)
00484 {
00485     if (!_isConnected) {
00486         count = 0;
00487         return RESULT_OPERATION_FAIL;
00488     }
00489 
00490     size_t   recvNodeCount =  0;
00491     _u32     startTs = getms();
00492     _u32     waitTime;
00493     u_result ans;
00494 
00495     while ((waitTime = getms() - startTs) <= timeout && recvNodeCount < count) {
00496         rplidar_response_measurement_node_t node;
00497         if (IS_FAIL(ans = _waitNode(&node, timeout - waitTime))) {
00498             return ans;
00499         }
00500         
00501         nodebuffer[recvNodeCount++] = node;
00502 
00503         if (recvNodeCount == count) return RESULT_OK;
00504     }
00505     count = recvNodeCount;
00506     return RESULT_OPERATION_TIMEOUT;
00507 }
00508 
00509 u_result RPlidarDriverSerialImpl::_sendCommand(_u8 cmd, const void * payload, size_t payloadsize)
00510 {
00511     _u8 pkt_header[10];
00512     rplidar_cmd_packet_t * header = reinterpret_cast<rplidar_cmd_packet_t * >(pkt_header);
00513     _u8 checksum = 0;
00514 
00515     if (!_isConnected) return RESULT_OPERATION_FAIL;
00516 
00517     if (payloadsize && payload) {
00518         cmd |= RPLIDAR_CMDFLAG_HAS_PAYLOAD;
00519     }
00520 
00521     header->syncByte = RPLIDAR_CMD_SYNC_BYTE;
00522     header->cmd_flag = cmd;
00523 
00524     // send header first
00525     _rxtx->senddata(pkt_header, 2) ;
00526 
00527     if (cmd & RPLIDAR_CMDFLAG_HAS_PAYLOAD) {
00528         checksum ^= RPLIDAR_CMD_SYNC_BYTE;
00529         checksum ^= cmd;
00530         checksum ^= (payloadsize & 0xFF);
00531 
00532         // calc checksum
00533         for (size_t pos = 0; pos < payloadsize; ++pos) {
00534             checksum ^= ((_u8 *)payload)[pos];
00535         }
00536 
00537         // send size
00538         _u8 sizebyte = payloadsize;
00539         _rxtx->senddata(&sizebyte, 1);
00540 
00541         // send payload
00542         _rxtx->senddata((const _u8 *)payload, sizebyte);
00543 
00544         // send checksum
00545         _rxtx->senddata(&checksum, 1);
00546     }
00547 
00548     return RESULT_OK;
00549 }
00550 
00551 
00552 u_result RPlidarDriverSerialImpl::_waitResponseHeader(rplidar_ans_header_t * header, _u32 timeout)
00553 {
00554     int  recvPos = 0;
00555     _u32 startTs = getms();
00556     _u8  recvBuffer[sizeof(rplidar_ans_header_t)];
00557     _u8  *headerBuffer = reinterpret_cast<_u8 *>(header);
00558     _u32 waitTime;
00559 
00560     while ((waitTime=getms() - startTs) <= timeout) {
00561         size_t remainSize = sizeof(rplidar_ans_header_t) - recvPos;
00562         size_t recvSize;
00563         
00564         int ans = _rxtx->waitfordata(remainSize, timeout - waitTime, &recvSize);
00565         if (ans == rp::hal::serial_rxtx::ANS_DEV_ERR) 
00566             return RESULT_OPERATION_FAIL;
00567         else if (ans == rp::hal::serial_rxtx::ANS_TIMEOUT)
00568             return RESULT_OPERATION_TIMEOUT;
00569         
00570         if(recvSize > remainSize) recvSize = remainSize;
00571         
00572         _rxtx->recvdata(recvBuffer, recvSize);
00573 
00574         for (size_t pos = 0; pos < recvSize; ++pos) {
00575             _u8 currentByte = recvBuffer[pos];
00576             switch (recvPos) {
00577             case 0:
00578                 if (currentByte != RPLIDAR_ANS_SYNC_BYTE1) {
00579                    continue;
00580                 }
00581                 
00582                 break;
00583             case 1:
00584                 if (currentByte != RPLIDAR_ANS_SYNC_BYTE2) {
00585                     recvPos = 0;
00586                     continue;
00587                 }
00588                 break;
00589             }
00590             headerBuffer[recvPos++] = currentByte;
00591 
00592             if (recvPos == sizeof(rplidar_ans_header_t)) {
00593                 return RESULT_OK;
00594             }
00595         }
00596     }
00597 
00598     return RESULT_OPERATION_TIMEOUT;
00599 }
00600 
00601 
00602 
00603 void RPlidarDriverSerialImpl::_disableDataGrabbing()
00604 {
00605     _isScanning = false;
00606     _cachethread.join();
00607 }
00608 
00609 }}}


rplidar_ros
Author(s):
autogenerated on Fri Aug 28 2015 12:46:43