From 0c4ccaec31190f94443fa406a61d779acec3db72 Mon Sep 17 00:00:00 2001 From: Max Ryan <161385834+maxryan4@users.noreply.github.com> Date: Mon, 22 Sep 2025 18:34:40 +0100 Subject: [PATCH 1/3] Added step to readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 11c5574..d9afa2f 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@ cd ldlidar_ros2_ws/src git clone https://github.com/ldrobotSensorTeam/ldlidar_ros2.git +cd ldlidar_ros2 + git submodule update --init --recursive ``` ## step 1: system setup From da0ca0a8c89d363061275e3be14d082fe6715a58 Mon Sep 17 00:00:00 2001 From: Max Ryan <161385834+maxryan4@users.noreply.github.com> Date: Mon, 22 Sep 2025 19:22:28 +0100 Subject: [PATCH 2/3] Updated readme --- .gitmodules | 3 --- README.md | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) delete mode 100644 .gitmodules diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index f1267c7..0000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "sdk"] - path = sdk -url=https://github.com/ldrobotSensorTeam/ldlidar_sdk.git \ No newline at end of file diff --git a/README.md b/README.md index d9afa2f..51caf22 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ mkdir -p ldlidar_ros2_ws/src cd ldlidar_ros2_ws/src -git clone https://github.com/ldrobotSensorTeam/ldlidar_ros2.git +git clone https://github.com/maxryan4/ldlidar_ros2.git cd ldlidar_ros2 From bf9e3a232165b26f5cef8df7fb87bbcda71062ab Mon Sep 17 00:00:00 2001 From: Max Ryan <161385834+maxryan4@users.noreply.github.com> Date: Mon, 22 Sep 2025 19:29:07 +0100 Subject: [PATCH 3/3] No longer uses submodules --- README.md | 4 - sdk | 1 - sdk/.gitignore | 3 + sdk/CMakeLists.txt | 47 ++ sdk/LICENSE | 21 + sdk/README.md | 32 ++ .../ldlidar_driver/ldlidar_dataprocess.h | 122 +++++ sdk/include/ldlidar_driver/ldlidar_datatype.h | 104 ++++ sdk/include/ldlidar_driver/ldlidar_driver.h | 92 ++++ .../ldlidar_driver/ldlidar_driver_linux.h | 163 +++++++ .../ldlidar_driver/ldlidar_driver_win.h | 152 ++++++ sdk/include/ldlidar_driver/ldlidar_protocol.h | 230 +++++++++ sdk/include/ldlidar_driver/log_module.h | 238 +++++++++ .../network_socket_interface_linux.h | 125 +++++ .../ldlidar_driver/serial_interface_linux.h | 81 ++++ .../ldlidar_driver/serial_interface_win.h | 124 +++++ sdk/include/ldlidar_driver/sl_transform.h | 50 ++ sdk/include/ldlidar_driver/slbf.h | 59 +++ sdk/include/ldlidar_driver/tofbf.h | 70 +++ sdk/sample/linux/CMakeLists.txt | 72 +++ sdk/sample/linux/README.md | 32 ++ sdk/sample/linux/README_CN.md | 32 ++ sdk/sample/linux/auto_build.sh | 45 ++ sdk/sample/linux/clean_build.sh | 14 + sdk/sample/linux/demo.cpp | 187 +++++++ sdk/sample/windows/CMakeLists.txt | 21 + sdk/sample/windows/README.md | 29 ++ sdk/sample/windows/README_CN.md | 29 ++ sdk/sample/windows/pic/1.png | Bin 0 -> 22098 bytes sdk/sample/windows/pic/2.png | Bin 0 -> 20972 bytes sdk/sample/windows/pic/3.png | Bin 0 -> 33064 bytes sdk/sample/windows/pic/4.png | Bin 0 -> 37830 bytes sdk/sample/windows/pic/5.png | Bin 0 -> 38669 bytes sdk/sample/windows/pic/6.png | Bin 0 -> 70511 bytes sdk/sample/windows/windemo.cpp | 163 +++++++ sdk/src/ldlidar_dataprocess.cpp | 284 +++++++++++ sdk/src/ldlidar_driver.cpp | 52 ++ sdk/src/ldlidar_driver_linux.cpp | 341 +++++++++++++ sdk/src/ldlidar_driver_win.cpp | 230 +++++++++ sdk/src/ldlidar_protocol.cpp | 189 ++++++++ sdk/src/log_module.cpp | 351 ++++++++++++++ sdk/src/network_socket_interface_linux.cpp | 458 ++++++++++++++++++ sdk/src/serial_interface_linux.cpp | 184 +++++++ sdk/src/serial_interface_win.cpp | 326 +++++++++++++ sdk/src/sl_transform.cpp | 110 +++++ sdk/src/slbf.cpp | 254 ++++++++++ sdk/src/tofbf.cpp | 272 +++++++++++ sdk/test/CMakeLists.txt | 88 ++++ sdk/test/auto_build_test.sh | 21 + sdk/test/clean_build.sh | 14 + sdk/test/test.cpp | 237 +++++++++ 51 files changed, 5748 insertions(+), 5 deletions(-) delete mode 160000 sdk create mode 100644 sdk/.gitignore create mode 100644 sdk/CMakeLists.txt create mode 100644 sdk/LICENSE create mode 100644 sdk/README.md create mode 100644 sdk/include/ldlidar_driver/ldlidar_dataprocess.h create mode 100644 sdk/include/ldlidar_driver/ldlidar_datatype.h create mode 100644 sdk/include/ldlidar_driver/ldlidar_driver.h create mode 100644 sdk/include/ldlidar_driver/ldlidar_driver_linux.h create mode 100644 sdk/include/ldlidar_driver/ldlidar_driver_win.h create mode 100644 sdk/include/ldlidar_driver/ldlidar_protocol.h create mode 100644 sdk/include/ldlidar_driver/log_module.h create mode 100644 sdk/include/ldlidar_driver/network_socket_interface_linux.h create mode 100644 sdk/include/ldlidar_driver/serial_interface_linux.h create mode 100644 sdk/include/ldlidar_driver/serial_interface_win.h create mode 100644 sdk/include/ldlidar_driver/sl_transform.h create mode 100644 sdk/include/ldlidar_driver/slbf.h create mode 100644 sdk/include/ldlidar_driver/tofbf.h create mode 100644 sdk/sample/linux/CMakeLists.txt create mode 100644 sdk/sample/linux/README.md create mode 100644 sdk/sample/linux/README_CN.md create mode 100644 sdk/sample/linux/auto_build.sh create mode 100644 sdk/sample/linux/clean_build.sh create mode 100644 sdk/sample/linux/demo.cpp create mode 100644 sdk/sample/windows/CMakeLists.txt create mode 100644 sdk/sample/windows/README.md create mode 100644 sdk/sample/windows/README_CN.md create mode 100644 sdk/sample/windows/pic/1.png create mode 100644 sdk/sample/windows/pic/2.png create mode 100644 sdk/sample/windows/pic/3.png create mode 100644 sdk/sample/windows/pic/4.png create mode 100644 sdk/sample/windows/pic/5.png create mode 100644 sdk/sample/windows/pic/6.png create mode 100644 sdk/sample/windows/windemo.cpp create mode 100644 sdk/src/ldlidar_dataprocess.cpp create mode 100644 sdk/src/ldlidar_driver.cpp create mode 100644 sdk/src/ldlidar_driver_linux.cpp create mode 100644 sdk/src/ldlidar_driver_win.cpp create mode 100644 sdk/src/ldlidar_protocol.cpp create mode 100644 sdk/src/log_module.cpp create mode 100644 sdk/src/network_socket_interface_linux.cpp create mode 100644 sdk/src/serial_interface_linux.cpp create mode 100644 sdk/src/serial_interface_win.cpp create mode 100644 sdk/src/sl_transform.cpp create mode 100644 sdk/src/slbf.cpp create mode 100644 sdk/src/tofbf.cpp create mode 100644 sdk/test/CMakeLists.txt create mode 100644 sdk/test/auto_build_test.sh create mode 100644 sdk/test/clean_build.sh create mode 100644 sdk/test/test.cpp diff --git a/README.md b/README.md index 51caf22..ab5bd76 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,6 @@ mkdir -p ldlidar_ros2_ws/src cd ldlidar_ros2_ws/src git clone https://github.com/maxryan4/ldlidar_ros2.git - -cd ldlidar_ros2 - -git submodule update --init --recursive ``` ## step 1: system setup - Connect the LiDAR to your system motherboard via an onboard serial port or usB-to-serial module (for example, CP2102 module). diff --git a/sdk b/sdk deleted file mode 160000 index 6148cd3..0000000 --- a/sdk +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 6148cd3c33ae39adbd7e12faf82567fa8f8e7b0d diff --git a/sdk/.gitignore b/sdk/.gitignore new file mode 100644 index 0000000..71660fc --- /dev/null +++ b/sdk/.gitignore @@ -0,0 +1,3 @@ +*.json +build/** +sample/windows/build/** \ No newline at end of file diff --git a/sdk/CMakeLists.txt b/sdk/CMakeLists.txt new file mode 100644 index 0000000..10402d4 --- /dev/null +++ b/sdk/CMakeLists.txt @@ -0,0 +1,47 @@ +# sub project +message(STATUS "operation system is ${CMAKE_SYSTEM}") + +if(CMAKE_SYSTEM_NAME MATCHES "Linux") +message(STATUS "current platform: Linux ") + +set(LDLIDAR_DRIVER_SOURCE_LINUX + ${CMAKE_CURRENT_SOURCE_DIR}/src/ldlidar_driver.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/ldlidar_driver_linux.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/ldlidar_dataprocess.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/ldlidar_protocol.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/log_module.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/network_socket_interface_linux.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/serial_interface_linux.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/sl_transform.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/slbf.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/tofbf.cpp +) + +add_library(ldlidar_driver STATIC + ${LDLIDAR_DRIVER_SOURCE_LINUX} +) + +elseif(CMAKE_SYSTEM_NAME MATCHES "Windows") +message(STATUS "current platform: Windows") + +set(LDLIDAR_DRIVER_SOURCE_WIN + ${CMAKE_CURRENT_SOURCE_DIR}/src/ldlidar_driver.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/ldlidar_driver_win.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/ldlidar_dataprocess.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/ldlidar_protocol.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/log_module.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/serial_interface_win.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/sl_transform.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/slbf.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/tofbf.cpp +) + +add_library(ldlidar_driver STATIC + ${LDLIDAR_DRIVER_SOURCE_WIN} +) + +else() + message(STATUS "other platform: ${CMAKE_SYSTEM_NAME}") +endif (CMAKE_SYSTEM_NAME MATCHES "Linux") + + diff --git a/sdk/LICENSE b/sdk/LICENSE new file mode 100644 index 0000000..b910c8c --- /dev/null +++ b/sdk/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 SHENZHEN LDROBOT CO., LTD. All rights + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/sdk/README.md b/sdk/README.md new file mode 100644 index 0000000..c2e62df --- /dev/null +++ b/sdk/README.md @@ -0,0 +1,32 @@ +# LDLiDAR SDK +This SDK is only applicable to the LiDAR products sold by Shenzhen LDROBOT Co., LTD. +| product models | range types | +| ---- | ---- | +| LDROBOT LiDAR LD06 | 2D DTOF | +| LDROBOT LiDAR LD19 | 2D DTOF | +| LDROBOT LiDAR LD14 | 2D Triangle| +| LDROBOT LiDAR LD14P | 2D Triangle| + +--- +## Get SDK +- download package file or use git. +```bash +$ cd ~ + +$ mkdir ldlidar_ws + +$ cd ldlidar_ws + +$ git clone https://github.com/ldrobotSensorTeam/ldlidar_sdk.git +``` + +--- +## Introduction to linux application example +- [EN](./sample/linux/README.md) +- [CN](./sample/linux/README_CN.md) + +--- + +## Introduction to windows application example +- [EN](./sample/windows/README.md) +- [CN](./sample/windows/README_CN.md) diff --git a/sdk/include/ldlidar_driver/ldlidar_dataprocess.h b/sdk/include/ldlidar_driver/ldlidar_dataprocess.h new file mode 100644 index 0000000..12ae886 --- /dev/null +++ b/sdk/include/ldlidar_driver/ldlidar_dataprocess.h @@ -0,0 +1,122 @@ +/** + * @file lipkg.h + * @author LDRobot (support@ldrobot.com) + * David Hu (hmd_hubei_cn@163.com) + * @brief LiDAR data protocol processing App + * This code is only applicable to LDROBOT LiDAR LD00 LD03 LD08 LD14 + * products sold by Shenzhen LDROBOT Co., LTD + * @version 1.0 + * @date 2023-03-12 + * + * @copyright Copyright (c) 2017-2023 SHENZHEN LDROBOT CO., LTD. All rights + * reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License in the file LICENSE + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef __LDLIDAR_DATAPROCESS_H__ +#define __LDLIDAR_DATAPROCESS_H__ + +#include + +#include +#include +#include + +#include "ldlidar_driver/slbf.h" +#include "ldlidar_driver/sl_transform.h" +#include "ldlidar_driver/tofbf.h" +#include "ldlidar_driver/ldlidar_protocol.h" + +namespace ldlidar { + +class LdLidarDataProcess { +public: + LdLidarDataProcess(); + + ~LdLidarDataProcess(); + + void SetProductType(LDType typenumber); + + void SetNoiseFilter(bool is_enable); + + void RegisterTimestampGetFunctional(std::function timestamp_handle); + + void CommReadCallback(const char *byte, size_t len); + + /** + * @brief get lidar scan data + */ + bool GetLaserScanData(Points2D& out); + + /** + * @brief get Lidar spin speed (Hz) + */ + double GetSpeed(void); + + LidarStatus GetLidarStatus(void); + + uint8_t GetLidarErrorCode(void); + + bool GetLidarPowerOnCommStatus(void); + + void ClearDataProcessStatus(void) { + is_frame_ready_ = false; + is_poweron_comm_normal_ = false; + lidarstatus_ = LidarStatus::NORMAL; + lidarerrorcode_ = LIDAR_NO_ERROR; + last_pkg_timestamp_ = 0; + lidar_scan_data_vec_.clear(); + tmp_lidar_scan_data_vec_.clear(); + } + +private: + int lidar_measure_freq_; + LDType typenumber_; + LidarStatus lidarstatus_; + uint8_t lidarerrorcode_; + bool is_frame_ready_; + bool is_noise_filter_; + uint16_t timestamp_; + double speed_; + std::function get_timestamp_; + bool is_poweron_comm_normal_; + uint64_t last_pkg_timestamp_; + + LdLidarProtocol* protocol_handle_; + Points2D lidar_scan_data_vec_; + Points2D tmp_lidar_scan_data_vec_; + std::mutex mutex_lock1_; + std::mutex mutex_lock2_; + + void SetLidarStatus(LidarStatus status); + + void SetLidarErrorCode(uint8_t errorcode); + + bool AnalysisOne(uint8_t byte); // parse single packet + + bool Parse(const uint8_t *data, long len); + + bool AssemblePacket(); // combine stantard data into data frames and calibrate + + bool IsFrameReady(void); // get Lidar data frame ready flag + + void ResetFrameReady(void); // reset frame ready flag + + void SetFrameReady(void); // set frame ready flag + + void SetLaserScanData(Points2D& src); + + Points2D GetLaserScanData(void); +}; + +} // namespace ldlidar + +#endif // __LDLIDAR_DATAPROCESS_H__ +/********************* (C) COPYRIGHT SHENZHEN LDROBOT CO., LTD *******END OF + * FILE ********/ \ No newline at end of file diff --git a/sdk/include/ldlidar_driver/ldlidar_datatype.h b/sdk/include/ldlidar_driver/ldlidar_datatype.h new file mode 100644 index 0000000..1def671 --- /dev/null +++ b/sdk/include/ldlidar_driver/ldlidar_datatype.h @@ -0,0 +1,104 @@ +/** + * @file ldlidar_datatype.h + * @author LDRobot (support@ldrobot.com) + * @brief lidar point data structure + * This code is only applicable to LDROBOT products + * sold by Shenzhen LDROBOT Co., LTD + * @version 0.1 + * @date 2021-11-09 + * + * @copyright Copyright (c) 2017-2023 SHENZHEN LDROBOT CO., LTD. All rights + * reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License in the file LICENSE + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef _LDLIDAR_POINT_DATA_H_ +#define _LDLIDAR_POINT_DATA_H_ + +#include + +#include +#include +#include + +#define LDLiDAR_SDK_VERSION_NUMBER "3.3.1" + +#define ANGLE_TO_RADIAN(angle) ((angle)*3141.59 / 180000) + +// lidar error code definition +#define LIDAR_NO_ERROR 0x00 +#define LIDAR_ERROR_BLOCKING 0x01 /* 雷达堵转 */ +#define LIDAR_ERROR_OCCLUSION 0x02 /* 雷达遮挡 */ +#define LIDAR_ERROR_BLOCKING_AND_OCCLUSION 0x03 /* 雷达堵转且遮挡 */ +// end lidar error code definition + +namespace ldlidar { + +enum class LDType { + NO_VER, + LD_14, + LD_14P, + LD_06, + LD_19, + STL_06P, + STL_26, + STL_27L, +}; + +enum class LidarStatus { + NORMAL, /* 雷达正常,可获取点云数据 */ + ERROR, /* 表明雷达出现异常错误,可获取雷达反馈的错误码了解具体错误 */ + DATA_TIME_OUT, /* 雷达点云数据包发布超时 */ + DATA_WAIT, /* 雷达点云数据包发布等待 */ + STOP, /* 雷达停止转动、未启动Driver */ +}; + +struct PointData { + // Polar coordinate representation + float angle; // Angle ranges from 0 to 359 degrees + uint16_t distance; // Distance is measured in millimeters + uint8_t intensity; // Intensity is 0 to 255 + //! System time when first range was measured in nanoseconds + uint64_t stamp; + // Cartesian coordinate representation + double x; + double y; + PointData(float angle, uint16_t distance, uint8_t intensity, uint64_t stamp = 0, double x = 0, double y = 0) { + this->angle = angle; + this->distance = distance; + this->intensity = intensity; + this->stamp = stamp; + this->x = x; + this->y = y; + } + PointData() {} +}; + +typedef std::vector Points2D; + +struct LaserScan { + //! System time when first range was measured in nanoseconds + uint64_t stamp; + //! Array of laser point + Points2D points; + + LaserScan &operator=(const LaserScan &data) { + this->stamp = data.stamp; + this->points = data.points; + return *this; + } +}; + + +} // namespace ldlidar + +#endif // _LDLIDAR_POINT_DATA_H_ + +/********************* (C) COPYRIGHT SHENZHEN LDROBOT CO., LTD *******END OF + * FILE ********/ \ No newline at end of file diff --git a/sdk/include/ldlidar_driver/ldlidar_driver.h b/sdk/include/ldlidar_driver/ldlidar_driver.h new file mode 100644 index 0000000..afed1c9 --- /dev/null +++ b/sdk/include/ldlidar_driver/ldlidar_driver.h @@ -0,0 +1,92 @@ +/** + * @file ldlidar_driver.h + * @author LDRobot (support@ldrobot.com) + * @brief ldlidar sdk interface + * This code is only applicable to LDROBOT LiDAR LD14 + * products sold by Shenzhen LDROBOT Co., LTD + * @version 0.1 + * @date 2021-05-12 + * + * @copyright Copyright (c) 2017-2023 SHENZHEN LDROBOT CO., LTD. All rights + * reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License in the file LICENSE + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef __LDLIDAR_DRIVER_SDK_INTERFACE_H__ +#define __LDLIDAR_DRIVER_SDK_INTERFACE_H__ + +#include +#include + +#include "ldlidar_driver/ldlidar_datatype.h" + +namespace ldlidar { + +class LDLidarDriver { +public: + LDLidarDriver(); + + virtual ~LDLidarDriver(); + + std::string GetLidarSdkVersionNumber(void); + + virtual void EnablePointCloudDataFilter(bool is_enable) = 0; + + virtual bool WaitLidarComm(int64_t timeout) = 0; + + virtual LidarStatus GetLaserScanData(Points2D& dst, int64_t timeout) = 0; + + virtual LidarStatus GetLaserScanData(LaserScan& dst, int64_t timeout) = 0; + + virtual bool GetLidarScanFreq(double& spin_hz) = 0; + + virtual void RegisterGetTimestampFunctional(std::function get_timestamp_handle) = 0; + + virtual uint8_t GetLidarErrorCode(void) = 0; + + /** + * @brief Start lidar driver node + * @param none + * @retval value is true, start is success; + * value is false, start is failed. + */ + virtual bool Start(void) = 0; + + /** + * @brief Stop lidar driver node + * @param none + * @retval value is true, stop is success; + * value is false, stop is failed. + */ + virtual bool Stop(void) = 0; + + /** + * @brief Get SDK(ldlidar driver) running status. + */ + static bool Ok(); + + /** + * @brief Set SDK(ldlidar driver) running status. + */ + static void SetLidarDriverStatus(bool status); + +protected: + bool is_start_flag_; + bool is_connect_flag_; + +private: + std::string sdk_pack_version_; + static bool is_ok_; +}; + +} // namespace ldlidar + +#endif // __LDLIDAR_DRIVER_NODE_H__ +/********************* (C) COPYRIGHT SHENZHEN LDROBOT CO., LTD *******END OF + * FILE ********/ \ No newline at end of file diff --git a/sdk/include/ldlidar_driver/ldlidar_driver_linux.h b/sdk/include/ldlidar_driver/ldlidar_driver_linux.h new file mode 100644 index 0000000..4eaa4c8 --- /dev/null +++ b/sdk/include/ldlidar_driver/ldlidar_driver_linux.h @@ -0,0 +1,163 @@ +/** + * @file ldlidar_driver.h + * @author LDRobot (support@ldrobot.com) + * @brief ldlidar sdk interface + * This code is only applicable to LDROBOT LiDAR LD14 + * products sold by Shenzhen LDROBOT Co., LTD + * @version 0.1 + * @date 2023-03 + * + * @copyright Copyright (c) 20017-2023 SHENZHEN LDROBOT CO., LTD. All rights + * reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License in the file LICENSE + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef __LDLIDAR_DRIVER_SDK_LINUX_INTERFACE_H__ +#define __LDLIDAR_DRIVER_SDK_LINUX_INTERFACE_H__ + +#include "ldlidar_driver/ldlidar_driver.h" +#include "ldlidar_driver/ldlidar_dataprocess.h" +#include "ldlidar_driver/serial_interface_linux.h" +#include "ldlidar_driver/network_socket_interface_linux.h" +#include "ldlidar_driver/log_module.h" + +namespace ldlidar { + +typedef enum CommunicationMode { + COMM_NO_NULL, + COMM_SERIAL_MODE, /* serial communication */ + COMM_UDP_CLIENT_MODE, /* network communication for UDP client */ + COMM_UDP_SERVER_MODE, /* network communication for UDP server */ + COMM_TCP_CLIENT_MODE, /* network communication for TCP client */ + COMM_TCP_SERVER_MODE /* network communication for TCP server */ +}CommunicationModeType; + +class LDLidarDriverLinuxInterface : public LDLidarDriver { +public: + LDLidarDriverLinuxInterface(); + + ~LDLidarDriverLinuxInterface(); + + /** + * @brief Communcation port open and assert initlization param + * @param product_name + * ldlidar product type: ldlidar::LDType, value: + * - ldlidar::LDType::NOVER + * - ldlidar::LDType::LD_14 + * .... + * - else definition in "ldlidar_driver/include/ldlidar_datatype.h" + * @param serial_port_name + * serial device system path, eg: "/dev/ttyUSB0" + * @param serial_baudrate + * serial communication baudrate value(> 0), unit is bit/s. + * @retval value is true, start is success; + * value is false, start is failed. + */ + bool Connect(LDType product_name, + std::string serial_port_name, + uint32_t serial_baudrate = 115200, + CommunicationModeType comm_mode = COMM_SERIAL_MODE); + + bool Connect(LDType product_name, + const char* server_ip, + const char* server_port, + CommunicationModeType comm_mode = COMM_TCP_CLIENT_MODE); + + bool Disconnect(void); + + void EnablePointCloudDataFilter(bool is_enable) override; + + /** + * @brief Whether the connection of the communication channel is normal after the lidar is powered on + * @param[in] + * *@param timeout: Wait timeout, in milliseconds + * @retval if times >= 1000, return false, communication connection is fail; + * if "times < 1000", return ture, communication connection is successful. + */ + bool WaitLidarComm(int64_t timeout = 1000) override; + + /** + * @brief get lidar laser scan point cloud data + * @param [output] + * *@param dst: type is ldlidar::Point2D, value is laser scan point cloud data + * @param [in] + * *@param timeout: Wait timeout, in milliseconds + * @retval value is ldlidar::LidarStatus Enum Type, definition in "include/ldlidar_datatype.h", value is: + * ldlidar::LidarStatus::NORMAL // 雷达正常 + * lldlidar::LidarStatus::ERROR // 雷达异常错误 + * .... + */ + LidarStatus GetLaserScanData(Points2D& dst, int64_t timeout = 1000) override; + + LidarStatus GetLaserScanData(LaserScan& dst, int64_t timeout = 1000) override; + + /** + * @brief get lidar scan frequence + * @param [output] + * *@param spin_hz: value is lidar scan frequence, unit is Hz + * @retval value is true, get sucess; + */ + bool GetLidarScanFreq(double& spin_hz) override; + + /** + * @brief register get timestamp handle functional. + * @param [input] + * *@param get_timestamp_handle: type is `uint64_t (*)(void)`, value is pointer get timestamp fuction. + * @retval none + */ + void RegisterGetTimestampFunctional(std::function get_timestamp_handle) override; + + /** + * @brief When the lidar is in an error state, get the error code fed back by the lidar + * @param none + * @retval errcode + */ + uint8_t GetLidarErrorCode(void) override; + + /** + * @brief Start lidar driver node + * @param none + * @retval value is true, start is success; + * value is false, start is failed. + */ + bool Start(void) override; + + /** + * @brief Stop lidar driver node + * @param none + * @retval value is true, stop is success; + * value is false, stop is failed. + */ + bool Stop(void) override; + + static LDLidarDriverLinuxInterface* Create(void) { + LDLidarDriverLinuxInterface* pdrv = new LDLidarDriverLinuxInterface(); + return pdrv; + } + + static void Destory(LDLidarDriverLinuxInterface* pdrv) { + if (pdrv != nullptr) { + delete pdrv; + } + } + +private: + LdLidarDataProcess* comm_pkg_; + SerialInterfaceLinux* comm_serial_; + TCPSocketInterfaceLinux* comm_tcp_network_; + UDPSocketInterfaceLinux* comm_udp_network_; + std::function register_get_timestamp_handle_; + std::chrono::_V2::steady_clock::time_point last_pubdata_times_; +}; + +} // namespace ldlidar + +#endif // __LDLIDAR_DRIVER_NODE_H__ +/********************* (C) COPYRIGHT SHENZHEN LDROBOT CO., LTD *******END OF + * FILE ********/ \ No newline at end of file diff --git a/sdk/include/ldlidar_driver/ldlidar_driver_win.h b/sdk/include/ldlidar_driver/ldlidar_driver_win.h new file mode 100644 index 0000000..0212a1d --- /dev/null +++ b/sdk/include/ldlidar_driver/ldlidar_driver_win.h @@ -0,0 +1,152 @@ +/** + * @file ldlidar_driver.h + * @author LDRobot (support@ldrobot.com) + * @brief ldlidar sdk interface + * This code is only applicable to LDROBOT LiDAR LD14 + * products sold by Shenzhen LDROBOT Co., LTD + * @version 0.1 + * @date 2023-03 + * + * @copyright Copyright (c) 2017-2023 SHENZHEN LDROBOT CO., LTD. All rights + * reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License in the file LICENSE + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef __LDLIDAR_DRIVER_SDK_WIN_INTERFACE_H__ +#define __LDLIDAR_DRIVER_SDK_WIN_INTERFACE_H__ + +#include "ldlidar_driver/ldlidar_driver.h" +#include "ldlidar_driver/ldlidar_dataprocess.h" +#include "ldlidar_driver/log_module.h" +#include "ldlidar_driver/serial_interface_win.h" + +namespace ldlidar { + +class LDLidarDriverWinInterface : public LDLidarDriver { +public: + LDLidarDriverWinInterface(); + + ~LDLidarDriverWinInterface(); + + /** + * @brief Communcation port open and assert initlization param + * @param product_name + * ldlidar product type: ldlidar::LDType, value: + * - ldlidar::LDType::NOVER + * - ldlidar::LDType::LD_14 + * .... + * - else definition in "ldlidar_driver/include/ldlidar_datatype.h" + * @param serial_port_name + * serial device system path, eg: "/dev/ttyUSB0" + * @param serial_baudrate + * serial communication baudrate value(> 0), unit is bit/s. + * @retval value is true, start is success; + * value is false, start is failed. + */ + bool Connect(LDType product_name, + std::string& serial_port_name, + PortParams& port_params); + + /** + * @brief Communication port close + * @retval value is ture, close success. + * value is false, close fail. + */ + bool Disconnect(void); + + void EnablePointCloudDataFilter(bool is_enable) override; + + /** + * @brief Whether the connection of the communication channel is normal after the lidar is powered on + * @param[in] + * *@param timeout: Wait timeout, in milliseconds + * @retval if times >= 1000, return false, communication connection is fail; + * if "times < 1000", return ture, communication connection is successful. + */ + bool WaitLidarComm(int64_t timeout = 1000) override; + + /** + * @brief get lidar laser scan point cloud data + * @param [output] + * *@param dst: type is ldlidar::Point2D, value is laser scan point cloud data + * @param [in] + * *@param timeout: Wait timeout, in milliseconds + * @retval value is ldlidar::LidarStatus Enum Type, definition in "include/ldlidar_datatype.h", value is: + * ldlidar::LidarStatus::NORMAL // 雷达正常 + * lldlidar::LidarStatus::ERROR // 雷达异常错误 + * .... + */ + LidarStatus GetLaserScanData(Points2D& dst, int64_t timeout = 1000) override; + + LidarStatus GetLaserScanData(LaserScan& dst, int64_t timeout = 1000) override; + + /** + * @brief get lidar scan frequence + * @param [output] + * *@param spin_hz: value is lidar scan frequence, unit is Hz + * @retval value is true, get sucess; + */ + bool GetLidarScanFreq(double& spin_hz) override; + + /** + * @brief register get timestamp handle functional. + * @param [input] + * *@param get_timestamp_handle: type is `uint64_t (*)(void)`, value is pointer get timestamp fuction. + * @retval none + */ + void RegisterGetTimestampFunctional(std::function get_timestamp_handle) override; + + /** + * @brief When the lidar is in an error state, get the error code fed back by the lidar + * @param none + * @retval errcode + */ + uint8_t GetLidarErrorCode(void) override; + + /** + * @brief Start lidar driver node + * @param none + * @retval value is true, start is success; + * value is false, start is failed. + */ + bool Start(void) override; + + /** + * @brief Stop lidar driver node + * @param none + * @retval value is true, stop is success; + * value is false, stop is failed. + */ + bool Stop(void) override; + + static LDLidarDriverWinInterface* Create(void) { + LDLidarDriverWinInterface* pdrv = new LDLidarDriverWinInterface(); + return pdrv; + } + + static void Destory(LDLidarDriverWinInterface* pdrv) { + if (pdrv != nullptr) { + delete pdrv; + } + } + +private: + // bool is_start_flag_; + // bool is_connect_flag_; + LdLidarDataProcess* comm_pkg_; + SerialInterfaceWin* comm_port_handle_; + std::function register_get_timestamp_handle_; + std::chrono::steady_clock::time_point last_pubdata_times_; +}; + +} // namespace ldlidar + +#endif // __LDLIDAR_DRIVER_NODE_H__ +/********************* (C) COPYRIGHT SHENZHEN LDROBOT CO., LTD *******END OF + * FILE ********/ \ No newline at end of file diff --git a/sdk/include/ldlidar_driver/ldlidar_protocol.h b/sdk/include/ldlidar_driver/ldlidar_protocol.h new file mode 100644 index 0000000..3cb03ba --- /dev/null +++ b/sdk/include/ldlidar_driver/ldlidar_protocol.h @@ -0,0 +1,230 @@ +/** +* @file ldlidar_protocol.h +* @author LDRobot (support@ldrobot.com) +* David Hu(hmd_hubei_cn@163.com) +* @brief +* @version 1.0 +* @date 2023.3.11 +* @note +* @copyright Copyright (c) 2017-2023 SHENZHEN LDROBOT CO., LTD. All rights reserved. +* Licensed under the MIT License (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License in the file LICENSE +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +**/ + +#ifndef __LDLIDAR_PROTOCOL_H__ +#define __LDLIDAR_PROTOCOL_H__ + +#include +#include + +namespace ldlidar { + +struct CmdIdAckFlag { + bool is_ack_start; + bool is_ack_stop; + bool is_ack_set_scan_freq; + bool is_ack_get_motor_board_ver; + + CmdIdAckFlag() { + is_ack_start = false; + is_ack_stop = false; + is_ack_set_scan_freq = false; + is_ack_get_motor_board_ver = false; + } + + void Clear() { + is_ack_start = false; + is_ack_stop = false; + is_ack_set_scan_freq = false; + is_ack_get_motor_board_ver = false; + } +}; + +#define CMD_ACK_PROTOCOL_DATABUFF_MAX_SIZE 50 +#define CMD_ACK_PROTOCOL_DATABUFF_MIN_SIZE 4 +#define CMD_ACK_PROTOCOL_DATABUFF_INDEX 3 +#define CMD_ACK_PROTOCOL_NO_DATABUFF_SIZE 4 + +typedef enum LidarCmdModeEnum { + SET_START_LIDAR_SCAN = 0xA0, + SET_STOP_LIDAR_SCAN = 0xA1, + SET_LIDAR_SCAN_FRE = 0xA2, + GET_LIDAR_MOTOR_BOARD_FIRMWARE = 0xA8 +} LidarCmdIDType; + +struct LidarCmdAckData { + LidarCmdIDType cmd_mode; + uint8_t databuff[CMD_ACK_PROTOCOL_DATABUFF_MAX_SIZE]; +}; + +#ifdef __linux__ + +typedef struct __attribute__((packed)) { + uint8_t header; + uint8_t mode; + uint8_t datalen; + uint32_t data; + uint8_t crc8; +} LidarCmdDataType; + +#else + +#pragma pack(1) +typedef struct { + uint8_t header; + uint8_t mode; + uint8_t datalen; + uint32_t data; + uint8_t crc8; +} LidarCmdDataType; +#pragma pack() + +#endif + +// ---- + +#define PKG_HEADER 0x54 +#define DATA_PKG_INFO 0x2C +#define POINT_PER_PACK 12 +#define HEALTH_PKG_INFO 0xE0 +#define MANUFACT_PKG_INF 0x0F + +#define GET_PKG_PCD 1 +#define GET_PKG_HEALTH 2 +#define GET_PKG_MANUFACT 3 +#define GET_PKG_ERROR 0 + +#ifdef __linux__ + +typedef struct __attribute__((packed)) { + uint8_t header; + uint8_t information; + uint16_t speed; + uint16_t product_version; + uint32_t sn_high; + uint32_t sn_low; + uint32_t hardware_version; + uint32_t firmware_version; + uint8_t crc8; +} LiDARManufactureInfoType; + +typedef struct __attribute__((packed)) { + uint16_t distance; + uint8_t intensity; +} LidarPointStructType; + +typedef struct __attribute__((packed)) { + uint8_t header; + uint8_t ver_len; + uint16_t speed; + uint16_t start_angle; + LidarPointStructType point[POINT_PER_PACK]; + uint16_t end_angle; + uint16_t timestamp; + uint8_t crc8; +} LiDARMeasureDataType; + +typedef struct __attribute__((packed)) { + uint8_t header; + uint8_t information; + uint8_t error_code; + uint8_t crc8; +} LiDARHealthInfoType; + +#else + +#pragma pack(1) +typedef struct { + uint8_t header; + uint8_t information; + uint16_t speed; + uint16_t product_version; + uint32_t sn_high; + uint32_t sn_low; + uint32_t hardware_version; + uint32_t firmware_version; + uint8_t crc8; +} LiDARManufactureInfoType; + +typedef struct { + uint16_t distance; + uint8_t intensity; +} LidarPointStructType; + +typedef struct { + uint8_t header; + uint8_t ver_len; + uint16_t speed; + uint16_t start_angle; + LidarPointStructType point[POINT_PER_PACK]; + uint16_t end_angle; + uint16_t timestamp; + uint8_t crc8; +} LiDARMeasureDataType; + +typedef struct { + uint8_t header; + uint8_t information; + uint8_t error_code; + uint8_t crc8; +} LiDARHealthInfoType; +#pragma pack() + +#endif + +class LdLidarProtocol { +public: + LdLidarProtocol(); + ~LdLidarProtocol(); + + /** + * @brief analysis data packet. . + * @param[in] + * * @param byte : input serial byte data + * @retval + * If the return value is GET_PKG_PCD macro, the lidar point cloud data is obtained + * If the return value is GET_PKG_HEALTH macro, the lidar health information is obtained. + * If the return value is GET_PKG_MANUFACT macro, the lidar manufacture information is obtained. + */ + uint8_t AnalysisDataPacket(uint8_t byte); + /** + * @brief get point cloud data. + */ + LiDARMeasureDataType& GetPCDPacketData(void); + /** + * @brief get health information + */ + LiDARHealthInfoType& GetHealthPacketData(void); + + /** + * @brief get manufacture information + */ + LiDARManufactureInfoType& GetManufactureInfoPacketData(void); + +private: + LiDARMeasureDataType pcdpkg_data_; + LiDARHealthInfoType healthpkg_data_; + LiDARManufactureInfoType manufacinfpkg_data_; +}; + +#ifdef __cplusplus +extern "C" { +#endif + +uint8_t CalCRC8(const uint8_t *data, uint16_t data_len); + +#ifdef __cplusplus +} +#endif + + +} // namespace ldlidar + +#endif //__LDLIDAR_PROTOCOL_H__ +/********************* (C) COPYRIGHT SHENZHEN LDROBOT CO., LTD *******END OF FILE ********/ diff --git a/sdk/include/ldlidar_driver/log_module.h b/sdk/include/ldlidar_driver/log_module.h new file mode 100644 index 0000000..5fc2ce5 --- /dev/null +++ b/sdk/include/ldlidar_driver/log_module.h @@ -0,0 +1,238 @@ +/** +* @file log_module.h +* @author David Hu (hmd_hubei_cn@163.com) +* @brief +* @version 0.1 +* @date 2022.08.10 +* @note +* @copyright Copyright (c) 2022 DAVID HU All rights reserved. +* Licensed under the MIT License (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License in the file LICENSE +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +**/ + +#ifndef __LOGMODULE_H_ +#define __LOGMODULE_H_ + + +#define ENABLE_LOG_DIS_OUTPUT + +#define ENABLE_CONSOLE_LOG_DISPALY + +//#define ENABLE_LOG_WRITE_TO_FILE + +#include +#include +#include +#include + +#ifndef __linux__ +#include +#else +//#include +#include +#define printf_s(fileptr,str) (fprintf(fileptr,"%s",str)) +#define __in +#endif // ?????????????????????? + + +struct LogVersion { + int n_version; + std::string str_descruble; +}; + + +class ILogRealization { +public: + virtual ~ILogRealization() { + + } + virtual void Initializion(const char* path = NULL) = 0; + virtual void LogPrintInf(const char* str) = 0; + virtual void LogPrintData(const char* str) = 0; + + void free() { + free(this); + //this = NULL; + }; +private: + virtual void free(ILogRealization *plogger) = 0 ; +}; + + +#define ILOGFREE(LogRealizationClass) virtual void free(ILogRealization* plogger) \ +{ \ + LogRealizationClass* prealization = static_cast(plogger); \ + if (prealization != NULL){ delete prealization;} \ +} + +class LogPrint :public ILogRealization { +public: + virtual void Initializion(const char* path = NULL); + virtual void free(ILogRealization *plogger); + virtual void LogPrintInf(const char* str); + virtual void LogPrintData(const char* str); + + inline std::string GetLogFilePathName(void) { + std::string curr_date_log_file; + char stdtime_str[50] = {0}; + time_t std_time = 0; + struct tm* local_time = NULL; + std_time = time(NULL); + local_time = localtime(&std_time); + snprintf(stdtime_str, 50, "./lds-%d-%d-%d-%d.log", + local_time->tm_year+1900, local_time->tm_mon+1, local_time->tm_mday, + local_time->tm_hour); + curr_date_log_file.assign(stdtime_str); + return curr_date_log_file; + } + + inline std::string GetOriginDataFilePathName(void) { + std::string curr_date_log_file; + char stdtime_str[50] = {0}; + time_t std_time = 0; + struct tm* local_time = NULL; + std_time = time(NULL); + local_time = localtime(&std_time); + snprintf(stdtime_str, 50, "./serialdata-%d-%d-%d-%d.log", + local_time->tm_year+1900, local_time->tm_mon+1, local_time->tm_mday, + local_time->tm_hour); + curr_date_log_file.assign(stdtime_str); + return curr_date_log_file; + } +}; + +#ifndef __linux__ +class LogOutputString :public ILogRealization { +public: + virtual void Initializion(const char* path = NULL) { + return ; + } + + virtual void LogPrintInf(const char* str) { + //OutputDebugString((LPCTSTR)str); /* 将字符串发送到调试器进行显示。*/ + //OutputDebugString("\r\n"); + printf("%s\r\n", str); /* 将字符串发送到控制台显示 */ + } + + virtual void LogPrintData(const char* str) { + //OutputDebugString((LPCTSTR)str); + //OutputDebugString("\r\n"); + printf("%s\r\n", str); + } + + ILOGFREE(LogOutputString) +/* + virtual void free(ILogRealization *plogger) + { + LogOutputString* poutput = static_cast(plogger); + if (poutput != NULL) + { + delete poutput; + } + } +*/ +}; +#endif + + +class LogModule { +public: + enum LogLevel { + DEBUG_LEVEL, + WARNING_LEVEL, + ERROR_LEVEL, + INFO_LEVEL + }; + + struct LOGMODULE_INFO { + LogLevel loglevel; + std::string str_filename; + std::string str_funcname; + int n_linenumber; + }logInfo_; + + ILogRealization* p_realization_; +public: + static LogModule* GetInstance( __in const char* filename, __in const char* funcname,__in int lineno, LogLevel level, ILogRealization* plog = NULL); + static LogModule* GetInstance(LogLevel level, ILogRealization* plog = NULL); + static LogModule* GetInstancePrintOriginData(LogLevel level, ILogRealization* plog = NULL); + + void LogPrintInf(const char* format,...); + void LogPrintNoLocationInf(const char* format,...); + void LogPrintOriginData(const char* format,...); + +private: + LogModule(); + + ~LogModule(); + + void InitLock(); + + void RealseLock(); + + void Lock(); + + void UnLock(); + + std::string GetCurrentISOTime(); + + std::string GetCurrentLocalTimeStamp(); + + std::string GetFormatValue(std::string str_value); + + std::string GetFormatValue(int n_value); + + std::string GetLevelValue(int level); + + static LogModule* s_plog_module_; + +#ifndef __linux__ + CRITICAL_SECTION mutex_lock_; +#else + pthread_mutex_t mutex_lock_; +#endif +}; + +#ifdef ENABLE_LOG_DIS_OUTPUT +#define LOG(level,format,...) LogModule::GetInstance(__FILE__, __FUNCTION__, __LINE__,level)->LogPrintInf(format,__VA_ARGS__); +#define LOG_DEBUG(format,...) LOG(LogModule::DEBUG_LEVEL,format,__VA_ARGS__) +#define LOG_INFO(format,...) LOG(LogModule::INFO_LEVEL,format,__VA_ARGS__) +#define LOG_WARN(format,...) LOG(LogModule::WARNING_LEVEL,format,__VA_ARGS__) +#define LOG_ERROR(format,...) LOG(LogModule::ERROR_LEVEL,format,__VA_ARGS__) +#else +#define LOG_DEBUG(format,...) do {} while(0) +#define LOG_INFO(format,...) do {} while(0) +#define LOG_WARN(format,...) do {} while(0) +#define LOG_ERROR(format,...) do {} while(0) +#endif + +#ifdef ENABLE_LOG_DIS_OUTPUT +#define LOG_LITE(level,format,...) LogModule::GetInstance(level)->LogPrintNoLocationInf(format,__VA_ARGS__); +#define LOG_DEBUG_LITE(format,...) LOG_LITE(LogModule::DEBUG_LEVEL,format,__VA_ARGS__) +#define LOG_INFO_LITE(format,...) LOG_LITE(LogModule::INFO_LEVEL,format,__VA_ARGS__) +#define LOG_WARN_LITE(format,...) LOG_LITE(LogModule::WARNING_LEVEL,format,__VA_ARGS__) +#define LOG_ERROR_LITE(format,...) LOG_LITE(LogModule::ERROR_LEVEL,format,__VA_ARGS__) +#else +#define LOG_DEBUG_LITE(format,...) do {} while(0) +#define LOG_INFO_LITE(format,...) do {} while(0) +#define LOG_WARN_LITE(format,...) do {} while(0) +#define LOG_ERROR_LITE(format,...) do {} while(0) +#endif + +#ifdef ENABLE_LOG_DIS_OUTPUT +#define LOG_PRINT(level,format,...) LogModule::GetInstancePrintOriginData(level)->LogPrintOriginData(format,__VA_ARGS__); +#define LOG_DEBUG_PRINT(format,...) LOG_PRINT(LogModule::DEBUG_LEVEL,format,__VA_ARGS__) +#define LOG_INFO_PRINT(format,...) LOG_PRINT(LogModule::INFO_LEVEL,format,__VA_ARGS__) +#else +#define LOG_DEBUG_PRINT(format,...) do {} while(0) +#define LOG_INFO_PRINT(format,...) do {} while(0) +#endif + +#endif//__LOGGER_MODULE_H__ +/********************* (C) COPYRIGHT DAVID HU *******END OF FILE ********/ diff --git a/sdk/include/ldlidar_driver/network_socket_interface_linux.h b/sdk/include/ldlidar_driver/network_socket_interface_linux.h new file mode 100644 index 0000000..6c569f6 --- /dev/null +++ b/sdk/include/ldlidar_driver/network_socket_interface_linux.h @@ -0,0 +1,125 @@ +/** + * @file network_socket_interface_linux.h + * @author LDRobot (support@ldrobot.com) + * @brief linux network App + * @version 0.1 + * @date 2022-09-05 + * + * @copyright Copyright (c) 2017-2023 SHENZHEN LDROBOT CO., LTD. All rights + * reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License in the file LICENSE + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __SOCKET_INTERFACE_LINUX_H__ +#define __SOCKET_INTERFACE_LINUX_H__ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ldlidar { + +typedef enum NetworkCommDevEnum { + NET_NULL, + UDP_SERVER, + UDP_CLIENT, + TCP_SERVER, + TCP_CLIENT +}NetCommDevTypeDef; + +class UDPSocketInterfaceLinux { +public: + UDPSocketInterfaceLinux(); + + ~UDPSocketInterfaceLinux(); + + bool CreateSocket(NetCommDevTypeDef obj, const char *ip, const char *port); + + bool CloseSocket(); + + bool TransToNet(uint8_t *tx_buf, uint32_t tx_buff_len, uint32_t *tx_len); + + void SetRecvCallback(std::function callback); + + bool IsClientAck() { return is_server_recv_ack_flag_.load();} + +private: + std::thread *recv_thread_; + long long recv_count_; + int32_t com_sockfd_; + NetCommDevTypeDef ncd_; + std::atomic is_cmd_created_, recv_thread_exit_flag_, is_server_recv_ack_flag_; + std::function recv_callback_; + std::string server_ip_, server_port_; + std::string client_ip_, client_port_; + + bool IsCreated() { return is_cmd_created_.load(); } + + bool RecvFromNet(uint8_t *rx_buf , uint32_t rx_buff_len, uint32_t *rx_len); + + static void RecvThreadProc(void *param); +}; + + +class TCPSocketInterfaceLinux { +public: + TCPSocketInterfaceLinux(); + + ~TCPSocketInterfaceLinux(); + + bool CreateSocket(NetCommDevTypeDef obj, const char *ip, const char *port); + + bool CloseSocket(); + + bool TransToNet(uint8_t *tx_buf, uint32_t tx_buff_len, uint32_t *tx_len); + + void SetRecvCallback(std::function callback); + + + +private: + std::thread *recv_thread_; + long long recv_count_; + int32_t com_sockfd_; + int32_t listend_client_sockfd_; //// server model used + NetCommDevTypeDef ncd_; + std::atomic is_cmd_created_, recv_thread_exit_flag_; + std::function recv_callback_; + + bool IsCreated() { return is_cmd_created_.load(); } + + bool RecvFromNet(uint8_t *rx_buf , uint32_t rx_buff_len, uint32_t *rx_len); + + static void RecvThreadProc(void *param); +}; + +} // namespace ldlidar +#endif // __SOCKET_INTERFACE_LINUX_H__ +/********************* (C) COPYRIGHT SHENZHEN LDROBOT CO., LTD *******END OF + * FILE ********/ \ No newline at end of file diff --git a/sdk/include/ldlidar_driver/serial_interface_linux.h b/sdk/include/ldlidar_driver/serial_interface_linux.h new file mode 100644 index 0000000..d3e4e94 --- /dev/null +++ b/sdk/include/ldlidar_driver/serial_interface_linux.h @@ -0,0 +1,81 @@ +/** + * @file cmd_interface_linux.h + * @author LDRobot (support@ldrobot.com) + * @brief linux serial port App + * @version 0.1 + * @date 2021-10-28 + * + * @copyright Copyright (c) 2017-2023 SHENZHEN LDROBOT CO., LTD. All rights + * reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License in the file LICENSE + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __LINUX_SERIAL_PORT_H__ +#define __LINUX_SERIAL_PORT_H__ + +#include +#include +#include +#include +#include +#include +#include +namespace asmtermios { +#include +} +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ldlidar { + +class SerialInterfaceLinux { +public: + SerialInterfaceLinux(); + ~SerialInterfaceLinux(); + // open serial port + bool Open(std::string &port_name, uint32_t com_baudrate); + // close serial port + bool Close(); + // receive from port channel data + bool ReadFromIO(uint8_t *rx_buf, uint32_t rx_buf_len, uint32_t *rx_len); + // transmit data to port channel + bool WriteToIo(const uint8_t *tx_buf, uint32_t tx_buf_len, uint32_t *tx_len); + // set receive port channel data callback deal with fuction + void SetReadCallback(std::function callback) { + read_callback_ = callback; + } + // whether open + bool IsOpened() { return is_cmd_opened_.load(); }; + +private: + std::thread *rx_thread_; + long long rx_count_; + int32_t com_handle_; + uint32_t com_baudrate_; + std::atomic is_cmd_opened_, rx_thread_exit_flag_; + std::function read_callback_; + static void RxThreadProc(void *param); +}; + +} // namespace ldlidar + +#endif //__LINUX_SERIAL_PORT_H__ + +/********************* (C) COPYRIGHT SHENZHEN LDROBOT CO., LTD *******END OF + * FILE ********/ \ No newline at end of file diff --git a/sdk/include/ldlidar_driver/serial_interface_win.h b/sdk/include/ldlidar_driver/serial_interface_win.h new file mode 100644 index 0000000..f250548 --- /dev/null +++ b/sdk/include/ldlidar_driver/serial_interface_win.h @@ -0,0 +1,124 @@ +/** + * @file serial_interface_win.h + * @author LDRobot (support@ldrobot.com) + * @brief Win32 serial port App + * @version 0.1 + * @date 2021-10-28 + * + * @copyright Copyright (c) 2017-2023 SHENZHEN LDROBOT CO., LTD. All rights + * reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License in the file LICENSE + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#include + +#include +#include +#include +#include + +struct PortInfo { + std::string name; + std::string description; + std::string pid; + std::string vid; +}; + +enum class BaudRate { + Baud1200 = 1200, + Baud2400 = 2400, + Baud4800 = 4800, + Baud9600 = 9600, + Baud19200 = 19200, + Baud38400 = 38400, + Baud57600 = 57600, + Baud115200 = 115200, + Baud230400 = 230400, + Unknonw = -1 +}; +enum class DataBits { + Data5 = 5, + Data6 = 6, + Data7 = 7, + Data8 = 8, + Unknonw = -1 +}; +enum class FlowControl { + NoFlowControl, + HardwareControl, + SoftwareControl, + Unknonw = -1 +}; +enum class Parity { + NoParity, + EvenParity, + OddParity, + SpaceParity, + MarkParity, + Unknonw = -1 +}; +enum class StopBits { OneStop = 0, TwoStop, OneAndHalfStop, Unknow }; + +struct PortParams { + std::string portName; + BaudRate baudrate = BaudRate::Baud115200; + Parity parity = Parity::NoParity; + DataBits dataBits = DataBits::Data8; + StopBits stopBits = StopBits::OneStop; + FlowControl flowControl = FlowControl::NoFlowControl; +}; + +class SerialInterfaceWin { + public: + SerialInterfaceWin(); + ~SerialInterfaceWin(); + + static bool availablePorts(std::vector &availabelPortInfo); + bool open(std::string port_name); + bool close(); + bool write(const char *tx_buf, uint32_t tx_buf_len); + int readAll(); + long long rxLength() { return mRxCounter; } + long long txLength() { return mTxCounter; } + void clearRxLength(void) { mRxCounter = 0; } + bool isOpen() { return mIsOpened; } + void setBufferSize(size_t size) { mMaxBuffSize = size; } + void setCommErrorCallback(std::function callback) { + commErrorHandle = callback; + } + void setReadCallback( + std::function callback) { + readCallback = callback; + } + + void setPortParams(PortParams params); + PortParams currPortParams(void); + + private: + bool mIsOpened; + HANDLE mComHandle; + std::thread *mRxThread; + static void rxThreadProc(SerialInterfaceWin *pClass); + size_t mByteToRead; + long long mRxCounter; + long long mTxCounter; + size_t mMaxBuffSize; + OVERLAPPED mOverlappedSend; + OVERLAPPED mOverlappedRecv; + std::atomic mRxThreadRunFlag; + + std::function commErrorHandle; + std::function readCallback; + + PortParams portParams; +}; + +/********************* (C) COPYRIGHT LD Robot *******END OF FILE ********/ \ No newline at end of file diff --git a/sdk/include/ldlidar_driver/sl_transform.h b/sdk/include/ldlidar_driver/sl_transform.h new file mode 100644 index 0000000..e543d03 --- /dev/null +++ b/sdk/include/ldlidar_driver/sl_transform.h @@ -0,0 +1,50 @@ +/** + * @file transform.h + * @author LDRobot (support@ldrobot.com) + * @brief Ranging center conversion with left and right hand system changes App + * This code is only applicable to LDROBOT LiDAR LD00 LD03 LD08 LD14 + * products sold by Shenzhen LDROBOT Co., LTD + * @version 0.1 + * @date 2021-11-09 + * + * @copyright Copyright (c) 2017-2023 SHENZHEN LDROBOT CO., LTD. All rights + * reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License in the file LICENSE + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef __TRANSFORM_H +#define __TRANSFORM_H + +#include + +#include +#include + +#include "ldlidar_driver/ldlidar_datatype.h" + +namespace ldlidar { + +class SlTransform { +private: + bool to_right_hand_ = true; + double offset_x_; + double offset_y_; + LDType version_; + +public: + SlTransform(LDType version, bool to_right_hand = false); + Points2D Transform(const Points2D &data); + ~SlTransform(); +}; + +} //namespace ldlidar + +#endif // __TRANSFORM_H +/********************* (C) COPYRIGHT SHENZHEN LDROBOT CO., LTD *******END OF + * FILE ********/ \ No newline at end of file diff --git a/sdk/include/ldlidar_driver/slbf.h b/sdk/include/ldlidar_driver/slbf.h new file mode 100644 index 0000000..cd1e553 --- /dev/null +++ b/sdk/include/ldlidar_driver/slbf.h @@ -0,0 +1,59 @@ +/** + * @file slbf.h + * @author LDRobot (support@ldrobot.com) + * @brief LiDAR near-range filtering algorithm + * This code is only applicable to LDROBOT LiDAR LD00 LD03 LD08 LD14 + * products sold by Shenzhen LDROBOT Co., LTD + * @version 0.1 + * @date 2021-11-09 + * + * @copyright Copyright (c) 2017-2023 SHENZHEN LDROBOT CO., LTD. All rights + * reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License in the file LICENSE + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef __SLBF_H_ +#define __SLBF_H_ + +#include + +#include + +#include "ldlidar_driver/ldlidar_datatype.h" + +namespace ldlidar { + +class Slbf { +private: + const int kConfidenceHigh = 200; + const int kConfidenceMiddle = 150; + const int kConfidenceLow = 92; + const int kScanFre = 2300; // Default scanning frequency, + // which can be changed according to radar protocol + double curr_speed_; + bool enable_strict_policy_; // whether strict filtering is enabled within 300 + // mm, the effective value may be lost, and the + // time sequence of recharging needs to be + // disabled + Slbf() = delete; + Slbf(const Slbf &) = delete; + Slbf &operator=(const Slbf &) = delete; + +public: + Slbf(int speed, bool strict_policy = true); + Points2D NearFilter(const Points2D &tmp) const; + void EnableStrictPolicy(bool enable); + ~Slbf(); +}; + +} // namespace ldlidar + +#endif // __SLBF_H_ +/********************* (C) COPYRIGHT SHENZHEN LDROBOT CO., LTD *******END OF + * FILE ********/ \ No newline at end of file diff --git a/sdk/include/ldlidar_driver/tofbf.h b/sdk/include/ldlidar_driver/tofbf.h new file mode 100644 index 0000000..152a6bd --- /dev/null +++ b/sdk/include/ldlidar_driver/tofbf.h @@ -0,0 +1,70 @@ +/** + * @file tofbf.h + * @author LDRobot (support@ldrobot.com) + * @brief LiDAR near-range filtering algorithm + * This code is only applicable to LDROBOT LiDAR LD06 products + * sold by Shenzhen LDROBOT Co., LTD + * @version 0.1 + * @date 2021-10-28 + * + * @copyright Copyright (c) 2017-2023 SHENZHEN LDROBOT CO., LTD. All rights + * reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License in the file LICENSE + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __TOFBF_H_ +#define __TOFBF_H_ + +#include + +#include +#include + +#include +#include + +#include "ldlidar_driver/ldlidar_datatype.h" + +namespace ldlidar { + +enum class FilterType{ + NO_FILTER, + NEAR_FILTER, + NOISE_FILTER +}; + +class Tofbf { + public: + Tofbf(int speed, LDType type); + ~Tofbf(); + std::vector Filter(const std::vector &tmp) const; + + private: + FilterType filter_type_; + // Low intensity threshold + int intensity_low_; + // Discrete points require higher intensity + int intensity_single_; + // Default scan frequency, to change, read according to radar protocol + int scan_frequency_; + double curr_speed_; + Tofbf() = delete; + Tofbf(const Tofbf &) = delete; + Tofbf &operator=(const Tofbf &) = delete; + std::vector NearFilter(const std::vector &tmp) const; + std::vector NoiseFilter(const std::vector &tmp) const; +}; + +} // namespace ldlidar + +#endif //__TOFBF_H_ + +/********************* (C) COPYRIGHT SHENZHEN LDROBOT CO., LTD *******END OF + * FILE ********/ \ No newline at end of file diff --git a/sdk/sample/linux/CMakeLists.txt b/sdk/sample/linux/CMakeLists.txt new file mode 100644 index 0000000..452385b --- /dev/null +++ b/sdk/sample/linux/CMakeLists.txt @@ -0,0 +1,72 @@ +cmake_minimum_required(VERSION 2.8.3) +project(ldlidar) + +if(${CMAKE_BUILD_TYPE} MATCHES "Release") + set(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -std=c++11 -Wall") + message(STATUS "Mode: Release") + message(STATUS "optional:-std=c++11 -Wall") +elseif(${CMAKE_BUILD_TYPE} MATCHES "Debug") + set(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -std=c++11 -Wall -Wextra -g2 -ggdb") + message(STATUS "Mode: Debug") + message(STATUS "optional:-std=c++11 -Wall -Wextra -g2 -ggdb") +else() + set(CMAKE_BUILD_TYPE "Debug") + set(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -std=c++11 -Wall -Wextra -g2 -ggdb") + message(STATUS "Mode: Debug") + message(STATUS "optional:-std=c++11 -Wall -Wextra -g2 -ggdb") +endif() + +########### +## Build ## +########### + +## Specify additional locations of header files +## Your package locations should be listed before other locations +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/../../include/ +) + +set(LDLIDAR_DRIVER_SOURCE_LINUX + ${CMAKE_CURRENT_SOURCE_DIR}/../../src/ldlidar_driver.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../../src/ldlidar_driver_linux.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../../src/ldlidar_dataprocess.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../../src/ldlidar_protocol.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../../src/log_module.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../../src/network_socket_interface_linux.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../../src/serial_interface_linux.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../../src/sl_transform.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../../src/slbf.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../../src/tofbf.cpp +) + +add_executable(${PROJECT_NAME} + demo.cpp +) + +add_library(ldlidar_driver_static STATIC + ${LDLIDAR_DRIVER_SOURCE_LINUX} +) + +add_library(ldlidar_driver_shared SHARED + ${LDLIDAR_DRIVER_SOURCE_LINUX} +) + +# rename library name +set_target_properties (ldlidar_driver_static PROPERTIES OUTPUT_NAME "ldlidar_driver") +set_target_properties (ldlidar_driver_shared PROPERTIES OUTPUT_NAME "ldlidar_driver") + +# binary file link to library +target_link_libraries(${PROJECT_NAME} ldlidar_driver_static pthread) + +########### +## Install ## +########### + +install(TARGETS ldlidar_driver_static ldlidar_driver_shared + ARCHIVE DESTINATION lib/ldlidar_driver + LIBRARY DESTINATION share/ldlidar_driver +) + +install(DIRECTORY ${PROJECT_SOURCE_DIR}/include + DESTINATION include +) diff --git a/sdk/sample/linux/README.md b/sdk/sample/linux/README.md new file mode 100644 index 0000000..30a0d94 --- /dev/null +++ b/sdk/sample/linux/README.md @@ -0,0 +1,32 @@ +# 1.system setup +- Connect the LiDAR to your system motherboard via an onboard serial port or usB-to-serial module (for example, CP2102 module). + +- Set the -x permission for the serial port device mounted by the lidar in the system (for example, /dev/ttyUSB0),In actual use, the LiDAR can be set according to the actual mounted status of your system, you can use 'ls -l /dev' command to view. + +``` bash +$ cd ~/ldlidar_ws/ldlidar_sdk/sample/linux + +$ sudo chmod 777 /dev/ttyUSB0 +``` + +# 2.build + +``` bash +$ cd ~/ldlidar_ws/ldlidar_sdk/sample/linux + +$ ./auto_build.sh +``` + +# 3.run +``` bash +$ ./build/ldlidar +# example: +# LDLiDAR LD14 +$ ./build/ldlidar LD14 /dev/ttyUSB0 +# LDLiDAR LD14P +$ ./build/ldlidar LD14P /dev/ttyUSB0 +# LDLiDAR LD06 +$ ./build/ldlidar LD06 /dev/ttyUSB0 +# LDLiDAR LD19 +$ ./build/ldlidar LD19 /dev/ttyUSB0 +``` \ No newline at end of file diff --git a/sdk/sample/linux/README_CN.md b/sdk/sample/linux/README_CN.md new file mode 100644 index 0000000..2399d76 --- /dev/null +++ b/sdk/sample/linux/README_CN.md @@ -0,0 +1,32 @@ +# 1.系统设置 + +- 第一步,通过板载串口或者USB转串口模块(例如,cp2102模块)的方式,使激光雷达连接到你的系统主板. +- 第二步,设置激光雷达在系统中挂载的串口设备-x权限,根据激光雷达在你的系统中的实际挂载情况来设置,可以使用`ls -l /dev`命令查看,(以/dev/ttyUSB0为例). + +``` bash +$ cd ~/ldlidar_ws/ldlidar_sdk/sample/linux + +$ sudo chmod 777 /dev/ttyUSB0 +``` + +# 2.编译 + +```bash +$ cd ~/ldlidar_ws/ldlidar_sdk/sample/linux + +$ ./auto_build.sh +``` + +# 3.运行 +``` bash +$ ./build/ldlidar +# example: +# LDLiDAR LD14 +$ ./build/ldlidar LD14 /dev/ttyUSB0 +# LDLiDAR LD14P +$ ./build/ldlidar LD14P /dev/ttyUSB0 +# LDLiDAR LD06 +$ ./build/ldlidar LD06 /dev/ttyUSB0 +# LDLiDAR LD19 +$ ./build/ldlidar LD19 /dev/ttyUSB0 +``` \ No newline at end of file diff --git a/sdk/sample/linux/auto_build.sh b/sdk/sample/linux/auto_build.sh new file mode 100644 index 0000000..d691ec1 --- /dev/null +++ b/sdk/sample/linux/auto_build.sh @@ -0,0 +1,45 @@ +#!/bin/bash +#Author: David Hu +#Date: 2022-09 + +# Exit on error +set -e + +BUILD_MODE_DEBUG=1 +BUILD_MODE_RELEASE=0 +OP_YES=1 + +echo "Start cmake build" +read -p "Please select build mode [Debug(1)/Release(0)]:" BUILD_MODE +read -p "Is install lib and include files on the workspace, please input [yes(1)/no[0]]:" IS_INSTALL + +if [ ! -e "./build" ];then + mkdir build + echo "create ./build/" +fi + +cd ./build + +if [ ${BUILD_MODE} == ${BUILD_MODE_DEBUG} ];then + cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=../install .. +elif [ ${BUILD_MODE} == ${BUILD_MODE_RELEASE} ];then + cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=../install .. +else + echo "build mode input error" + exit 0 +fi + +make + +if [ ${IS_INSTALL} == ${OP_YES} ] +then + if [ ! -e "../install" ] + then + mkdir ../install + echo "create ../install/" + fi + make install + echo "build and install finished." +else + echo "build finished." +fi \ No newline at end of file diff --git a/sdk/sample/linux/clean_build.sh b/sdk/sample/linux/clean_build.sh new file mode 100644 index 0000000..d58f6d7 --- /dev/null +++ b/sdk/sample/linux/clean_build.sh @@ -0,0 +1,14 @@ +#!/bin/bash +#Author: David Hu +#Date: 2022-09 +if [ -e "./build" ];then + rm -rf build/ + echo "del ./build/" +fi + +if [ -e "./install" ];then + rm -rf install/ + echo "del ./install/" +fi + +echo "del is ok....." \ No newline at end of file diff --git a/sdk/sample/linux/demo.cpp b/sdk/sample/linux/demo.cpp new file mode 100644 index 0000000..2b6ed20 --- /dev/null +++ b/sdk/sample/linux/demo.cpp @@ -0,0 +1,187 @@ +/** + * @file main.cpp + * @author LDRobot (support@ldrobot.com) + * @brief main process App + * This code is only applicable to LDROBOT LiDAR LD00 LD03 LD08 LD14 + * products sold by Shenzhen LDROBOT Co., LTD + * @version 0.1 + * @date 2021-11-08 + * + * @copyright Copyright (c) 2017-2023 SHENZHEN LDROBOT CO., LTD. All rights + * reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License in the file LICENSE + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ldlidar_driver/ldlidar_driver_linux.h" + +uint64_t GetTimestamp(void) { + std::chrono::time_point tp = + std::chrono::time_point_cast(std::chrono::system_clock::now()); + auto tmp = std::chrono::duration_cast(tp.time_since_epoch()); + return ((uint64_t)tmp.count()); +} + +// void LidarPowerOn(void) { +// LOG_DEBUG("Lidar Power On",""); +// // ... +// } + +// void LidarPowerOff(void) { +// LOG_DEBUG("Lidar Power Off",""); +// // ... +// } + +struct LdsInfoStruct { + std::string ldtype_str; + ldlidar::LDType ldtype_enum; + uint32_t baudrate; +}; + +LdsInfoStruct LdsInfoArrary[4] = { + {"LD14", ldlidar::LDType::LD_14, 115200}, + {"LD14P", ldlidar::LDType::LD_14P, 230400}, + {"LD06", ldlidar::LDType::LD_06, 230400}, + {"LD19", ldlidar::LDType::LD_19, 230400}, +}; + +ldlidar::LDType GetLdsType(std::string in_str) { + for (auto item : LdsInfoArrary) { + if (!strcmp(in_str.c_str(), item.ldtype_str.c_str())) { + return item.ldtype_enum; + } + } + return ldlidar::LDType::NO_VER; +} + +uint32_t GetLdsSerialPortBaudrateValue(std::string in_str) { + for (auto item : LdsInfoArrary) { + if (!strcmp(in_str.c_str(), item.ldtype_str.c_str())) { + return item.baudrate; + } + } + return 0; +} + +int main(int argc, char **argv) { + + if (argc != 3) { + LOG_INFO("cmd error",""); + LOG_INFO("please input: ./ldlidar ",""); + LOG_INFO("example:",""); + LOG_INFO("./ldlidar LD14 /dev/ttyUSB0",""); + LOG_INFO("./ldlidar LD14P /dev/ttyUSB0",""); + LOG_INFO("./ldlidar LD06 /dev/ttyUSB0",""); + LOG_INFO("./ldlidar LD19 /dev/ttyUSB0",""); + exit(EXIT_FAILURE); + } + + std::string ldlidar_type_str(argv[1]); + std::string serial_port_name(argv[2]); + + // select ldrobot lidar sensor type. + ldlidar::LDType ldlidar_type_dest; + ldlidar_type_dest = GetLdsType(ldlidar_type_str); + if (ldlidar_type_dest == ldlidar::LDType::NO_VER) { + LOG_WARN("ldlidar_type_str value is not sure: %s", ldlidar_type_str.c_str()); + exit(EXIT_FAILURE); + } + + // if use serial communications interface, as select serial baudrate paramters. + uint32_t serial_baudrate_val; + serial_baudrate_val = GetLdsSerialPortBaudrateValue(ldlidar_type_str); + if (!serial_baudrate_val) { + LOG_WARN("ldlidar_type_str value is not sure: %s", ldlidar_type_str.c_str()); + exit(EXIT_FAILURE); + } + + ldlidar::LDLidarDriverLinuxInterface* lidar_drv = + ldlidar::LDLidarDriverLinuxInterface::Create(); + + LOG_INFO("LDLiDAR SDK Pack Version is %s", lidar_drv->GetLidarSdkVersionNumber().c_str()); + + lidar_drv->RegisterGetTimestampFunctional(std::bind(&GetTimestamp)); + + lidar_drv->EnablePointCloudDataFilter(true); + + if (lidar_drv->Connect(ldlidar_type_dest, serial_port_name, serial_baudrate_val)) { + LOG_INFO("ldlidar serial connect is success",""); + // LidarPowerOn(); + } else { + LOG_ERROR("ldlidar serial connect is fail",""); + exit(EXIT_FAILURE); + } + + if (lidar_drv->WaitLidarComm(3500)) { + LOG_INFO("ldlidar communication is normal.",""); + } else { + LOG_ERROR("ldlidar communication is abnormal.",""); + lidar_drv->Disconnect(); + } + + if (lidar_drv->Start()) { + LOG_INFO_LITE("ldlidar driver start is success.",""); + } else { + LOG_ERROR_LITE("ldlidar driver start is fail.",""); + } + + ldlidar::Points2D laser_scan_points; + while (ldlidar::LDLidarDriverLinuxInterface::Ok()) { + + switch (lidar_drv->GetLaserScanData(laser_scan_points, 1500)){ + case ldlidar::LidarStatus::NORMAL: { + double lidar_scan_freq = 0; + lidar_drv->GetLidarScanFreq(lidar_scan_freq); +#ifdef __LP64__ + LOG_INFO_LITE("speed(Hz):%f, size:%d,stamp_begin:%lu, stamp_end:%lu", + lidar_scan_freq, laser_scan_points.size(), laser_scan_points.front().stamp, laser_scan_points.back().stamp); +#else + LOG_INFO_LITE("speed(Hz):%f, size:%d,stamp_begin:%llu, stamp_end:%llu", + lidar_scan_freq, laser_scan_points.size(), laser_scan_points.front().stamp, laser_scan_points.back().stamp); +#endif + // output 2d point cloud data +#if 0 + for (auto point : laser_scan_points) { +#ifdef __LP64__ + LOG_INFO_LITE("stamp(ns):%lu,angle:%f,distance(mm):%d,intensity:%d", + point.stamp, point.angle, point.distance, point.intensity); +#else + LOG_INFO_LITE("stamp(ns):%llu,angle:%f,distance(mm):%d,intensity:%d", + point.stamp, point.angle, point.distance, point.intensity); +#endif + } +#endif + break; + } + case ldlidar::LidarStatus::DATA_TIME_OUT: { + LOG_ERROR_LITE("point cloud data publish time out, please check your lidar device.",""); + lidar_drv->Stop(); + break; + } + case ldlidar::LidarStatus::DATA_WAIT: { + break; + } + default: + break; + } + + usleep(1000*166); // sleep 166ms , 6hz + } + + lidar_drv->Stop(); + lidar_drv->Disconnect(); + // LidarPowerOff(); + + ldlidar::LDLidarDriverLinuxInterface::Destory(lidar_drv); + + return 0; +} + +/********************* (C) COPYRIGHT SHENZHEN LDROBOT CO., LTD *******END OF + * FILE ********/ diff --git a/sdk/sample/windows/CMakeLists.txt b/sdk/sample/windows/CMakeLists.txt new file mode 100644 index 0000000..0f6312a --- /dev/null +++ b/sdk/sample/windows/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 3.5) + +project (ldlidar_driver_win LANGUAGES CXX) + +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/../../include +) + +add_executable(demo + ${CMAKE_CURRENT_SOURCE_DIR}/windemo.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../../src/ldlidar_driver.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../../src/ldlidar_driver_win.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../../src/serial_interface_win.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../../src/ldlidar_dataprocess.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../../src/ldlidar_protocol.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../../src/log_module.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../../src/sl_transform.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../../src/slbf.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../../src/tofbf.cpp +) +target_compile_features(demo PUBLIC cxx_std_11) \ No newline at end of file diff --git a/sdk/sample/windows/README.md b/sdk/sample/windows/README.md new file mode 100644 index 0000000..68246dd --- /dev/null +++ b/sdk/sample/windows/README.md @@ -0,0 +1,29 @@ +# Use CMake to generate visual studio C++ projects +> Prerequisites The visual studio IDE and CMake Tool must be installed on the PC. + +## Method 1: Command line mode +- Open the powershell terminal in the 'sample/windows' directory and run the following command to create a C++ project in the' Visual Studio 15 2017 Win64 'environment as an example. +``` powershell +mkdir build + +cd build + +cmake -G "Visual Studio 15 2017 Win64" .. / +``` + +## Method 2: CMake GUI + +- Create a `build` folder in the `sample/windows` director + + - ![](./pic/1.png) + +- Run CMake GUI, complete related configuration, generate VC++ project, compile and run + - + + - + + - + + - + + - \ No newline at end of file diff --git a/sdk/sample/windows/README_CN.md b/sdk/sample/windows/README_CN.md new file mode 100644 index 0000000..46fc6ae --- /dev/null +++ b/sdk/sample/windows/README_CN.md @@ -0,0 +1,29 @@ +# 使用CMake生成visual studio C++ 工程 +> 前提条件,PC上需要提前安装有visual studio IDE 和 CMake Tool。 + +## 方法一:命令行方式 +- 在`sample/windows`目录下打开powershell终端,以创建`Visual Studio 15 2017 Win64`环境下的C++工程为例,运行如下命令。 +```powershell +mkdir build # create `build` folder. + +cd build + +cmake -G "Visual Studio 15 2017 Win64" ../ +``` + +## 方法二: CMake GUI + +- 在`sample/windows` 目录下创建`build` 文件夹 + + - ![](./pic/1.png) + +- 运行CMake GUI,完成相关配置,生成VC++工程并编译运行 + - + + - + + - + + - + + - \ No newline at end of file diff --git a/sdk/sample/windows/pic/1.png b/sdk/sample/windows/pic/1.png new file mode 100644 index 0000000000000000000000000000000000000000..b701846d2e36d8eddaebc4050e303635578aac4d GIT binary patch literal 22098 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 d<7@t>{HIgX*JyfM0eK)L literal 0 HcmV?d00001 diff --git a/sdk/sample/windows/pic/3.png b/sdk/sample/windows/pic/3.png new file mode 100644 index 0000000000000000000000000000000000000000..7e88ad88281d7177e499f2f1f0625cd85f7bcc71 GIT binary patch literal 33064 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 ZshddhHl1(>FL)cGs`yBuOzzp+{{>w0zy|;T literal 0 HcmV?d00001 diff --git a/sdk/sample/windows/pic/4.png b/sdk/sample/windows/pic/4.png new file mode 100644 index 0000000000000000000000000000000000000000..ccc917a03230aa8f9d472a593ad0931a21771e87 GIT binary patch literal 37830 zcmb@tbyOTd_bp10Kp?og1Oh=q2n2Tq4;tJiKyY`L5L`oW5ANiELQl_hb#>J_d+*w(CQMmT8WWui9RUFWQ&t9~ihzKGi-3Swi}oBi zW7zA@1N?bmFQe^*fPmEt|Mx6`1&bUwiR&z><^0Lc)Y;9@(FEa>t%bdzle39F@z@dq z0u6#JNKDN=eSgVEjq;+M{qbr)YF&kf;{_4J*Fbmk-emCft7n!ie~4ri1+Q_&w1+<# zV+KK2+BGMZ&+8g>_9X1aEi`nuo3FNBNLnpy@Na$2v49|}z-$O)$`<-%oK$6z19Iv} zsPInxtG2ENmN`d8G6&ktF7c0MbyzaKB*J+qiThO)p<{K`kah5p!ixs2 zEfmkAdjk)>Lq%y2R;rQgq~oiY z!sq56g&)r+3rVQ1Ii;|vocWwjsn~yj#*WkfbCt!G7}HHfuNElGApPavLa*iiKWYeD zZ%4a-eNynxiTD-@yqIup9;mU6#5VQ581U;}nE?mZz=?i=k$1BOx`8~)?n}oH!2gt1 zGkUOH?gSy665Tf6(8y3cD-Rg2;)BUc93+x7PU@}b2gcy1BfD4VhZEU~)2NMBW*7)R zvsVXxw>dW)^0&G+(40Z$w+{|D6z;|ZxYs7K$3oW-{Xdjy4my0`x}UQrhrGuYx)#-n zgv7*#PujCrKwQ5j#R$-+ijj23rZ(OBLYwG%9O#&@qxgK~6mvCWG?)GN5EmBRk zw7Lq%giw`X-07Z9d~<0zN!f@v71}vkUZw&bYv9;@w2e#HO?mI&pbKauq^a|+tiHu94kcA3P zrOvwF8>dy|+l!j3uSIF}*)WaFY^Hf~cCIT;oV?5NI&22>MGN35+1T}Or(n2WwLkUd z#(4AW4;M5xqFybYsqUsHHHP(1Fow3S{?6HfOCyMSG9Pu1e| z{Y8Q)-EE1!M7Doa^6Q?n^V?%;zw2~mmm1eoZoiDndi?n4CGUcn>9c7ueFq`-@_lqP z>yj3|H5wiso?S99Fc3a$X_b3?+SIk}`z1|XGK1m|fxKTj&+uYve|kF^e-(<4;rm`*t}Kh-mxK~r^Lg~PCcwycAOXRgNa-HL4Sn# zFl?>kuJQBLAu(HGxL;RJ&FNT`)t4Id%-kocqj#Y%d!M`u)$ndk^2%j;a}l2*2p6*G zB`1S=?3SM)pdsS;hl+7f3Ug@b-fb5jGb8mPg1qmEA>D%gSEYzQQcu;P9PlUhsUD`hT zJ|pB@gsY|zGHVv1;BeuzKSC#%W(I8bCJ)*Q)oF1`B(Qc$QTJx~4yb43N~ zq76O_bMeD0l^hEO3c1-u3$!vr-azt5DoUr>xh?4vx<)fk!I8~!r4Fj{XvY;lZS5Bt z`Xi`@4mSoJ`EL~-EfA9jQ7{tab8$U>0ZMS2FT>w6Bs%-m2iwSw%hUs|q_?`J&5;c* zyY)zwB|1-EKOY5g1xi9C2tSCp59cRMJ-q^*~o(zpuXo49{R6< zr;su*=qOR8@zJ(qSjso-h*(eM#GohkTkrPsIE2U?FPdob7!pW#^RYY-WI6Ef^_$uj{ETaikK z3xEV$mLs4er!7{qiJuP^@OrvqT%Y9Rhu7OUaz6TaLcT zwINkf-^3K6PwmR_0`{GBzDOvN_&DPP~EG{=n+J;xTYg;zH%K-db>niY&ru z(X@Cd)Twe|Iz1~wUwARX?psXu_GQBcu4;7{1sztO^ukt>rhHqPWitz%pqjRc zl?g+}ns>_pOrLtR7VjQy7#73%P{faK+2f84K~!2=0Kf+!D$nDu6I;}Jn&EHUqf5Mu z^QrbGIf?Dh*czquu6dx;5TjCoc{46P?K_Dde|S5+J0CA9ZP>jHjnJdK3cP3?iAH)l zXICQChu;SIEerl)L_m1{SpQ;eJ-(dlqGO~!Z>AWaLxA;2u9i9(tW!Sk86VS36MfEO zx3Wl9v$Inw3c5Wz&bRNAeK1nxo#?$vj{XU z-fmQB90N=ij=HLk8hU zl^r>rO08w8KpqM6df)+5<9;^U<8FC3z99#x9T$v(12(xGYd@Sat-*&aUU92cDe%h{ zDkdG?O!}HaPO|9_vRpI2t1s}B-{HZtqr|v6H&_3Hk68pfSU+3V4MhJZ%8;~@N5wsf#d{cVI1hcE3;=m4=2<#uMy@pf566?1~|~+vWpPqY5Q^ z>1xWe8b)?HR}A~h4~U*#Uht`=14ttT%f$%bh;B~@27ulZpE@>X*kQ03k$zx|^7&j# z*MVrttm}jiKag6#6S||LT?OmgwS|ui1df?!D%iv@7Iz}SFryG znQIv~8a0T}uY!ym73Y&JE+q~#z(cejJ)P){x~>}i?iw422qbaGDFuULLI$28lsd9Y z41WW62b{n7c!PY+$@z6c=nK1Z*00}RkrFKo+#WC+1|X;djGg{KQjq}wQ?d*fSoO2s z2GjW2=UszwhG2d;`e51CX3N2Q3jVHRrBT@ppwxgf-U@&mm@NCU4osaOu*kLUqB~5d zOka0xD6DX~;`Ud9j#|dY%_V?176HC>S>d!QPF&Y>2r5575Isz2HmtNMu2AXoS>L9% zDMSs_bTx2yz;->lQJAZusWld3z7Hby?e;RJclBJXJHMnx^L;)pbj8Al9Vv|c3W$Fc zAP~9X8UVN>9X7FEhY<=wV08Q0_O>~8>1Sr$%>=qPp|a4aIFB8Q<}E30|2m1?zM{4V zJGGvak?3|HQt&LAQqp$A*rCKbAbr)Mh=z#n zhlT`BZo5|Cv#PA46ATE7wwq1bHs9mMro&ruioVtJxrAsa+w=!s=b}#)T;BGl^l)tp zh$(a$E&n7k{`j7Zb29q6k!VVm*z^caR2FQ|vcu6mWbEs^RO^EeS<5}I&h@UX@t#i8 zR(crGnoJblX8L|+Gd0Ac1AP<&Ed;PmpHe@;1!c;Wlnk5gf-cFfi@K)9a+oyrO~qnK zK|X7*xvqTH!gA!@;Ytpf#LHifh_#mw@R{XVZX)`1PO_pepliSKpzDmWI&|T7U>%F$ z1)$jP>P5FFA`cRAfI@&&gSdWu>?fby#Ukfeu&o1BI@Oimfq@4}h@&Dhj(*(_=dBCc2Lz4s#&AW(xGq-q@Amha_4)_tXeMIvm5!F1 z{p1lRi%y+CFr46$*!pf#WVOHrtjcy3m&F}?fo4;3D*!*aQ&2kMSGxKU04`p*B+3?F zc#dL0{VHPu9$*dt-1P%YrqyDd!fbo6)N9*(G0Y20dkY&3h$YE{Cm^{0E?q?zGZRnt zTW;ySVPoTL$It@u@AOVJW@-=BFQWCZqFdihbM@j|XCD9wI%9jPL(OY)qt#G4>Q>cP z%GVxV-zbh6s2`LM`8oi?n*-1`cy!WpwXfY!h`r0n*;6a|J($|__}Pt?KG2U{TVIVR zRnt2O5NE{rR!?D;ujTdQuU`n>!ULZ|gv_|Y7D~edFAbfBI&C(hnZmE9pB|LF-+N>E z`N<2v)@%>%6lfw85uOh4T|t9Pne}vje%3o-|2mZJZMtAsY!Z0Vr#l0pV|Z+)pwqDz%-oRs;YmKI%S61id{vqYHS=FZ6k8a=&?O1&%Sr1xBH)FjK0g0 zogVU;)_}y%@)plF0&`SCYoNii`wmJ?=f%6oqORuGPBi-{jMi&Xxu=sA`@1Bf)$hlM&j&<; z&~Syt5I{6nka0sFPg*$v)YwZuY}ozwJ7l!vxU87&j3+EwHn!IS5*lrt#}}8EKTj_^ zyMST)H^}Zyv6&xAEBk@>VNL!8(#+Rsqtv1C#3!$R!1cSdw3n*jQ5PGwkX8tQmgHx) z$O9Ir<0+VlJhoLZz+nKg7CYCHSB3yG%5hy5qGhx?N4m8oF#6>%9h}1?j$`DuPr1t* zI$f$MfrEVQEsPfM?x8V5uD8A8ux zXp>6H9`{)Xv*U0O^Zu#%uPb~sU)KDVB!1G45 zTWI91D&&|yAHeVz%FPb04sNx0Vg!^Lbm_*D0sC@eXTxQ9hNTgoxz1wTcfJwh;7E=r zrkS+;WLld5KZf=sA<91*zF_vzt@^n|Pt;V;$dxP8;~p6-7R$GjSYNzb(aIxvPwxbH zP2thynP_sZgW7ita<|-0nv(N4^(zs;MFI+Ku99d?;VHw8~zp-_szAixKx`l4gA(Q^gmqt zFlYM&p8xBJoM;YHi7%2U4UYd&%Xl9P{eR9%dCyV&yOgh64*5R^YtULVrFE+;b5OBz zct1YEM7T~iQMs)ok&`S%=dP2Cr0o4X4)E1{nw5jb629hKIO7a#5ov^f z50s$WsMgbOA*^3jcbE^ujwe-)tV+EyQCTI7%z5&j=~8E)#Zrg zNFUm0;j^w4>aGY1nMb&vSuz)A+|`sr>Hj?JF?&aKDHW)5ZXie z=lr`5ou>O$dgzbm&JnxfxfXprt)gluPF_cvpIe#43EU9U^xM2tB^fOF_o@{ttDr5I z7a5XGL$$0mL#V%M9~xK;qe41bu zaWx={KHKGQnfys=N+9`tZb3Pq;3BpA1_-4JJJrStBy&}+@+|Yk$lEb8HJvDS$C@mi z%!5WuteBM0(_r|#=&epJr7%~uw?4~fK|#ybw7iU%I~?sVXnb=hU0#JJ-hQrwc^Lso ze^z=$h`wt-&Hb#~lg~Z$Euk{TE4lYx2KuR8P#z1Y1=!KM$b!wRKi(`yIBF{vXtklNDAWfKej-OGpNijZs^~uL$uM z&xNb`X$gN;d`l4U86k(ywmZ!JHGClH90UIgIP{nZW9fYON4)UI4`q_gdj{lus2c^m zPoQ{u-t@44A5a)F1pNPUARFO^05VABG{fp27`Rb$1v-hH#Xkh6lYuGZU=!t?B@JDIdMoA@*;BD9O2M2b#%l- zLPFANbLSvp(?QOP`Sy8@76%DEPMIoHf}IYt511tB6Xh1k!~n`JM>68whYvy7*|d7THye|M zJK}o(#>Ws|gD5L2a}N=tjUZx!mj_rZfR^DOW}e(VoM3$wzxVC5I#bmOqjCIIYN%38 zW^3Xm5J2;LnE9!pHFKKZbBCPGjwb{3{?jRKDy-Ma^>js%%$7B`QL*gQKlC??iY8r6 zP0iYZ!p3w1tQ|Z^uYEdX+cjqGQ(B!BHX?gJw6S0DDxdvuf9`SN=guoQsMG2f-r!}=AulGD zr3k z`Kd5uyjM0-W8j;1M{-7|=}GS{FFl=Anas9RIi4X`o9tc7sb9NG`pdYQg@RsYwpv;p z*Q;U0ThEiZQR>zkQyyP|YW_jgT)PCak!hS4k0G#HL1`5o0=%=*I6R zQz?3Me(yroSw~sw^GZ%l6+2LkSM#ybXWZPwLVLq*qXw_mIg+GrmCLvFj?g0g#IzMJ zo3(AX{xu6mm#Lrp&UU_GT}4Ln`La`so#)=%61xC&;{7nSiQZ7>!FrnZt!a7zY{vCd zlrH$MM$oW%11#@@4yjTOvEs;P3rO`HSLrc>qd`+F_JVU8uVN&GCt`)ob&5xTsx2OU zfHoG_#zd6)3kwSpo()QuV%I{^-y?d;T$2k(b&25Ie5Ls%j{(IMD4B5af|qT*m4m0N zsD4>Nam|^NN`R0hU*?Q+f%lV1w5%Y;n4-?7SK6|P4ct_v=Leg`2K+fdGOo!a*MH+D4>eW?3!x*R%NOM46xcHz&3@f&hG&4d7vHhfWK(3t!^X_1dKHJ9( zdruZ;=LQ{`eYHfK%R72Gtk|L>B}A}r9u=YZVwm36i+~COGy6Oz%&a;<}_m7?}4W8gw z=$TqbI#J*;;F%mf!x1hNtvt5h_9?aYy<`^ps714$rG7 zVk$bbANlr@o5@dNgPGWaR3f-4bW@k1%pnG^A9^<7guujcZlDw@opQL;F^5w6iE90&J6j`S`h#KdA z@c1PrCM_f5KmwO`*w1*^)ozqUucP)8ETF8)#E_y+g8<+=CLbT)_TFC4WUe${%p1jf zUlE|`*rcS8Ixe^Hr!o-E-@kJ&|GO6epwL(|2n14P)Q4WCRZX{?{XK%WI`#hp^{uiQ zyvYZu%O4dKPCEVogzy$0`I&DgwJEGUHE`46f`S73%YAJP zI#VX!-FOH{->(LPS(rNAr=t~5 zyuJ9y^fcR1*w}XZA#7>mV)@gf!j}+j6mjj(nnZu={j8Fvz4Tk#maU{L%)#=Ba@e@I z4D2cdCx@*^Yn6|?l~rE`@ut1}9?!B)3Pm1XFfcGUgh%5=!7|Y-c;im}XtaI-je9Fp zu6=g%I8bJ$W0yg3F)>`)w%f3h+A{}h7F3p($#l-|0owj#?dncNPcKsBVMpouEbEE2 z^Q}5fctpg_KEsb@?)1ST`$`^M-4gn3Ov$xgo72>c?v@MDgQ-)e{pd!piF>das+~l22H%J z=O^!~vzMF&5Qb*snL$k9Cs;v1fAY}+eua#Wel7zk%bneLAK%QaN&(SEPz-Ez$Qnl# z!vTuBAeHt1YRBOJYRCU2BLCkG8r}2bUx(9Cb4vpv_bc#hfDtf~DFk(H9vqYf($~<) z2=S{Zjnri93;+k!_N(G~`OTm1%@_hyb!c!zrKEzb7+(Tui4qkP0~<-$i>f} z5h94$`vGBgyU6fBro#4kp(ennj<_r9zT6t3<`-A=KVOxw9B+W1~4G25E$2F0J}qI+1c4&L+Pw+HR!Y$ z{w;vVuOA+P#Wt;P=SscD8vr^af0Y_UX3P%^9k186IO6`J7bn*l|JtNi&-raQ zY(Fm?;GF!Jf8Ya1&(oNB{=q+1#&F&DSoc4=&vh}}@5`6o_vPjd8>Z=;8RsaBKBTWz zBT~r09bt7Mwnkm_p*P=?RWF=mryp{zKM|GSH~-z&NdNTdQ(|MfQiCq!?#BZnZe^lP zB?hdx1w5!6R=r!GwCU8pcO56>x7h9dE{XLfj@5xo_VW78rSRQ8=lSI&S;q<#8Zj<> zH4|Ni@Tq^(;HcHIQ5vHZ9ZRr+Dd zMCi3OHQneALjgJSE2{jSfLO31{8YEI-B-zm%z{z3@KkONtHzH|S#&*{ALFhY-aAO* zPBa$==X!HSh>!-0kUChAe4S2m;DSyP(d_rfZA;LPpo_#3yc}ir4ofGtUH`^S$sUcL zdv45OHfVcmI`Az?b(J@7{k`3?pt>ZYzuwnm9=psv=#<9uJn`n5_cW6lIQs;Ui^QRZ#5xsz#Y9G@lixhGfW}+}vs30Sm$-+Tb?`u>IhSn?+;nE#^2ZkKR1f5e#MNou@mD4u%`uSzQmx#a+}BXDz+aQ0a2m4%IrUVvU0Q~3}V-1Cy7F6R{m7cTbu z*SWG@&4X(nYHIaMeYkG-qt+TdGbQs9&`a4r6eggzhl_JT8(Z6!W)JEf`F)gbI;Q9L zWTqNht?BPJoq<9;Eaf|PY@S`Ai(u+Q&8_vK;7SGsc5J^%q6XJM_B?lagB1^^z|kki zlbX|?`$7oaTlm|UobUIv-$8>Dmycf+I_&1>rYB`Dp&mF6)r~-^$)ZiRTq@BzZ$|BU z0bonE`OcL1nvoHX=?I6|A@;c_Q=`sI?`=m&Wl#V)DHs!)?f?^Pc0ZvD4hi9KJJjnj z0V=Q#qaS8L?QD+YfRYPoYZKHRZfI=GEh<72&cX&3&YdV?$G*JyDkYAkKf4i?yg8n} z{Sc!5A>>P!^QA`)d6b0p3xMK%iQ`-H+aZIaT(($lYdxDx8yDy2y#mO9ZxGM^v+C5L0R{s=z*<$GpLx-* zeX+De2d)PQL>zDzSS)G)gJN{+Y<1b|D9VilpifR!9UU@9N5_fT*^pf67)Nu5^y>%T zFQ%qIc^TWn;*j7;JM5qJI-ZxGZ`c=3s8www*(O0t?4bBRa_)b5tp7dO!IM1G#JFR1mvu z_1wGqy)iQ`V61dqU-M#1<&ate_6op=8z21fW`HIEqOEd2F@leYFf}Sh^8dlV#@g?I zrn7u5NliyngS2Z+=ig4KI)hYt0apju>Iv*-bsqNK^7xBcxD`RkS%AkOEF67vw$-X} zN0+ny9(Yr0Hf{uXQh+v*ncbNxbhz0_g4GPajc{X*n*(5upm-ul|OpULhzk_>p>vCxHu zh2z`3N*J)aHFy{hB&a-#_DIYuEXE|-_Z)U~u&X=Fm9*O9imKVMtmO?8UfHgrD=1Zg z`JeCCJB3+f4c<@iExv1g5wD_S0@AtGQZrBqC>ggR2;uUL?szSN-3X9GF~YY+J2=IF zaLKK&C$zD#VPa>GF$_HEX**Gz$|TO z_bLaN-d}+{AEHc_=@;BUIeoCem7`gkXWX7bgx=?q(!7rAXu?l7}AAh>^m z?RSkNe6x=4edv@Wp{Ysi>FK#f!6wO~9s_fC_=BI_{w_K)vWYA*Gd4Chubl>8WtZRY zQIP1Zp1IXLpVMYC*50!}v$I-yx(A`>w|kPyBpb3hKGO6$f;xam1$0Nt5wJ|!A*zWq zm@O!6G@ujkx@6_&=eIwopG8CPSO;Q40RbB~Qx zt4j3h%m?VZ`jg1zoH55dYGIuNVi-Bg_w<43k4CMG;w_=_B7ii-+PFxNd2hC=UqX$* z>vl;omLU{!W;Ra#79U%+%hu9zNQIU?ldT8vAEm{_o(bO{OTcv~a4R54dbhWko};1y zk4W1o3`8(XWgA{fN=l1`EXv6MmDkG{q03}jLm^yqSRISO9sZ7kNrFL+d?Hm`=oW~6 zC@Di5gAi9GXUd?*U1*BuQ{{z-`7Iv}A`U|6uj>@2(-o1qN2lqUmGXy{a;{O&SBrmL zZf$S7i97t=mOW6Z{q8x4Shf6tMcT;7M`U2Zsi;VYgZ3GiA`BwdUkmkCpLVfgfG9B+ zzY;fRm~!~=DQ~`gBfBO-<4D~0JoD0JW6#ASy(Uj)BXEtKhEzFk8{U$;CsTSvbY zrcffd-Iwdb1`&))-uRkv^osq|+S^+1%B9!pQxk)EQOejDvV<+ohigt+J=m zZF%JAzTE-;WXoa8-tpba6BU4yjz{0r!r(jA0bA9y6_K`n&I)5|VYVF57i^XL8_E^N zb^4{eQIE6oP^Ra#bLssiy@w7P=gAzp26mLC2bN&B)aX+oo|qGJ86`JtWJCanPoDy4 zc+<4lTpIWEn=Y28y!1UzW?Wa&fJd=O3uRK5iE~USKh&6&Yey_!UU99Ql!AWb^5jVU z@IGZDB~CEZB{M3EFQnG2pM&c5#iI|_)>wMaJm=Q>?7;Z8Cinh#W@1ki>cSqGx?#(Z zTqX=9PYLz-AlCNT(FQ_HF`V^m`5^-|6#_~j4(-Y+*7|F!5WY7dL~p>ex&~<#4d0KP zNfJ~%H5{0qW^AG6PfObeRbG0eY=u{urytsl^>0$SeM({4H=bNOy{(9b3^}VGA$?Ot zsA8?@Dr6c^8oE^3^NMl&gR`-@~k*O(~B!bKqY6rWeDH z^ChOm0Y~2Wq=CTM0o|Gr2m2vchVI=p8?N5LA}H^|0Z_3+Y!8 zA=HTe2jPK9xr1Hj{4frYN_JQ=lLq~VDzy%C-`rOc=*W1@@A*8K7#A&uLd+a zD)hj&y7J9!mqwKb+%tE;WNPd$rm8vKo?A?ry`F2oOku0^(Qmb_o3&+J93rpvz)>`= zyYtCQ@?||8sW_>H@d|8uU;mgF1Z)ZuE@`p57VX;wGr8^ZQl1X|*7V7SMb^j+pmeab zw3IC1R-yOxyRA}sT--NcbqZw9TJ5-S&!0a80F=Gs=PyZ1bY1p8wJTO;d(cvOYgdSK zNbh5k?peLtUT_Zv8oOL95d=BurR1Ajrh+`~tyoqR8%jS8?DO&nu#YsrX1ohz(7dh= z2|KU**gZS4-Q#SR66M@9Xz1?V>NWWTzh)T85SUFk2WaJ;pkQQpIQoJ_Vn2Gs$^4H%BIytTpsLB^LFjvddS}BE-+&PA;8;=6P5afdgn>ck zP75)+!OPOc41O1M>1c|sbUr70z#W^W-)&s>#3)qE=ms7TZbJ|d5*ls{CDDE-JX5A!u%`o%l90ew#CjCZ@~>%4Fx%DGivYM8kTp(x!)6c`YFcx8>bSq$PX2U1 z{uKGor2~GTVZG=)rOJ9UKIyedgkn@4s?|F0s3QD2^1TvR zUtcde>~`+KEE*(YaxaVI@o6Oy7@mX zyKwvqWp0G8DP7KHXWK9yVG6bHh#LfVvpK_04 zco7KL00el903`pSdXWUUe}Je!0{{@%Qt!YpO8O}bSPfBSdL8oh2(aL&SPqYjfD{y> zc1V13+>KTTNehE=>gu@Q+9iO7Mo1`wM<)zdg}?#06Z~9~o@3a?DWKKh8}z|o>S!t< z%U0BHBRBu2urwQ4N`cG2B|xhIKmM@?@`fmr6{N}p=ttj6q`qJ^?N-3?(sGa&Zxg@= zTt7S+dHJ4jLKeXG40jLt@dH_`YMM+DH1q#reV#b15&bJTT7=~=t%Qo+vcXNFFaNb7 z0V)3PQ$C|RqDm33U2W{1|Lil+^7u=BC0XB*y9E+FJ!mQ7mI4uA0Lr={^Rqk%#5D!@ zynlDV!qhJO6l4D^Pf&E#e=2JK_Xl4}2PP}2;5&;D?uQSOMJprhhIDdzgb5!+r)(8sx?k*D z?SMvmT$n;X!7G1*zcW@pCg@K3ds<`dMbps9;SeSiiQ&7lSm82WX6M&FPat%N2nU?C zH_cp(IS4KSWd64a6Qpc7&N;aG0SL6`ob7tRUIA6E2E|S=H2kl9(Zo}J$&f6ICE-qd z&6+rMzq%47<3mCb!p8~~bVf3jl630qhpbsfI=WQa#WXE_WR1@RK)ArZn?-Q0b_M>0QtZf49y(67J_fHLg68CKDy zOa7nLTyx@$9kOS>FUVX2`{^~+a-K7o&ykUVmF)?`CxWh&5X_gNUOVUb=Kp~-;$38j z&fZxcyrLsV=l)qj&y^?pA%1X|%mk@Y37G9~P2Lf+f2#_96kHT7mAmtCef-Sn`nw&2 zA=Wl0@7a>7mEm?_ZuBR*u;0;$)Q->N$*&kKFx$Zu|N{+PqVGAVFlbTAv=CL#| zp$+k;%x0g$ztfo@m0bX4WT$L^TT)uuitT7c5p+ptT_i3$6*spSutTLJJzJzaUgXMW zkQEwg{`_R!(5cH%A)J^wz|BsFG}XH4MoT472$yhfdC9-xVhW$wP16?Dt_+wMLTgcV zSw?=9AWT)puS-^HVw3?_-zRjV|IG*JKwtstU+8xuNOj%slQ`V*c)O=nZ%ODaoV6>BgKX#dA3K-H<$nsv|My`JPnYOC zKAqb=@D&qqr>o-^@T$SBBf@7%l|MUGi~;|QVv3Wn9FilFf5%K+p*cl&Nbrw-2Xk;8 z+DO^>wZYy^#Vl;P*7Xa#WbAKIUT7nZGImgWWn{4KL12-(DJfzpG|bpI9`o5#+$kr^ z3B;LJ%ZJ-?s{&mNe!a8O?HFp(jAnqWqb!mf6NdUscp2dFU(wXtk^X!*K@sKs{2#xVhYX9_uK1AT2xBZ#8Z1Jjy1P!_(4l& z+$xfuON~9HPLK#eH^xz*w{d$=XPr~4J-x`;jWdZWuby9F72mp!@}krEaTo zk*GmlB*sJ05;Q0Qt1~bjY6blMZ0Pc%0>c}@PL`n(+QguS`Na^qQg%G-Q`teT%xa_f zItnq^?xOP7d}=5b5~g}rcznrw&Y2)cV!OE-f$e!r(q}^y$H$m#2Ri{BC;akXvWmsR zN5IIBj=sRwZ53cH5IL*hlEw zFPbX5GMSEOmzbA1^nR4+@81)6;Fgh4=o&v=E5@JYdVRrR&`zmdV#4Jk>a#EK{S%D- zw*=#(ZAwXgHj@Z7ueaLN2LYfVQ3(e_trAAX3xu1!WBXNyeeV7Km%iD~gqo5L4z=N6 zPw-S~qDo-&sW?}?MN3*zxfBwXv6E(b;Cc4UPpZ+cJ!N&%3XGCrHG6$IYn; zc6R#gu39RG3MY~uDlU(0k~8AMx&Bm#_%7rp>~HD@dK~s`kyz5lvb+Qym{~8lTzlSp z7vo@d?oHjxx4+a??W~*xbS42r`FosK(6G5+Wf^*|}(68#W`F_eAbQi-A~=QOG@6;VERpo7&un9x8aEE za;D-rE(&@`q*|$Aav*&V{`SXI@d};0{=(4D_? z1KXET(qAd3r@Mw497XzDs&0-E_bZo+<{9J8UrJ@nbsUzy%<=t#*}GdaQGY~3mShXk z4{mp=`Dy+NWWg1~HCw_utI;8uwY8d~#EC?HTC1`$8*|XyY?eh zY8#CoBgx|Q6*085v`X$ndNG*MFF``AyvfIx#CyhMdJcQzK5QozHRs%Ceg!MLIb+r| zhc`l&0kOGs^7~Q%W2;6k-5JxaO61{R7ybPDBVM`FQt|TN%|)!%J#ncH^&d8-i}tPh zhuSAH@`zeb(&n}D&o;D`>##6|*tAwUUar*~Ur`P)##~{Jq1wwW7+866&QKpnXXL1w zFm~n=YYX$???>S*U;*H$A28ZvJl#Rts& zs8ipe{T`549^59ux!txJA`D8LaItaoBV`^5whQsxYe|rBm?6k{3_Z8IPWJ6Nn~{o~ z88-P@QN}c9>8F({OHO7P7aK-L(SN&Xc+qT>n%R|(oHpxIZ_QPPbs*EPs%CP<1aY!g zjCRSc!<++C-bPnl%uA(g8F)t{peJt0p}zX>UVzOI=9ek%e!S$R8X>38QLF9B&@?Y7 zrDbONKEAZ zLl?kN)o?ofGYAZg$W~U-h)&(){_{4?HlZeMW#*)xHtT#1L7CK*mt={dKlAgjX%$16 ztM;10uY9IAUN9*`N7Rg_MG>{FwjkNa++Dw@M2rjDoF;T57pd*1*om~}IRkxwe%ZM5 zTUk?VXG1ggI-Cr*zG$ITP}j2w6_r5F3+C>vbBCOeRPs||!*aTWonxM{{T_&ZiSYZE zI>z&AFXxveV^Y(WNm~*!c(?Zxc0cKI=E97OT;4ViAziSga{PQaxHT4VO3F4pezzM# zyk^**c=*_%%;WL_bW=$2VIu7~?Q2WaMiJk#sZasu+O<=4JukU}P3je#_+0gt*6@f7 za9s(^a(9}RePmxn%C|qW{yjePQ3p%W{_P;x@>r5`f!bpETkd^BI1km$dUo~O_+U!S z`))Zth<@Kp5V~ZxRlMV(k5#MZe!U<*w>P-i6Rtdaqm?H`hak`;2uMsI zvwKHe(S{C3;c{onc&sq!ZO*HO>NK%(pMkI4r&SIa2|>SZC)|~7JfWq-UpKkYdiu7P zn+W_(BU>d|>OAhVuachIsq-)SkW)Y{r)$_SXN!5jBfJ$=2hX>ZtujxKA*NrkSvSq_z71>t4wo{hbga&t+^fn-D+ zsP{41SOnvE<^zc;yoK=dxlcGuoP!&jx`oe=Du<4tVw7y_dW7-K|=XE2f8DXzF^j@~$BTpp44Ynahb=A!CkUe7N<<4B#b-d1+ znPf2&lFMF=^1@=|MYd+V1U8_=;tshJwpzUGvqP{|Y3aNW@$pYT7$1VmL!G$R6G@fB zz8`Xnhs9(O<}+nL{a)9G#XK)3rlW%-laPF2pbM%}3JX=$m^m9iYeelt!pEnfb7Z!6 zpgWvpf5dupMPk&zSCJd?eDsL4ULZJnrwZ9g@+CjQ0rv2+wH?TxU>Wug10Q;yL1Xw* zwb;-Q0aF55EICu}z^4cVsvbZG*5@j`jUCixzkqGTsbN2I7>xkp&n{$P>~)ffZ_Ml? zA-ih$J4bfM!+kv#Vo}89$?ea4;QNRB3MHw22x;0gOs1N>#g1{+SK5Q3H>qz6x7>Ve zzKLpB#XGvhr@^m{{Y#8VH~J+AB%(APlo<+K$s_UIcfBw?8jzWsfu^~P>>)Eya?gN5 z*1F8GxFTI>no6m+2hfRA?)-x{ej{1T3k%C-?kx?nZ4U>jywi8AeSa1zmO8{T0}phM zFBYowxjprjt{-PF$vX>|scYLf4Kx${9uD|Di2onr-aD+RcH0-l&w>?H1f;8=C{?=D zd@53;N$()N_udi}1?d6;A`p}+gn+crK}3RpfV2P!5b2N*dMF7=&ctu6eb!ocpL6eX z&f5F_ktff5XM4+d#~8oSW>4|!TmkJXvH>T*b`wAH)JBcLm04i*PPl{YvaVOgDYBpx z*+9XPX0Q45Z_6>u0PzHJ0)p;i@%vNOf`lGr)lV<vPRPW($Onf=j!$f!Y zANBVdzMXu;!rj;&*kJNGwItz$-M|l0ug*Pzd_Ko<-u&!~y6dl+2ke|DF^Q;2af7#p z4~0_G2TMvY({*Zhpg$G=rhM0=-`*|)@b7zx#aBh$k)e;KbC%^U zhwBOOv?}|27-RYFe;g$Ls5Dn!h~N#XA7#1}ax<@uQLFT8&GR2O zj(VgcaBhL3s+$z<{(VG*v&_OkxbM%-st1x>gA4&@MTYBTOr9@q6cBlVeHi<^LzvX@ zT3lS0kNGWEWwlqOcod*F>!vX89#g)OCtt{q@V#6up12F{IJ`TVIHUA3>er4AU=(6(sCyD2Vv-YWc53OZ_9`Or!srr9 zY?Fqlw)}x>xW~rYJ*n?9gw0Qfpj8v83HoZg=*v;&bs-7e-*5mXGriQx%GvQ>Q@g5l4bzUfd>Zqgw$J}q%FgxPBMV5fY`I*0He0gH zWqe-(P=l7u%*3D668mB`uteJR9tASQp8=UnT`yT!MW?mN=le`n}W(x zxsD)r)B+y4l2H~VA*+TN3?+0{?w47=WDi;(&>(}`tHwv%s;?d*LNl;^n0b=+6t zCpw7p=7HhutM~^co=`R()|ghT6({|()VV%ao>RIIKAx%3(c8;jjNmSo^oM04=PnGS z^<0!A!%}&M$&UWouOR8bQe+R=_gurNqHfPByhaudov^PCmuiy^4#-CL$~4_joW3)y zBJJ^&Nu+=&%e;B9T;*ZlCz(9jZQMs}43}Z~ef(gjtHGB1S8)dPlZG|^EVB(k9_h9#&d^0{S23wop&9xe4&&l|E z=riaTwD906r#>n;`J<9E451w#t)j{Y#VG#q&U5|Wl`kdQwFFibYI{|!-R)`(Jniv? z>PjD!UzvrNBZ@yUHaejOuaJX7OAp6%KTCq{iC*~A4STl~1;4L5a3}lA)oC6;F&sKS zr*Qmy(z&orj@Rwrut3gn#c83v>tUh$ZfoKr*ElY~{Fq%B<{}=28ZCWn_EPza$P@1N z+{?%lFt7Be4zI2MJ)=E;&FI=BF6MRf<>aRGQjv<|PO$8&Wz0dl*$?td@4`otrn;W@ zcL@8|jg#j7bQ%vo`ysFF>;+(Aq0>bEdu1i_!5psSos8SJ(WfgLIiIAg#ghzt2PmTZU$0CV%qbKSV@=?)Z>dMFq$kY-`v8g1Kn%fRPM zSi{w+(b%45!$Qjp=`{=Yit;Fv7KVGyfn5p_S{)s?2e15^y(P)%D1kb;U0izgruOi6 z6TtLf(+ol-O?4cMze$cUTyAfr>N_VVovc>a-m1U*3;BD76s9dEqEEkr>c1cN;NpPN zbGnYGeVu!oqH?&Te2vvS?m3CY1E$jCsQIC0UmW1CDwNF!3T1nR<5*U9?gE90Ufts@ zX1=Fz(x=XSg-$C;=AV|2tseBZ7_=1!q|Ij!_A96P5_g4Jd;wJ(vk=18-XNg$%I!s3 zM*NM{+oy1hNWV4uKKpcG;h~@BONjci{5J}01AN9$bQZO)r0yz@-5+6O$s77KEa5+e zDU;#X3}ZVl4CBt{VvpwBwF0qgoux0W?`r%x&Um6C%h35d_qR&j7RktmTD7hycV$&W zB{Q?tX-_m5ojAt9b-y@|xcjYy+aG=2#q5;y{BdFWqz^9lZ=EfmCGF7Z9na8(zFOvD zJ=2Ee=B7s^$`~s(xgh!fjK>>KZ%NKZSVTub(^n|HxBsp%JGl11wCZeHyfSAhoj6wY11kj?OOtm6 zgR_it2=YIgP`iK-V6mkkR|eEuE=(_`*=kf2-dwdP6_EgCh^ObBr8nHpaQ#_)j|N|S zbh1Lfwhusr4=WR=WwZZL@wQ-}D~}MZx1<|?lbb+7EdCKKV zu*w(AN@q{M{%$NkgYnVwr~dQWAC#{vwG0Uja~2WoTP8=H?+v|3>udiZbVn%ulT_1F z7bWlgyRN8t0qXkw(Z-+A?(dwg%XK~ax?V@$^*nqPBR4&Hu*$dldvekIL?@RJ_i@Lx z3e?ys(M0`@@YKZ@Q9XXUO1WxDL9sG*i^`{uik@@^;8CU``_;wJPG0^di*de7$$OKb zw#1n778BX3Q?{l>WSuoG`~#QX)7-niRvL$WnyjCLJT~~yuj(SYi5@ovM7L$wIz>Q} zkQadhko3(-87uzF#v8Jil4hQJO*%EKwnx%ex{eB*enHoUWxRh8@anw)O-Dagz7pn{ za3FNs%uXR$`Y~W3{2(EJPEb(|(_0^N089H16(axpO(YX6^}z;~vq(J%+iDTApuap(V|&Mz>t4NWAb~T-k{`||Bx_~OciXKU_J7G|6|Ynrd$q#=IS=sa%uJsAueJNO zdli? zvL%IOo$j}5z&(V><3n;2ec6&tjr07`CdIJ%0|*hncqv&>d#OA==rrKh;5|A9aD?6_ z=ulkT4M_Pw#xF8G5}Ot~YQALpUhV+g{P3NwvhG_*0j&_5ZK4IZDE3)I6M@^`z6{rx zYzov=JYdf5w~VQ-XiVpm{XH<*6z~`p2}J8Xey|dx0wKH$ZPOsqzKt2p`mb@_qun1- zPks2iC%k@jO0Oqqh3HYEM>if@w%&{rBexmYt)b}iwFN0%Qzqj)B-#C>jpKBU-c(vM zO8|QQJb;@nB}=TVydzIRzD=%E?V5S7=ViKwNG*bz8S`}lk9C2*T=8Tu6u8YY&h$H# zPhFjR+Mqt0`Sn%1zucQQpL1tpH<+_Qr4cQs{iyi9&C|{*e?JG`v2K1X3#4&&{^)@I zPSAY($HBYm3J$+nu2aYUBUj`9ZUxzZ5IZ14o9g&)O3VEJ5>xp9yyl-ANoqtOavmtT z^~dvPhoG>(oLK*qlkl6W{{Lxtb31lxoz~VuPV(VvSwEzP#I`hj{UyTgW}H$@Q~o@D z+#>pq6AuwHH&TwTF&NpqX;UA6FmoQK1WxqqlJ@?ulo;A7NpkBbCUhQND&mk#}eVi;Fq*pF+hn@x?eB({s8`-F{PY7I@DIXarAfZ`oH<4f*T>v z?&!&%?aOM<`n_6)z-xDuj~cumUOoo=eL16k;^>f{52&q5N0w|e|A_|xJK4v zY*@Pq|FyU>{~d(=+Tm!pDPmE7aW0PkM%upEdsSkquGR=;_SZ{2|989n|3GA=N@XNN zw%g!+f(%FdNAQN<<;??Qh)<%;(V+>mMgI}E{1W2?xAX_{bLr^P<7)#q&d{WhYI*#> z#pVBskI%CCk#{|&fSk&qpwr(*2gH37{J8OoLVnCRHx`zb=D4z@5TQLSSNT7U;-&|b z?cN1u7p%pZIRLKk4=T zz%_N%acVq&K^PNNvm83$YB;M|<&Nh{g_B5TM1liL9(l(>3`X24E5{mImpjDKI^B=a*_vkHKR?58gFI%O5Ob=Q?JQNnloXQMW^~DU!`N z@Sgin)y*`JXES%X9>}FvQcKpQ@=*q_XQTQ;pLl=4W(B*G*+J@JrleYvqIG(@nD5pK9Z=jK4?$lL03)(#EnHf@SZ z;M*QFF(s>^@Qjb(S=*f5y&Im~bG-@!gsDQPWyYu!6vnw!kS5iq#41z6Z`<4ya(K<5 zS@)(T^{xM%At4dn*sS$}-b!S20t8fYShG@Y2J%T+T+-7v%l28|vvhnTi8cL}DM>Y) z{h@Cgs&(Q~pq#%>x?54?;rkMu0FbmLFrNv-KUtV^W z=fo}s7Rz@dvH&RwUgRZ^C5O4MHoRMCHVUzeLq+zo6^EpQyo^0^b=7;NdUG}k2>lM0 zox)2(V{v$@jc&S!bCg>$!8AwnKK?Go(=-PQCx9fE^yG2(@!-ry7%MeT21Ix zY-Q8xuT*3}Lp-l%ct)ciJg$CyD5V;<+LdD4(!=Pi*Q~zTl$h%PRmV1HrY?}xtFn`! zk0^Y7K||s{TtO_x{f7N-bYh*-aQq(D@2(X0hF2p5GuG#Ndi4VJWUHxMnOAg%6w1Z1 zLgeRJ*-yf^v#=v1Qlr9vzDNeg;n-Eb>Xg@yaFiga3EB$8)c@*0GlVDu8d(c!ZCSo+ z)Grj2A`=nagfnuX!1c=Nn_EpFU$(N|DhDIqJ?aXQI0U;5UYb0OsiRUyMRh{9gQTp! zr2ah?z+ZQN{MRsfZUTuY`$~GhTLs0`dHFrrIB$|hlz|1t;hCUtN9$u{iFb8g_p;XW z&%XCQGfdon>PdJE(hQ{8;kX8_e_*}~@eD*w0RORhCZxFS zI%yGLa<`h2*JJ3STJxuA8}uoB+X0sWNbpQ(ImYDk-;+s7@v`FFsp@8J3Q9N<+(r`~h06u+suXWGGqfe1P zY`ag!iRb58U-(0})ODlaC7`^#Bh`;)wpbC@Ljlh>K@O#IXrzJV$=b4;b0JU^3T2Cx zJVRG++Da6Lo{ssL6d^>plVMFpkL@f$s7sX{*@>Y80|Uh6LEaV`WqnLTw1sbAE$%W@u<86D zY(&uD46y!Auk~+j>|(sE2}0`^Hik4rSAbr`G1wJ4z&n6ZEagI|>^DNCn}HjdL}`F< z^j?_EdDDB-QrWk9!B=+eQf7(R<;0QAv-8-pr^&|QD>znzjUQXYJ$wKanp~@R& z2R+8ERm&GQ;OIj#gJx_VIEdkQ zcjvoP8*Zr&?$0i!vOx4O*&~i!OB^!mS>yf3grYO8`>${>N9UTxrU#`m6yygq3VDX= zJb5SCyouxpolB5$9E!Uer#xTk^O9b0#>upv`)#@JVfDqj)xq`jLeC*IrVQu7(wlWf zLy{_VM%jPz^N)h{>V8%_7MC@zGOMEFSVPHa@`H$3xia*zo_#iqVH`+nl5NGQTU->Q z5O%L9bG>@{7M)NukZ2974w;w~dg=5zKjZeJM~o>QHtNIQxSsImTyLwdznUqsf>rks z5zHWEOSWn#^1(9-)>*C-eDl47_bG;=I*$eT(ejss1=pHfT9dMZh9r$IwQxGVhjgW} zQsl45tO<#9`(&;cY*vdHn;n)fSUXIlinMq8hWs#0AA6+NFj!SFR1Rn*Bg0~G(rJa+ znA@L|(tWPN?FEHc_b3Y=Pj59d$F4QRO4xo$=+(-iPKyqu|iMiZ#^ za}!!GK8q7}Q}9`c&k8scS@EHHbtoe`EBufxej>mz z40tHZsugYgw$PORqGBy3r$PF5pRWsX^4qzi1<>~e2Yq>m4ifA9*ZXkSl!W^qVSncM zUz1p&^PK(~*;pennyT5zt>^--z8iP^;aKo3;@Ka0hh+=!q5GRcp;2^CnKf}S@4_zo z1-olyl)lv!ttEF_yQ%I5{`sagb-^0F6^r&fqtXDsww zZo~Wb&wh~V=uJ`3jFrJF-Z4(#_sD73X||8KsWB@h;>Hmpm|JkdmCQd1 z?F1PO02T%#l9jJX#_`Q&w(#FL= zMH#gW31X4P75t`Y$G7mWCE(@2Wft&=ZQ8!RLreC`!BXznTvrmYoe}KaZv%(&P&c9* z0T)DGhlz_R{^wO7Og9AJo}tnT5hMdZo<{>x%jiejfeg*4ZLf_6_GWx3CXP%MOGz6v z8|a;8+dSf-Kt-UQ1z%Dn54o~%ExBF<;A^3W`<2`va2fC+oshwqGQKf63v=^f`SoUA zGps$ztOdxE4F%-U&ZOUfupB4dm&^C$<<0$1o7^tK5a#5#IsKg2f*#E?`nI zBy{k4S%Erzr^2fvA%7}fw}0E-ex8k&?Ltb7)L`c4C+bPylfay^bYJD23(*_c4dZ*! z)zot}1UV;8546*5#;@!tn+Tc;@^+~zOg`#riROI%GI^$K^u1G6%ifq7EG{li_T|wie#srJ zKXhqDaXmx_S1%pT`qGJSK;JvEmNnd*v>=M+*MSy*TLC2iW73ymSR%V+Sd24oLsz1t zJ-RE9I;^@x4FsD!p9kq_@KVnL;|06cL(2DADUFUzIYWF>7b_R^mM2N$Jz^xOO`ixC z5%xlF)Ku}uV@u51{2LbE-^3cp+ij|%@7MH~f+3U1wq3Z9TVu5OnF(~puv~+rOEB}( zd8RW;E~XY4qJ3%TCGUd$z;pz7Nmv+lepAbe8c#S*_l!ApVLW1J2_Vl*WEfGR0;3aw zVm&`)?A?F!OSjzGP}c+ zgR`jvg}}@5?g;Knc#wehz*>DggA>-H#6nUaNi)TO`gV)i1NO0fwaIdnVNG7E&D#0X z)fnrk^qx*z5VhR3^SH-IQq7$^)-Ev9@ln##nW3m+^^&&mN0?&z*r;Mpjjm!$W5z(^ z8-7<#)Ckwx!O=*S`{oi2G+8%tV`+M#4h%Q`t0?c+ur6`OB*t$Q;a?vnUL5S`ehVLT zHp^TXs(`gZVn0(_55FVvCJR0SgVN23gE+iU-pp_GsvM3 z+`6hSIW>+8BXp2F(@NCnORmP|IJ%f5ZtrlJb8%R42|s;spk>aUWL>2<{{u{G(i=YI zgdszcpoI9hmH{CAR7oO|6=_XWontFm*OLecMm7|WV3)nI6}w>pHv)`Z8F0voz*9sc z*WIui@9hHKR#Jzf={IlICGD?B81|`Q7zV;sd$&D^))}_8!{13d`0jV^q+MBeUiEDq zv$nAUCKrw^kCF-N#rup8e8VRY13MI8RIo*K=nX4M8D*r{s**OZUomd1L}4=zT^PNu z^YRX4CU>HOLIP=MJ?IBCL7)UcQDV?=t?MJMPvwCNa@q?bZDy6XM=^PF0F67&!Rw~q zyOQd#VDmc9smEOFan^?^;GO*9t@z}n3s|=6C)c4_0;kn#XFuyP_9G0JUYqJY5pCk< zM_bB>ccSw;0N(iQoM5+E2$VmQs;YvVuR)sMC^B|2-~5H@{lp5KWp<3Wj}KK86lJ}2 zQN?J;jMiOowDB(5MD8h{5RHAZPlImGCqF!DyXr)$cwcQ)rHC2ejSp?%micXltpBk~ zliA0ZnPO7EEodHHXM2?`giC4dECjn(*Oj@47cDxR7{M-3 zDn|4g?uB46@H8@Jdg1t?XI-HYB-kR{hg>(m@H);by$uvseU{AVM%ZXWp4d~r${XS6 zMZkxv!`zTKf3DtQ@(~Qxl!|{MzU3RNB7awXzbgKZPsHr+Fwjhd5?{1p0RqetMzMTW6Q8BRs zpiz|nhX}*16iNaBQ<@6GPo2q^vX)Iy`=?L59TD1t#6myAf0R*0+FO*5d)n11wy z9Q3-@s^B5sx2mPhAeBzi6`{9;9LU`$rACUx%Lz2WyDA<7DcYLr7w_!GPe=Kqx*Nk& z>vC&$v&_IS>A<3LDIuA{hh=i9-9tFY7S(~Y zn}q<2AAEMY}u!&wKlFhg;eoQEEoRFRYq(rRirle{{2cq+mI*T8CYGViZG0wK3A?Q z*~#9xps27R4B$*fK|Ja zPdEVQq&4IwrUu(5(s5jZ=GdXoDD3v*7J;@A1NkZ&cWzTDjn-0P&yOX560%gQP^J1r z(S+QJn$Z%HoUGv%Ns!ywu5H*_CM>9eK-sd$gkj)5?AgJa}khC z1f>X`tJ%h*-Uc~{MWae_86!0v(zCuPj(%QY^B=Z`?mIoEXqmNc?71|Hs2*2CD>?C6 zm$=bZ|CAr(3xA`lS>Cp@Z!WOT>E?D3Vrkw*-w3J-PsNLRP@=^NkR~aDB%(G0LDk_Y zihpAP&i`CkKWjJFyFvCN{+)1q4<&sleNV|*Y!b~M(P(HWf|c(Iv;hk6h(6v|-K&d6 zHupZ5>(7Db&%YKE#|H(_s1w)B6_lp99lxf$#f~I%QNN$i9vK&F3_}!#hTW?u32<}0 zva7n3#@JsTPrG(7+K5)ARNEZPSKIOe8c{Ac3`RA@+iF<`1cW!j!wyXQA&sOv1RWCy z+UTZlQC^jGqs14Y{Z{QY9{${REe>wM!U%^F2*f}dl zyF*+sOCwuo1y`7&ns0wit3-a-4YMw}sp+^wtEJk8I{1&U=Rfcu4;sf?!)e)JFfz5; zcrfB(YJpRNDB=s-A$Z^^EhqS}#x_1ip<8M>q0PVcYH?MlcDd(t9iOpE5<Xw`uj)Sf zyE!mxO$dvjC-py8=I5wfr|7*CGeuhmCu!Ch>Ti15lXJ+s6M7R@4CAaoV-cT*hxTvE z0(?e{+9%A+9-Z8~{XzpLE_%O+2r8k_?=f+@iZ0*O|Ji0ToYaHV-t zwMW;N1Sm}lS%vM`j;2+4geiL>Kei5B!WI(xa6>YEdE;}qTO_}iTKljWpsHnnOh+ zaL!K-=Sey|+}|>qDfS+@Gr`7Tv1&u4UOg~cUst7WVAF%QcS{QmCOPmSusd7%nAg^& zXvfj@u1t7DK|{~hKsn`v&Z#>I=aUfDGIKgw-6ZneD(b8NJhdj`f}=e}L+1=T@*;+s zM^5=Xt=cL$;fN$@O}A-c!NX%Z=pC0f2rBS>K4#Al1=&g+yESHw+e)Y)vaOpAxdxCsn@Q(0yfpg2KfqFUhpA(QMGHxm zjE3b_F9Hi$Mai%}bSRVsfyUxQTi?lXdm@bBpN@|@g-vvxg)ztJQMROhzIg#H$SEx4 zL9cVx_J{+;lE%WFp2FPyo0)?i&TiF$evyR2$kx02EbIRY%yJ<<);hVZg6fgb(*wOH zWz2P$k9`A_sN+vh0D2}X?f89i%dITc(nM>kyIx1!7Ir3M zP)L=0oW*VeL#yfv@-tDV1oc_kT9Ioj#vuot){li>LXTp`o^F=I`Db(2@l(Z1DOVaq zzoss!PfId79T{>q%EU85Z@ieCc|ZL-I_2r8NkZVg^QmY5^6O{3+ejM~LzT<29Kkf$ zS2MPObI>0nZK4lSP(`73Qb6M~rxZgPx{xZRIf&s^$l94K5^9uU?!wv{3&8>i6~kFp zFn1Mfc(6yKYg$|+Ne}>QMC@7F;2rz@;GaezcdgQAV0@KS-d@Q8B9kRO(mrAiK(b$| z-MnS>X-pBuFZs0wnUVpmMM%A{sI0cVxb89f7gkPPzX@RPS)?0P!ukM$c0*cNlQSHYZt&cC`A=_#wvw%|Sq2n}sY+HakMTl|{alSDSy{);M75_YZRGq$>1waA#=Hzf9hbb*Ob+ zSas@alHziLwZ}85zJbLB6l|Qact?R3f`;0wTR%N=E0L%+oR$bVa(6JSIb=z`D3BZHF@Sy znp-j|d5HRTmzg>xU|k-)90W%@Qyk5yl|*Ba((iHWfS>Ku{h^;tBTUy_dZ4dQ9LS%{ z$qwLCukElS)OyTolDW9j$@F4>SLfpGWd++6sQA#os>4ipowHW*ZDZUh z2qiHwPAY)BP1Mn@)vd!B6gCA#hPtw72TZ02c)&^_dB}+<9EDU(P`rcImvYc~Xhv46 z4zYKTH#`6bn9vg*?{3MgyHqHxwvWp-$!qHAqpinVRcu|-MY_g>R_udR>7C5)E28RZ zru$f|>k#=!B=ti^VXNWmaw~v({p}PlRT)iNc)`|CBlSG89d7YINS0G=kaWUU;>e1* zoYE9(&~ZnTT9mlzZk=X@2I<|4>w|xF4hOV7&UXW_e^mFz%+VVrcZg%~65Vkgc<*5m z;_L;*)7!WIGu@9p5HTO%C9fu4B<(?d|L9_Tmo;U_;JcyEp0Q0b&JeK%IjLu8ib-sNJkTDjNyb)?!E5LYS9s#v zmDsHK;fh}olh274N;a=K6r}gaJE6VmMoXYLPoG2Iw3?X}BLua`!&*NB`&;9QsRA?} z+3BVk&RjkZ-0jtpWq$)~`r4YeEZ~@}mxF~gyO(2zZ1O))9$V>oqZ&;#yT0PXBe^lYvBc*R6PJkAaod2)@_zgwXp7q@9kuN@-fku@lERd zZ9bIk89=u#0;n0OtU$Q)baTVbzFdg6wVoPr`5-R0X5)sbL zSW^SfBz7nOyhf^lp>(`AVLmuJFM>t0ktO@6F z@R1q>j*LKa!t&#Zxk?JJMd1-z;${L8vd2_ofM7tgXv9iyC~3z6Dz>G-TR~fNbz9o;FUl%n3Hi3zK7qh-ifNq1Iqc0V zH{tD)v;dF77mM&Y4(v~v*3E>8E7Dtfu1p7NZhV)9VKwmJ={|>%bHF7;5>R#<)b#bT z)omw0*vxeXlArKgFhASK!ZG>r5H9=B62ZioC1mVM?FnS5qi zFK7D{B=xsp@wJUZ+32g#6#ksO{+o8AscACc2!ML&=w2$=*~8P87Whu?nTPS}c>0dQ zOPr&UWTcSauk^b1%?JD(gBeQadnL9?Th{h;-ls74O4YgoW+wWi#0xx0XaPoKuXuPG z9|~fhx!3^C!+&`<+cS!FbuMuao)iTHqpdf`%j9`BQ`7FNro18giZfEyx=p4jkF`wQ zB6T7j(#tzEa7K>Cdd@ZV=usDwEx(_eHCy0PoP;srDq4bc7&-V;3LZD*mx>->_P$;l z6*>@*f+tE3M^~hLBViR%YK?MAL2a2qZgTg8KQSR}Fi;mtUsFUyq88Ug(PO|7M@MIG zr*Nn4R!GAyEHYI=2NisFy$+fa{)kA>=X&Xs&;(c_MeP9D%bu;Xwr9TzogAA|^Fn?% z{iQxk&l?YYB!V&!VKKxz3PHYRu6e@8bDcuQ=X_V5-MwsGSq6O2i&7R+S9F{JOG)BX zSlTYF>EOFo@wlnv>sn(E{2DYeA#|Qj#v_=830DDXW+B|UgCh0vKn+FqGX9;yD>z5- z81H5$af7e6A|8W%i|3)ErLTe>(Jd|=Zc>qVB8x2)UC4E}rKp!O z2NksJex~As{4L5#Fu4^F-_nFe!(xG{7LF19YVXoHqm8PD|97c2Be_Jw{3%;Vc|~yJOJ7_LA#F6JPH}N%dz>Vi0QM>w^YFi{J&{)FB%|w%p-W|jTm6-Y zQ$+nS)aLUwRiv7yHCtWVoiPv{otk5OavkCpn+|gR6i;{7_HbsSl+>DP{{}m6HQ0D> zJ=dYBnKyt0_-E<>gQ{X`TL~`)B~+m)K;XHP-3eGzB$Yg1ml=L68BXL5ce1xuq>{V3 ziRsb`Mvp0fSx)fcZ6)vr4;jDC-TU_mJ}6OfmNH$WksF@@d_^Ikv8A~0dn0xdgSzI? zGt~OFiqhvtIZo%@5z^uZCqf_ZS|FhsyOwNWB6ip`nWSimfd5=Xt}dLzbLh?8?@<#@ z3j(Ob6Q-{?IyJk$!0ys940i{BY-^E7Tj5ry-8hp29Eb614(gCqQU_K(xTtklH^RKJ zDV3;6DM}PcQ|ut+pqh`SSSEWjD&_?N!q>gsd?&a6O7Z^`oJN&Lf;0F(7o1+pyA{dY zExU3hhj$hP(MEQ`36MG%x}6e}8V}@sJI7C-`w6(`&R1})pUTh}jf~6-FN8bV7~>;f zX>dSq6;gj2W8$YfeqIO_U2|Alk=331T;w^=9S7K45stoZGioFHN)Fc^=8#TKnKKIH zV@g*(Uka-HIlmi(<{1ZE8_oEiU~wS`+{8dO+`>#rbSnAqD_3-grj4J*E2Pc5i-%<` z14l=1Ns*`ZwiS(zZqmI4YM;Pw7ylLW3C`yRH<+Ml0Vbf-kpaLVPq+!V5~5?&H#Rj8 z6KrhV(i&j6b zQC;L;pcd77iw$KnFQFHd+M9&9)MilISL$^WtCIEQ+wN4uBshv72tH6XCd}`bg1ggI zqLRF<{a7rO4iF=&qN&zg-v9wy!2-M8qu`%im^B6W&4*ht^zF(%tvZ+H zU>-`?M(mh%wvr~St17^|8LRkQ@{0(HpY`XWDq7ZVg7txg>n&d}xSZ8GTr8kDtNed0 zbp?IK$J(d?>O(*QQ|P^Zj8_{RaOV~FFKJj)fhyXbH+e&>w2Pb~v*3QyZ`Wg<6@1sg z#sdcIe-gpXPXRak)6sVp+C4zH8EbPTsUiFAIcRTvno^0Dlm zoxOsNtZ4Cc=PTca?6Ne}^6zYryZu*n++=y(;Gqb9FJXRO9i*CDAcJTYAaCeCqsvTF z`xHgHdx8TuVr5}ag@GS?5q?MF*62;Ldj-E+7|v2{Y4-t2txCmB5o7Kje00w7X}XR+ zAb00|#Z~lZIKcaFR&AF5J7zmrI(#cNH`~+{x&7AMJk@=}%PwBQ?xr2U(Rl_&3}F_W z3bQpPCOMh|$4cuNz@z zHN|-*`fhhaDzRa@Mq;ZDx6_#k_ReVuKS<@;+G}nRceRD(zJ6XEhdu+m=xP(STTdsw zsWufVu<(X#0H~RP`kmvM7IPgAZgpmOa48oht$fTxP*r;wL*KJZVFAGY%nb;Nz znp(G;?dv8B1n@2Ry6Lx8Hu~N2DAVCAG@4A_MOUGE%wT~>z^eVZ^FIi*j;vSuT*?d| zWRl9IZqo&P;Tk9-ipAH@_G7go=oFvN%4sN zsqVEzhJJ*>y2%779n}GINF{Ve}2YjBj%$^Z{8o@MHJd|O~}Sqp+oyA5#7IEZ8b z-3~^Xs#mGBSZzRGpA#gKjK#FZn39Xk_47YhB+t$Kg_B-q_lppCT&h9+fRI6Kk0AtX zW)7MK$c5d*d>g+3QQCk;4}getXScLCtnntrn6SvI!e;3`gp`_75uE0!l|WABzGi2S zTE|f-5`FvWfV-Mbh{LWRJ6S<6^Os$MzfD{_RTtBfzvvcR@*2!<%*# zBC^=6))OKNxV|fB{C> zqr?W8#4}3B+goo9cn5-q+bTo1KfxOrqGcA>INj{ploH8 zE%+w>Mo1mR&krW#x7Vs2I7T0%4!=5W(jtEz?7inrOD;k>N9-x{H{mHhQ8M(#XtEsU zjGg>Q4q>qO)z;pwq4SAhI8U}qn_29SHfu1b$eY@cmBBv%>fI{r%+Mptm3pn*%bs3c z4tBFHOr2`)TN*~u7IiJjPV^m(qAZ3&b@dJGKr9WLlV&jHp>|5Lm@HdrYFhFHg?OCI zVZHC}dVOhWNh0F-bY9I-hyrvIxqz=*-2}o4o>l@y_u(Z)5Hg_-h|YqKY_=X^Xf6F? zfD7#Xc6|I95w{dcz~LvD#}cBva>UFu0-v@1zV+9sD!mPDS;2{4Ow+tgh~@F}hlL)7 z>O3Ac>559gJWl?3tGS>%)!jStWimgJ#sf6bA>V!A&%^TCNUdg?WG%VfIBF{sv5U74 zZ7XjCbtGI#3Oby-!l0^lpW< zwb`L4SK+1Zt6n7NYlp^_qjSM5LLFiV!=LADNQ#%CVv*z2%Z5}q?#enl$9H3HBspWA97mt zUumsv9K(0d&HN4=NqH%oI7W*WQ<;_mWuE~q`|^h@F?{^jisp>sCB0y~02LtKD+s(4 zw1YDDlBEYi$N+2TG%$MHQSf(8anrKp?Tp?FYY8U$$a28tymPK?<6OVQ5zGJl;?&-s z=b3=U7c`N3l0XZc#*b~^suTqYO)dpDICU6%NSMt#GNOJqI6Cmyu$3k9-~}XtN8G&a zYBPrO*Qs5Px{iAFR9%6zwl^YS<-HUsc3=F`K`^R!g+`g)V`1k5PCr}Nh!O-Tf_C3jVn9hWWJ7lqYN^XQ<+4YX z4Z7Z<164>~aZ9$nZ?a~eLJ z3D3Nd&)8ANI~IGg;v^4MhBU>;JEwf^^+--_#tV*^>ztnPtaaD)>n+68D>a2~4&BP5 zX>+hJWCk>gKv8=5%F* z2vmmO<_dR$$2-~9l~msOIuuD;zp!^_@H10sfPJfqO3VHD`%R2Raa8$+^1KC|!I!h0 zLSMcS(<|dYXM1~3Vh2MKen@om3ri!Z{xgUW7qDsId$9!vs*+qY&-}G0DA!?*S=}I^ z%36D}x%w@$$DTTVxyM7m1#}S!e@S_2ktL+e8V!esk6N85vRz!-PYw17Vd0*7#zbDj_H{IRF3v07*qoM6N<$f-!EEfB*mh literal 0 HcmV?d00001 diff --git a/sdk/sample/windows/pic/5.png b/sdk/sample/windows/pic/5.png new file mode 100644 index 0000000000000000000000000000000000000000..4282e78b8f09771f3fe09b1cdcd342cf9aac7870 GIT binary patch literal 38669 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

nEfCgwYpq^mUz$idgP2)(I8bXE7H`;Z2f-TN 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℘(QfuKD#P;K~`WS-g``C-B|69rgIlwQu+;LEyONX<4gUY zLDmbI#(e({vL*{$i=Ct~%3~;8P)&#o>0eO;oP>a*VK*Hfw&`*2x7wWdd_u@EJ|y3* zR3IbJ@QU}_a9Cfv6RlwDUQ@4zwEa=CLUpg0xb0udbx5~Tbq9ay+Fnw$&iry_{@K8b zXE}{S_)Q*m=w|lq!;`S~|7S3(Z14X9vreMn`vtSceqO~Qq1zP_5COcaWhL;@v#4l* z6z+y)W;Vx(i2JN>d6{uB_}v(Ql@1h4$wMXQsR9><8($LRbQ1V-ma@(`4T(VnVU7ov zkjJ)qN3+-DOlhij$IOuUMoJGbJ=o&1Fg*|+CVnKh?7Fkn;bs>Cac$>+o*CCw@7yoP zxr2ofCFfwHdcmZe6&eFStC*KMiXZ#bxx8GAf^^#MD*Cvx;FMmsW@$S>4lgo5}$l;40+{epMR~2W_IT7O=_&8yJ z;_G*f`gH%7R&+v=YHFp<6j|peOXz4>>;pTZ10 zIk2;#-ysz7VT;K6T83O%kf?7!s@u*$&-dV=6eppCpAy=<8VLuYEsoq&#FHfXQEg;xjtFne zu&}C{{n^lgyaCF@=dk}Pj*@w?NU<+nJD4iZZ%lL@0N`;og z;gaj({CBl=E3EZV&k{zRKa1JR#W7`N3`J)%GJMwrs)+QXUqxZ@9vr_spT-w2RV=R` za1CxHCy8HOFT!0%_29_s>DrPg6j?!IM_QR=OGrcCxBfxtxWI1Bmlha(cz)IWew8UX33*V_Z~Q zr=w8z&2zqZW?tB>UOgk@6c2mun+@O&;a)A8> z3cBNyQ%8TWHY^tn{CMp@`0L5v@z;nj6Gbyipnn?Mr0EU+5Fo#Yp%SKdFj_i2zWTV( zWoKuYxr*s|zh_Bz?qj1_i(82>GwJD}C%j2WkI_8DC$c=Kk*Qfgu7+BLS(pdlG2~?u za*W3wewy}a3gfqiTOzi1hjnVIKqybFKiO_IVbo3{Ohan)YvC2}OsN&HCaquWi7T$I zy)@;j?4kSOSSeDktF*ez(iMO00fm==|7?Ln;qaYbc0@jrMABMhX=aQNcik11fas(! zg-B5-oM|$Eju8?hxT*N|s=@608A(fy@sgS=uJ49NwH*O`l0 z&nL_MwGBYk+UZmfe)YlUO;Q6ZDKl%XJ6j&!CP=$>T58W0AUosZ7x&5))w{d>3-anf zu(15yeKys=dwd{;;+H7-$WvWE+jgJYez;7oMNUXE$n=t{9#Mr7&Tl&)KX^^ zZlr=D=HGM=#9A;ghy+(uF@z~XvT91PP0n_6|IjVRy^4g7V48PEJQSDCk2(G$o(6 z5{-u~5!SNe>M&bA5RsjUyJ7A^c>cmoY0m9q%@z#Uo0DnK=7L1EYo>4eKAfvN*MGo* zd5l05W-t_97MyP`78lGJRsEL8H|i4SLgHW* zu7DY1-a!!w&sd2-^6sSe(|7f&k{`WLtT~;hQzD?0%w(|tV*i>V^gdOqULpz&#!PC7 z7Cn}MJ8Dn66&4nTTPmiLM_`VoX6(?+tCRsI4az5!_n7he=+`DcUm%UNFPm^(lgfK* zy^u<+)NPy+Yx#yG_ldMhE6vjEFL~eEZqS{>Z|U9Kvr&aBYg_HuMBd#d9fAN2Q)-wOErC*CX&Gvq`x z%^Pyl9IyJmxQ;kBc*z0#$SWuyV0TnU{32)aYZIvxr_hvp6G|jAvo$!K+tRgve2%+w zAkra`W1o>;Mf3X7K%K+;kwQ`6+sIXqe()l;THMC;CfR)f=vJOB61J-?7wdAv8QYo^ zhM&j_CQZlWaeGCu70V{CEBv*_Uwa+iqKXZtU${gg#Y+)9(zl~?^an;mU<3He?ei_U zf&qz&@j;H^3&zGz4;{vw53p^*w79}@Dw?j995K^8 z!IR%LE&YQ;RA5Z7I<6W7+P}S zUW|4T>x91=nbuG`HK$}c6-9MUN8rj$CBv-lD*lZF8=gyzoVkDhKAfbEw?;j&PBzF_HIt|(b_xcI>A4A73hY&;$`F(EFp*$XcjC7SBK z@swF9o1RD4z>jMAXwIYmwS4wT6ZeZMN(C?W*<2b6OqH^E3!Hr*=EtIZhGvL zcvI)Ttm#wsbhPBe5q#mp)85EIyRZlpRL)k3lq0V<$hsws*2%wA)roJpsl8LSC`@yQ z?iP_0a4gKGOImeCm*c?vCOtjf zPoMp*^5*YaA(*g_cf9ZDxNlL+$;pYEG-`6#q=+SDDS|r#n zf*aN%c>9pNMaQxJ{#(zR3552-5<+|K7edSQ4?=ruSz+tE4?IxJ9HV+a_SyLqUaCw7 z2uBPD~y!g^1U9-JeY8HuU+P5Om|?rcL$92?9$5V3^Oiv5n! z`djPiMO%eCR5eJq{z7T3WP%w2<3)=1YS8IwMCSqoZXo4n@G`#ZBii z2Q}M4#S#UWX$~T~CqqN*gnEfy!j1k2W$z=t2{fMLx76UTKrpo6AAyKv(FKE(FJA_mmsl-gp`=v6!WbGIx zGKw@$o3B&FFW!3rlLPr1+m*^ugoIb($s-)eK`zX1iAEgqDqxZc3sd>+x>exOC}mpf zyJvixnj&Z|%RlHBGLeETP&`0WTw8&jO&o2z4XoeVC2yCJ*KoXx!e9m z>^5@$RbX@DnV*PhL6vP3{9EI-tKgl!JzQHomBOKsx^kuBqI*6jDLOX5AeJLh%~zV2 zhlg~|(7t#}$&IIx(Fj*D_w=l3hOHQ3Wl=i`If=XJgcoGej1`3aP;uL~@XCtPRJ$i0>5-4F>wn1Y zsMs{2{EeW(lLX%IXH~J)6E6F}K|rnBS*#Qq3~pY68R|C-P=CN_GnM{|Y!sXG3A04q zp;X=82dJR9aLJYhSS+h2=KrVZY?o=lmr>50&?@fxC1m>am;Ij)L+}4oBfz)E|@K^}~ z%kSP%lkU`V(ea-ET!ke7m&qYl2>`D5_|-?lXBAT!Qd_oo`8mb>RZ~AT zslAuGBYco%6R0dxrm3Ay=Vm)uxm~@ss_Lyf*PXM?(mSMR-F|SNr2Oxs<<#x^yWv{Q zD_nd#_IT_wqJR;nIe4eqFZYsp?|UU~bsJ}d7^JcH06kSO1+NhmBzmgeku*!TGa9rB zVUb;2VVYdy-){Dn>tq-3oMoNv7Da%a4D6fq&TvBek8jJumhfBOoSYlL zZxupUi`+ZG{Ln^O|9)f*IY)w~pxD>oe&ohq5Fzl>kCW3WsUk)aEwVR@Z4Ej4ryC#F z*4CPs>eoFfuH|VRX(4B;)b&F(e+!X{>r+yY-TPq)4+M9KaHUEd-gEz1mH3f=#cwqN z{}=pr4EXKKxP+~Y>-(xPNvz6nAh64N%hs)oz(Rk%4B)p)1b(}X<$uI)lbLkCo?C)p z|3PVYnsiSt3W%)u9n$^zXG-Nd#((=)%gc^W5cBsN%!@zYw`yE$n~;GR@bHy|(!uT@ zKMn%|43u+1$FMUjA+(&~*P*l~$01JW5mn3F-`vUxTT>*;_2Qe|C`k=Qs zvjZ2-!)J|_u0K!aQp2wGxs-DZu*1^7|LiD(AjXJ}F$pU-5g~u@mLZX*U36tVO7At8 z_+A0K1T)0vU$uAt7tEN0a2pp9@dmKc{Qc*a05Ag^1PDgYI(Gr?fIR}J-cN`)USVN& zSEZYs#pps&q$?*^2fXG7_KB;Z(02p_gk>av^_tC1Mhu4c8w^W2lcWs>EO_e`9uGgd zjkifsXB{Ij)8LJ(jgQ2#t|jaV;Dp-LbRD|{<8n;NLMy=*`vCrwJ5ne~zz@)w(?+@? z3VH$&@FV8*qZ4T|{Z;0FX?nXMH>65sc7LB;@99{aL;d z7#lpO=_hq9qKvEKSl1H%p}0LiI=Q*M$!%_xn_dmvhWL&|oGX}97;8b39Q6Sd8$wBg zA5At28LQlGR!V8p#x>ToJ19OiFt4Z9IzP2E0#ODA_V%s(a(tnAzNu%$)n^Pf5)~*? zt;Y(fj948G%Y=|vhhfTw@3TP{!;ic)gaziE)i0~Z-^IjUuh%0k%! zhbt^0tZGx-3U@tf=L==~n8vymhB6*7=Rsp~xnNv1{K#vkwR~NE+E>_{U)D`5Ga|gm z;0cJjT)u(o-Ix7}mfS2pa>RgDzL^|2>MwuL&S$>((+_*=zI7>$>J~-p{m0lujC+nJ zXPWp?j4B0E#5)QO+0=jjxWX};(|S6l=I%WV*#v2o>!h+J{(;|QQ!%-`E6RM3$DlcL zAazvHl|)O{4c_!>wfN%Pq`W-SgXuNP3m2~M@-SocZTktkD?Q&qLtrj@m}EQ4d?*n^ zsiB5qCc}>l;)M!s4y}0f;<-)Q#)J9%Yi zBFk1ZXPD{J3tg%{^e3ut>CmSyAn_n(?&l0OCNSGe_6aI^h7{x3l$*;6dGJ;dZniK= zPFBqk>m@{*@H%LJ_D5}U&28;&S&{Yudohs82kH3nEew{|3zO@by zhEQ9QD6?yp*J^L3l<#=IuS$0McC*b@P4WIw!gSBcsnbZKdJNw7SQ^mV8>1xzEgyFH zpUPKK1);$Q{0|_YmIA|_ZJM3g;Bz&$w3r;QV>Hk${F2?wA_YmNHiVLgpcfK~; z9J&VjVWo~20<{ebiX*F9+n09~Sg!1rq%6ayJ#Dlz9}5&!sZ_MOu3co*ph;X9#-ISb zahtUwWsNZ3c=9LC!Z)tpfzBz&A51z_03BEA+P!|W%jh_!QTr5U#Fdrxs)#OEdI@nD zoGlyM4IEFVzHtPr9TyeyCy7}gqPKUFE-Ws%4w|iDo!F8iapB#OR-3Q{(4h1Yd_bfg zWW^Y&Go-4Beh}tQeBo(AZX*uVsKx=`-o4k6okJoaG>q!f{Y#KqTvVl?DEBbj*QAHe z=_YrOY{CmxSy!d0tYEShH`zF`<}N!^XMajzImTq}W&$96Ex#J3u#A+qa4Nwq@_)i| z8P5EL<*s%`Bpm=EN6Jvp;LbAb``}+!{!9zSK0oX{m1JD*Dz4W%UwAlwFLA2DH2~sT z%UMB~8i05dTp`ryBpdmn$rPn!xwBxC zwSbqblPD-`pehplii`HToa7N4`DiV9n!&IY3>~RWQ+(cYL<{!@yK?k3Z?F%zl~MGK%G4QDBsl zHwtaYfb)Q~>?tCP8`47bxrDR`s=|2Xk#4_JYAGlFFDbS2k8ZB{V>wPL!WU$B$^O$(_h(Ep zjX6SWfi8oBVlr1z>Q>E*)Oi)#iaw)(-n;RQzLp|E1mORcmFe+Xub14w3r6c-Hpcwy zMk!l+EV=pDuOtxv+XWWw``9VRMwl~1cNc>D+3^@xA#xNehU|R9eJ3QRUyPy83)qm- z>ZeoYoS!mcExfO`6=QLO?A20A&<^_Y$_Y_An6AzXAMxXUf2jOH?ep}rs?#k78tKpE z^E-&mkk*k*Fsl!DZpbX(-W}Xf$Z0K3k`iy3tC`r!`sl@7t+AkvhDFdEYD}8neEsUx zVYd7H$LpRcDY)TrK0iA*0K!-1+5z&)AT`nD<2(V5)h7?Xg>3>voe7Z#@;_<9-S(N$ zQRrxdBcIL82!p)%8ho+V*48kFG_8J>+bT1d**rzm2n5%E;Jk(_ip$h%nc$7rbVNUh zT^;oZ$ysMQfs49eyz^!e64wYH)s`4FEtoz-K>4F0V>s9^Mmwr^=}_1dm=j{S{}_R# zh4{?GeoXANe}9uONWiBadc^ww3^*%z{R$}yF3F+moLu&=3qk;lNR9al39HlmIY}7t7qU5gLT$* ze|Ds*=ZO0Q&*@|XBH#i8;A9=}CI?WnSVnyJmXxEwX7r$12fi^KX+fPD`NzErZA%<%&21lW%`LEi^$s`&8@sL8y4tVoUiaOM zXA$Q+=jJTlw)k)2?V#PcD^u4U@Pg#0J1dT=?Rd#{H9i%z8u~xGk;X}}CxwC53_#%N L>gTe~DWM4fXU{Rj literal 0 HcmV?d00001 diff --git a/sdk/sample/windows/windemo.cpp b/sdk/sample/windows/windemo.cpp new file mode 100644 index 0000000..1c35ff0 --- /dev/null +++ b/sdk/sample/windows/windemo.cpp @@ -0,0 +1,163 @@ +/** + * @file main.cpp + * @author LDRobot (support@ldrobot.com) + * @brief main process App + * This code is only applicable to LDROBOT LiDAR LD00 LD03 LD08 LD14 + * products sold by Shenzhen LDROBOT Co., LTD + * @version 1.0 + * @date 2023-2-20 + * + * @copyright Copyright (c) 2017-2023 SHENZHEN LDROBOT CO., LTD. All rights + * reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License in the file LICENSE + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "ldlidar_driver/ldlidar_driver_win.h" + +uint64_t GetTimestamp(void) { + std::chrono::time_point + tp = std::chrono::time_point_cast( + std::chrono::system_clock::now()); + auto tmp = std::chrono::duration_cast( + tp.time_since_epoch()); + return ((uint64_t)tmp.count()); +} + +int main(int argc, char** argv) { + struct lidar_inf_struct { + uint8_t index; + std::string type_name; + ldlidar::LDType type_number; + uint32_t baudrate; + }; + + const int inf_array_size = 4; + lidar_inf_struct inf_array[inf_array_size] = { + {0, "LDLiDAR LD06", ldlidar::LDType::LD_06, 230400}, + {1, "LDLiDAR LD14", ldlidar::LDType::LD_14, 115200}, + {2, "LDLiDAR LD14P", ldlidar::LDType::LD_14P, 230400}, + {3, "LDLiDAR LD19", ldlidar::LDType::LD_19, 230400}}; + + printf("The SDK support ldlidar product:\n"); + for (auto inf_base : inf_array) { + printf("index:[%d] --- %s\n", inf_base.index, inf_base.type_name.c_str()); + } + uint8_t product_index; + printf("\nplease select product and input index value:\n"); + scanf_s("%d", &product_index); + if ((product_index < 0) || (product_index >= inf_array_size)) { + return -1; + } + + PortParams port_param; + port_param.baudrate = (BaudRate)inf_array[product_index].baudrate; + ldlidar::LDType lidar_type_number = inf_array[product_index].type_number; + + std::vector info; + SerialInterfaceWin::availablePorts(info); // get serial port device info + printf("\nAvailable port:\n\n"); + int port_index = 0; + for (auto port_item : info) { + printf("index:[%d] %s, %s,\n", + port_index++, port_item.name.c_str(), + port_item.description.c_str()); + } + + printf("\nplease select port and input index value:\n"); + scanf_s("%d", &port_index); + if (port_index > info.size()) { + return -1; + } + + ldlidar::LDLidarDriverWinInterface* ldlidar_drv = + ldlidar::LDLidarDriverWinInterface::Create(); + + LOG_INFO_LITE("LDLiDAR SDK Pack Version is %s", + ldlidar_drv->GetLidarSdkVersionNumber().c_str()); + + ldlidar_drv->RegisterGetTimestampFunctional(std::bind(&GetTimestamp)); + + ldlidar_drv->EnablePointCloudDataFilter(true); + + if (ldlidar_drv->Connect(lidar_type_number, info[port_index].name, port_param)) { + LOG_INFO_LITE("ldlidar serial connect is success", ""); + } else { + LOG_ERROR_LITE("ldlidar serial connect is fail", ""); + exit(EXIT_FAILURE); + } + + if (ldlidar_drv->WaitLidarComm(3500)) { + LOG_INFO_LITE("ldlidar communication is normal.", ""); + } else { + LOG_ERROR_LITE("ldlidar communication is abnormal.", ""); + ldlidar_drv->Disconnect(); + } + + if (ldlidar_drv->Start()) { + LOG_INFO_LITE("ldlidar driver start is success.",""); + } else { + LOG_ERROR_LITE("ldlidar driver start is fail.",""); + } + + ldlidar::Points2D laser_scan_points; + while (ldlidar::LDLidarDriverWinInterface::Ok()) { + switch (ldlidar_drv->GetLaserScanData(laser_scan_points, 1500)) { + case ldlidar::LidarStatus::NORMAL: { + double lidar_scan_freq = 0; + ldlidar_drv->GetLidarScanFreq(lidar_scan_freq); +#ifdef _WIN64 + LOG_INFO_LITE("speed(Hz):%f, size:%d,stamp_begin:%lu, stamp_end:%lu", + lidar_scan_freq, laser_scan_points.size(), + laser_scan_points.front().stamp, + laser_scan_points.back().stamp); +#else + LOG_INFO_LITE("speed(Hz):%f, size:%d,stamp_begin:%llu, stamp_end:%llu", + lidar_scan_freq, laser_scan_points.size(), + laser_scan_points.front().stamp, + laser_scan_points.back().stamp); +#endif + // output 2d point cloud data +#if 0 + for (auto point : laser_scan_points) { +#ifdef _WIN64 + LOG_INFO_LITE("stamp(ns):%lu,angle:%f,distance(mm):%d,intensity:%d", + point.stamp, point.angle, point.distance, + point.intensity); +#else + LOG_INFO_LITE("stamp(ns):%llu,angle:%f,distance(mm):%d,intensity:%d", + point.stamp, point.angle, point.distance, + point.intensity); +#endif + } +#endif + break; + } + case ldlidar::LidarStatus::DATA_TIME_OUT: { + LOG_ERROR_LITE("point cloud data publish time out, please check your lidar device.",""); + ldlidar_drv->Stop(); + break; + } + case ldlidar::LidarStatus::DATA_WAIT: { + break; + } + default: + break; + } + + Sleep(166); // sleep 166ms , 6hz + } + + ldlidar_drv->Stop(); + ldlidar_drv->Disconnect(); + + ldlidar::LDLidarDriverWinInterface::Destory(ldlidar_drv); + + system("pause"); + return 0; +} \ No newline at end of file diff --git a/sdk/src/ldlidar_dataprocess.cpp b/sdk/src/ldlidar_dataprocess.cpp new file mode 100644 index 0000000..5983402 --- /dev/null +++ b/sdk/src/ldlidar_dataprocess.cpp @@ -0,0 +1,284 @@ +/** + * @file lipkg.cpp + * @author LDRobot (support@ldrobot.com) + * David Hu (hmd_hubei_cn@163.com) + * @brief LiDAR data protocol processing App + * @version 1.0 + * @date 2023-03-12 + * + * @copyright Copyright (c) 2017-2023 SHENZHEN LDROBOT CO., LTD. All rights + * reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License in the file LICENSE + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "ldlidar_driver/ldlidar_dataprocess.h" + +namespace ldlidar { + +LdLidarDataProcess::LdLidarDataProcess() + : lidar_measure_freq_(2300), + typenumber_(LDType::NO_VER), + lidarstatus_(LidarStatus::NORMAL), + lidarerrorcode_(LIDAR_NO_ERROR), + is_frame_ready_(false), + is_noise_filter_(false), + timestamp_(0), + speed_(0), + get_timestamp_(nullptr), + is_poweron_comm_normal_(false), + last_pkg_timestamp_(0), + protocol_handle_(new LdLidarProtocol()) { + +} + +LdLidarDataProcess::~LdLidarDataProcess() { + if (protocol_handle_ != nullptr) { + delete protocol_handle_; + } +} + +void LdLidarDataProcess::SetProductType(LDType typenumber) { + typenumber_ = typenumber; + switch (typenumber) { + case LDType::LD_14: + lidar_measure_freq_ = 2300; + break; + case LDType::LD_14P: + lidar_measure_freq_ = 4000; + break; + case LDType::LD_06: + case LDType::LD_19: + lidar_measure_freq_ = 4500; + break; + case LDType::STL_06P: + case LDType::STL_26: + lidar_measure_freq_ = 5000; + break; + case LDType::STL_27L: + lidar_measure_freq_ = 21600; + break; + default : + lidar_measure_freq_ = 2300; + break; + } +} + +void LdLidarDataProcess::SetNoiseFilter(bool is_enable) { + is_noise_filter_ = is_enable; +} + +void LdLidarDataProcess::RegisterTimestampGetFunctional(std::function timestamp_handle) { + get_timestamp_ = timestamp_handle; +} + +bool LdLidarDataProcess::Parse(const uint8_t *data, long len) { + for (int i = 0; i < len; i++) { + + uint8_t ret = protocol_handle_->AnalysisDataPacket(data[i]); + if (ret == GET_PKG_PCD) { + LiDARMeasureDataType datapkg = protocol_handle_->GetPCDPacketData(); + is_poweron_comm_normal_ = true; + speed_ = datapkg.speed; + timestamp_ = datapkg.timestamp; + // parse a package is success + double diff = (datapkg.end_angle / 100 - datapkg.start_angle / 100 + 360) % 360; + if (diff <= ((double)datapkg.speed * POINT_PER_PACK / lidar_measure_freq_ * 1.5)) { + if (0 == last_pkg_timestamp_) { + last_pkg_timestamp_ = get_timestamp_(); + } else { + uint64_t current_pack_stamp = get_timestamp_(); + int pkg_point_number = POINT_PER_PACK; + double pack_stamp_point_step = + static_cast(current_pack_stamp - last_pkg_timestamp_) / static_cast(pkg_point_number - 1); + uint32_t diff =((uint32_t)datapkg.end_angle + 36000 - (uint32_t)datapkg.start_angle) % 36000; + float step = diff / (POINT_PER_PACK - 1) / 100.0; + float start = (double)datapkg.start_angle / 100.0; + PointData data; + for (int i = 0; i < POINT_PER_PACK; i++) { + data.distance = datapkg.point[i].distance; + data.angle = start + i * step; + if (data.angle >= 360.0) { + data.angle -= 360.0; + } + data.intensity = datapkg.point[i].intensity; + data.stamp = static_cast(last_pkg_timestamp_ + (pack_stamp_point_step * i)); + tmp_lidar_scan_data_vec_.push_back(PointData(data.angle, data.distance, data.intensity, data.stamp)); + } + last_pkg_timestamp_ = current_pack_stamp; //// update last pkg timestamp + } + } + } + } + + return true; +} + +bool LdLidarDataProcess::AssemblePacket() { + float last_angle = 0; + Points2D tmp, data; + int count = 0; + + if (speed_ <= 0) { + tmp_lidar_scan_data_vec_.erase(tmp_lidar_scan_data_vec_.begin(), tmp_lidar_scan_data_vec_.end()); + return false; + } + + for (auto n : tmp_lidar_scan_data_vec_) { + // wait for enough data, need enough data to show a circle + // enough data has been obtained + if ((n.angle < 20.0) && (last_angle > 340.0)) { + if ((count * GetSpeed()) > (lidar_measure_freq_ * 1.4)) { + if (count >= (int)tmp_lidar_scan_data_vec_.size()) { + tmp_lidar_scan_data_vec_.clear(); + } else { + tmp_lidar_scan_data_vec_.erase(tmp_lidar_scan_data_vec_.begin(), tmp_lidar_scan_data_vec_.begin() + count); + } + return false; + } + data.insert(data.begin(), tmp_lidar_scan_data_vec_.begin(), tmp_lidar_scan_data_vec_.begin() + count); + + switch (typenumber_) { + case LDType::LD_14: + case LDType::LD_14P: { + SlTransform trans(typenumber_); + data = trans.Transform(data); // transform raw data to stantard data + if (is_noise_filter_ && (typenumber_ != LDType::LD_14P)) { + std::sort(data.begin(), data.end(), + [](PointData a, PointData b) { return a.angle < b.angle;}); + Slbf sb(speed_); + tmp = sb.NearFilter(data); // filter noise point + } else { + tmp = data; + } + break; + } + case LDType::LD_06: + case LDType::LD_19: + case LDType::STL_06P: + case LDType::STL_26: + case LDType::STL_27L: { + if (is_noise_filter_) { + Tofbf tofbfLd(speed_, typenumber_); + tmp = tofbfLd.Filter(data); // filter noise point + } else { + tmp = data; + } + break; + } + default : { + tmp = data; + break; + } + } + + std::sort(tmp.begin(), tmp.end(), + [](PointData a, PointData b) { return a.stamp < b.stamp; }); + if (tmp.size() > 0) { + SetLaserScanData(tmp); + SetFrameReady(); + + if (count >= (int)tmp_lidar_scan_data_vec_.size()) { + tmp_lidar_scan_data_vec_.clear(); + } else { + tmp_lidar_scan_data_vec_.erase(tmp_lidar_scan_data_vec_.begin(), tmp_lidar_scan_data_vec_.begin() + count); + } + return true; + } + } + count++; + + if ((count * GetSpeed()) > (lidar_measure_freq_ * 2)) { + if (count >= (int)tmp_lidar_scan_data_vec_.size()) { + tmp_lidar_scan_data_vec_.clear(); + } else { + tmp_lidar_scan_data_vec_.erase(tmp_lidar_scan_data_vec_.begin(), tmp_lidar_scan_data_vec_.begin() + count); + } + return false; + } + + last_angle = n.angle; + } + + return false; +} + +void LdLidarDataProcess::CommReadCallback(const char *byte, size_t len) { + if (Parse((uint8_t *)byte, len)) { + AssemblePacket(); + } +} + +bool LdLidarDataProcess::GetLaserScanData(Points2D& out) { + if (IsFrameReady()) { + ResetFrameReady(); + out = GetLaserScanData(); + return true; + } else { + return false; + } +} + +double LdLidarDataProcess::GetSpeed(void) { + return (speed_ / 360.0); // unit is Hz +} + +LidarStatus LdLidarDataProcess::GetLidarStatus(void) { + return lidarstatus_; +} + +uint8_t LdLidarDataProcess::GetLidarErrorCode(void) { + return lidarerrorcode_; +} + +bool LdLidarDataProcess::GetLidarPowerOnCommStatus(void) { + if (is_poweron_comm_normal_) { + is_poweron_comm_normal_ = false; + return true; + } else { + return false; + } +} + +void LdLidarDataProcess::SetLidarStatus(LidarStatus status) { + lidarstatus_ = status; +} + +void LdLidarDataProcess::SetLidarErrorCode(uint8_t errorcode) { + lidarerrorcode_ = errorcode; +} + +bool LdLidarDataProcess::IsFrameReady(void) { + std::lock_guard lg(mutex_lock1_); + return is_frame_ready_; +} + +void LdLidarDataProcess::ResetFrameReady(void) { + std::lock_guard lg(mutex_lock1_); + is_frame_ready_ = false; +} + +void LdLidarDataProcess::SetFrameReady(void) { + std::lock_guard lg(mutex_lock1_); + is_frame_ready_ = true; +} + +void LdLidarDataProcess::SetLaserScanData(Points2D& src) { + std::lock_guard lg(mutex_lock2_); + lidar_scan_data_vec_ = src; +} + +Points2D LdLidarDataProcess::GetLaserScanData(void) { + std::lock_guard lg(mutex_lock2_); + return lidar_scan_data_vec_; +} + +} // namespace ldlidar + +/********************* (C) COPYRIGHT SHENZHEN LDROBOT CO., LTD *******END OF + * FILE ********/ \ No newline at end of file diff --git a/sdk/src/ldlidar_driver.cpp b/sdk/src/ldlidar_driver.cpp new file mode 100644 index 0000000..f3709c1 --- /dev/null +++ b/sdk/src/ldlidar_driver.cpp @@ -0,0 +1,52 @@ +/** + * @file ldlidar_driver.cpp + * @author LDRobot (support@ldrobot.com) + * @brief ldlidar sdk interface + * This code is only applicable to LDROBOT LiDAR products + * sold by Shenzhen LDROBOT Co., LTD + * @version 0.1 + * @date 2021-05-12 + * + * @copyright Copyright (c) 2017-2023 SHENZHEN LDROBOT CO., LTD. All rights + * reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License in the file LICENSE + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "ldlidar_driver/ldlidar_driver.h" + +namespace ldlidar { + +bool LDLidarDriver::is_ok_ = false; + +LDLidarDriver::LDLidarDriver() : + is_start_flag_(false), + is_connect_flag_(false), + sdk_pack_version_(LDLiDAR_SDK_VERSION_NUMBER) { + +} + +LDLidarDriver::~LDLidarDriver() { + +} + +std::string LDLidarDriver::GetLidarSdkVersionNumber(void) { + return sdk_pack_version_; +} + +bool LDLidarDriver::Ok() { + return is_ok_; +} + +void LDLidarDriver::SetLidarDriverStatus(bool status) { + is_ok_ = status; +} + +} // namespace ldlidar +/********************* (C) COPYRIGHT SHENZHEN LDROBOT CO., LTD *******END OF + * FILE ********/ \ No newline at end of file diff --git a/sdk/src/ldlidar_driver_linux.cpp b/sdk/src/ldlidar_driver_linux.cpp new file mode 100644 index 0000000..00daaf2 --- /dev/null +++ b/sdk/src/ldlidar_driver_linux.cpp @@ -0,0 +1,341 @@ +/** + * @file ldlidar_driver.cpp + * @author LDRobot (support@ldrobot.com) + * @brief ldlidar sdk interface + * This code is only applicable to LDROBOT LiDAR products + * sold by Shenzhen LDROBOT Co., LTD + * @version 0.1 + * @date 2023-03 + * + * @copyright Copyright (c) 2017-2023 SHENZHEN LDROBOT CO., LTD. All rights + * reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License in the file LICENSE + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "ldlidar_driver/ldlidar_driver_linux.h" + +namespace ldlidar { + +LDLidarDriverLinuxInterface::LDLidarDriverLinuxInterface() : comm_pkg_(new LdLidarDataProcess()), + comm_serial_(new SerialInterfaceLinux()), + comm_tcp_network_(new TCPSocketInterfaceLinux()), + comm_udp_network_(new UDPSocketInterfaceLinux()){ + + last_pubdata_times_ = std::chrono::steady_clock::now(); +} + +LDLidarDriverLinuxInterface::~LDLidarDriverLinuxInterface() { + if (comm_pkg_ != nullptr) { + delete comm_pkg_; + } + + if (comm_serial_ != nullptr) { + delete comm_serial_; + } + + if (comm_tcp_network_ != nullptr) { + delete comm_tcp_network_; + } + + if (comm_udp_network_ != nullptr) { + delete comm_udp_network_; + } +} + +bool LDLidarDriverLinuxInterface::Connect(LDType product_name, + std::string serial_port_name, + uint32_t serial_baudrate, + CommunicationModeType comm_mode) { + + if (is_connect_flag_) { + return true; + } + + if (serial_port_name.empty()) { + LOG_ERROR("input is empty.",""); + return false; + } + + if (register_get_timestamp_handle_ == nullptr) { + LOG_ERROR("get timestamp fuctional is not register.",""); + return false; + } + + comm_pkg_->ClearDataProcessStatus(); + comm_pkg_->RegisterTimestampGetFunctional(register_get_timestamp_handle_); + comm_pkg_->SetProductType(product_name); + + if (COMM_SERIAL_MODE == comm_mode) { + comm_serial_->SetReadCallback(std::bind( + &LdLidarDataProcess::CommReadCallback, comm_pkg_, std::placeholders::_1, std::placeholders::_2)); + if (!comm_serial_->Open(serial_port_name, serial_baudrate)) { + LOG_ERROR("serial is not open:%s", serial_port_name.c_str()); + return false; + } + } else { + LOG_ERROR("input value is not \"ldlidar::COMM_SERIAL_MODE\"",""); + return false; + } + + is_connect_flag_ = true; + + SetLidarDriverStatus(true); + + return true; +} + +bool LDLidarDriverLinuxInterface::Connect(LDType product_name, + const char* server_ip, + const char* server_port, + CommunicationModeType comm_mode) { + + if (is_connect_flag_) { + return true; + } + + if (LDType::NO_VER == product_name) { + LOG_ERROR("input is abnormal.",""); + return false; + } + + if ((server_ip == nullptr) || (server_port == nullptr)) { + LOG_ERROR("input server_ip or server_port is null",""); + return false; + } + + if ((COMM_NO_NULL == comm_mode) || (COMM_SERIAL_MODE == comm_mode)) { + LOG_ERROR("input comm_mode param is error.",""); + return false; + } + + if (register_get_timestamp_handle_ == nullptr) { + LOG_ERROR("get timestamp fuctional is not register.",""); + return false; + } + + comm_pkg_->ClearDataProcessStatus(); + comm_pkg_->RegisterTimestampGetFunctional(register_get_timestamp_handle_); + comm_pkg_->SetProductType(product_name); + + switch (comm_mode) { + case COMM_TCP_CLIENT_MODE: { + comm_tcp_network_->SetRecvCallback(std::bind( + &LdLidarDataProcess::CommReadCallback, comm_pkg_, std::placeholders::_1, std::placeholders::_2)); + bool result = comm_tcp_network_->CreateSocket(TCP_CLIENT, server_ip, server_port); + if (!result) { + LOG_ERROR("client host: create socket is fail.",""); + return false; + } + LOG_INFO("client host: create socket is ok.",""); + } + break; + case COMM_TCP_SERVER_MODE: { + comm_tcp_network_->SetRecvCallback(std::bind( + &LdLidarDataProcess::CommReadCallback, comm_pkg_, std::placeholders::_1, std::placeholders::_2)); + bool result = comm_tcp_network_->CreateSocket(TCP_SERVER, server_ip, server_port); + if (!result) { + LOG_ERROR("server host: create socket is fail.",""); + return false; + } + LOG_INFO("server host: create socket is ok.",""); + } + break; + case COMM_UDP_CLIENT_MODE: { + comm_udp_network_->SetRecvCallback(std::bind( + &LdLidarDataProcess::CommReadCallback, comm_pkg_, std::placeholders::_1, std::placeholders::_2)); + bool result = comm_udp_network_->CreateSocket(UDP_CLIENT, server_ip, server_port); + if (!result) { + LOG_ERROR("client host: create socket is fail.",""); + return false; + } + // 主动向服务端发布消息使服务端保存客户端ip,port 信息,建立沟通渠道 + uint8_t trans_buf[4] = {0xa5, 0x5a, 0x00, 0x00}; + uint32_t tx_len; + if (!comm_udp_network_->TransToNet((uint8_t *)trans_buf, sizeof(trans_buf), &tx_len)) { + LOG_ERROR("client host: send request to server is fail. %s", strerror(errno)); + return false; + } + LOG_INFO("client host: create socket is ok.",""); + } + break; + case COMM_UDP_SERVER_MODE: { + comm_udp_network_->SetRecvCallback(std::bind( + &LdLidarDataProcess::CommReadCallback, comm_pkg_, std::placeholders::_1, std::placeholders::_2)); + bool result = comm_udp_network_->CreateSocket(UDP_SERVER, server_ip, server_port); + if (!result) { + LOG_ERROR("server host: create socket is fail.",""); + return false; + } + LOG_INFO("server host: create socket is ok.",""); + LOG_INFO("server host: wait client ack connect..",""); + while (!comm_udp_network_->IsClientAck()) { + usleep(1000); + } + } + break; + default: { + LOG_ERROR("input comm_mode param is error.",""); + return false; + } + break; + } + + is_connect_flag_ = true; + + SetLidarDriverStatus(true); + + return true; +} + +bool LDLidarDriverLinuxInterface::Disconnect(void) { + if (!is_connect_flag_) { + return true; + } + + SetLidarDriverStatus(false); + + comm_serial_->Close(); + comm_tcp_network_->CloseSocket(); + comm_udp_network_->CloseSocket(); + + is_connect_flag_ = false; + + return true; +} + +void LDLidarDriverLinuxInterface::EnablePointCloudDataFilter(bool is_enable) { + comm_pkg_->SetNoiseFilter(is_enable); +} + +bool LDLidarDriverLinuxInterface::WaitLidarComm(int64_t timeout) { + auto last_time = std::chrono::steady_clock::now(); + + bool is_recvflag = false; + do { + if (comm_pkg_->GetLidarPowerOnCommStatus()) { + is_recvflag = true; + } + usleep(1000); + } while (!is_recvflag && (std::chrono::duration_cast( + std::chrono::steady_clock::now() - last_time).count() < timeout)); + + if (is_recvflag) { + SetLidarDriverStatus(true); + return true; + } else { + SetLidarDriverStatus(false); + return false; + } +} + +LidarStatus LDLidarDriverLinuxInterface::GetLaserScanData(Points2D& dst, int64_t timeout) { + if (!is_start_flag_) { + return LidarStatus::STOP; + } + + LidarStatus status = comm_pkg_->GetLidarStatus(); + if (LidarStatus::NORMAL == status) { + if (comm_pkg_->GetLaserScanData(dst)) { + last_pubdata_times_ = std::chrono::steady_clock::now(); + return LidarStatus::NORMAL; + } + + if (std::chrono::duration_cast( + std::chrono::steady_clock::now() - last_pubdata_times_).count() > timeout) { + return LidarStatus::DATA_TIME_OUT; + } else { + return LidarStatus::DATA_WAIT; + } + } else { + last_pubdata_times_ = std::chrono::steady_clock::now(); + return status; + } +} + +LidarStatus LDLidarDriverLinuxInterface::GetLaserScanData(LaserScan& dst, int64_t timeout) { + if (!is_start_flag_) { + return LidarStatus::STOP; + } + + LidarStatus status = comm_pkg_->GetLidarStatus(); + if (LidarStatus::NORMAL == status) { + Points2D recvpcd; + if (comm_pkg_->GetLaserScanData(recvpcd)) { + last_pubdata_times_ = std::chrono::steady_clock::now(); + dst.stamp = recvpcd.front().stamp; + dst.points = recvpcd; + return LidarStatus::NORMAL; + } + + if (std::chrono::duration_cast( + std::chrono::steady_clock::now() - last_pubdata_times_).count() > timeout) { + return LidarStatus::DATA_TIME_OUT; + } else { + return LidarStatus::DATA_WAIT; + } + } else { + last_pubdata_times_ = std::chrono::steady_clock::now(); + return status; + } +} + +bool LDLidarDriverLinuxInterface::GetLidarScanFreq(double& spin_hz) { + if (!is_start_flag_) { + return false; + } + spin_hz = comm_pkg_->GetSpeed(); + return true; +} + +void LDLidarDriverLinuxInterface::RegisterGetTimestampFunctional(std::function get_timestamp_handle) { + register_get_timestamp_handle_ = get_timestamp_handle; +} + +uint8_t LDLidarDriverLinuxInterface::GetLidarErrorCode(void) { + if (!is_start_flag_) { + return LIDAR_NO_ERROR; + } + + uint8_t errcode = comm_pkg_->GetLidarErrorCode(); + return errcode; +} + +bool LDLidarDriverLinuxInterface::Start(void) { + if (is_start_flag_) { + return true; + } + + if (!is_connect_flag_) { + return false; + } + + is_start_flag_ = true; + + last_pubdata_times_ = std::chrono::steady_clock::now(); + + SetLidarDriverStatus(true); + + return true; +} + +bool LDLidarDriverLinuxInterface::Stop(void) { + if (!is_start_flag_) { + return true; + } + + SetLidarDriverStatus(false); + + is_start_flag_ = false; + + return true; +} + +} // namespace ldlidar +/********************* (C) COPYRIGHT SHENZHEN LDROBOT CO., LTD *******END OF + * FILE ********/ \ No newline at end of file diff --git a/sdk/src/ldlidar_driver_win.cpp b/sdk/src/ldlidar_driver_win.cpp new file mode 100644 index 0000000..6eabc02 --- /dev/null +++ b/sdk/src/ldlidar_driver_win.cpp @@ -0,0 +1,230 @@ +/** + * @file ldlidar_driver.cpp + * @author LDRobot (support@ldrobot.com) + * @brief ldlidar sdk interface + * This code is only applicable to LDROBOT LiDAR products + * sold by Shenzhen LDROBOT Co., LTD + * @version 0.1 + * @date 2023-03 + * + * @copyright Copyright (c) 2017-2023 SHENZHEN LDROBOT CO., LTD. All rights + * reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License in the file LICENSE + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "ldlidar_driver/ldlidar_driver_win.h" + +namespace ldlidar { + +LDLidarDriverWinInterface::LDLidarDriverWinInterface() : + comm_pkg_(new LdLidarDataProcess()), + comm_port_handle_(new SerialInterfaceWin()) { + + last_pubdata_times_ = std::chrono::steady_clock::now(); +} + +LDLidarDriverWinInterface::~LDLidarDriverWinInterface() { + if (comm_pkg_ != nullptr) { + delete comm_pkg_; + } + + if (comm_port_handle_ != nullptr) { + delete comm_port_handle_; + } +} + +bool LDLidarDriverWinInterface::Connect(LDType product_name, + std::string& serial_port_name, + PortParams& port_params) { + + if (is_connect_flag_) { + return true; + } + + if (serial_port_name.empty()) { + LOG_ERROR("input is empty.",""); + return false; + } + + if (register_get_timestamp_handle_ == nullptr) { + LOG_ERROR("get timestamp fuctional is not register.",""); + return false; + } + + comm_pkg_->ClearDataProcessStatus(); + comm_pkg_->RegisterTimestampGetFunctional(register_get_timestamp_handle_); + comm_pkg_->SetProductType(product_name); + + // Win32 + // 设置串口参数 + comm_port_handle_->setPortParams(port_params); + // 设置串口接收回调函数 + comm_port_handle_->setReadCallback(std::bind( + &LdLidarDataProcess::CommReadCallback, comm_pkg_, std::placeholders::_1, std::placeholders::_2)); + // 打开串口 + if (!comm_port_handle_->open(serial_port_name)) { + LOG_ERROR("serial is not open:%s", serial_port_name.c_str()); + return false; + } + + is_connect_flag_ = true; + + SetLidarDriverStatus(true); + + return true; +} + +bool LDLidarDriverWinInterface::Disconnect(void) { + if (!is_connect_flag_) { + return true; + } + + SetLidarDriverStatus(false); + + comm_port_handle_->close(); + + is_connect_flag_ = false; + + return true; +} + +void LDLidarDriverWinInterface::EnablePointCloudDataFilter(bool is_enable) { + comm_pkg_->SetNoiseFilter(is_enable); +} + +bool LDLidarDriverWinInterface::WaitLidarComm(int64_t timeout) { + if (!is_connect_flag_) { + return false; + } + + auto last_time = std::chrono::steady_clock::now(); + + bool is_recvflag = false; + do { + if (comm_pkg_->GetLidarPowerOnCommStatus()) { + is_recvflag = true; + } + Sleep(1); + } while (!is_recvflag && (std::chrono::duration_cast( + std::chrono::steady_clock::now() - last_time).count() < timeout)); + + if (is_recvflag) { + SetLidarDriverStatus(true); + return true; + } else { + SetLidarDriverStatus(false); + return false; + } +} + +LidarStatus LDLidarDriverWinInterface::GetLaserScanData(Points2D& dst, int64_t timeout) { + if (!is_start_flag_) { + return LidarStatus::STOP; + } + + LidarStatus status = comm_pkg_->GetLidarStatus(); + if (LidarStatus::NORMAL == status) { + if (comm_pkg_->GetLaserScanData(dst)) { + last_pubdata_times_ = std::chrono::steady_clock::now(); + return LidarStatus::NORMAL; + } + + if (std::chrono::duration_cast( + std::chrono::steady_clock::now() - last_pubdata_times_).count() > timeout) { + return LidarStatus::DATA_TIME_OUT; + } else { + return LidarStatus::DATA_WAIT; + } + } else { + last_pubdata_times_ = std::chrono::steady_clock::now(); + return status; + } +} + +LidarStatus LDLidarDriverWinInterface::GetLaserScanData(LaserScan& dst, int64_t timeout) { + if (!is_start_flag_) { + return LidarStatus::STOP; + } + + LidarStatus status = comm_pkg_->GetLidarStatus(); + if (LidarStatus::NORMAL == status) { + Points2D recvpcd; + if (comm_pkg_->GetLaserScanData(recvpcd)) { + last_pubdata_times_ = std::chrono::steady_clock::now(); + dst.stamp = recvpcd.front().stamp; + dst.points = recvpcd; + return LidarStatus::NORMAL; + } + + if (std::chrono::duration_cast( + std::chrono::steady_clock::now() - last_pubdata_times_).count() > timeout) { + return LidarStatus::DATA_TIME_OUT; + } else { + return LidarStatus::DATA_WAIT; + } + } else { + last_pubdata_times_ = std::chrono::steady_clock::now(); + return status; + } +} + +bool LDLidarDriverWinInterface::GetLidarScanFreq(double& spin_hz) { + if (!is_start_flag_) { + return false; + } + spin_hz = comm_pkg_->GetSpeed(); + return true; +} + +void LDLidarDriverWinInterface::RegisterGetTimestampFunctional(std::function get_timestamp_handle) { + register_get_timestamp_handle_ = get_timestamp_handle; +} + +uint8_t LDLidarDriverWinInterface::GetLidarErrorCode(void) { + if (!is_start_flag_) { + return LIDAR_NO_ERROR; + } + + uint8_t errcode = comm_pkg_->GetLidarErrorCode(); + return errcode; +} + +bool LDLidarDriverWinInterface::Start(void) { + if (is_start_flag_) { + return true; + } + + if (!is_connect_flag_) { + return false; + } + + is_start_flag_ = true; + + last_pubdata_times_ = std::chrono::steady_clock::now(); + + SetLidarDriverStatus(true); + + return true; +} + +bool LDLidarDriverWinInterface::Stop(void) { + if (!is_start_flag_) { + return true; + } + + SetLidarDriverStatus(false); + + is_start_flag_ = false; + + return true; +} + +} // namespace ldlidar +/********************* (C) COPYRIGHT SHENZHEN LDROBOT CO., LTD *******END OF + * FILE ********/ \ No newline at end of file diff --git a/sdk/src/ldlidar_protocol.cpp b/sdk/src/ldlidar_protocol.cpp new file mode 100644 index 0000000..ae786a8 --- /dev/null +++ b/sdk/src/ldlidar_protocol.cpp @@ -0,0 +1,189 @@ +/** +* @file ldlidar_protocol.cpp +* @author LDRobot (support@ldrobot.com) +* David Hu(hmd_hubei_cn@163.com) +* @brief +* @version 1.0 +* @date 2023.3.11 +* @note +* @copyright Copyright (c) 2017-2023 SHENZHEN LDROBOT CO., LTD. All rights reserved. +* Licensed under the MIT License (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License in the file LICENSE +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +**/ + +#include "ldlidar_driver/ldlidar_protocol.h" + +namespace ldlidar { + +// LD protocol +static const uint8_t CrcTable[256] = { + 0x00, 0x4d, 0x9a, 0xd7, 0x79, 0x34, 0xe3, 0xae, 0xf2, 0xbf, 0x68, 0x25, + 0x8b, 0xc6, 0x11, 0x5c, 0xa9, 0xe4, 0x33, 0x7e, 0xd0, 0x9d, 0x4a, 0x07, + 0x5b, 0x16, 0xc1, 0x8c, 0x22, 0x6f, 0xb8, 0xf5, 0x1f, 0x52, 0x85, 0xc8, + 0x66, 0x2b, 0xfc, 0xb1, 0xed, 0xa0, 0x77, 0x3a, 0x94, 0xd9, 0x0e, 0x43, + 0xb6, 0xfb, 0x2c, 0x61, 0xcf, 0x82, 0x55, 0x18, 0x44, 0x09, 0xde, 0x93, + 0x3d, 0x70, 0xa7, 0xea, 0x3e, 0x73, 0xa4, 0xe9, 0x47, 0x0a, 0xdd, 0x90, + 0xcc, 0x81, 0x56, 0x1b, 0xb5, 0xf8, 0x2f, 0x62, 0x97, 0xda, 0x0d, 0x40, + 0xee, 0xa3, 0x74, 0x39, 0x65, 0x28, 0xff, 0xb2, 0x1c, 0x51, 0x86, 0xcb, + 0x21, 0x6c, 0xbb, 0xf6, 0x58, 0x15, 0xc2, 0x8f, 0xd3, 0x9e, 0x49, 0x04, + 0xaa, 0xe7, 0x30, 0x7d, 0x88, 0xc5, 0x12, 0x5f, 0xf1, 0xbc, 0x6b, 0x26, + 0x7a, 0x37, 0xe0, 0xad, 0x03, 0x4e, 0x99, 0xd4, 0x7c, 0x31, 0xe6, 0xab, + 0x05, 0x48, 0x9f, 0xd2, 0x8e, 0xc3, 0x14, 0x59, 0xf7, 0xba, 0x6d, 0x20, + 0xd5, 0x98, 0x4f, 0x02, 0xac, 0xe1, 0x36, 0x7b, 0x27, 0x6a, 0xbd, 0xf0, + 0x5e, 0x13, 0xc4, 0x89, 0x63, 0x2e, 0xf9, 0xb4, 0x1a, 0x57, 0x80, 0xcd, + 0x91, 0xdc, 0x0b, 0x46, 0xe8, 0xa5, 0x72, 0x3f, 0xca, 0x87, 0x50, 0x1d, + 0xb3, 0xfe, 0x29, 0x64, 0x38, 0x75, 0xa2, 0xef, 0x41, 0x0c, 0xdb, 0x96, + 0x42, 0x0f, 0xd8, 0x95, 0x3b, 0x76, 0xa1, 0xec, 0xb0, 0xfd, 0x2a, 0x67, + 0xc9, 0x84, 0x53, 0x1e, 0xeb, 0xa6, 0x71, 0x3c, 0x92, 0xdf, 0x08, 0x45, + 0x19, 0x54, 0x83, 0xce, 0x60, 0x2d, 0xfa, 0xb7, 0x5d, 0x10, 0xc7, 0x8a, + 0x24, 0x69, 0xbe, 0xf3, 0xaf, 0xe2, 0x35, 0x78, 0xd6, 0x9b, 0x4c, 0x01, + 0xf4, 0xb9, 0x6e, 0x23, 0x8d, 0xc0, 0x17, 0x5a, 0x06, 0x4b, 0x9c, 0xd1, + 0x7f, 0x32, 0xe5, 0xa8}; + +uint8_t CalCRC8(const uint8_t *data, uint16_t data_len) { + uint8_t crc = 0; + while (data_len--) { + crc = CrcTable[(crc ^ *data) & 0xff]; + data++; + } + return crc; +} +// << LD protocol + +LdLidarProtocol::LdLidarProtocol() { + +} + +LdLidarProtocol::~LdLidarProtocol() { + +} + +uint8_t LdLidarProtocol::AnalysisDataPacket(uint8_t byte) { + static enum { + HEADER, + VER_LEN, + DATA, + DATA_HEALTH, + DATA_MANUFACTURE, + } state = HEADER; + static uint16_t count = 0; + static uint8_t tmp[128] = {0}; + static uint16_t pkg_count = sizeof(LiDARMeasureDataType); + static uint16_t pkghealth_count = sizeof(LiDARHealthInfoType); + static uint16_t pkgmanufac_count = sizeof(LiDARManufactureInfoType); + static bool check_healthinf_flag = false; + + switch (state) { + case HEADER: { + if (byte == PKG_HEADER) { + tmp[count++] = byte; + state = VER_LEN; + } else { + if (check_healthinf_flag) { + check_healthinf_flag = false; + } + } + break; + } + case VER_LEN: { + if (byte == DATA_PKG_INFO) { + tmp[count++] = byte; + state = DATA; + if (check_healthinf_flag) { + check_healthinf_flag = false; + return GET_PKG_HEALTH; + } + } else if (byte == HEALTH_PKG_INFO) { + tmp[count++] = byte; + state = DATA_HEALTH; + if (check_healthinf_flag) { + check_healthinf_flag = false; + return GET_PKG_HEALTH; + } + } else if (byte == MANUFACT_PKG_INF) { + tmp[count++] = byte; + state = DATA_MANUFACTURE; + } else { + state = HEADER; + count = 0; + if (check_healthinf_flag) { + check_healthinf_flag = false; + } + return GET_PKG_ERROR; + } + break; + } + case DATA: { + tmp[count++] = byte; + if (count >= pkg_count) { + memcpy((uint8_t *)&pcdpkg_data_, tmp, pkg_count); + uint8_t crc = CalCRC8((uint8_t *)&pcdpkg_data_, pkg_count - 1); + state = HEADER; + count = 0; + if (crc == pcdpkg_data_.crc8) { + return GET_PKG_PCD; + } else { + return GET_PKG_ERROR; + } + } + break; + } + case DATA_HEALTH: { + tmp[count++] = byte; + if (count >= pkghealth_count) { + memcpy((uint8_t *)&healthpkg_data_, tmp, pkghealth_count); + uint8_t crc = CalCRC8((uint8_t *)&healthpkg_data_, pkghealth_count - 1); + state = HEADER; + count = 0; + if (crc == healthpkg_data_.crc8) { + check_healthinf_flag = true; + } else { + check_healthinf_flag = false; + } + return GET_PKG_ERROR; + } + break; + } + case DATA_MANUFACTURE: { + tmp[count++] = byte; + if (count >= pkgmanufac_count) { + memcpy((uint8_t *)&manufacinfpkg_data_, tmp, pkgmanufac_count); + uint8_t crc = CalCRC8((uint8_t *)&manufacinfpkg_data_, pkgmanufac_count - 1); + state = HEADER; + count = 0; + if (crc == manufacinfpkg_data_.crc8) { + return GET_PKG_MANUFACT; + } else { + return GET_PKG_ERROR; + } + } + break; + } + default: { + break; + } + } + + return GET_PKG_ERROR; +} + +LiDARMeasureDataType& LdLidarProtocol::GetPCDPacketData(void) { + return pcdpkg_data_; +} + +LiDARHealthInfoType& LdLidarProtocol::GetHealthPacketData(void) { + return healthpkg_data_; +} + +LiDARManufactureInfoType& LdLidarProtocol::GetManufactureInfoPacketData(void) { + return manufacinfpkg_data_; +} + +} // namespace ldlidar +/********************* (C) COPYRIGHT SHENZHEN LDROBOT CO., LTD *******END OF FILE ********/ diff --git a/sdk/src/log_module.cpp b/sdk/src/log_module.cpp new file mode 100644 index 0000000..98fa5ae --- /dev/null +++ b/sdk/src/log_module.cpp @@ -0,0 +1,351 @@ +/** +* @file log_module.cpp +* @author David Hu (hmd_hubei_cn@163.com) +* @brief +* @version 0.1 +* @date 2022.08.10 +* @note +* @copyright Copyright (c) 2022 DAVID HU All rights reserved. +* Licensed under the MIT License (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License in the file LICENSE +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +**/ +#include "ldlidar_driver/log_module.h" + +#include +#include +#include + +#ifndef __linux__ +#include +#pragma comment(lib, "comsuppw.lib") +#else +#include +#endif + +/* 使用vswprintf会出现奔溃的情况如果,传入数据大于 VA_PARAMETER_MAX 就会出现崩溃 */ +#define VA_PARAMETER_MAX (1024 * 2) + +LogModule* LogModule::s_plog_module_ = NULL; + + +LogModule* LogModule::GetInstance(__in const char* filename, __in const char* funcname, __in int lineno,LogLevel level,ILogRealization* plog) { + if (s_plog_module_ == NULL) { + s_plog_module_ = new LogModule(); + } + s_plog_module_->logInfo_.str_filename = filename; + s_plog_module_->logInfo_.str_funcname = funcname; + s_plog_module_->logInfo_.n_linenumber = lineno; + s_plog_module_->logInfo_.loglevel = level; + + if (plog != NULL) { + s_plog_module_->p_realization_->free(); + s_plog_module_->p_realization_ = plog; + } + return s_plog_module_; +} + +LogModule* LogModule::GetInstance(LogLevel level, ILogRealization* plog) { + if (s_plog_module_ == NULL) { + s_plog_module_ = new LogModule(); + } + s_plog_module_->logInfo_.loglevel = level; + + if (plog != NULL) { + s_plog_module_->p_realization_->free(); + s_plog_module_->p_realization_ = plog; + } + return s_plog_module_; +} + +LogModule* LogModule::GetInstancePrintOriginData(LogLevel level, ILogRealization* plog) { + if (s_plog_module_ == NULL) { + s_plog_module_ = new LogModule(); + } + s_plog_module_->logInfo_.loglevel = level; + + if (plog != NULL) { + s_plog_module_->p_realization_->free(); + s_plog_module_->p_realization_ = plog; + } + return s_plog_module_; +} + +LogModule::LogModule() { + logInfo_.n_linenumber = -1; + logInfo_.str_filename = ""; + logInfo_.str_funcname = ""; +#ifndef __linux__ + p_realization_ = new LogOutputString(); +#else + p_realization_ = new LogPrint(); +#endif + InitLock(); +} + +LogModule::~LogModule() { + RealseLock(); +} + +void LogModule::LogPrintInf(const char* format,...) { + Lock(); + if (p_realization_) { + std::string str_temp; + str_temp.append("[LOG]"); + // Time [week month day hours:minutes:seconds year] + str_temp.append(GetFormatValue(GetCurrentISOTime())); + // Stamp uint is ns + str_temp.append(GetCurrentLocalTimeStamp()); + // LogLevel + str_temp.append(GetLevelValue(logInfo_.loglevel)); + // File name + str_temp.append(GetFormatValue(logInfo_.str_filename)); + // Function name + str_temp.append(GetFormatValue(logInfo_.str_funcname)); + // Line number + str_temp.append(GetFormatValue(logInfo_.n_linenumber)); + + va_list ptr; + va_start(ptr, format); + char c_value[VA_PARAMETER_MAX] = {0}; + vsnprintf(c_value,sizeof(c_value),format,ptr); + va_end(ptr); + + str_temp.append(GetFormatValue(c_value)); + + p_realization_->LogPrintInf(str_temp.c_str()); + } + UnLock(); +} + +void LogModule::LogPrintNoLocationInf(const char* format,...) { + Lock(); + if (p_realization_) { + std::string str_temp; + // manufacture + str_temp.append("[LOG]"); + // time [week month day hours:minutes:seconds year] + str_temp.append(GetFormatValue(GetCurrentISOTime())); + // stamp uint is ns + str_temp.append(GetCurrentLocalTimeStamp()); + //LogLevel + str_temp.append(GetLevelValue(logInfo_.loglevel)); + + va_list ptr; + va_start(ptr, format); + char c_value[VA_PARAMETER_MAX] = {0}; + vsnprintf(c_value,sizeof(c_value),format,ptr); + va_end(ptr); + + str_temp.append(GetFormatValue(c_value)); + + p_realization_->LogPrintInf(str_temp.c_str()); + } + UnLock(); +} + +void LogModule::LogPrintOriginData(const char* format,...) { + Lock(); + if (p_realization_) { + std::string str_temp; + + switch (logInfo_.loglevel) { + case INFO_LEVEL: { + str_temp.append("[LOG]"); + str_temp.append(GetFormatValue(GetCurrentISOTime())); + str_temp.append(GetCurrentLocalTimeStamp()); + str_temp.append(GetLevelValue(logInfo_.loglevel)); + break; + } + default: { + break; + } + } + + va_list ptr; + va_start(ptr, format); + char c_value[VA_PARAMETER_MAX] = {0}; + vsnprintf(c_value,sizeof(c_value),format,ptr); + va_end(ptr); + + str_temp.append(c_value); + + p_realization_->LogPrintData(str_temp.c_str()); + } + UnLock(); +} + +void LogModule::InitLock() { +#ifndef __linux__ + InitializeCriticalSection(&mutex_lock_); +#else + pthread_mutex_init(&mutex_lock_,NULL); +#endif +} + +void LogModule::RealseLock() { +#ifndef __linux__ + DeleteCriticalSection(&mutex_lock_); +#else + pthread_mutex_unlock(&mutex_lock_); +#endif +} + +void LogModule::Lock() { +#ifndef __linux__ + EnterCriticalSection(&mutex_lock_); +#else + pthread_mutex_lock(&mutex_lock_); +#endif +} + +void LogModule::UnLock() { +#ifndef __linux__ + LeaveCriticalSection(&mutex_lock_); +#else + pthread_mutex_unlock(&mutex_lock_); +#endif +} + +std::string LogModule::GetCurrentISOTime() { + std::string curr_time; +#if 0 + //Current date/time based on current time + time_t now = time(0); + // Convert current time to string + curr_time.assign(ctime(&now)); + // Last charactor of currentTime is "\n", so remove it + std::string current_time = curr_time.substr(0, curr_time.size()-1); + return current_time; +#else + char stdtime_str[50] = {0}; + time_t std_time = 0; + struct tm* local_time = NULL; + std_time = time(NULL); + local_time = localtime(&std_time); + snprintf(stdtime_str, 50, "%d-%d-%d,%d:%d:%d", + local_time->tm_year+1900, local_time->tm_mon+1, local_time->tm_mday, + local_time->tm_hour, local_time->tm_min, local_time->tm_sec); + curr_time.assign(stdtime_str); + return curr_time; +#endif +} + +std::string LogModule::GetCurrentLocalTimeStamp() { + std::string stamp_str; + std::chrono::time_point tp = + std::chrono::time_point_cast(std::chrono::system_clock::now()); + auto tmp = std::chrono::duration_cast(tp.time_since_epoch()); + uint64_t timestamp = (uint64_t)tmp.count(); + char s_stamp[100] = {0}; +#ifdef __LP64__ + snprintf(s_stamp, 100, "[%lu.%lu]", (timestamp/1000000000), (timestamp%1000000000)); +#else +#ifdef _WIN64 + snprintf(s_stamp, 100, "[%lu.%lu]", (timestamp/1000000000), (timestamp%1000000000)); +#else + snprintf(s_stamp, 100, "[%llu.%llu]", (timestamp/1000000000), (timestamp%1000000000)); +#endif +#endif + stamp_str = s_stamp; + + return stamp_str; +} + +std::string LogModule::GetFormatValue(std::string str_value) { + std::string str_temp; + str_temp.append("["); + str_temp.append(str_value); + str_temp.append("]"); + return str_temp; +} + +std::string LogModule::GetFormatValue(int n_value) { + std::string str_temp; + str_temp.append("["); + char c_value[16]; + sprintf(c_value,"%d",n_value); + str_temp.append(c_value); + str_temp.append("]"); + return str_temp; +} + +std::string LogModule::GetLevelValue(int level){ + std::string tmp; + switch (level) { + case DEBUG_LEVEL: + tmp = "DEBUG"; + break; + case WARNING_LEVEL: + tmp = "WARN"; + break; + case ERROR_LEVEL: + tmp = "ERROR"; + break; + case INFO_LEVEL: + tmp = "INFO"; + break; + default: + tmp = "UnKnown"; + break; + } + std::string str_temp; + str_temp.append("["); + str_temp.append(tmp); + str_temp.append("]"); + return str_temp; +} + +void LogPrint::Initializion(const char* path) { + printf("%s", path); + return ; +} + +void LogPrint::free(ILogRealization *plogger) { + LogPrint* pOutput = static_cast(plogger); + if (pOutput != NULL) { + delete pOutput; + } +} + +void LogPrint::LogPrintInf(const char* str) { +#ifdef ENABLE_CONSOLE_LOG_DISPALY + printf("%s\r\n", str); +#endif + +#ifdef ENABLE_LOG_WRITE_TO_FILE + std::string log_filename = GetLogFilePathName(); + FILE *fp = fopen(log_filename.c_str() ,"a"); + if(!fp) { + printf("%s open filed!\n", log_filename.c_str()); + return ; + } + printf_s(fp,str); + printf_s(fp,"\r\n"); + fclose(fp); +#endif +} + +void LogPrint::LogPrintData(const char* str) { +#ifdef ENABLE_CONSOLE_LOG_DISPALY + printf("%s", str); +#endif + +#ifdef ENABLE_LOG_WRITE_TO_FILE + std::string log_filename = GetOriginDataFilePathName(); + FILE *fp = fopen(log_filename.c_str() ,"a"); + if(!fp) { + printf("%s open filed!\n", log_filename.c_str()); + return ; + } + printf_s(fp,str); + fclose(fp); +#endif +} + +/********************* (C) COPYRIGHT SHENZHEN LDROBOT CO., LTD *******END OF FILE ********/ \ No newline at end of file diff --git a/sdk/src/network_socket_interface_linux.cpp b/sdk/src/network_socket_interface_linux.cpp new file mode 100644 index 0000000..5eb138d --- /dev/null +++ b/sdk/src/network_socket_interface_linux.cpp @@ -0,0 +1,458 @@ +/** + * @file network_socket_interface_linux.cpp + * @author LDRobot (support@ldrobot.com) + * @brief linux network App + * @version 0.1 + * @date 2022-09-05 + * + * @copyright Copyright (c) 2017-2023 SHENZHEN LDROBOT CO., LTD. All rights + * reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License in the file LICENSE + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ldlidar_driver/network_socket_interface_linux.h" +#include "ldlidar_driver/log_module.h" + +#define MAX_RECV_BUF_LEN 4096 + +namespace ldlidar { + +UDPSocketInterfaceLinux::UDPSocketInterfaceLinux() : recv_thread_(nullptr), + recv_count_(0), + com_sockfd_(-1), + ncd_(NET_NULL), + is_cmd_created_(false), + recv_thread_exit_flag_(true), + is_server_recv_ack_flag_(false), + recv_callback_(nullptr) { + +} + +UDPSocketInterfaceLinux::~UDPSocketInterfaceLinux() { + CloseSocket(); +} + +bool UDPSocketInterfaceLinux::CreateSocket(NetCommDevTypeDef obj, const char *ip, const char *port) { + + if (is_cmd_created_ == true) { + return true; + } + + if ((ip == nullptr) || (port == nullptr)) { + LOG_ERROR("UDP,input ip address or port number is null",""); + return false; + } + + if ((UDP_CLIENT == obj) || (UDP_SERVER == obj)) { + com_sockfd_ = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if(com_sockfd_ == -1) { + LOG_DEBUG("UDP,fail to socket. %s", strerror(errno)); + return false; + } + } else { + LOG_ERROR("UDP,input value is error",""); + return false; + } + + struct sockaddr_in net_addr; + int addrlen = sizeof(net_addr); + bzero(&net_addr, sizeof(net_addr)); + net_addr.sin_family = AF_INET; + net_addr.sin_addr.s_addr = inet_addr(ip); + net_addr.sin_port = htons((short)atoi(port)); + + if(obj == UDP_SERVER){ + if(-1 == bind(com_sockfd_, (const struct sockaddr*)&net_addr, (socklen_t)addrlen)) { + LOG_ERROR("UDP,fail to bind. %s", strerror(errno)); + if (com_sockfd_ != -1) { + close(com_sockfd_); + com_sockfd_ = -1; + } + return false; + } + LOG_INFO_LITE("UDP,bind success..",""); + } + + ncd_ = obj; + server_ip_ = ip; + server_port_ = port; + + // create recv process thread. + recv_thread_exit_flag_ = false; + recv_thread_ = new std::thread(RecvThreadProc, this); + is_cmd_created_ = true; + + return true; +} + +bool UDPSocketInterfaceLinux::CloseSocket() { + if (is_cmd_created_ == false) { + return true; + } + + recv_thread_exit_flag_ = true; + + if (com_sockfd_ != -1) { + close(com_sockfd_); + com_sockfd_ = -1; + } + + if ((recv_thread_ != nullptr) && recv_thread_->joinable()) { + recv_thread_->join(); + delete recv_thread_; + recv_thread_ = nullptr; + } + + is_cmd_created_ = false; + + return true; +} + +bool UDPSocketInterfaceLinux::RecvFromNet(uint8_t *rx_buf , uint32_t rx_buff_len, uint32_t *rx_len) { + static timespec timeout = {0, (long)(100 * 1e6)}; + int32_t len = -1; + struct sockaddr_in sender_addr_inf; + bzero(&sender_addr_inf, sizeof(sender_addr_inf)); + int addrlen = sizeof(struct sockaddr_in); + + if (IsCreated()) { + //// setting and init fd table + fd_set recv_fds; + FD_ZERO(&recv_fds); + FD_SET(com_sockfd_, &recv_fds); + int ret = pselect(com_sockfd_ + 1, &recv_fds, NULL, NULL, &timeout, NULL); + if (ret < 0) { + // select was interrputed + if (errno == EINTR) { + return false; + } + } else if (ret == 0) { + // timeout + return false; + } + + if (FD_ISSET(com_sockfd_, &recv_fds)) { + len = (int32_t)recvfrom(com_sockfd_, rx_buf, rx_buff_len, 0, + (struct sockaddr *)&sender_addr_inf, (socklen_t *)&addrlen); + if ((len != -1) && (rx_len != nullptr)) { + *rx_len = len; + + std::string sender_ip = inet_ntoa(sender_addr_inf.sin_addr); + uint16_t sender_port = ntohs(sender_addr_inf.sin_port); + char sender_port_str[10] = {0}; + snprintf(sender_port_str, 10, "%d", sender_port); + + if (ncd_ == UDP_SERVER) { + if (!is_server_recv_ack_flag_.load()) { + ///// 保存与服务端通信的客户端IP和端口号,仅支持一对一 + client_ip_ = sender_ip; + client_port_ = sender_port_str; + is_server_recv_ack_flag_.store(true); + } + } + + // printf("Recv lens:%d\n", len); + // printf("Sender IP:%s\n", sender_ip.c_str()); + // printf("Sender port:%s\n", sender_port_str); + } + } + } + + return ((len == -1) ? false : true); +} + +bool UDPSocketInterfaceLinux::TransToNet(uint8_t *tx_buf, uint32_t tx_buff_len, uint32_t *tx_len) { + int32_t len = -1; + struct sockaddr_in recver_net_addr; + int addrlen = sizeof(recver_net_addr); + bzero(&recver_net_addr, sizeof(recver_net_addr)); + recver_net_addr.sin_family = AF_INET; + + if (IsCreated()) { + if (ncd_ == UDP_CLIENT) { + recver_net_addr.sin_addr.s_addr = inet_addr(server_ip_.c_str()); + recver_net_addr.sin_port = htons((short)atoi(server_port_.c_str())); + } else if (ncd_ == UDP_SERVER) { + if (!is_server_recv_ack_flag_.load()) { + return false; + } else { + recver_net_addr.sin_addr.s_addr = inet_addr(client_ip_.c_str()); + recver_net_addr.sin_port = htons((short)atoi(client_port_.c_str())); + } + } + + len = (int32_t)sendto(com_sockfd_, tx_buf, tx_buff_len, 0, + (struct sockaddr *)&recver_net_addr, (socklen_t)addrlen); + if ((len != -1) && (tx_len != nullptr)) { + *tx_len = len; + // printf("send lens:%d\n", len); + // printf("Reciver IP:%s\n", inet_ntoa(recver_net_addr.sin_addr)); + // printf("Reciver port:%d\n", ntohs(recver_net_addr.sin_port)); + } + } + + return ((len == -1) ? false : true); +} + +void UDPSocketInterfaceLinux::SetRecvCallback(std::function callback) { + recv_callback_ = callback; +} + +void UDPSocketInterfaceLinux::RecvThreadProc(void *param) { + UDPSocketInterfaceLinux *cmd_if = (UDPSocketInterfaceLinux *)param; + char *recieve_buff = new char[MAX_RECV_BUF_LEN + 1]; + + while (!cmd_if->recv_thread_exit_flag_.load()) { + uint32_t recved = 0; + bool res = cmd_if->RecvFromNet((uint8_t *)recieve_buff, MAX_RECV_BUF_LEN, &recved); + if (res && recved) { + cmd_if->recv_count_ += recved; + if (cmd_if->recv_callback_ != nullptr) { + cmd_if->recv_callback_(recieve_buff, recved); + } + } + } + + delete[] recieve_buff; +} + + +TCPSocketInterfaceLinux::TCPSocketInterfaceLinux() : recv_thread_(nullptr), + recv_count_(0), + com_sockfd_(-1), + listend_client_sockfd_(-1), + is_cmd_created_(false), + recv_thread_exit_flag_(false), + recv_callback_(nullptr) { + +} + +TCPSocketInterfaceLinux::~TCPSocketInterfaceLinux() { + CloseSocket(); +} + +bool TCPSocketInterfaceLinux::CreateSocket(NetCommDevTypeDef obj, const char *ip, const char *port) { + if (is_cmd_created_ == true) { + return true; + } + + if ((ip == nullptr) || (port == nullptr)) { + LOG_ERROR("TCP,input ip address or port number is null",""); + return false; + } + + if ((TCP_CLIENT == obj) || (TCP_SERVER == obj)) { + com_sockfd_ = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if(com_sockfd_ == -1) { + LOG_DEBUG("TCP,fail to socket. %s", strerror(errno)); + return false; + } + } else { + LOG_ERROR("TCP,input value is error",""); + return false; + } + + struct sockaddr_in net_addr; + int addrlen = sizeof(net_addr); + bzero(&net_addr, sizeof(net_addr)); + net_addr.sin_family = AF_INET; + net_addr.sin_addr.s_addr = inet_addr(ip); + net_addr.sin_port = htons((short)atoi(port)); + + ncd_ = obj; + + if (TCP_SERVER == obj) { + // bind sockfd + if (-1 == bind(com_sockfd_,(const struct sockaddr*)&net_addr,(socklen_t)addrlen)) { + LOG_ERROR("TCP,fail to bind. %s", strerror(errno)); + if (com_sockfd_ != -1) { + close(com_sockfd_); + com_sockfd_ = -1; + } + return false; + } + LOG_INFO_LITE("TCP,bind success..",""); + + // sockfd in listen + if (-1 == listen(com_sockfd_, 1)) { + LOG_ERROR("TCP,fail to listen. %s", strerror(errno)); + if (com_sockfd_ != -1) { + close(com_sockfd_); + com_sockfd_ = -1; + } + return false; + } + LOG_INFO_LITE("TCP,wait client connect.",""); + + // wait client request. one by one + struct sockaddr_in client_addr; + int addrlens = sizeof(client_addr); + bzero(&client_addr, sizeof(client_addr)); + + if (-1 == (listend_client_sockfd_ = accept(com_sockfd_, (struct sockaddr*)&client_addr, (socklen_t*)&addrlens))) { + LOG_ERROR("TCP,fail to accept. %s", strerror(errno)); + if (com_sockfd_ != -1) { + close(com_sockfd_); + com_sockfd_ = -1; + } + return false; + } + LOG_INFO_LITE("TCP,connect_client_fd: %d", listend_client_sockfd_); + LOG_INFO_LITE("TCP,client port: %d", ntohs(client_addr.sin_port)); + LOG_INFO_LITE("TCP,client ip: %s", inet_ntoa(client_addr.sin_addr)); + + } else if (TCP_CLIENT == obj) { + // connect to server + if (-1 == connect(com_sockfd_, (const struct sockaddr*)&net_addr, (socklen_t)addrlen)) { + LOG_ERROR("TCP,connect server error. %s", strerror(errno)); + if (com_sockfd_ != -1) { + close(com_sockfd_); + com_sockfd_ = -1; + } + return false; + } + LOG_INFO_LITE("TCP,connect server success.",""); + } + + // create recv process thread. + recv_thread_exit_flag_ = false; + recv_thread_ = new std::thread(RecvThreadProc, this); + is_cmd_created_ = true; + + return true; +} + +bool TCPSocketInterfaceLinux::CloseSocket() { + if (is_cmd_created_ == false) { + return true; + } + + recv_thread_exit_flag_ = true; + + if (com_sockfd_ != -1) { + close(com_sockfd_); + com_sockfd_ = -1; + } + + if (listend_client_sockfd_ != -1) { + close(listend_client_sockfd_); + listend_client_sockfd_ = -1; + } + + if ((recv_thread_ != nullptr) && recv_thread_->joinable()) { + recv_thread_->join(); + delete recv_thread_; + recv_thread_ = nullptr; + } + + is_cmd_created_ = false; + + return true; +} + +bool TCPSocketInterfaceLinux::RecvFromNet(uint8_t *rx_buf , uint32_t rx_buff_len, uint32_t *rx_len) { + static timespec timeout = {0, (long)(100 * 1e6)}; + int32_t len = -1; + + if (IsCreated()) { + if (TCP_CLIENT == ncd_) { + //// io multiplexing + fd_set recv_fds; + FD_ZERO(&recv_fds); + FD_SET(com_sockfd_, &recv_fds); + int ret = pselect(com_sockfd_ + 1, &recv_fds, NULL, NULL, &timeout, NULL); + if (ret < 0) { + // select was interrputed + if (errno == EINTR) { + return false; + } + } else if (ret == 0) { + // timeout + return false; + } + + if (FD_ISSET(com_sockfd_, &recv_fds)) { + len = (int32_t)recv(com_sockfd_, rx_buf, rx_buff_len, 0); + if ((len != -1) && (rx_len != nullptr)) { + *rx_len = len; + } + } + } else if (TCP_SERVER == ncd_) { + fd_set recv_fds; + FD_ZERO(&recv_fds); + FD_SET(listend_client_sockfd_, &recv_fds); + int ret = pselect(listend_client_sockfd_ + 1, &recv_fds, NULL, NULL, &timeout, NULL); + if (ret < 0) { + // select was interrputed + if (errno == EINTR) { + return false; + } + } else if (ret == 0) { + // timeout + return false; + } + + if (FD_ISSET(listend_client_sockfd_, &recv_fds)) { + len = (int32_t)recv(listend_client_sockfd_, rx_buf, rx_buff_len, 0); + if ((len != -1) && (rx_len != nullptr)) { + *rx_len = len; + } + } + } + } + + return ((len == -1) ? false : true); +} + +bool TCPSocketInterfaceLinux::TransToNet(uint8_t *tx_buf, uint32_t tx_buff_len, uint32_t *tx_len) { + int32_t len = 1; + + if (IsCreated()) { + if (TCP_CLIENT == ncd_) { + len = (int32_t)send(com_sockfd_, tx_buf, tx_buff_len, 0); + if ((len != -1) && (tx_len == nullptr)) { + *tx_len = len; + } + } else if (TCP_SERVER == ncd_) { + len = (int32_t)send(listend_client_sockfd_, tx_buf, tx_buff_len, 0); + if ((len != -1) && (tx_len != nullptr)) { + *tx_len = len; + } + } + } + + return ((len == -1) ? false : true); +} + +void TCPSocketInterfaceLinux::SetRecvCallback(std::function callback) { + recv_callback_ = callback; +} + +void TCPSocketInterfaceLinux::RecvThreadProc(void *param) { + TCPSocketInterfaceLinux *cmd_if = (TCPSocketInterfaceLinux *)param; + char *recieve_buff = new char[MAX_RECV_BUF_LEN + 1]; + + while (!cmd_if->recv_thread_exit_flag_.load()) { + uint32_t recved = 0; + bool res = cmd_if->RecvFromNet((uint8_t *)recieve_buff, MAX_RECV_BUF_LEN, &recved); + if (res && recved) { + cmd_if->recv_count_ += recved; + if (cmd_if->recv_callback_ != nullptr) { + cmd_if->recv_callback_(recieve_buff, recved); + } + } + } + + delete[] recieve_buff; +} + +} // namespace ldlidar +/********************* (C) COPYRIGHT SHENZHEN LDROBOT CO., LTD *******END OF + * FILE ********/ \ No newline at end of file diff --git a/sdk/src/serial_interface_linux.cpp b/sdk/src/serial_interface_linux.cpp new file mode 100644 index 0000000..dbbadcf --- /dev/null +++ b/sdk/src/serial_interface_linux.cpp @@ -0,0 +1,184 @@ +/** + * @file cmd_interface_linux.cpp + * @author LDRobot (support@ldrobot.com) + * @brief linux serial port App + * @version 0.1 + * @date 2021-10-28 + * + * @copyright Copyright (c) 2017-2023 SHENZHEN LDROBOT CO., LTD. All rights + * reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License in the file LICENSE + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ldlidar_driver/serial_interface_linux.h" +#include "ldlidar_driver/log_module.h" + + +#define MAX_ACK_BUF_LEN 4096 + +namespace ldlidar { + +SerialInterfaceLinux::SerialInterfaceLinux() + : rx_thread_(nullptr), rx_count_(0), read_callback_(nullptr) { + com_handle_ = -1; + com_baudrate_ = 0; +} + +SerialInterfaceLinux::~SerialInterfaceLinux() { Close(); } + +bool SerialInterfaceLinux::Open(std::string &port_name, uint32_t com_baudrate) { + int flags = (O_RDWR | O_NOCTTY | O_NONBLOCK); + + com_handle_ = open(port_name.c_str(), flags); + if (-1 == com_handle_) { + LOG_ERROR("Open open error,%s", strerror(errno)); + return false; + } + + com_baudrate_ = com_baudrate; + + struct asmtermios::termios2 options; + if (ioctl(com_handle_, _IOC(_IOC_READ, 'T', 0x2A, sizeof(struct asmtermios::termios2)), &options)) { + LOG_ERROR("TCGETS2 first error,%s", strerror(errno)); + if (com_handle_ != -1) { + close(com_handle_); + com_handle_ = -1; + } + return false; + } + + options.c_cflag |= (tcflag_t)(CLOCAL | CREAD | CS8); + options.c_cflag &= (tcflag_t) ~(CSTOPB | PARENB); + options.c_lflag &= (tcflag_t) ~(ICANON | ECHO | ECHOE | ECHOK | ECHONL | + ISIG | IEXTEN); //|ECHOPRT + options.c_oflag &= (tcflag_t) ~(OPOST); + options.c_iflag &= (tcflag_t) ~(IXON | IXOFF | INLCR | IGNCR | ICRNL | IGNBRK); + + options.c_cflag &= ~CBAUD; + options.c_cflag |= BOTHER; + options.c_ispeed = this->com_baudrate_; + options.c_ospeed = this->com_baudrate_; + + options.c_cc[VMIN] = 0; + options.c_cc[VTIME] = 0; + + if (ioctl(com_handle_, _IOC(_IOC_WRITE, 'T', 0x2B, sizeof(struct asmtermios::termios2)), &options)) { + LOG_ERROR("TCSETS2 error,%s", strerror(errno)); + if (com_handle_ != -1) { + close(com_handle_); + com_handle_ = -1; + } + return false; + } + + if (ioctl(com_handle_, _IOC(_IOC_READ, 'T', 0x2A, sizeof(struct asmtermios::termios2)), &options)) { + LOG_ERROR("TCGETS2 second error,%s", strerror(errno)); + if (com_handle_ != -1) { + close(com_handle_); + com_handle_ = -1; + } + return false; + } + + tcflush(com_handle_, TCIFLUSH); + + rx_thread_exit_flag_ = false; + rx_thread_ = new std::thread(RxThreadProc, this); + is_cmd_opened_ = true; + + return true; +} + +bool SerialInterfaceLinux::Close() { + if (is_cmd_opened_ == false) { + return true; + } + + rx_thread_exit_flag_ = true; + + if (com_handle_ != -1) { + close(com_handle_); + com_handle_ = -1; + } + + if ((rx_thread_ != nullptr) && rx_thread_->joinable()) { + rx_thread_->join(); + delete rx_thread_; + rx_thread_ = nullptr; + } + + is_cmd_opened_ = false; + + return true; +} + +bool SerialInterfaceLinux::ReadFromIO(uint8_t *rx_buf, uint32_t rx_buf_len, + uint32_t *rx_len) { + static timespec timeout = {0, (long)(100 * 1e6)}; + int32_t len = -1; + + if (IsOpened()) { + fd_set read_fds; + FD_ZERO(&read_fds); + FD_SET(com_handle_, &read_fds); + int r = pselect(com_handle_ + 1, &read_fds, NULL, NULL, &timeout, NULL); + if (r < 0) { + // Select was interrupted + if (errno == EINTR) { + return false; + } + } else if (r == 0) { // timeout + return false; + } + + if (FD_ISSET(com_handle_, &read_fds)) { + len = (int32_t)read(com_handle_, rx_buf, rx_buf_len); + if ((len != -1) && rx_len) { + *rx_len = len; + } + } + } + return len == -1 ? false : true; +} + +bool SerialInterfaceLinux::WriteToIo(const uint8_t *tx_buf, uint32_t tx_buf_len, + uint32_t *tx_len) { + int32_t len = -1; + + if (IsOpened()) { + len = (int32_t)write(com_handle_, tx_buf, tx_buf_len); + if ((len != -1) && tx_len) { + *tx_len = len; + } + } + return len == -1 ? false : true; +} + +void SerialInterfaceLinux::RxThreadProc(void *param) { + SerialInterfaceLinux *cmd_if = (SerialInterfaceLinux *)param; + char *rx_buf = new char[MAX_ACK_BUF_LEN + 1]; + while (!cmd_if->rx_thread_exit_flag_.load()) { + uint32_t readed = 0; + bool res = cmd_if->ReadFromIO((uint8_t *)rx_buf, MAX_ACK_BUF_LEN, &readed); + if (res && readed) { + cmd_if->rx_count_ += readed; + if (cmd_if->read_callback_ != nullptr) { + cmd_if->read_callback_(rx_buf, readed); + } + } + } + + delete[] rx_buf; +} + +} // namespace ldlidar + +/********************* (C) COPYRIGHT SHENZHEN LDROBOT CO., LTD *******END OF + * FILE ********/ \ No newline at end of file diff --git a/sdk/src/serial_interface_win.cpp b/sdk/src/serial_interface_win.cpp new file mode 100644 index 0000000..c5a52c8 --- /dev/null +++ b/sdk/src/serial_interface_win.cpp @@ -0,0 +1,326 @@ +/** + * @file serial_interface_win.cpp + * @author LDRobot (support@ldrobot.com) + * @brief Win32 serial port App + * @version 0.1 + * @date 2021-10-28 + * + * @copyright Copyright (c) 2017-2023 SHENZHEN LDROBOT CO., LTD. All rights + * reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License in the file LICENSE + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ldlidar_driver/serial_interface_win.h" +#include "ldlidar_driver/log_module.h" + +#include +#include +#include + +#include +#include +#include +#include + +#pragma comment(lib, "SetupAPI.lib") +#pragma comment(lib, "Cfgmgr32.lib") + +using namespace std; + +SerialInterfaceWin::SerialInterfaceWin() + : mIsOpened(false), + mByteToRead(4096), + mRxCounter(0), + mTxCounter(0), + commErrorHandle(nullptr), + readCallback(nullptr), + mRxThread(nullptr), + mMaxBuffSize(4096) { + mComHandle = INVALID_HANDLE_VALUE; + memset(&mOverlappedSend, 0, sizeof(OVERLAPPED)); + memset(&mOverlappedRecv, 0, sizeof(OVERLAPPED)); + mOverlappedSend.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + mOverlappedRecv.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); +} + +SerialInterfaceWin::~SerialInterfaceWin() { + if (mOverlappedRecv.hEvent != INVALID_HANDLE_VALUE) { + CloseHandle(mOverlappedRecv.hEvent); + mOverlappedRecv.hEvent = INVALID_HANDLE_VALUE; + } + if (mOverlappedSend.hEvent != INVALID_HANDLE_VALUE) { + CloseHandle(mOverlappedSend.hEvent); + mOverlappedSend.hEvent = INVALID_HANDLE_VALUE; + } +} + +std::string wcharToChar(const wchar_t *wp, UINT encode = CP_ACP) { + std::string str; + int len = + WideCharToMultiByte(encode, 0, wp, (int)wcslen(wp), NULL, 0, NULL, NULL); + char *m_char = new char[len + 1]; + WideCharToMultiByte(encode, 0, wp, (int)wcslen(wp), m_char, len, NULL, NULL); + m_char[len] = '\0'; + str = m_char; + delete m_char; + return str; +} + +bool SerialInterfaceWin::availablePorts(vector &availabelPortInfo) { + DWORD dwGuids = 0; + TCHAR propBuf[1024]; + PortInfo portInfo; + + availabelPortInfo.clear(); + + SetupDiClassGuidsFromName(_T("Ports"), NULL, 0, &dwGuids); + if (dwGuids == 0) return false; + + GUID *pGuids = new GUID[dwGuids]; + SetupDiClassGuidsFromName(_T("Ports"), pGuids, dwGuids, &dwGuids); + + for (DWORD i = 0; i < dwGuids; i++) { + HDEVINFO hDevInfo = + SetupDiGetClassDevs(&pGuids[i], NULL, NULL, DIGCF_PRESENT); + if (hDevInfo == INVALID_HANDLE_VALUE) break; + + for (int index = 0;; index++) { + SP_DEVINFO_DATA devInfoData; + + devInfoData.cbSize = sizeof(SP_DEVINFO_DATA); + + if (!SetupDiEnumDeviceInfo(hDevInfo, index, &devInfoData)) { + break; + } + propBuf[0] = 0; + + CM_Get_Device_ID(devInfoData.DevInst, propBuf, 1024, 0); + + string s(propBuf); + + string wusb = s.substr(0, 3); + + if (wusb == "USB") { + portInfo.vid = s.substr(8, 4); + portInfo.pid = s.substr(17, 4); + } else { + portInfo.pid = "None"; + portInfo.vid = "None"; + } + + if (!SetupDiGetDeviceRegistryProperty(hDevInfo, &devInfoData, + SPDRP_DEVICEDESC, NULL, + (PBYTE)propBuf, MAX_PATH - 1, NULL)) + continue; + + portInfo.description = propBuf; + + HKEY hDeviceKey = + SetupDiOpenDevRegKey(hDevInfo, &devInfoData, DICS_FLAG_GLOBAL, 0, + DIREG_DEV, KEY_QUERY_VALUE); + if (hDeviceKey) { + propBuf[0] = 0; + DWORD dw_size = sizeof(propBuf); + DWORD dw_type = 0; + + if ((RegQueryValueEx(hDeviceKey, _T("PortName"), NULL, &dw_type, + (LPBYTE)propBuf, &dw_size) == ERROR_SUCCESS) && + (dw_type == REG_SZ)) { + portInfo.name = propBuf; + availabelPortInfo.push_back(portInfo); + } + } + } + SetupDiDestroyDeviceInfoList(hDevInfo); + } + delete[] pGuids; + + return true; +} + +bool SerialInterfaceWin::open(string portName) { + if (mIsOpened) close(); + + portParams.portName = portName; + + std::string name = "\\\\.\\" + portName; + + mComHandle = ::CreateFile(name.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL); + + if (mComHandle == INVALID_HANDLE_VALUE) { + CloseHandle(mComHandle); + return false; + } + + PurgeComm(mComHandle, + PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR); + if (SetupComm(mComHandle, (DWORD)mMaxBuffSize, (DWORD)mMaxBuffSize) == 0) { + CloseHandle(mComHandle); + return false; + } + + DCB dcb; + dcb.DCBlength = sizeof(DCB); + if (GetCommState(mComHandle, &dcb) == 0) { + CloseHandle(mComHandle); + return false; + } else { + dcb.BaudRate = (DWORD)portParams.baudrate; + dcb.ByteSize = (BYTE)portParams.dataBits; + dcb.Parity = (BYTE)portParams.parity; + dcb.StopBits = (BYTE)portParams.stopBits; + if (portParams.flowControl == FlowControl::SoftwareControl) { + dcb.fOutX = 1; + dcb.fInX = 1; + } else if (portParams.flowControl == FlowControl::HardwareControl) { + dcb.fRtsControl = 0; + dcb.fDtrControl = 1; + } else { + dcb.fOutX = 0; + dcb.fInX = 0; + dcb.fRtsControl = 0; + dcb.fDtrControl = 0; + } + + if (SetCommState(mComHandle, &dcb) == 0) { + CloseHandle(mComHandle); + return false; + } + } + + COMMTIMEOUTS timeouts; + timeouts.ReadIntervalTimeout = 2; + timeouts.ReadTotalTimeoutMultiplier = 0; + timeouts.ReadTotalTimeoutConstant = 5; + timeouts.WriteTotalTimeoutConstant = 50000; + timeouts.WriteTotalTimeoutMultiplier = 1000; + SetCommTimeouts(mComHandle, &timeouts); + + mIsOpened = true; + + mRxThreadRunFlag = true; + mRxThread = new std::thread(rxThreadProc, this); + + return true; +} + +bool SerialInterfaceWin::close() { + if (!mIsOpened) return true; + + mIsOpened = false; + mRxThreadRunFlag = false; + + if (mComHandle != INVALID_HANDLE_VALUE) { + CloseHandle(mComHandle); + mComHandle = INVALID_HANDLE_VALUE; + } + + try { + if (mRxThread->joinable()) mRxThread->join(); + } catch (const std::system_error &e) { + LOG_INFO("Caught system_error with code:%d, meaning:%s", e.code(), e.what()); + } + delete mRxThread; + mRxThread = nullptr; + + return true; +} + +int SerialInterfaceWin::readAll() { return 0; } + +bool SerialInterfaceWin::write(const char *txBuf, uint32_t txBufLen) { + DWORD txLen = 0; + if (mIsOpened) { + ResetEvent(mOverlappedSend.hEvent); + uint32_t res = WriteFile(mComHandle, txBuf, txBufLen, (LPDWORD)&txLen, + &mOverlappedSend); + if (!res) { + if (GetLastError() == ERROR_IO_PENDING) { + WaitForSingleObject(mOverlappedSend.hEvent, INFINITE); + GetOverlappedResult(mComHandle, &mOverlappedSend, (LPDWORD)&txLen, + FALSE); + mTxCounter += txLen; + return true; + } + } + } + + return false; +} + +void SerialInterfaceWin::setPortParams(PortParams params) { + memcpy(&portParams, ¶ms, sizeof(params)); +} + +PortParams SerialInterfaceWin::currPortParams(void) { return portParams; } + +void SerialInterfaceWin::rxThreadProc(SerialInterfaceWin *pClass) { + char *rxBuf = new char[pClass->mMaxBuffSize + 1]; + ResetEvent(pClass->mOverlappedRecv.hEvent); + LOG_INFO("rx thread start...",""); + + map error_code{ + {ERROR_INVALID_PARAMETER, "Invalid parameter"}, + {ERROR_ACCESS_DENIED, "Access denied"}, + {ERROR_INVALID_HANDLE, "Open failed"}, + {ERROR_BAD_COMMAND, "Illegal disconnection"}, + }; + + while (pClass->mRxThreadRunFlag) { + uint32_t readed = 0; + bool res = ReadFile(pClass->mComHandle, rxBuf, (DWORD)pClass->mByteToRead, + (LPDWORD)&readed, &pClass->mOverlappedRecv); + if (res == FALSE) { + long last_error = GetLastError(); + switch (last_error) { + case ERROR_IO_PENDING: { + if (WaitForSingleObject( + pClass->mOverlappedRecv.hEvent, INFINITE) == WAIT_OBJECT_0) { + GetOverlappedResult(pClass->mComHandle, &pClass->mOverlappedRecv, (LPDWORD)&readed, FALSE); + if (readed) { + pClass->mRxCounter += readed; + // callback + if (pClass->readCallback != nullptr) { + pClass->readCallback(rxBuf, readed); + } + } + } + break; + } + case ERROR_INVALID_PARAMETER: /* 系统错误 erroe code:87 */ + case ERROR_ACCESS_DENIED: /* 拒绝访问 erroe code:5 */ + case ERROR_INVALID_HANDLE: /* 打开串口失败 erroe code:6 */ + case ERROR_BAD_COMMAND: /* 连接过程中非法断开 erroe code:22 */ + { + /* 不能再这里及回调函数中调用close,因为close中有join。在当前线程中join自己,会造成死锁 */ + if (pClass->commErrorHandle != nullptr) + pClass->commErrorHandle(error_code[last_error]); + pClass->mRxThreadRunFlag = false; + + break; + } + + default: /* 发生其他错误,其中有串口读写中断开串口连接的错误(错误22) */ + { + if (pClass->commErrorHandle != nullptr) + pClass->commErrorHandle("Unknow error"); + break; + } + } + + ResetEvent(pClass->mOverlappedRecv.hEvent); + } + } + + delete[] rxBuf; +} +/********************* (C) COPYRIGHT LD Robot *******END OF FILE ********/ diff --git a/sdk/src/sl_transform.cpp b/sdk/src/sl_transform.cpp new file mode 100644 index 0000000..d03b4eb --- /dev/null +++ b/sdk/src/sl_transform.cpp @@ -0,0 +1,110 @@ +/** + * @file transform.cpp + * @author LDRobot (support@ldrobot.com) + * @brief Ranging center conversion with left and right hand system changes App + * This code is only applicable to LDROBOT LiDAR LD00 LD03 LD08 LD14 + * products sold by Shenzhen LDROBOT Co., LTD + * @version 0.1 + * @date 2021-11-09 + * + * @copyright Copyright (c) 2017-2023 SHENZHEN LDROBOT CO., LTD. All rights + * reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License in the file LICENSE + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ldlidar_driver/sl_transform.h" + +namespace ldlidar { + +/*! + \brief transfer the origin to the center of lidar circle + \param[in] + \arg version lidar version, different versions have different + parameter settings \arg data lidar raw data \arg to_right_hand_ a flag + whether convert to right-hand coordinate system + + \param[out] data + \retval Data after coordinate conversion +*/ +SlTransform::SlTransform(LDType version, bool to_right_hand) { + switch (version) { + case LDType::LD_14: + case LDType::LD_14P: + offset_x_ = 5.9; + offset_y_ = -18.975571; + break; + default: + offset_x_ = 5.9; + offset_y_ = -20.14; + break; + } + to_right_hand_ = to_right_hand; + version_ = version; +} + +Points2D SlTransform::Transform(const Points2D &data) { + Points2D tmp2; + static double last_shift_delta = 0; + for (auto n : data) { + // transfer the origin to the center of lidar circle + // The default direction of radar rotation is clockwise + // transfer to the right-hand coordinate system + double angle; + if (n.distance > 0) { + double x = n.distance + offset_x_; + double y = n.distance * 0.11923 + offset_y_; + double shift = atan(y / x) * 180.f / 3.14159; + // Choose whether to use the right-hand system according to the flag + if (to_right_hand_) { + float right_hand = (360.f - n.angle); + angle = right_hand + shift; + } else { + angle = n.angle - shift; + } + last_shift_delta = shift; + } else { + if (to_right_hand_) { + float right_hand = (360.f - n.angle); + angle = right_hand + last_shift_delta; + } else { + angle = n.angle - last_shift_delta; + } + } + + if (angle > 360) { + angle -= 360; + } + if (angle < 0) { + angle += 360; + } + + switch (version_) { + case LDType::LD_14: + case LDType::LD_14P: + if (n.distance == 0) { + tmp2.push_back(PointData(angle, n.distance, 0, n.stamp)); + } else { + tmp2.push_back(PointData(angle, n.distance, n.intensity, n.stamp)); + } + break; + default: + break; + } + } + + return tmp2; +} + +SlTransform::~SlTransform() {} + +} // namespace ldlidar + +/********************* (C) COPYRIGHT SHENZHEN LDROBOT CO., LTD *******END OF + * FILE ********/ \ No newline at end of file diff --git a/sdk/src/slbf.cpp b/sdk/src/slbf.cpp new file mode 100644 index 0000000..ec6329d --- /dev/null +++ b/sdk/src/slbf.cpp @@ -0,0 +1,254 @@ +/** + * @file slbf.cpp + * @author LDRobot (support@ldrobot.com) + * @brief LiDAR near-range filtering algorithm + * This code is only applicable to LDROBOT LiDAR LD00 LD03 LD08 LD14 + * products sold by Shenzhen LDROBOT Co., LTD + * @version 0.1 + * @date 2021-11-09 + * + * @copyright Copyright (c) 2017-2023 SHENZHEN LDROBOT CO., LTD. All rights + * reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License in the file LICENSE + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "ldlidar_driver/slbf.h" + +namespace ldlidar { + +/*! + \brief Set current speed + \param[in] + \arg speed Current lidar speed + \arg strict_policy The flag to enable very strict filtering + \param[out] none + \retval none +*/ +Slbf::Slbf(int speed, bool strict_policy) { + curr_speed_ = speed; + enable_strict_policy_ = strict_policy; +} + +Slbf::~Slbf() {} + +/*! + \brief Filter within 1m to filter out unreasonable data points + \param[in] + \arg data A circle of lidar data packed + \param[out] none + \retval Standard data +*/ +Points2D Slbf::NearFilter(const Points2D &data) const { + Points2D normal, pending, item; + std::vector group; + int sunshine_amount = 0; + int dis_limit = 0; + + for (auto n : data) { + if (n.distance < 20000) { + pending.push_back(n); + } else { + normal.push_back(n); + } + } + + if (data.empty()) return normal; + + double angle_delta_up_limit = curr_speed_ / kScanFre * 1.5; + double angle_delta_down_limit = curr_speed_ / kScanFre - 0.18; + std::sort(pending.begin(), pending.end(), [](PointData a, PointData b) { return a.angle < b.angle; }); + + PointData last(-10, 0, 0); + + for (auto n : pending) { + dis_limit = 50; + if (n.distance > 1000) dis_limit = n.distance / 20; + if (fabs(n.angle - last.angle) > angle_delta_up_limit || + abs(n.distance - last.distance) > dis_limit) { + if (item.empty() == false) { + group.push_back(item); + item.clear(); + } + } + item.push_back(n); + last = n; + } + + if (item.empty() == false) group.push_back(item); + + if (group.empty()) return normal; + + auto first_item = group.front().front(); + auto last_item = group.back().back(); + dis_limit = (first_item.distance + last_item.distance) / 2 / 20; + if (dis_limit < 50) dis_limit = 50; + if (fabs(first_item.angle + 360.f - last_item.angle) < angle_delta_up_limit && + abs(first_item.distance - last_item.distance) < dis_limit) { + if (group.size() > 1) { + group.front().insert(group.front().begin(), group.back().begin(), group.back().end()); + group.erase(group.end() - 1); + } + } + + for (auto n : group) { + if (n.size() == 0) continue; + + if (n.size() > 35) { + normal.insert(normal.end(), n.begin(), n.end()); + continue; + } + + for (auto m : n) { + int flag = m.intensity & 0x01; + sunshine_amount += (flag == 1); + } + + double sunshine_rate = (double)sunshine_amount / (double)n.size(); + + double confidence_avg = 0; + double dis_avg = 0; + for (auto m : n) { + confidence_avg += m.intensity; + dis_avg += m.distance; + } + confidence_avg /= n.size(); + dis_avg /= n.size(); + + if (dis_avg > 1000 && sunshine_rate < 0.2 && + confidence_avg > kConfidenceHigh && n.size() > 2) { + normal.insert(normal.end(), n.begin(), n.end()); + continue; + } + + if (sunshine_rate > 0.5 && confidence_avg < kConfidenceLow) { + for (auto& point : n) { + point.distance = 0; + point.intensity = 0; + } + normal.insert(normal.end(), n.begin(), n.end()); + continue; + } + + if (enable_strict_policy_) { + if (dis_avg > 8100 && confidence_avg < kConfidenceLow && n.size() < 1) { + for (auto& point : n) { + point.distance = 0; + point.intensity = 0; + } + normal.insert(normal.end(), n.begin(), n.end()); + continue; + } else if (dis_avg > 6000 && confidence_avg < kConfidenceLow && n.size() < 2) { + for (auto& point : n) { + point.distance = 0; + point.intensity = 0; + } + normal.insert(normal.end(), n.begin(), n.end()); + continue; + } else if (dis_avg > 4000 && confidence_avg < kConfidenceHigh && n.size() < 2) { + for (auto& point : n) { + point.distance = 0; + point.intensity = 0; + } + normal.insert(normal.end(), n.begin(), n.end()); + continue; + } else if (dis_avg > 300 /*&& confidence_avg < kConfidenceHigh */ && n.size() < 2) { + for (auto& point : n) { + point.distance = 0; + point.intensity = 0; + } + normal.insert(normal.end(), n.begin(), n.end()); + continue; + } + + if (dis_avg < 300 && confidence_avg < kConfidenceHigh && n.size() < 3) { + for (auto& point : n) { + point.distance = 0; + point.intensity = 0; + } + normal.insert(normal.end(), n.begin(), n.end()); + continue; + } + + if (dis_avg < 300 && sunshine_rate > 0.5 && + confidence_avg < kConfidenceMiddle && n.size() < 5) { + for (auto& point : n) { + point.distance = 0; + point.intensity = 0; + } + normal.insert(normal.end(), n.begin(), n.end()); + continue; + } + + if (dis_avg < 200 && sunshine_rate > 0.4 && + confidence_avg < kConfidenceMiddle && n.size() < 6) { + for (auto& point : n) { + point.distance = 0; + point.intensity = 0; + } + normal.insert(normal.end(), n.begin(), n.end()); + continue; + } + + if (dis_avg < 500 && sunshine_rate > 0.9 && n.size() < 3) { + for (auto& point : n) { + point.distance = 0; + point.intensity = 0; + } + normal.insert(normal.end(), n.begin(), n.end()); + continue; + } + + if (dis_avg < 200 && confidence_avg < kConfidenceMiddle && n.size() < 3) { + for (auto& point : n) { + point.distance = 0; + point.intensity = 0; + } + normal.insert(normal.end(), n.begin(), n.end()); + continue; + } + } + + double diff_avg = 0; + int n_size = (int)n.size(); + for (int i = 1; i < n_size; i++) { + if (n[i].angle > n[i - 1].angle) { + diff_avg += fabs(n[i].angle - n[i - 1].angle); + } else { + diff_avg += fabs(n[i].angle + 360.0 - n[i - 1].angle); + } + } + diff_avg /= (double)(n.size() - 1); + + if (diff_avg > angle_delta_down_limit) { + normal.insert(normal.end(), n.begin(), n.end()); + } else { + for (auto& point : n) { + point.distance = 0; + point.intensity = 0; + } + normal.insert(normal.end(), n.begin(), n.end()); + } + } + + return normal; +} + +/*! + \brief Enable strong filtering + \param[in] + \arg enable : true ,false + \param[out] none + \retval +*/ +void Slbf::EnableStrictPolicy(bool enable) { enable_strict_policy_ = enable; } + +} // namespace ldlidar + +/********************* (C) COPYRIGHT SHENZHEN LDROBOT CO., LTD *******END OF + * FILE ********/ diff --git a/sdk/src/tofbf.cpp b/sdk/src/tofbf.cpp new file mode 100644 index 0000000..dfeef9f --- /dev/null +++ b/sdk/src/tofbf.cpp @@ -0,0 +1,272 @@ +/** + * @file tofbf.cpp + * @author LDRobot (support@ldrobot.com) + * @brief LiDAR near-range filtering algorithm + * This code is only applicable to LDROBOT LiDAR LD06 products + * sold by Shenzhen LDROBOT Co., LTD + * @version 0.1 + * @date 2021-10-28 + * + * @copyright Copyright (c) 2017-2023 SHENZHEN LDROBOT CO., LTD. All rights + * reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License in the file LICENSE + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ldlidar_driver/tofbf.h" + +namespace ldlidar { + +/** + * @brief Construct a new Tofbf:: Tofbf object + * @param [in] + * @param speed current lidar speed + */ +Tofbf::Tofbf(int speed, LDType type) { + curr_speed_ = speed; // ldliar spin speed, unit is Degrees per second + switch (type) + { + case LDType::LD_06: + case LDType::LD_19: + intensity_low_ = 15; + intensity_single_ = 220; + scan_frequency_ = 4500; + filter_type_ = FilterType::NEAR_FILTER; + break; + case LDType::STL_06P: + case LDType::STL_26: + case LDType::STL_27L: + filter_type_ = FilterType::NOISE_FILTER; + break; + default: + std::cout << "tofbf input ldlidar type error!" << std::endl; + filter_type_ = FilterType::NO_FILTER; + break; + } +} + +Tofbf::~Tofbf() {} + +/** + * @brief The tof lidar filter + * @param [in] + * @param tmp lidar point data + * @return std::vector + */ +std::vector Tofbf::Filter( + const std::vector &tmp) const { + std::vector normal; + + switch(filter_type_){ + case FilterType::NEAR_FILTER: + normal = NearFilter(tmp); + break; + + case FilterType::NOISE_FILTER: + normal = NoiseFilter(tmp); + break; + + default: + normal = tmp; + break; + } + + return normal; +} + +std::vector Tofbf::NearFilter( + const std::vector &tmp) const { + std::vector normal, pending, item; + std::vector> group; + + // Remove points within 5m + for (auto n : tmp) { + if (n.distance < 5000) { + pending.push_back(n); + } else { + normal.push_back(n); + } + } + + if (tmp.empty()) return normal; + + double angle_delta_up_limit = curr_speed_ / scan_frequency_ * 2; + + // sort + std::sort(pending.begin(), pending.end(), + [](PointData a, PointData b) { return a.angle < b.angle; }); + + PointData last(-10, 0, 0); + // group + for (auto n : pending) { + if (fabs(n.angle - last.angle) > angle_delta_up_limit || + fabs(n.distance - last.distance) > last.distance * 0.03) { + if (item.empty() == false) { + group.push_back(item); + item.clear(); + } + } + item.push_back(n); + last = n; + } + // push back last item + if (item.empty() == false) group.push_back(item); + + if (group.empty()) return normal; + + // Connection 0 degree and 359 degree + auto first_item = group.front().front(); + auto last_item = group.back().back(); + if (fabs(first_item.angle + 360.f - last_item.angle) < angle_delta_up_limit && + fabs(first_item.distance - last_item.distance) < last.distance * 0.03) { + group.front().insert(group.front().begin(), group.back().begin(), group.back().end()); + group.erase(group.end() - 1); + } + // selection + for (auto n : group) { + if (n.size() == 0) continue; + // No filtering if there are many points + if (n.size() > 15) { + normal.insert(normal.end(), n.begin(), n.end()); + continue; + } + + // Filter out those with few points + if (n.size() < 3) { + int c = 0; + for (auto m : n) { + c += m.intensity; + } + c /= n.size(); + if (c < intensity_single_) { + for (auto& point: n) { + point.distance = 0; + point.intensity = 0; + } + normal.insert(normal.end(), n.begin(), n.end()); + continue; + } + } + + // Calculate the mean value of distance and intensity + double intensity_avg = 0; + double dis_avg = 0; + for (auto m : n) { + intensity_avg += m.intensity; + dis_avg += m.distance; + } + intensity_avg /= n.size(); + dis_avg /= n.size(); + + // High intensity, no filtering + if (intensity_avg > intensity_low_) { + normal.insert(normal.end(), n.begin(), n.end()); + continue; + } else { + for (auto& point : n) { + point.distance = 0; + point.intensity = 0; + } + normal.insert(normal.end(), n.begin(), n.end()); + continue; + } + } + + return normal; +} + +std::vector Tofbf::NoiseFilter( + const std::vector &tmp) const { + std::vector normal; + PointData last_data, next_data; + + if (tmp.empty()) return normal; + + // Traversing the point data + int count = 0; + for (auto n : tmp) { + if(count == 0) { + last_data = tmp[tmp.size() - 1]; + } else { + last_data = tmp[count - 1]; + } + + if(count == (int)(tmp.size() - 1)) { + next_data = tmp[0]; + } else { + next_data =tmp[count + 1]; + } + count++; + + // Remove points with the opposite trend within 500mm + if(n.distance < 500) { + if((n.distance + 10 < last_data.distance && n.distance + 10 < next_data.distance) + || (n.distance > last_data.distance + 10 && n.distance > next_data.distance + 10)) { + if(n.intensity < 60) { + n.intensity = 0; + n.distance = 0; + normal.push_back(n); + continue; + } + } else if((n.distance + 7 < last_data.distance && n.distance + 7 < next_data.distance) + || (n.distance > last_data.distance + 7 && n.distance > next_data.distance + 7)) { + if(n.intensity < 45) { + n.intensity = 0; + n.distance = 0; + normal.push_back(n); + continue; + } + } else if((n.distance + 5 < last_data.distance && n.distance + 5 < next_data.distance) + || (n.distance > last_data.distance + 5 && n.distance > next_data.distance + 5)) { + if(n.intensity < 30) { + n.intensity = 0; + n.distance = 0; + normal.push_back(n); + continue; + } + } + } + + // Remove points with very low intensity within 5m + if (n.distance < 5000) { + if(n.distance < 200) { + if(n.intensity < 25) { + n.intensity = 0; + n.distance = 0; + normal.push_back(n); + continue; + } + } else { + if(n.intensity < 10) { + n.intensity = 0; + n.distance = 0; + normal.push_back(n); + continue; + } + } + + if((n.distance + 30 < last_data.distance || n.distance > last_data.distance + 30) + &&(n.distance + 30 < next_data.distance || n.distance > next_data.distance + 30)) { + if((n.distance < 2000 && n.intensity < 30) || n.intensity < 20) { + n.intensity = 0; + n.distance = 0; + normal.push_back(n); + continue; + } + } + } + normal.push_back(n); + } + return normal; +} + +} // namespace ldlidar + +/********************* (C) COPYRIGHT SHENZHEN LDROBOT CO., LTD *******END OF + * FILE ********/ \ No newline at end of file diff --git a/sdk/test/CMakeLists.txt b/sdk/test/CMakeLists.txt new file mode 100644 index 0000000..e4184c1 --- /dev/null +++ b/sdk/test/CMakeLists.txt @@ -0,0 +1,88 @@ +cmake_minimum_required(VERSION 2.8.3) +project(test) + +if(${CMAKE_BUILD_TYPE} MATCHES "Release") + #set(CMAKE_BUILD_TYPE "Release") + set(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -std=c++11 -Wall") + #set(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -std=c++11 -O3 -Wall") + message(STATUS "Mode: Release") + message(STATUS "optional:-std=c++11 -Wall") +else() + #set(CMAKE_BUILD_TYPE "Debug") + set(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -std=c++11 -Wall -Wextra -Wpedantic -g2 -ggdb") + #set(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -std=c++11 -O2 -Wall -Wextra -Wpedantic -g2 -ggdb") + message(STATUS "Mode: Debug") + message(STATUS "optional:-std=c++11 -Wall -Wextra -Wpedantic -g2 -ggdb") +endif() + +if(${USE_WIRINGPI_LIB} MATCHES "ON") + add_definitions(-D"USE_WIRINGPI") + message(STATUS "-- #define USE_WIRINGPI") +endif() + + +########### +## Build ## +########### + +## Specify additional locations of header files +## Your package locations should be listed before other locations +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/../include/ +) + +set(LDLIDAR_DRIVER_SOURCE_LINUX + ${CMAKE_CURRENT_SOURCE_DIR}/../src/ldlidar_driver.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../src/ldlidar_driver_linux.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../src/ldlidar_protocol.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../src/ldlidar_dataprocess.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../src/log_module.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../src/network_socket_interface_linux.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../src/serial_interface_linux.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../src/sl_transform.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../src/slbf.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../src/tofbf.cpp +) + +add_executable(${PROJECT_NAME}_node + ${CMAKE_CURRENT_SOURCE_DIR}/test.cpp +) + +add_library(ldlidar_driver_static STATIC + ${LDLIDAR_DRIVER_SOURCE_LINUX} +) + +add_library(ldlidar_driver_shared SHARED + ${LDLIDAR_DRIVER_SOURCE_LINUX} +) + +# rename library name +set_target_properties (ldlidar_driver_static PROPERTIES OUTPUT_NAME "ldlidar_driver") +set_target_properties (ldlidar_driver_shared PROPERTIES OUTPUT_NAME "ldlidar_driver") + +# binary file link to library +if(${USE_WIRINGPI_LIB} MATCHES "ON") +target_link_libraries(${PROJECT_NAME}_node + ldlidar_driver_static + pthread + wiringPi +) +else() +target_link_libraries(${PROJECT_NAME}_node + ldlidar_driver_static + pthread +) +endif() + +########### +## Install ## +########### + +#INSTALL(TARGETS ldlidar_driver_static ldlidar_driver_shared +# ARCHIVE DESTINATION lib +# LIBRARY DESTINATION share +#) + +#INSTALL(DIRECTORY ${PROJECT_SOURCE_DIR}/ldlidar_driver/include +# DESTINATION include +#) diff --git a/sdk/test/auto_build_test.sh b/sdk/test/auto_build_test.sh new file mode 100644 index 0000000..ed88db7 --- /dev/null +++ b/sdk/test/auto_build_test.sh @@ -0,0 +1,21 @@ +#!/bin/bash +#Author: David Hu +#Date: 2022-09 + +# Exit on error +set -e + +echo "Start cmake build" + +if [ ! -e "./build" ];then + mkdir build + echo "create ./build/" +fi + +cd ./build + +cmake -DCMAKE_BUILD_TYPE=Debug .. + +make -j2 + +echo "build finished." \ No newline at end of file diff --git a/sdk/test/clean_build.sh b/sdk/test/clean_build.sh new file mode 100644 index 0000000..d58f6d7 --- /dev/null +++ b/sdk/test/clean_build.sh @@ -0,0 +1,14 @@ +#!/bin/bash +#Author: David Hu +#Date: 2022-09 +if [ -e "./build" ];then + rm -rf build/ + echo "del ./build/" +fi + +if [ -e "./install" ];then + rm -rf install/ + echo "del ./install/" +fi + +echo "del is ok....." \ No newline at end of file diff --git a/sdk/test/test.cpp b/sdk/test/test.cpp new file mode 100644 index 0000000..df0f126 --- /dev/null +++ b/sdk/test/test.cpp @@ -0,0 +1,237 @@ +/** + * @file test.cpp + * @author LDRobot (support@ldrobot.com) + * David Hu (hmd_hubei_cn@163.com) + * @brief sdk function test + * @version 3.0 + * @date 2022-12-09 + * + * @copyright Copyright (c) 2020 SHENZHEN LDROBOT CO., LTD. All rights + * reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License in the file LICENSE + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ldlidar_driver/ldlidar_driver_linux.h" + +#ifdef USE_WIRINGPI +#include +#endif + +uint64_t GetTimestamp(void) { + std::chrono::time_point tp = + std::chrono::time_point_cast(std::chrono::system_clock::now()); + auto tmp = std::chrono::duration_cast(tp.time_since_epoch()); + return ((uint64_t)tmp.count()); +} + +bool ControlPinInit(void) { + // raspberrypi wiringPi GPIO Init +#ifdef USE_WIRINGPI + if(wiringPiSetup() == -1) { + return false; + } + pinMode(25,OUTPUT); // set wiringPi 25 Pin number is outuput Mode. +#endif + return true; +} + +void LidarPowerOn(void) { + LOG_DEBUG("Lidar Power On",""); +#ifdef USE_WIRINGPI + digitalWrite(25,HIGH); +#endif +} + +void LidarPowerOff(void) { + LOG_DEBUG("Lidar Power Off",""); +#ifdef USE_WIRINGPI + digitalWrite(25,LOW); +#endif +} + +void AbortTesting(void) { + LOG_WARN("Testing abort",""); + LidarPowerOff(); + exit(EXIT_FAILURE); +} + +struct LdsInfoStruct { + std::string ldtype_str; + ldlidar::LDType ldtype_enum; + uint32_t baudrate; +}; + +LdsInfoStruct LdsInfoArrary[4] = { + {"LD14", ldlidar::LDType::LD_14, 115200}, + {"LD14P", ldlidar::LDType::LD_14P, 230400}, + {"LD06", ldlidar::LDType::LD_06, 230400}, + {"LD19", ldlidar::LDType::LD_19, 230400}, +}; + +ldlidar::LDType GetLdsType(std::string in_str) { + for (auto item : LdsInfoArrary) { + if (!strcmp(in_str.c_str(), item.ldtype_str.c_str())) { + return item.ldtype_enum; + } + } + return ldlidar::LDType::NO_VER; +} + +uint32_t GetLdsSerialPortBaudrateValue(std::string in_str) { + for (auto item : LdsInfoArrary) { + if (!strcmp(in_str.c_str(), item.ldtype_str.c_str())) { + return item.baudrate; + } + } + return 0; +} + +int test(int argc, char **argv) { + + if (argc != 3) { + LOG_INFO("cmd error",""); + LOG_INFO("please input: ./test_node ",""); + LOG_INFO("example:",""); + LOG_INFO("./test_node LD14 /dev/ttyUSB0",""); + LOG_INFO("./test_node LD14P /dev/ttyUSB0",""); + LOG_INFO("./test_node LD06 /dev/ttyUSB0",""); + LOG_INFO("./test_node LD19 /dev/ttyUSB0",""); + exit(EXIT_FAILURE); + } + + std::string ldlidar_type_str(argv[1]); + std::string serial_port_name(argv[2]); + + // select ldrobot lidar sensor type. + ldlidar::LDType ldlidar_type_dest; + ldlidar_type_dest = GetLdsType(ldlidar_type_str); + if (ldlidar_type_dest == ldlidar::LDType::NO_VER) { + LOG_WARN("ldlidar_type_str value is not sure: %s", ldlidar_type_str.c_str()); + exit(EXIT_FAILURE); + } + + // if use serial communications interface, as select serial baudrate paramters. + uint32_t serial_baudrate_val; + serial_baudrate_val = GetLdsSerialPortBaudrateValue(ldlidar_type_str); + if (!serial_baudrate_val) { + LOG_WARN("ldlidar_type_str value is not sure: %s", ldlidar_type_str.c_str()); + exit(EXIT_FAILURE); + } + + ldlidar::LDLidarDriverLinuxInterface* lidar_drv = + ldlidar::LDLidarDriverLinuxInterface::Create(); + + LOG_INFO("LDLiDAR SDK Pack Version is %s", lidar_drv->GetLidarSdkVersionNumber().c_str()); + + lidar_drv->RegisterGetTimestampFunctional(std::bind(&GetTimestamp)); + + lidar_drv->EnablePointCloudDataFilter(true); + + if (lidar_drv->Connect(ldlidar_type_dest, serial_port_name, serial_baudrate_val)) { + LOG_INFO("ldlidar serial connect is success",""); + // LidarPowerOn(); + } else { + LOG_ERROR("ldlidar serial connect is fail",""); + AbortTesting(); + } + + if (lidar_drv->WaitLidarComm(3500)) { + LOG_INFO("ldlidar communication is normal.",""); + } else { + LOG_ERROR("ldlidar communication is abnormal.",""); + lidar_drv->Disconnect(); + AbortTesting(); + } + + if (lidar_drv->Start()) { + LOG_INFO_LITE("ldlidar driver start is success.",""); + } else { + LOG_ERROR_LITE("ldlidar driver start is fail.",""); + } + + ldlidar::Points2D laser_scan_points; + int cnt = 100; + while (ldlidar::LDLidarDriverLinuxInterface::Ok()) { + if ((cnt--) <= 0) { + lidar_drv->Stop(); + } + + switch (lidar_drv->GetLaserScanData(laser_scan_points, 1500)){ + case ldlidar::LidarStatus::NORMAL: { + double lidar_scan_freq = 0; + lidar_drv->GetLidarScanFreq(lidar_scan_freq); +#ifdef __LP64__ + LOG_INFO_LITE("speed(Hz):%f, size:%d,stamp_begin:%lu, stamp_end:%lu", + lidar_scan_freq, laser_scan_points.size(), laser_scan_points.front().stamp, laser_scan_points.back().stamp); +#else + LOG_INFO_LITE("speed(Hz):%f, size:%d,stamp_begin:%llu, stamp_end:%llu", + lidar_scan_freq, laser_scan_points.size(), laser_scan_points.front().stamp, laser_scan_points.back().stamp); +#endif + // output 2d point cloud data +#if 0 + for (auto point : laser_scan_points) { +#ifdef __LP64__ + LOG_INFO_LITE("stamp(ns):%lu,angle:%f,distance(mm):%d,intensity:%d", + point.stamp, point.angle, point.distance, point.intensity); +#else + LOG_INFO_LITE("stamp(ns):%llu,angle:%f,distance(mm):%d,intensity:%d", + point.stamp, point.angle, point.distance, point.intensity); +#endif + } +#endif + break; + } + case ldlidar::LidarStatus::DATA_TIME_OUT: { + LOG_ERROR_LITE("point cloud data publish time out, please check your lidar device.",""); + lidar_drv->Stop(); + AbortTesting(); + break; + } + case ldlidar::LidarStatus::DATA_WAIT: { + break; + } + default: + break; + } + + usleep(1000*166); // sleep 166ms , 6hz + } + + lidar_drv->Stop(); + lidar_drv->Disconnect(); + sleep(3); + + ldlidar::LDLidarDriverLinuxInterface::Destory(lidar_drv); + + return 0; +} + +int main(int argc, char** argv) { + + if (!ControlPinInit()) { + LOG_ERROR("Control pin Setup failed.",""); + exit(EXIT_FAILURE); + } + + for (int i = 0; i < 10000; i++) { + test(argc, argv); + } + + LOG_INFO("test is end.",""); + while (1) { + sleep(1); + } + + return 0; +} + + +/********************* (C) COPYRIGHT SHENZHEN LDROBOT CO., LTD *******END OF + * FILE ********/