问题 网上思岚激光雷达的应用教程一上来就是基于ROS2系统的。这种方法可以通过GUI方式查看测试数据,直观又强大。
但是如果想环境和依赖简单一点,硬件资源占用少一点,可以用思岚提供的SDK。SDK 是以持C++源代码的方式提供的,自带例程。
项目需要python读取的雷达数据。一开始是由docker中的ROS2作为数据处理和通讯的中转站。但发现占用资源多,操作有点复杂。
转而考虑python直接调用sdk, 由于头文件非常多ctypes方式没怎么尝试就放弃了。中途用qt6调sdk然后通过UDP传给python,虽然数据通了,但部署得交叉编译还得依赖qt6. 性能也有损失。
最后就用到主角 pybind11 (blog中搜索 PyBind11简明教程 ) 和 外观设计模式 实现 python 调用 cpp 的 sdk
工程目录 1 2 3 4 5 6 7 8 9 10 11 ├── build ├── CMakeLists.txt ├── main.cpp ├── mylib.h ├── pywrap.cpp ├── readme.md ├── sdk │ ├── CMakeLists.txt │ ├── include │ └── src └── test.py
封装SDK 外观设计模式简单来说就是把零散复杂的SDK根据使用流程做一个抽象和封装。
让别人调用的时候不再关注流程和操作上的技术细节。
比如此处的mylib.h (如果将类的实现分开写道单独的如mylib.cpp文件中,python调用的时候会报错,好像是CPP编译器给函数名增加了一些信息。)
注意:
本项目仅用的串口,网络通讯方式已被省略,如有需要请参考SDK例程
由于udev已经将雷达串口映射为 “/dev/rplidar” 波特率100000 请自行修改。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 #ifndef __MY_RADAR #define __MY_RADAR #include <iostream> #include <stdio.h> #include <stdlib.h> #include <signal.h> #include <string.h> #include <vector> #include <pybind11/pybind11.h> #include <pybind11/stl.h> namespace py = pybind11;#include "sl_lidar.h" #include "sl_lidar_driver.h" #ifndef _countof #define _countof(_Array) (int)(sizeof(_Array) / sizeof(_Array[0])) #endif #ifdef _WIN32 #include <Windows.h> #define delay(x) ::Sleep(x) #else #include <unistd.h> static inline void delay (sl_word_size_t ms) { while (ms >= 1000 ) { usleep (1000 * 1000 ); ms -= 1000 ; }; if (ms != 0 ) usleep (ms * 1000 ); } #endif using namespace sl;class MyClass {public : MyClass (int com) { IChannel* _channel; sl_result op_result; std::cout << "init" << std::endl; drv = *createLidarDriver (); if (!drv) { fprintf (stderr, "insufficent memory, exit\n" ); exit (-2 ); } sl_lidar_response_device_info_t devinfo; bool connectSuccess = false ; _channel = (*createSerialPortChannel ("/dev/rplidar" , 1000000 )); if (SL_IS_OK ((drv)->connect (_channel))) { op_result = drv->getDeviceInfo (devinfo); if (SL_IS_OK (op_result)) { connectSuccess = true ; } else { delete drv; drv = NULL ; } } if (!connectSuccess) { fprintf (stderr, "Error, cannot bind to the specified serial port.\n" ); goto on_finished; } printf ("SLAMTEC LIDAR S/N: " ); for (int pos = 0 ; pos < 16 ; ++pos) { printf ("%02X" , devinfo.serialnum[pos]); } printf ("\n" "Firmware Ver: %d.%02d\n" "Hardware Rev: %d\n" , devinfo.firmware_version >> 8 , devinfo.firmware_version & 0xFF , (int )devinfo.hardware_version); if (!checkSLAMTECLIDARHealth (drv)) { goto on_finished; } drv->setMotorSpeed (); drv->startScan (0 , 1 ); on_finished: return ; }; ~MyClass () { std::cout << "uninit" << std::endl; drv->stop (); delay (200 ); if (drv) { drv->setMotorSpeed (0 ); delete drv; drv = NULL ; } }; void run (void ) { std::cout << "run" << std::endl; if (drv) { sl_lidar_response_measurement_node_hq_t nodes[8192 ]; size_t count = _countof(nodes); sl_result op_result = drv->grabScanDataHq (nodes, count); if (SL_IS_OK (op_result)) { drv->ascendScanData (nodes, count); if (data_angle.size () != count) { data_angle.resize (count); data_dist.resize (count); data_quality.resize (count); } for (int pos = 0 ; pos < (int )count; ++pos) { data_angle.at (pos) = (nodes[pos].angle_z_q14 * 90.f ) / 16384.f ; data_dist.at (pos) = nodes[pos].dist_mm_q2 / 4.0f ; data_quality.at (pos) = nodes[pos].quality >> SL_LIDAR_RESP_MEASUREMENT_QUALITY_SHIFT; } } } }; const std::vector<double >& get_angle () const { return data_angle;} const std::vector<double >& get_dist () const { return data_dist;} const std::vector<int >& get_quality () const { return data_quality;} private : std::vector<double > data_angle; std::vector<double > data_dist; std::vector<int > data_quality; ILidarDriver* drv; bool checkSLAMTECLIDARHealth (ILidarDriver* drv) { sl_result op_result; sl_lidar_response_device_health_t healthinfo; op_result = drv->getHealth (healthinfo); if (SL_IS_OK (op_result)) { printf ("SLAMTEC Lidar health status : %d\n" , healthinfo.status); if (healthinfo.status == SL_LIDAR_STATUS_ERROR) { fprintf (stderr, "Error, slamtec lidar internal error detected. Please reboot the device to retry.\n" ); return false ; } else { return true ; } } else { fprintf (stderr, "Error, cannot retrieve the lidar health code: %x\n" , op_result); return false ; } } }; #endif
虽然内容很多,但对于 python 上层调用方来讲。就只有
构造函数:雷达初始化,完成准备工作
run函数:串口将雷达数据保存到私有变量
get_angle/get_dist/data_quality等数据传递函数:从私有变量将数据传递给python
析构函数:关闭雷达释放资源
这也是上面提到的 外观设计模式
创建 python 包装器所需的内容 照猫画虎实现的 pywrap.cpp 文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <pybind11/pybind11.h> #include "mylib.h" namespace py = pybind11;constexpr auto byref = py::return_value_policy::reference_internal;PYBIND11_MODULE (MyLib, m) { m.doc () = "optional module docstring" ; py::class_ <MyClass>(m, "MyClass" ) .def (py::init <int >()) .def ("run" , &MyClass::run, py::call_guard <py::gil_scoped_release>()) .def ("get_angle" , &MyClass::get_angle) .def ("get_dist" , &MyClass::get_dist) .def ("get_quality" , &MyClass::get_quality) ; }
前期调试 此文件仅用于前期验证库文件功能是否正常
main.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include <stdio.h> #include <stdlib.h> #include <signal.h> #include <string.h> #include "sl_lidar.h" #include "sl_lidar_driver.h" #include "mylib.h" int main (int argc, const char * argv[]) { MyClass radar (1 ) ; radar.run (); return 0 ; }
编译生成库文件 安装 pybind11。安装方式除了下面,还有 pip 和 conda 等
1 sudo apt install pybind11-dev
将 sdk 和 pybing11 教程中的 cmake 文件结合到一起(注意pybind11_DIR 文件目录根据自己的实际情况修改)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 cmake_minimum_required (VERSION 3.10 )project (MyLib)set (CMAKE_CXX_STANDARD 11 )set (PYBIND11_PYTHON_VERSION 3.6 )set (CMAKE_CXX_FLAGS "-Wall -Wextra -fPIC" )set (pybind11_DIR "/usr/local/lib/python3.6/dist-packages/pybind11/share/cmake/pybind11" )find_package (pybind11 REQUIRED)find_package (Threads REQUIRED)set (LIB_PATH ${CMAKE_CURRENT_SOURCE_DIR} /lib)list (APPEND HEAD_PATH ${CMAKE_CURRENT_SOURCE_DIR} /sdk/include )list (APPEND HEAD_PATH ${CMAKE_CURRENT_SOURCE_DIR} /sdk/src)set (RADRA_LIB "rplidar_driver" )add_subdirectory (sdk)include_directories (${HEAD_PATH} )link_directories (${LIB_PATH} )pybind11_add_module(${PROJECT_NAME} pywrap.cpp) target_compile_definitions (${PROJECT_NAME} PRIVATE VERSION_INFO=${EXAMPLE_VERSION_INFO} )target_include_directories (${PROJECT_NAME} PRIVATE ${PYBIND11_INCLUDE_DIRS} )target_link_libraries (${PROJECT_NAME} PRIVATE ${RADRA_LIB} Threads::Threads)
编译库
1 2 3 4 mkdir buildcd buildcmake .. make
顺利的话会生成类似 MyLib.cpython-36m-aarch64-linux-gnu.so
文件
python 调用 test.py
1 2 3 4 5 6 7 8 9 10 11 12 13 import sysimport numpy as npsys.path.append("build/" ) from MyLib import MyClassradar = MyClass(1 ) while True : radar.run() l_angle = radar.get_angle() l_dist = radar.get_dist() l_quality = radar.get_quality() print (l_angle)
测试
可以按 Ctrl+C
停止