Detour

来自MDCS wiki
Maintenance script讨论 | 贡献2026年4月11日 (六) 16:32的版本 (add)
(差异) ←上一版本 | 最后版本 (差异) | 下一版本→ (差异)
跳到导航 跳到搜索

DetourLite独立插件开发

1. 前言

Detour仓库里的 `Standalone` 目录给出了一个可直接复用的扩展方式:把扩展代码单独编译成 `*Plugin.dll`,部署到 DetourLite 的 `plugins` 目录,在不改动 `DetourCore` / `DetourDance` 主工程的前提下扩展算法核。

这种 Standalone Plugin 适合以下场景:

  • 增加自定义 HTTP API。
  • 对接 ROS2、原生 C/C++ 动态库、UDP/TCP 传感器。
  • 新增自定义 2D / 3D 传感器组件类型。
  • 在启动时执行一次性初始化逻辑。

仓库中的参考工程:

  • `Standalone/DemoPlugin`
  • `Standalone/StandaloneSensors`
  • `Standalone/DllImportSensor`

2. 加载机制

DetourLite 的插件加载逻辑在 `DetourLite/DetourLiteMain.cs` 中,规则很直接:

  • 启动时扫描当前目录下 `plugins` 文件夹以及所有子目录。
  • 所有文件名匹配 `*Plugin.dll` 的程序集都会被 `Assembly.LoadFrom(...)` 加载。
  • 如果程序集里导出了一个名为 `Plugin` 的类,并且该类里存在 `static void Main()`,则该方法会在 `DetourLib.Init()` 之后被调用。

因此有两个层次的能力:

  • 只要 DLL 名称以 `Plugin.dll` 结尾,程序集就会被加载。
  • 如果还提供 `Plugin.Main()`,就可以执行启动初始化逻辑。

这意味着:

  • 纯组件型插件 可以只提供导出的组件类,不一定非要写 `Plugin.Main()`。
  • 启动型插件 则应提供 `Plugin.Main()`,在里面注册 HTTP 路由、启动线程、初始化第三方库等。

3. 组件是如何注册进 Detour 的

DetourCore 使用 `DetourCore/Misc/JSONLoader.cs` 从当前 AppDomain 中所有已加载程序集的导出类型里查找组件类型。

所以自定义组件要满足下面几个条件:

  • 类型必须是 `public`。
  • 类型必须继承 `LayoutDefinition.Component`,通常继承 `Lidar.Lidar2D` 或 `DetourDance.Lidar3D`。
  • 类型必须有无参构造函数。
  • 类型所在程序集必须在 Detour 读取 `detour.json` 之前已经被加载。

组件类型通常用属性声明:

[LayoutDefinition.ComponentType(Name = "ROS2d lidar")]
public class ROS2Lidar2D : Lidar.Lidar2D
{
}

公开字段可配合以下属性暴露到参数界面和状态输出:

  • `FieldMember`
  • `StatusMember`
  • `MethodMember`

4. 你可以做的两类插件

4.1 启动型插件

这类插件只关心 DetourLite 启动后执行逻辑,不一定要新增配置里的组件类型。最常见用途是挂额外 API。

`Standalone/DemoPlugin/loader.cs` 就是这种模式。它在 `Plugin.Main()` 里注册了:

  • `/eraseCloud`
  • `/get2dlm`
  • `/mycall`
  • `/getPCD`

最小示例:

using DetourCore;

namespace MyStandalonePlugin
{
    public class Plugin
    {
        public static void Main()
        {
            PicoHttpServer.AddGetHandler("/helloPlugin", () =>
            {
                return "hello from standalone plugin";
            });
        }
    }
}

4.2 组件型插件

这类插件把新传感器或新部件编译进独立 DLL,让 DetourLite 在读取 `detour.json` 时识别出来。

仓库里的对应示例:

  • `Standalone/DemoPlugin/ros2lidar2d.cs`
  • `Standalone/StandaloneSensors/Mid360Lidar.cs`
  • `Standalone/StandaloneSensors/LeishenC16Lidar.cs`
  • `Standalone/DllImportSensor/CppLidar.cs`

5. 2D 传感器插件写法

2D 传感器通常继承 `DetourCore.CartDefinition.Lidar.Lidar2D`,重点是重写两个方法:

  • `InitReadLidar()`
  • `ReadLidar()`

最小骨架:

using System.Threading;
using DetourCore.CartDefinition;

[LayoutDefinition.ComponentType(Name = "My UDP Lidar")]
public class MyUdpLidar : Lidar.Lidar2D
{
    private readonly object sync = new();
    private Lidar.LidarFrame latest = new();
    private int scanC = 0;

    public override void InitReadLidar()
    {
        new Thread(() =>
        {
            while (true)
            {
                latest = new Lidar.LidarFrame()
                {
                    counter = ++scanC,
                    raw = new[]
                    {
                        new Lidar.Lidar2D.RawLidar
                        {
                            th = 0,
                            d = 1000,
                            intensity = 1
                        }
                    }
                };

                lock (sync)
                    Monitor.PulseAll(sync);
            }
        }).Start();
    }

    public override Lidar.LidarFrame ReadLidar()
    {
        lock (sync)
            Monitor.Wait(sync);
        return latest;
    }
}

2D 输出约定:

  • `th`:角度,单位是
  • `d`:距离,单位是 毫米
  • `intensity`:反射强度,`float`。
  • `counter`:单调递增帧号。

Detour 的 2D 预处理、去拖尾、车体滤波、反光板提取、激光里程计等逻辑都在基类中完成,所以插件作者只需要稳定地产生原始帧。

6. 3D 传感器插件写法

3D 传感器通常继承 `DetourDance.Lidar3D`,同样重写:

  • `InitReadLidar()`
  • `ReadLidar()`

3D 返回值是 `LidarOutput3D`,关键字段如下:

  • `azimuth`:水平角,单位度。
  • `altitude`:俯仰角,单位度。
  • `d`:距离,单位毫米。
  • `intensity`:反射强度。
  • `progression`:该点在当前扫描周期里的进度,推荐归一化到 `0..1`。
  • `tick`:单调递增扫描序号。

如果你的数据源来自原生库,可以参考 `Standalone/DllImportSensor/CppLidar.cs`;如果来自 ROS2,可以参考 `Standalone/DemoPlugin/ros2lidar2d.cs`;如果来自 UDP 包流,可以参考 `Standalone/StandaloneSensors/*`。

7. 开发环境与引用 DLL

Detour 独立插件不是通过 NuGet 分发 SDK,而是直接引用发布到 MDCS 站点的参考 DLL。

运行包下载入口:

插件开发常用引用:

可选辅助引用:

为方便拉取这些 DLL,提供一个下载脚本:

典型用法:

Invoke-WebRequest `
  -Uri "https://mdcs.lessokaji.com/res/get-detour-plugin-sdk.ps1" `
  -OutFile ".\get-detour-plugin-sdk.ps1"

powershell -ExecutionPolicy Bypass `
  -File .\get-detour-plugin-sdk.ps1 `
  -Destination .\tools

执行后会得到:

  • `tools\RefDetourCore.dll`
  • `tools\RefDetourDance.dll`
  • `tools\deps\RefFundamentalLib.dll`
  • `tools\deps\LessokajiWeaverUtilities.dll`

8. csproj 参考写法

`Standalone/DemoPlugin/DemoPlugin.csproj` 给出了一个可直接工作的最小模式。

一个简化版示例如下:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Library</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <Reference Include="FundamentalLib">
      <HintPath>..\tools\deps\RefFundamentalLib.dll</HintPath>
    </Reference>
    <Reference Include="DetourCore">
      <HintPath>..\tools\RefDetourCore.dll</HintPath>
    </Reference>
    <Reference Include="DetourDance">
      <HintPath>..\tools\RefDetourDance.dll</HintPath>
    </Reference>
    <Reference Include="LessokajiWeaverUtilities">
      <HintPath>..\tools\deps\LessokajiWeaverUtilities.dll</HintPath>
    </Reference>
  </ItemGroup>
</Project>

如果希望本地构建后直接落到运行目录,可以像 `Standalone/DemoPlugin` 那样把 `OutputPath` 设到 DetourLite 的 `plugins` 目录,这对调试最方便。

9. 构建与部署

建议流程:

  1. 在独立工程中编译插件 DLL。
  2. 确认文件名以 `Plugin.dll` 结尾,例如 `StandaloneSensorsPlugin.dll`。
  3. 把 DLL 复制到 DetourLite 运行目录下的 `plugins` 子目录。
  4. 如果插件依赖额外原生库,也一起放到 DetourLite 可搜索到的位置。
  5. 启动 DetourLite,检查控制台是否打印:
    • 找到并加载了 `*Plugin.dll`
    • 找到了 `Plugin.Main()`
    • 或至少程序集被成功加载

本仓库当前已验证可编译的示例:

  • `Standalone/DemoPlugin`
  • `Standalone/StandaloneSensors`
  • `Standalone/DllImportSensor`

10. 当前限制

这一点非常重要:

  • DetourLite 当前会自动扫描 `plugins\*Plugin.dll`。
  • Windows 版 Detour.exe 当前仓库里 没有 对等的自动插件加载器。

这会带来两个直接影响:

  • 如果插件只是给 DetourLite 增加 HTTP API,这没有问题。
  • 如果插件新增了自定义组件类型,而你又想在 Windows `Detour.exe` 里直接通过 UI 新建、编辑、加载这种组件,那么客户端进程也必须先把同一个程序集加载进来,否则 UI 的类型列表和 JSON 反序列化都看不到该类型。

换句话说,当前代码最稳妥的使用方式 是:

  • 纯扩展 API:只部署到 DetourLite。
  • 自定义组件:优先用于 Lite 端独立运行场景;如果还要配合 Windows UI 使用,则需要让客户端也加载同一个插件程序集。

11. 推荐开发方法

结合仓库里的 `Standalone` 示例,推荐按下面的顺序开发:

  1. 先做一个只包含 `Plugin.Main()` 的最小插件,确认 DLL 能被 DetourLite 发现并执行。
  2. 再加一个最小 HTTP 路由,确认运行时初始化逻辑没问题。
  3. 如果要接传感器,再新建一个继承 `Lidar2D` 或 `Lidar3D` 的类型,先返回最小假数据。
  4. 假数据跑通后,再接入 ROS2 / UDP / 原生 DLL 等真实数据源。
  5. 最后再补 `FieldMember` / `StatusMember`,把参数和状态整理到可维护的程度。

这样排查最简单,能快速区分是:

  • 加载失败,
  • 初始化失败,
  • 组件识别失败,
  • 还是数据源本身有问题。

12. 结语

Standalone Plugin 的核心价值是:把项目级扩展放在独立 DLL 里维护,而不是不断改 Detour 主工程。对于项目交付、客户定制、特定传感器桥接、快速试验都更合适。

如果只是想扩 API,写启动型插件即可;如果要接自定义雷达,就按 `Lidar2D` / `Lidar3D` 的约定返回原始帧,让 Detour 继续负责后面的预处理和 SLAM 计算。