WLED LD2410_v2 Usermod 实战:用 24GHz 毫米波雷达实现移动检测与 MQTT/Home Assistant 集成
【免费下载链接】WLEDControl WS2812B and many more types of digital RGB LEDs with an ESP32 over WiFi!项目地址: https://gitcode.com/GitHub_Trending/wl/WLED
本文基于 WLED 官方仓库中的 LD2410_v2 usermod 及其完整实现源码展开,讲解如何把 LD2410 毫米波雷达传感器接入 WLED(ESP32),实现“移动/静止”两种存在检测状态:状态不仅会显示在 WLED Web UI 的 Info 页面,还会发布到 MQTT 话题并自动完成 Home Assistant Discovery。读完本篇后,你可以独立完成该 usermod 的编译启用、串口引脚配置、MQTT 话题订阅,以及 Home Assistant 中 motion/occupancy 二进制传感器的落地,并能对照源码理解其 1 秒轮询与 5 分钟心跳保活的完整数据流。
一、功能定位与硬件前提
该 usermod 的核心功能是读取 LD2410 移动/存在(movement/presence)雷达传感器的状态,并将两类状态分别呈现:
- 移动状态(movement):检测到运动目标;
- 静止状态(stationary):检测到静止目标(有人但不动)。
原始文档明确了两个关键前提:
- 需要第二路 UART:传感器通过独立串口(ESP32 上的
Serial1)通信,占用一对 GPIO 引脚; - 仅在 ESP32 上经过测试:不支持 ESP8266 等平台。
从源码 LD2410_v2.cpp 还能看到第三个硬性前提:
#ifdef WLED_DISABLE_MQTT #error "This user mod requires MQTT to be enabled." #endif也就是说,如果在构建时禁用了 MQTT,该模块会直接在编译期报错。文档中的 Dependencies 一节也对应说明:数据通过 MQTT 发布,必须先启用 WLED 的 MQTT 同步接口。
二、依赖库:ncmreynolds/ld2410
该 usermod 本身不包含 LD2410 协议实现,而是依赖 PlatformIO 注册表中的ncmreynolds/ld2410库,版本约束见 library.json:
{ "name": "LD2410_v2", "build": { "libArchive": false }, "dependencies": { "ncmreynolds/ld2410": "^0.1.3" } }两个细节值得注意:
^0.1.3表示允许 0.1.x 范围内的较新版本;"build": { "libArchive": false }是 WLED usermod 的强制要求——usermod 必须直接链接进可执行文件而不是归档成静态库。构建脚本 load_usermods.py 会在构建期检查此项,缺失时会以ERROR: libArchive=false is missing on usermod(s) ...报错并终止构建。
WLED 的构建系统在启用 usermod 时,会自动把该库声明的dependencies一起解析为lib_deps,无需手工在 platformio 配置里重复声明。
三、编译启用:custom_usermods 机制
3.1 在 platformio_override.ini 中启用
原始文档给出的编译方式为:在custom_usermods中加入该模块名(例如放入 platformio_override.ini),文档示例如下:
[env:usermod_USERMOD_LD2410_esp32dev] extends = env:esp32dev custom_usermods = ${env:esp32dev.custom_usermods} LD2140_v2勘误提示:原文档中出现的
LD2140/LD2140_v2是笔误(“4”与“1”位置颠倒),且文档 H1 标题误写为 “BH1750 usermod”,均为复制残留。实际目录名为 usermods/LD2410_v2,应填写LD2410_v2(或简写LD2410)。
这里的关键在于${env:esp32dev.custom_usermods}写法:它继承所选基础环境(如esp32dev)已有的 usermod 列表并追加本模块,避免清空原有配置。仓库中的示例文件 platformio_override.sample.ini 展示了完全相同的继承模式,例如:
custom_usermods = ${env:esp32dev.custom_usermods} Temperature four_line_display_ALT3.2 名称解析规则:为什么可以省略 _v2 后缀
从构建脚本 load_usermods.py 的find_usermod()可以看到解析逻辑:
def find_usermod(mod: str) -> Path: mp = usermod_dir / mod # 精确匹配 if mp.exists(): return mp mp = usermod_dir / f"{mod}_v2" # 尝试 name_v2 if mp.exists(): return mp mp = usermod_dir / f"usermod_v2_{mod}" # 尝试 usermod_v2_name if mp.exists(): return mp raise RuntimeError(f"Couldn't locate module {mod} in usermods directory!")因此写LD2410会被自动解析到usermods/LD2410_v2/目录,写LD2410_v2则直接命中。这与 AGENTS.md 中 “usermod_v2_prefix or_v2suffix can be omitted” 的规范一致。解析成功后,模块以symlink://形式注入lib_deps参与编译。
四、初始化与串口通信:setup() 的实现
启用模块后,WLED 启动阶段会调用Usermod::setup()。该模块的 setup() 实现为:
void setup() { Serial1.begin(256000, SERIAL_8N1, uart_rx_pin, uart_tx_pin); Serial.print(F("\nLD2410 radar sensor initialising: ")); if(radar.begin(Serial1)){ Serial.println(F("OK")); } else { Serial.println(F("not connected")); } initDone = true; }要点:
- 使用 ESP32 的Serial1(第二路 UART),波特率256000,8N1 格式——这是 LD2410 模块的出厂通信参数;
- 默认引脚:
default_uart_rx = 19、default_uart_tx = 18(源码 L18-L19); - 初始化结果会打印到串口监视器(
OK/not connected),但注意:即使radar.begin()返回失败,initDone仍会置为 true,后续loop()会持续尝试,并在每秒的连接检查中更新sensorFound标志。
五、主循环逻辑:1 秒轮询 + 状态变化发布 + 5 分钟心跳
loop()每轮主循环被调用一次,其完整逻辑(源码 L103-L133)可以拆解为四层:
- 快速退出:
if (!enabled || strip.isUpdating()) return;—— 模块被禁用、或 LED 灯带正在刷新时直接返回。注释中特别提示:在超长灯带上strip.isUpdating()可能恒为 true,此时轮询会被永久跳过,需要“update accordingly”; - 每秒一次采样:
radar.read()每次都会执行,但状态评估与发布被节流到1000ms 间隔(curr_time - lastTime > 1000); - 连接检查优先:
sensorFound = radar.isConnected(),未连上传感器时直接 return,不发布任何状态; - 变化触发发布 + 心跳:
presenceDetected()得到静止状态,movingTargetDetected()得到移动状态;- 仅当状态相对上一次发生变化时才发布 MQTT 消息;
- 若 5 分钟内没有任何状态变化,会主动重发当前两个状态作为“活着”的心跳(
curr_time - last_mqtt_sent > 1000*60*5),保证 HA 侧的expire_after不会误判离线。
stationary_detected = radar.presenceDetected(); if(stationary_detected != last_stationary_state){ if (WLED_MQTT_CONNECTED){ publishMqtt("/ld2410/stationary", stationary_detected ? "ON":"OFF", false); last_stationary_state = stationary_detected; } } movement_detected = radar.movingTargetDetected(); if(movement_detected != last_movement_state){ if (WLED_MQTT_CONNECTED){ publishMqtt("/ld2410/movement", movement_detected ? "ON":"OFF", false); last_movement_state = movement_detected; } }其中WLED_MQTT_CONNECTED是 WLED 核心提供的宏,定义于 wled00/wled.h:#define WLED_MQTT_CONNECTED (mqtt != nullptr && mqtt->connected()),即“MQTT 客户端存在且已连接”。所有发布都通过它做双重保护(循环内判断一次,publishMqtt()内部再判断一次),避免未连接时调用mqtt->publish()崩溃。
六、MQTT 话题结构与 Home Assistant Discovery
6.1 状态话题
原文档说明状态发布到/movement和/stationary话题;结合 源码 中publishMqtt()的拼接逻辑(mqttDeviceTopic+ 子话题),实际完整话题为:
| 话题 | 载荷 | 触发时机 |
|---|---|---|
<WLED设备话题>/ld2410/movement | ON/OFF | 移动状态变化、5 分钟心跳 |
<WLED设备话题>/ld2410/stationary | ON/OFF | 静止状态变化、5 分钟心跳 |
<WLED设备话题>/ld2410/status | I am alive! | MQTT 连接建立且传感器已连接时(onMqttConnect) |
所有消息均为非保留(retain = false)发布。
6.2 Home Assistant 自动发现
源码中HomeAssistantDiscovery默认为true(L25)。MQTT 首次连接后,_mqttInitialize()会调用_createMqttSensor()为两个状态各发布一条发现配置(L44-L85):
- 配置话题:
homeassistant/binary_sensor/<mqttClientID>/Movement/config与.../Stationary/config; - 设备类:Movement 为
motion,Stationary 为occupancy; - 载荷约定:
payload_on = "ON",payload_off = "OFF"; - 离线判定:
expire_after = 1800(30 分钟无消息 HA 判定实体离线——与第五节的 5 分钟心跳配合,正常情况下不会误报); - 设备聚合:两个传感器挂在同一虚拟设备下(
identifiers = wled-sensor-<mqttClientID>,manufacturer 为WLED,model 为FOSS,sw_version 取当前固件版本串),在 HA 中呈现为一个 WLED 设备下的两个二进制传感器。
这意味着在 WLED 中开启 MQTT 同步后,Home Assistant 中无需手动建模即可出现“移动”和“占用”两个传感器实体。
七、Web UI Info 页面展示
addToJsonInfo()(L136-L156)向/json/info的u(usermod)对象中追加两个数组,Web UI 的 Info 区块会显示为两行文本:
| 场景 | "LD2410 Stationary" 显示 | "LD2410 Movement" 显示 |
|---|---|---|
| 模块被禁用 | disabled | disabled |
| 未检测到传感器 | LD2410 Not Found | (空) |
| 正常工作 | Sta ON/Sta OFF | Mov ON/Mov OFF |
这是无需 MQTT 客户端即可肉眼确认传感器是否在线、状态是否正常跳变的最快途径。
八、配置项详解:enabled 与 UART 引脚
8.1 持久化配置
addToConfig()/readFromConfig()(L158-L188)定义了写入 cfg.json 的键(节点名LD2410Usermod):
| 键名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
enabled | bool | true | 启用/禁用 usermod |
uart_rx_pin | int8 | 19 | Serial1 的 GPIO 输入引脚 |
uart_tx_pin | int8 | 18 | Serial1 的 GPIO 输出引脚 |
readFromConfig()中使用了三参数getJsonValue()的缺省回退写法——即使 cfg.json 被手工编辑导致某个键缺失,也能用默认值兜底(源码注释明确说明这一设计考虑)。
8.2 一个需要留意的实现细节
原文档称 “Usermod 设置页可以配置 RX/TX 引脚”。但从源码结构看,当前实现没有重写appendConfigData()(该方法在 Usermod 基类 中用于向设置页注入 UI 控件),因此设置页面上实际没有可编辑的引脚滑杆;addToConfig()每次保存设置时写入的也是编译期默认值19/18。也就是说,若要改用其他 UART 引脚,只能修改源码中的default_uart_rx/default_uart_tx后重新编译——这是使用与二开该模块时最容易被文档与代码差异“坑到”的一点。
九、注册机制与唯一 ID
模块通过 WLED 的 linker-section 注册机制接入主循环:
static LD2410Usermod ld2410_v2; REGISTER_USERMOD(ld2410_v2);其中REGISTER_USERMOD宏定义在 wled00/fcn_declare.h,本质是把模块实例指针放入名为usermods的 DYNARRAY 链接段,UsermodManager启动时遍历该段完成 setup/loop 调度。
本模块重写了getId()并返回USERMOD_ID_LD2410(值为 52,注册于 wled00/const.h)。按 AGENTS.md 的规范,唯一 ID 只在需要跨 usermod 查找、PinManager 引脚所有权、或在/json/info的um数组中可识别时才必须——本模块采用固定 ID 使其在 JSON info 输出中可被明确辨识。
十、排障清单
结合源码与文档,常见故障可按以下路径排查:
- Info 页显示 "LD2410 Not Found":
radar.isConnected()为 false。确认接线是否接对了 Serial1 的 RX/TX(交叉连接:WLED TX → 模块 RX,WLED RX → 模块 TX)、供电是否正常、串口监视器中是否打印了not connected; - MQTT 一直无消息:先确认 WLED 已启用 MQTT 同步接口且
WLED_MQTT_CONNECTED为真(可在 HA 中观察.../ld2410/status话题的I am alive!消息是否收到);未连接时模块按设计不发布任何状态; - 构建报错 “This user mod requires MQTT to be enabled.”:当前构建环境定义了
WLED_DISABLE_MQTT,需更换启用 MQTT 的环境; - 超长灯带上状态永远不更新:
strip.isUpdating()恒真导致loop()提前返回,这是源码注释中明确提示的已知限制; - 名称写错导致编译找不到模块:
find_usermod()会抛出Couldn't locate module ...,注意区分LD2410(正确)与文档笔误LD2140。
十一、版本沿革
文档 Change log 记录:2024 年 6 月由 @wesleygas 创建。当前仓库中的实现(1 秒轮询、HA Discovery、5 分钟心跳、I am alive!状态话题)即为该 v2 版本的完整形态,代码位于 usermods/LD2410_v2/LD2410_v2.cpp,依赖声明位于 usermods/LD2410_v2/library.json。
参考文件
- 模块文档:usermods/LD2410_v2/readme.md
- 模块实现:usermods/LD2410_v2/LD2410_v2.cpp
- 依赖声明:usermods/LD2410_v2/library.json
- Usermod 基类与注册宏:wled00/fcn_declare.h
- Usermod ID 定义:wled00/const.h
- MQTT 连接状态宏:wled00/wled.h
- usermod 构建解析脚本:pio-scripts/load_usermods.py
- 构建配置示例:platformio_override.sample.ini
- Usermod 开发规范:AGENTS.md
【免费下载链接】WLEDControl WS2812B and many more types of digital RGB LEDs with an ESP32 over WiFi!项目地址: https://gitcode.com/GitHub_Trending/wl/WLED
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考