如何适配新的雷达

来自MDCS wiki2
跳到导航 跳到搜索


概述 / Overview

本页是 激光雷达插件适配 的总入口。MDCS 把雷达 / 相机视为一类 传感器插件,通过 Medulla2 的 `IOObject` 体系热插拔。开发新雷达适配的目标是写一个 .NET DLL,包含名为 `MainIOObject` 的类,继承自下列基类之一:

This is the entry page for adapting a new lidar to MDCS. Medulla2 treats lidars (and cameras) as sensor plugins: a single .NET DLL whose entry class is named `MainIOObject` and inherits from one of the bases below.

传感器 / Sensor 基类 / Base class 文件 / File 详细教程 / Detailed guide
2D 激光雷达 / 2D lidar `Lidar2DIOObject` `D:\src\M2\OfficialPlugins\LidarController\Lidar2DIOObject.cs` 2D激光雷达适配
3D 激光雷达 / 3D lidar `Lidar3DIOObject` `D:\src\M2\OfficialPlugins\LidarController\Lidar3DIOObject.cs` 3D激光雷达适配
相机(2D/3D)/ Camera `IOObject`(约定 `MainIOObject` 类名)/ `IOObject` (convention: class name `MainIOObject`) `D:\src\M2\MedullaCore\Types\IO.cs:47` 3D相机适配

适配工作量速查 / Adaptation effort cheat-sheet

难度 / Difficulty 雷达类型 / Lidar type 典型工作 / Typical work 工时 / Effort
⭐ Easy 厂商提供 .NET SDK 的 2D / 3D 雷达 / 2D/3D with vendor .NET SDK 包一层适配,把 SDK 回调写到 cachedLidar / Wrap SDK callbacks into `cachedLidar` 0.5–1 d
⭐⭐ Medium 协议公开但需自己解析报文(TCP / UDP)/ Open protocol, parse frames 帧解析 + 字节序 + 点云转换 / Frame parsing + endianness + point conversion 1–3 d
⭐⭐⭐ Hard 协议私有 / 半私有,需逆向 / 现场抓包 / Closed protocol, reverse-engineer + 协议分析 + 异常恢复 / Plus protocol analysis & failure recovery 3–10 d

适配前先做的事 / Before you start

1. 拿到雷达的硬件说明书与通信手册(角分辨率、扫描频率、协议、数据格式)。
Get the hardware datasheet and protocol manual (angular resolution, scan rate, protocol, frame layout). 2. 用厂商工具确认雷达 数据流通 — 设备通电、连上电脑、能看到点云。
Use the vendor tool to verify the device powers up and streams. 3. 决定使用的通讯方式(TCP / UDP / 串口 / USB CAN)。
Pick the transport (TCP / UDP / serial / USB CAN). 4. 用 MDCS-plugin-helper 创建工程骨架:
Scaffold the project with MDCS-plugin-helper:

   cd MDCS-plugin-helper
   python generate.py
   # 选择 lidar sensor plugin / Select "lidar sensor plugin"
   # 输入工程名,例如 / Project name e.g.: WLR716Lidar

关键概念 / Key concepts

数据模型 / Data model

雷达插件每扫完一帧,把点云填入基类的 `cachedLidar` 字段(`LidarPoint2D[]` 或 `LidarPoint3D[]`),然后调用 `output()`。`output()` 把当前帧序列化到 `DObject`(共享内存);Detour 在另一进程订阅这个 DObject 拿数据。

A lidar plugin fills its frame into `cachedLidar` (`LidarPoint2D[]` for 2D, `LidarPoint3D[]` for 3D) once per full scan, then calls `output()`. `output()` serialises to a named `DObject` (shared memory); Detour subscribes from another process and reads from there.

2D 点 / 2D point:

public class LidarPoint2D
{
    public float th;          // 角度 / angle (rad or degrees per plugin)
    public float d;            // 距离 / distance (mm)
    public float intensity;    // 归一化反射率 / normalised reflectivity
}

3D 点 / 3D point:

public class LidarPoint3D
{
    public float d;            // 距离 / distance
    public float azimuth;      // 方位角 / azimuth
    public float altitude;     // 俯仰角 / altitude
    public float intensity;
    public float progression;  // 用于多回波合并 / multi-echo
}

生命周期 / Lifecycle

 io load plugins/WLR716Lidar.dll  →  Reflection 找到 MainIOObject 并实例化
                                 →  插件 Start(...) 启动通信线程
                                 →  线程内循环: 读包 → 解析 → fill cachedLidar → output()
                                 →  Medulla / Detour 在 DObject 上等到帧后继续工作

启动命令(`startup.iocmd`): The Medulla startup script (`startup.iocmd`) wires it up:

lidar = io load plugins/WLR716Lidar.dll
lidar Start 192.168.0.2 2110

关键字段 / Key fields

字段 / Field 类型 含义 / Meaning
`cachedLidar` `LidarPoint2D[]` / `3D[]` 最新一整帧点云 / latest full-scan cloud
`frame` `long` 帧计数器,单调递增 / monotonically increasing frame counter
`scanC` `int` 雷达自上电起的帧号 / device's own frame count since power-on
`interval` `double` 上一帧到当前帧的耗时(ms)/ elapsed ms since previous frame
`tick` `long` 输出时刻的系统 tick / system tick at `output()`
`angleBias` `float` 角度偏置;用于硬件倒装或安装角误差 / angle offset (mount correction)
`mirror` `bool` 倒装标志,true 时 `th := -th` / mirror flag — negates `th`
`ReflexRange` `float` 反射率归一化分母 / reflectivity normaliser
`angleStart/End` `float` 输出帧的角度过滤区间 / output FOV filter
`minDist/maxDist` `float` 距离过滤区间 / distance filter
`pointsN` `int` 当前帧点数(output 前更新)/ point count

插件发现 / Plugin discovery

Medulla 加载 DLL 时通过反射查找 类名为 `MainIOObject` 的类型,并要求其有无参构造函数。无属性标记。 At load time Medulla looks for the type named `MainIOObject` via reflection; it must have a parameterless constructor. There is no `[Plugin]` attribute.

最小骨架 / Minimal skeleton

using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Threading;
using LidarController;

namespace WLR716Lidar
{
    public class MainIOObject : Lidar2DIOObject
    {
        private string _ip;
        private int _port = 2110;

        public MainIOObject()
        {
            // 在出厂正装情况下,雷达正前方对应的扫描起 / 止角度(看产品手册)
            // For factory-default mounting, the scan start/end relative to lidar forward.
            ScanStartAngle = -135;
            ScanEndAngle   = 135;
            ScanAngleSgn   = 1;        // 逆时针 / counter-clockwise
        }

        public void Start(string ip, int port = 2110)
        {
            _ip = ip; _port = port;
            new Thread(() =>
            {
                while (true)
                {
                    try { Loop(); }
                    catch (Exception ex)
                    {
                        Console.WriteLine($"{ex.Message}");
                        Thread.Sleep(1000);
                    }
                }
            }).Start();
        }

        private void Loop()
        {
            using var tcp = new TcpClient(_ip, _port);
            using var ns  = tcp.GetStream();

            var thetaList   = new List<float>();
            var distList    = new List<int>();
            var intensList  = new List<float>();
            var ticStart    = DateTime.Now;

            while (true)
            {
                // ... read frame header + payload (vendor-specific) ...
                // when one full scan accumulated:
                var cloud = new List<LidarPoint2D>(thetaList.Count);
                for (int i = 0; i < thetaList.Count; i++)
                    cloud.Add(new LidarPoint2D {
                        th = (mirror ? -thetaList[i] : thetaList[i]) + angleBias,
                        d  = distList[i],
                        intensity = intensList[i] / ReflexRange
                    });

                cachedLidar = cloud.ToArray();
                frame    += 1;
                scanC    += 1;
                interval  = (DateTime.Now - ticStart).TotalMilliseconds;
                ticStart  = DateTime.Now;
                tick      = DateTime.Now.Ticks;
                output();

                thetaList.Clear(); distList.Clear(); intensList.Clear();
            }
        }
    }
}

详细的协议解析、安装校准与 3D 雷达细节见对应的子页面: For per-protocol parsing, install-time calibration and 3D-specific details, see the dedicated pages:

校准与对齐 / Calibration & alignment

雷达适配完成后必做: After the plugin works, always:

  1. 外参标定 / Extrinsic calibration: 把雷达坐标系对齐到车体坐标系。教程见 标定激光雷达外参
  2. 水平度复核 / Horizontality check: 用水平仪 ± IMU 反馈确认雷达扫描平面与地面平行(高位叉车场景尤其重要)。
  3. 时间戳一致性 / Timestamp coherence: 多传感器 SLAM 场景中,`tick` 与系统 NTP 必须一致;> 50 ms 偏差会引起 SLAM 漂移。
  4. 强度归一化 / Intensity normalisation: 通过 `ReflexRange` 调整,让反光板的强度明显高于普通墙面(在 Detour 可视化中表现为偏红)。

Detour 端怎么收 / Detour-side consumption

Detour 在 `D:\src\Detour\DetourCore\CartDefinition\Lidar.cs:302-311` 通过 `DObject(name)` 订阅雷达数据,等待 `Wait()` 后反序列化 `LidarOutput`,再喂给 SLAM。所以插件作者完全不需要直接调用 Detour API — 把帧塞进 `cachedLidar` + 调 `output()` 即可。

Detour subscribes via `DObject(name)` and deserialises `LidarOutput` once `Wait()` returns. Plugin authors don't call Detour APIs directly — `cachedLidar` + `output()` is the whole contract.

外部里程计 / IMU 数据是另一条通路:通过 `TightCoupler.PostExternalFeed(...)`(见 `D:\src\Detour\DetourCore\Algorithms\TightCoupler.ExternalCoupler.cs`)。雷达插件本身不走这条。

External odometry / IMU enters Detour via a separate path (`TightCoupler.PostExternalFeed`). That is not the lidar plugin's job.

失败模式与对策 / Common failures

  • 雷达连不上 / Can't connect: 检查 IP / 防火墙,看 `Start()` 抛出的异常被外层 catch 后 sleep 重连。
  • 解析帧长不对 / Wrong frame length: 字节序错误,确认大端 / 小端;用 vendor 工具抓 1 帧对比。
  • 点云 0° 不在正前方 / 0° not aligned to forward: 用 `angleBias` 或 `ScanStartAngle/EndAngle` 修正。
  • 反射率值不合理 / Reflectivity off: 调整 `ReflexRange`;不同雷达把强度 / 反光率 / 回波 三个量混用,要看手册区分。
  • 帧率不稳定 / Jittery frame rate: 多线程同步问题。把读包改为完整 `while(want_len) Read`(同 `TestHe3051\Class1.cs`)。

工程示例 / Working example

完整可运行示例:`D:\src\cookbook\MDCS-plugin-helper\TestHe3051\Class1.cs`(145 行,TCP 流,He3051 雷达)。

Complete working example: `D:\src\cookbook\MDCS-plugin-helper\TestHe3051\Class1.cs` (145 lines, TCP, He3051 lidar).

相关页面 / See also