Livox激光雷达主控库开发:从SDK封装到多雷达同步数据采集实战

发布时间:2026/8/30 15:52:05

Livox激光雷达主控库开发:从SDK封装到多雷达同步数据采集实战
简介本资源是Livox激光雷达官方SDK的C主控库完整实现面向嵌入式开发工程师、机器人SLAM开发者及三维感知算法研究人员用于雷达设备接入、数据采集、参数配置与实时控制等二次开发任务。压缩包共760个文件涵盖268个C源码核心驱动与通信逻辑、188个头文件接口定义与结构体声明、126个备份文件zbak以及构建脚本sh/m4/awk、工程配置dsp/dsw/conf、文档md/readme/license和测试工具cpp/py/bat整体体积仅2.02MB轻量且结构完整。已有100人下载学习适合作为Livox MID-360、Horizon等系列雷达的快速集成基础读者可直接复用通信协议栈、点云解析模块与多线程管理框架并参考配套构建脚本与跨平台编译配置显著降低硬件对接门槛与调试成本。1. 项目缘起为什么我们需要一个“主控库”最近在折腾Livox激光雷达特别是像Mid-360这样的设备时很多朋友都会遇到一个共同的困惑官方提供的SDK功能强大但上手门槛不低。尤其是当你需要同时管理多个雷达、处理复杂的点云数据流、或者想快速搭建一个稳定的数据采集应用时直接使用底层SDK接口往往会陷入大量重复的初始化、连接管理、数据同步和线程控制代码中。这就像给你一堆精密的汽车零部件发动机、变速箱、底盘让你自己从零开始组装一辆能稳定上路的车虽然自由度高但效率低下且容易出错。“Livox SDK 主控库_Livox”这个概念或者说我们实践中构建的这样一个库就是为了解决这个问题而生的。它不是一个官方发布的全新SDK而是一个基于官方Livox SDK二次封装、高度集成的应用程序框架或“脚手架”。它的核心目标是把那些在每一个Livox雷达项目中都需要重复编写的“脏活累活”标准化、模块化让你能更专注于上层业务逻辑比如点云算法处理、建图或者目标检测而不是整天和设备的连接状态、数据包的解析时序搏斗。简单来说这个“主控库”扮演了“车队调度中心”和“数据流水线总管”的角色。想象一下你要用多台Livox Mid-360进行大规模的室内外扫描每台雷达都有自己的IP、需要独立的连接、产生高速的数据流。主控库能帮你统一管理所有这些雷达的生命周期发现、连接、配置、启动、停止、断开并提供一个清晰、统一的接口来获取已经预处理好的点云数据。它把官方SDK中异步、回调式的API封装成更符合我们编程习惯的同步或事件驱动模型大大降低了开发复杂度。2. 核心架构设计从“零件”到“整车”的组装逻辑要构建一个稳健好用的主控库不能只是对官方API做简单的包装。我们需要深入理解Livox SDK的工作机制并设计一个能应对真实场景复杂性的架构。下面是我在实践中总结出的一套核心架构主要包含以下几个层次2.1 设备管理层雷达的“户籍管理员”这是最底层直接与Livox SDK的DeviceManager和Livox SDK API交互。它的职责非常明确设备发现与枚举监听网络自动发现同一网段内所有Livox雷达包括Mid-360, Avia, Horizon等系列。这里不能简单调用一次发现就了事需要实现一个后台线程定时扫描并维护一个实时更新的设备列表处理设备的上下线事件。连接池管理为每一个物理雷达创建一个独立的连接句柄和控制上下文。这个上下文需要保存设备的所有状态信息IP地址、广播码、连接状态、固件版本、当前参数如扫描模式、回波模式、IMU状态等。生命周期控制提供统一的connect(),disconnect(),startSampling(),stopSampling()接口。关键在于这些接口内部需要处理大量的异常情况比如网络闪断、雷达意外断电、指令超时等并向上层提供明确的状态反馈和错误码而不是让程序直接崩溃。注意Livox SDK的连接和采样启动是异步的通过回调函数通知结果。在主控库中我们通常会将它们封装成同步等待带超时或返回Future/Promise的模式这样上层逻辑写起来更直观。例如bool success device-connect(5000);表示尝试连接最多等待5秒。2.2 数据流引擎高速数据的“交通枢纽”这是主控库的核心和性能关键点。Livox雷达的数据速率很高Mid-360在非重复扫描模式下单台每秒就能产生超过30万个点。多台同时工作时数据流如同洪水。数据回调接管我们需要注册并接管官方SDK的OnSampleCallback或OnPointCloudCallback。在这个底层回调里绝对不能进行任何复杂的处理或长时间的操作否则会阻塞SDK内部的数据接收线程导致数据包丢失。这里的黄金法则是快进快出。环形缓冲区Ring Buffer底层回调函数只做一件事将收到的原始数据包LivoxEthPacket或点云结构体以最快的速度压入一个预先分配好的环形缓冲区。这个缓冲区是多生产者多个雷达回调线程单消费者我们的处理线程或无锁队列的理想选择能有效解决并发冲突避免动态内存分配带来的性能抖动。数据处理线程一个或多个独立的工作线程从环形缓冲区中取出原始数据。在这里我们进行耗时的操作点云解析与拼接将数据包解析成XYZI坐标和强度格式的点。坐标系变换根据雷达的安装位置和外参通过手眼标定得到将点云从雷达坐标系转换到统一的车辆或世界坐标系。时间同步与插值如果使用了外部同步源如PPSGPToD需要根据数据包中的时间戳进行精确对齐。对于移动平台还需要结合IMU数据Mid-360内置IMU进行运动补偿消除雷达自身运动带来的点云畸变。点云滤波简单的直通滤波、统计离群点移除可以在这一层做以减轻后续算法的压力。2.3 配置与状态服务设备的“遥控中心”一个友好的主控库必须提供便捷、安全的设备参数配置能力。配置持久化与加载支持从JSON、YAML或XML文件加载整个“车队”的配置。包括每个雷达的IP、期望的扫描模式重复/非重复、回波模式、坐标系外参、是否启用IMU等。参数热更新在不重启数据采集的前提下能否动态修改某个雷达的扫描模式这是一个高级但很有用的功能。实现它需要仔细处理配置变更前后数据流的一致性。状态监控与上报主控库应该提供一个实时状态看板能够查询每个雷达的连接状态、数据吞吐量、CPU占用、温度等信息并在异常时通过日志或事件通知上层应用。2.4 对外接口层简洁的“驾驶舱”这是库的使用者直接接触的部分。设计原则是“高内聚低耦合”提供少数几个核心类和方法。LivoxController主入口类。通过一个单例或工厂模式获取实例。bool init(const std::string config_path)初始化加载配置。bool start()启动所有雷达开始数据流。std::shared_ptrPointCloud getLatestCloud(const std::string device_sn)获取指定雷达最新的一帧点云。这里返回的应该是已经过坐标系变换和基本滤波的点云。void registerCloudCallback(std::functionvoid(const std::string sn, const PointCloud) callback)注册回调函数每当有新点云处理完成时自动调用。这是更事件驱动的模式。void stop(),void deinit()停止和清理资源。3. 实战开发从零搭建主控库的关键步骤与坑位理论说完了我们来看看具体怎么实现以及里面有哪些“坑”。3.1 环境搭建与依赖管理首先你需要官方的Livox SDK。通常我们选择C版本因为性能控制更直接。不建议直接用ROS的livox_ros_driver作为基础因为它本身已经是一个较为复杂的节点我们需要的更底层一些。获取SDK从Livox官网或GitHub下载最新版本的Livox SDK for C。解压后重点关注include/和lib/目录。项目结构创建一个清晰的CMake项目。我的习惯是这样组织livox_master_lib/ ├── CMakeLists.txt ├── include/livox_master/ # 对外头文件 ├── src/ # 内部实现 │ ├── device_manager.cpp │ ├── data_engine.cpp │ └── controller.cpp ├── third_party/ # 放置Livox SDK │ └── livox_sdk/ └── examples/ # 使用示例CMake配置关键是要正确链接Livox SDK的库文件通常是livox_sdk_static.lib或liblivox_sdk.a和对应的网络库如ws2_32on Windows。确保编译平台x64和SDK一致。3.2 实现设备管理连接不是“一锤子买卖”在device_manager.cpp中我们实现设备发现。官方示例中的发现是阻塞的。我们需要将其改造成异步的。// 伪代码示例 class DeviceManagerImpl { private: std::thread discovery_thread_; std::atomicbool is_discovery_running_{false}; std::mapstd::string, DeviceInfo online_devices_; // 使用SN作为key std::mutex devices_mutex_; void discoveryLoop() { while (is_discovery_running_) { std::this_thread::sleep_for(std::chrono::seconds(2)); // 每2秒扫描一次 DeviceInfo* devices nullptr; uint8_t count 0; // 调用SDK发现函数 if (LivoxSdk_GetDeviceList(devices, count) 0) { std::lock_guardstd::mutex lock(devices_mutex_); // 更新online_devices_处理新增和消失的设备 // 触发设备上线/下线事件回调 } } } public: bool startDiscovery() { if (is_discovery_running_) return true; // 初始化SDK if (LivoxSdk_Init() ! 0) { /* 处理错误 */ } is_discovery_running_ true; discovery_thread_ std::thread(DeviceManagerImpl::discoveryLoop, this); return true; } };踩坑记录1广播码与连接顺序。首次连接Livox雷达需要广播码默认是“0.0.0.0”。主控库应该提供设置广播码的接口。另外同时连接多台雷达时最好顺序连接并等待每台连接成功后再连下一台避免网络拥堵或SDK内部状态混乱。我曾遇到过同时发起10台雷达连接导致其中几台一直超时失败的情况。3.3 实现数据引擎性能与稳定性的博弈这是最考验功力的部分。我们以使用OnSampleCallback接收原始数据包为例。// 伪代码数据引擎的核心 class DataEngineImpl { private: struct PacketBuffer { LivoxEthPacket packet; uint64_t receive_timestamp_ns; // 收到数据包的系统时间 std::string device_sn; }; moodycamel::ConcurrentQueuePacketBuffer packet_queue_; // 一个高性能的无锁队列 // SDK的数据回调运行在SDK的内部线程 static void sdkDataCallback(uint8_t handle, LivoxEthPacket* data, uint32_t data_num, void* client_data) { auto* self static_castDataEngineImpl*(client_data); for (uint32_t i 0; i data_num; i) { PacketBuffer buf; buf.packet data[i]; buf.receive_timestamp_ns getSystemTimestamp(); buf.device_sn self-getSnByHandle(handle); // 根据句柄查找设备SN // 以非阻塞方式入队 while (!self-packet_queue_.try_enqueue(buf)) { // 队列满这是严重问题。可以丢弃最旧的数据或增加队列容量。 // 记录警告日志监控此情况。 self-packet_queue_.try_dequeue(buf); // 尝试丢弃一个 } } } // 独立的数据处理线程函数 void processingLoop() { PacketBuffer buf; std::mapstd::string, PointCloudAccumulator accumulators; // 每个设备一个累加器 while (is_processing_running_) { if (packet_queue_.try_dequeue(buf)) { auto acc accumulators[buf.device_sn]; // 解析数据包到acc acc.parsePacket(buf.packet, buf.receive_timestamp_ns); // 如果acc攒够了一帧例如根据时间或点数判断 if (acc.isFrameReady()) { auto cloud acc.getFrameAndReset(); // 进行坐标系变换、滤波... // 发布处理好的点云放入输出队列或触发回调 publishCloud(buf.device_sn, cloud); } } else { // 队列空短暂休眠避免空转消耗CPU std::this_thread::sleep_for(std::chrono::microseconds(100)); } } } };踩坑记录2队列容量与内存管理。环形缓冲区或无锁队列的容量设置至关重要。太小容易在数据处理线程繁忙时被填满导致丢包太大会占用过多内存。对于Mid-360一个数据包约1300字节一秒上万包。建议根据数据速率和处理能力动态调整。同时PointCloud对象尽量复用内存池避免频繁的new/delete导致内存碎片。踩坑记录3时间戳的处理。Livox数据包内有自己的时间戳基于雷达上电时间。getSystemTimestamp()必须使用高精度单调时钟如std::chrono::steady_clock。将雷达时间戳与系统时间戳对齐是后续多雷达同步和运动补偿的基础。这个对齐操作需要在连接成功后通过获取几个数据包并计算延迟来粗略估计不是一个精确值但对于非严苛的同步应用已足够。3.4 坐标系变换与外参集成这是让点云变得有用的关键一步。每台雷达在车体或机器人上都有一个安装位置和姿态6自由度外参x, y, z, roll, pitch, yaw。外参配置在主控库的配置文件中为每个雷达的序列号配置一个4x4的变换矩阵T_lidar_to_base。实时变换在数据处理线程中对解析出的每一个点P_lidar执行变换P_base T_lidar_to_base * P_lidar。这个矩阵乘法运算量不小需要优化。可以使用Eigen库并确保启用SSE/AVX指令集编译优化。外参标定主控库可以集成一个简单的标定辅助功能例如让两台雷达同时扫描一个共同区域然后通过ICP等算法自动计算外参这比手动测量要准确得多。4. 进阶话题主控库如何应对复杂场景一个基础的主控库能工作后我们还需要考虑更多工业级应用场景。4.1 多雷达时间同步对于Mid-360这类内置IMU和支持PPS的雷达高精度同步是发挥其性能的关键。硬件连接将所有雷达的PPS秒脉冲线和GPRMC时间信息线连接到同一个GNSS接收机或时间同步主机上。SDK配置通过SetDeviceParameters接口将雷达的同步模式设置为PPS或PTP。软件处理在主控库中解析数据包时不仅要看雷达内部时间戳还要结合PPS上升沿对应的系统绝对时间UTC来为每个点云帧赋予一个精确的全局时间戳。这样来自不同雷达的点云以及雷达点云与相机图像、IMU数据之间才能进行毫秒甚至微秒级对齐。4.2 断线重连与状态恢复网络不稳定或雷达重启是常态。主控库必须有完善的容错机制。心跳检测除了依赖SDK的底层连接状态可以定期如每秒向雷达发送一个查询状态的指令如果没有响应则判定为“心跳丢失”。分级状态定义更细粒度的设备状态如DISCONNECTED、CONNECTING、CONNECTED、SAMPLING、ERROR等。自动重连策略当检测到连接断开时不是立即重试而是进入一个退避重连循环例如等待1秒、2秒、4秒...指数退避避免在雷达重启期间疯狂重连。重连成功后应自动恢复之前的采样配置。4.3 资源监控与日志系统一个好的主控库应该是可观测的。性能统计实时计算并输出每个雷达的数据率MB/s pts/s、处理队列深度、处理延迟从数据包接收到发布点云的时间差。健康检查监控雷达上报的温度、电压等状态信息异常时告警。结构化日志使用spdlog这样的库输出不同级别INFO, WARN, ERROR的日志并记录关键事件设备连接、断开、开始采样、错误码。日志中要包含设备SN和时间戳方便问题追溯。5. 封装与部署让主控库真正好用最后为了让这个库易于集成和使用我们还需要做以下工作创建清晰的API头文件将所有公共接口在一个头文件如livox_master.h中暴露。使用前向声明和Pimpl指针指向实现 idiom来隐藏内部实现细节减少编译依赖。编写完整的文档至少包括API参考每个类、方法的说明。入门指南从CMake配置到运行第一个示例的步骤。配置说明配置文件中每个参数的含义。常见问题解答记录像“连接失败怎么办”、“点云数据延迟大怎么办”这类问题的排查步骤。提供多种使用范例example_single.cpp连接单台雷达并打印点云。example_multi.cpp连接多台雷达并显示各自点云。example_with_ros2.cpp如何将主控库处理好的点云发布为ROS2的sensor_msgs/msg/PointCloud2话题。这是最常见的集成场景。打包与分发使用CPack生成deb/rpm包或者导出CMake targets让其他项目能简单地通过find_package(LivoxMaster)来引入。构建这样一个“Livox SDK主控库”的过程本质上是一个将底层硬件复杂性封装成稳定、易用软件服务的过程。它没有算法上的高深突破但充满了工程上的细节考量多线程安全、资源管理、性能优化、异常处理。当你完成它并看到上层应用开发者可以只用寥寥几行代码就驱动起一个庞大的Livox雷达阵列稳定地输出高质量、已对齐的点云数据时你会觉得所有这些繁琐的工作都是值得的。这或许就是基础设施开发的魅力所在——你搭建的舞台能让更多人跳出更优美的舞蹈。本文还有配套的精品资源点击获取

相关新闻

杜邦Pyralux ML高导热层压板:兼顾散热与耐温的功率电子基材解析

杜邦Pyralux ML高导热层压板:兼顾散热与耐温的功率电子基材解析

2026/8/30 15:52:05

1. 这块层压板到底解决了什么问题做电子材料这行的人,看到“高频”“高导热”“极端环境”这几个词凑在一起,第一反应往往不是兴奋,而是头疼。因为这些需求在传统FR-4板上几乎是互相打架的:导热好的材料介电性能往往拉胯&#xff…

3300W Rugged AC充电器深度拆解:硬件架构与实战应用

3300W Rugged AC充电器深度拆解:硬件架构与实战应用

2026/8/30 15:52:05

Green Watt Power这次放出来的3300W AC充电器,我盯了很久。说实话,工业/商用电源圈里,大功率充电设备并不少见,但“3300W”这个数字卡在一个非常微妙的位置——比普通车载充电机高出一截,又比动辄几十千瓦的直流桩轻便…

汽车AI SoC供电设计:从PMIC选型到量产调试的完整指南

汽车AI SoC供电设计:从PMIC选型到量产调试的完整指南

2026/8/30 15:52:05

做汽车AI的供电系统,这几年越来越像是解一道高难度综合题。PMIC、AI、SoC这三个词放在一起,表面上说的是电源管理芯片给智能驾驶芯片供电,实际上牵扯到算力功耗、功能安全、热设计、EMI、量产一致性,每一个方向都能单独写一本书。…

2018年GitHub最流行50大Python开源项目

2018年GitHub最流行50大Python开源项目

2026/8/30 17:02:08

其一这是当下极为火爆的编程语言当中的一种之一, 其二在其之上存在大量热门的开源众包平台的情况, 其三近来那那个开源众包平台评选出来了在2018年时期内里最流行的50个开源项目情况如下:1) Model如果你对机器学习和深度学习有所了解,那么肯定听说过的大…

Ewwii:一个可扩展的Linux桌面Widget系统实战指南

Ewwii:一个可扩展的Linux桌面Widget系统实战指南

2026/8/30 17:02:08

Ewwii 是一个面向 Linux 桌面的可扩展 widget 系统。它解决的问题很具体:Linux 桌面没有一个统一的小组件规范,系统状态、音乐控制、日历、快捷启动、文字提醒这些内容,要么用桌面环境自带模块拼,要么靠一堆独立脚本和工具硬凑&am…

网易游戏运营管理实习面经:笔试、业务面与终面全流程复盘

网易游戏运营管理实习面经:笔试、业务面与终面全流程复盘

2026/8/30 17:02:08

2021年春天,我盯着网易游戏校招官网上的“游戏运营管理实习生”岗位犹豫了很久。身边投这个岗的人不少,但能走完流程的没几个,前一年秋招甚至有同学挂在笔试环节。犹豫没什么用,我改完简历按下提交的那一刻,其实心里也…

2026年下半年黑龙江省应急管理厅事业单位公开招聘工作人员4人公告

2026年下半年黑龙江省应急管理厅事业单位公开招聘工作人员4人公告

2026/8/30 17:02:08

按照《黑龙江省事业单位公开招聘工作人员实施细则》, 以及《关于进一步做好全省事业单位公开招聘工作的通知》等相关规定, 经过深入研究之后作出决定, 现在就公开招聘事业单位工作人员的有关事宜进行如下公告:一、招聘计划这一回计划招聘事业单位的工作人员, 总计存在4个名额。…

如何运行Python文件 , 如何在Linux中运行Python源文件

如何运行Python文件 , 如何在Linux中运行Python源文件

2026/8/30 17:02:08

这部文本会针对大家去讲述, 怎样去运行文件, 以及, 怎样于Linux之中运行源文件, 所对应的知识要点, 期望对各位能有帮助, 可别忘记收藏这个站点哦。倘若要去编写脚本, 能够借助文本编辑器比如去创建一个.txt文件, 随后把文件后缀改成.py。紧接着在命令提示符(cmd)里运行E:\test…

从创意到可运行程序:用Python快速搭建原型的实用指南

从创意到可运行程序:用Python快速搭建原型的实用指南

2026/8/30 16:52:08

“我有个绝妙的idea,就差一个程序员”——这句话在技术圈被当成段子已经很多年了。放在十年前,它的潜台词基本是“我不会写代码,所以什么都做不了”。但放到现在,这句话其实可以翻译成另一层意思:我手里有一个需求&…

备战数据库管理工程师校招:索引、事务、备份恢复核心考点解析

备战数据库管理工程师校招:索引、事务、备份恢复核心考点解析

2026/8/30 0:01:07

每年校招季我都会接触不少准备数据库方向笔试的同学,看到最多的状态就是:简历上写着“熟悉 MySQL”“了解索引优化”,一碰到数据库管理工程师的笔试卷,却在索引、事务、锁、备份恢复这些题目上翻车。网易这套 2018 校园招聘数据库…

数字电路时序基石:深入理解建立时间与保持时间

数字电路时序基石:深入理解建立时间与保持时间

2026/8/30 0:01:07

1. 这不是“背公式”的事:时间参数到底在约束什么你翻过数字电路教材,一定见过这两个词:建立时间(Setup Time)和保持时间(Hold Time)。它们常被并列写在触发器(Flip-Flop&#xff09…

蓝桥杯国赛超声波测距机:从单片机原理到嵌入式系统实战

蓝桥杯国赛超声波测距机:从单片机原理到嵌入式系统实战

2026/8/30 0:01:07

1. 项目缘起:从赛题到超声波测距机的诞生第八届蓝桥杯单片机设计与开发国赛的题目,我至今记忆犹新。它没有直接给出一个花哨的名字,而是用“超声波测距机”这个朴实无华的功能描述,精准地勾勒出了考核的核心。对于当时备赛的我而言…

备战数据库管理工程师校招:索引、事务、备份恢复核心考点解析

备战数据库管理工程师校招:索引、事务、备份恢复核心考点解析

2026/8/30 0:01:07

每年校招季我都会接触不少准备数据库方向笔试的同学,看到最多的状态就是:简历上写着“熟悉 MySQL”“了解索引优化”,一碰到数据库管理工程师的笔试卷,却在索引、事务、锁、备份恢复这些题目上翻车。网易这套 2018 校园招聘数据库…

数字电路时序基石:深入理解建立时间与保持时间

数字电路时序基石:深入理解建立时间与保持时间

2026/8/30 0:01:07

1. 这不是“背公式”的事:时间参数到底在约束什么你翻过数字电路教材,一定见过这两个词:建立时间(Setup Time)和保持时间(Hold Time)。它们常被并列写在触发器(Flip-Flop&#xff09…

蓝桥杯国赛超声波测距机:从单片机原理到嵌入式系统实战

蓝桥杯国赛超声波测距机:从单片机原理到嵌入式系统实战

2026/8/30 0:01:07

1. 项目缘起:从赛题到超声波测距机的诞生第八届蓝桥杯单片机设计与开发国赛的题目,我至今记忆犹新。它没有直接给出一个花哨的名字,而是用“超声波测距机”这个朴实无华的功能描述,精准地勾勒出了考核的核心。对于当时备赛的我而言…

摆脱论文困扰!盘点2026年全网爆红的的AI论文写作工具

摆脱论文困扰!盘点2026年全网爆红的的AI论文写作工具

2026/8/28 7:35:26

一天写完毕业论文在2026年已不再是天方夜谭。2026年最炸裂、实测能大幅提速的AI论文写作工具,覆盖选题构思、文献整理、内容生成、格式排版等核心场景,真正帮你高效搞定论文难题。 一、全流程王者:一站式搞定论文全链路(一天定稿首…

导师推荐!2026最新AI论文工具测评与实用推荐

导师推荐!2026最新AI论文工具测评与实用推荐

2026/8/28 7:34:51

2026年真正好用的AI论文工具,核心看生成的论文质量、低AI味、格式正确、学术适配四大指标。综合实测,千笔AI、ThouPen、豆包、DeepSeek、Grammarly 是当前最值得推荐的梯队,覆盖从免费到付费、从中文到英文、从文科到理工的全场景需求。 一、…

告别游戏崩溃:XCOM 2模组管理器的智能革命

告别游戏崩溃:XCOM 2模组管理器的智能革命

2026/8/28 7:34:35

告别游戏崩溃:XCOM 2模组管理器的智能革命 【免费下载链接】xcom2-launcher The Alternative Mod Launcher (AML) is a replacement for the default game launchers from XCOM 2 and XCOM Chimera Squad. 项目地址: https://gitcode.com/gh_mirrors/xc/xcom2-lau…