From 1c3e48d59f604dd8b0430f277d4d52bd5d45152d Mon Sep 17 00:00:00 2001 From: 7iwind <7wind@7iwind.cn> Date: Thu, 30 Jul 2026 10:49:44 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E6=8E=A5=E6=94=B6dis?= =?UTF-8?q?patch=E5=8F=8D=E5=90=91=E8=B7=AF=E7=94=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DESIGN.md | 424 +++++++---------------------------- README.md | 199 ++++------------ TEST_CASES.md | 62 ++--- build/DataRouterConfig.cmake | 19 +- build/example | Bin 17280 -> 17488 bytes build/test_datarouter | Bin 31064 -> 31320 bytes datarouter.c | 35 ++- datarouter.h | 27 ++- example.c | 73 +++--- tests/test_datarouter.c | 198 ++++++++-------- 10 files changed, 373 insertions(+), 664 deletions(-) diff --git a/DESIGN.md b/DESIGN.md index c671a88..668fe85 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -2,208 +2,57 @@ ## 1. 设计目标 -轻量级 C 语言数据路由库。发送与接收能力分开声明,端口绑定 `send_type` 和外设 handler 列表。调用 `dr_send_(data, len)` 时自动遍历端口表,广播到所有 `send_type` 匹配的端口。 +轻量级 C 语言数据路由库。发送能力与接收能力分开声明,端口可独立设置 `send_type` 和 `recv_type`。发送时自动广播到所有匹配端口,接收时通过 `dr_port_dispatch()` 查表自动路由到回调。 ### 核心设计 ``` -DR_SEND_CAPABILITIES DR_RECV_CAPABILITIES - DR_SEND(sensor, ...) DR_RECV(rtcm, ...) - DR_SEND(status, ...) DR_RECV(command, ...) - │ │ - ▼ ▼ - send_sensor(data, len) on_recv_rtcm(port, data, len) - │ (用户实现回调) - ▼ +发送: dr_send_sensor(data, len) + │ + ▼ 遍历端口表 → send_type 匹配? - │ - ▼ + │ + ▼ for each handler(port, data) ─── UART / TCP / CAN -``` -**关键**:send 无 port_id 参数,自动路由到所有匹配端口。 - -### 设计原则 - -| 原则 | 说明 | -|------|------| -| 收发分离 | 发送/接收能力在配置中分开声明,按需组合 | -| 编译期确定能力 | 所有能力在 `dr_config.h` 中声明,库自动生成代码 | -| 运行期绑定外设 | 端口创建后,通过 `add_handler` 注册物理外设发送函数 | -| 自动广播 | `send_(data, len)` 遍历所有端口,自动匹配 send_type | -| 不关心物理外设 | 库不依赖任何外设 API,只调用用户提供的 handler | -| 零动态内存 | 纯静态数组,无 malloc/free | - ---- - -## 2. 整体架构 - -### 2.1 模块依赖 - -```plantuml -@startuml -left to right direction - -rectangle "用户代码" as User { - component "应用逻辑\n调 send_(data, len)" as App - component "dr_config.h\n声明能力" as Cfg - component "外设 handler\nUART / TCP / CAN" as Handlers - component "接收回调\non_recv_()" as Callbacks -} - -database "DataRouter 库" as Lib { - component "datarouter.h\n公共 API + X-macro" as Hdr - component "datarouter.c\n端口表+遍历路由" as Impl -} - -App --> Hdr -Cfg --> Hdr -Impl --> Cfg -Handlers --> Impl : add_handler -Callbacks --> Impl : 实现 on_recv_ -@enduml -``` - -### 2.2 文件结构 - -| 文件 | 职责 | -|------|------| -| `dr_config.h` | 声明 `SEND_CAPABILITIES` 和 `RECV_CAPABILITIES` | -| `datarouter.h` | 公共 API + X-macro 展开 | -| `datarouter.c` | 端口表 + send 遍历 + 弱符号回调 | -| `example.c` | 使用示例 | -| `tests/test_datarouter.c` | 22 个单元测试 | - ---- - -## 3. 收发分离设计 - -### 3.1 配置声明 - -```c -#define DR_SEND_CAPABILITIES \ - DR_SEND(sensor, "Sensor data report") \ - DR_SEND(status, "Device status report") - -#define DR_RECV_CAPABILITIES \ - DR_RECV(rtcm, "RTCM correction data") \ - DR_RECV(command, "Remote control command") -``` - -### 3.2 展开产物 - -``` -DR_SEND_CAPABILITIES 展开: - enum: sensor = 0, status = 1, SEND_CAP_COUNT = 2 - 发送函数: int dr_send_sensor(const void *data, size_t len); - int dr_send_status(const void *data, size_t len); - 元数据: send_names[] = {"sensor", "status"} - send_descs[] = {"Sensor data report", "Device status report"} - -DR_RECV_CAPABILITIES 展开: - enum: rtcm = 0, command = 1, RECV_CAP_COUNT = 2 - 接收回调: void dr_on_recv_rtcm(uint32_t port, const void*, size_t); - void dr_on_recv_command(uint32_t port, const void*, size_t); - 弱符号默认: 空实现,用户覆盖 - 元数据: recv_names[] = {"rtcm", "command"} - recv_descs[] = {"RTCM correction data", "Remote control command"} -``` - -### 3.3 端口示例 - -``` -端口 A: send_type = sensor, handlers=[uart_send, tcp_send] -端口 B: send_type = sensor, handlers=[can_send] -端口 C: send_type = status, handlers=[uart_send] -``` - -调用 `send_sensor("temp", 4)` → A 和 B 同时触发。 - ---- - -## 4. X-macro 代码生成 - -```plantuml -@startuml -== datarouter.h == - --> dr_config.h: 获得两组宏列表 - -datarouter.h -> "1": #define DR_SEND(id,desc) DR_SEND_##id, -note right: 发送 enum - -datarouter.h -> "2": #define DR_RECV(id,desc) DR_RECV_##id, -note right: 接收 enum - -datarouter.h -> "3": #define DR_SEND(id,desc) int send_##id(data, len); -note right: 发送函数声明 - -datarouter.h -> "4": #define DR_RECV(id,desc) void on_recv_##id(...); -note right: 接收回调声明 - -== datarouter.c == - -datarouter.c -> "5": 弱符号回调默认实现 -datarouter.c -> "6": 发送函数实现(遍历端口表) -datarouter.c -> "7~8": send_names[] / send_descs[] / recv_names[] / recv_descs[] -@enduml +接收: dr_port_dispatch(port, data, len) + │ + ▼ + 查 recv_table[port.recv_type] + │ + ▼ + dr_on_recv_rtcm(port, data, len) ← 用户实现 ``` --- -## 5. 数据结构 +## 2. 数据结构 -### 5.1 核心数据类型 - -```plantuml -@startuml -skinparam classAttributeIconSize 0 - -enum "dr_send_cap" as S { SENSOR, STATUS, SEND_CAP_COUNT } -enum "dr_recv_cap" as R { RTCM, COMMAND, RECV_CAP_COUNT } - -class "port_entry_t" as Entry { - - int send_type - - dr_send_handler_t handlers[] - - int count - - uint8_t in_use -} - -class "dr_send_handler_t" as Handler { - + void (*)(uint32_t port_id, const void *data, size_t len) -} - -class "发送函数" as Send { - + int send_sensor(data, len) - + int send_status(data, len) -} - -class "接收回调" as Recv { - + void on_recv_rtcm(port, data, len) ← 用户实现 - + void on_recv_command(port, data, len) -} - -Entry --> S : send_type -Entry --> Handler : handlers[] -Send --> Entry : 遍历端口表 → 匹配 send_type → 遍历 handlers -Recv --> Entry : 用户根据业务调对应回调 -@enduml -``` - -### 5.2 `port_entry_t` +### 2.1 `port_entry_t` ```c #define DR_MAX_HANDLERS_PER_PORT 4 typedef struct { - int send_type; - dr_send_handler_t handlers[DR_MAX_HANDLERS_PER_PORT]; - int count; - uint8_t in_use; + int send_type; // DR_SEND_xxx / -1 + int recv_type; // DR_RECV_xxx / -1 + dr_send_handler_t handlers[DR_MAX_HANDLERS_PER_PORT]; + int count; + uint8_t in_use; } port_entry_t; ``` -### 5.3 端口表 +### 2.2 接收回调函数指针表 + +```c +// 编译期由 X-macro 生成,DR_RECV_rtcm 的枚举值即为数组下标 +static void (*const recv_table[DR_RECV_CAP_COUNT])(uint32_t, const void*, size_t) = { + dr_on_recv_rtcm, + dr_on_recv_command, +}; +``` + +### 2.3 端口表 ```c #define DR_MAX_PORTS 64 @@ -213,224 +62,121 @@ static uint8_t initialized; --- -## 6. API 参考 +## 3. API -### 6.1 初始化 - -```c -void dr_init(void); // 幂等初始化 -void dr_reset(void); // 重置全部状态 -``` - -### 6.2 端口管理 +### 3.1 端口管理 ```c dr_port_t dr_port_create(void); -void dr_port_delete(dr_port_t port); +void dr_port_delete(dr_port_t port); -void dr_port_set_send_type(port, int cap_id); -int dr_port_get_send_type(port); +void dr_port_set_send_type(port, int cap_id); +int dr_port_get_send_type(port); +void dr_port_set_recv_type(port, int cap_id); +int dr_port_get_recv_type(port); -int dr_port_add_handler(port, handler); -int dr_port_remove_handler(port, handler); -void dr_port_clear_handlers(port); -int dr_port_handler_count(port); +int dr_port_add_handler(port, handler); +int dr_port_remove_handler(port, handler); +void dr_port_clear_handlers(port); +int dr_port_handler_count(port); + +void dr_port_dispatch(port, data, len); // ← 自动路由接收 ``` -### 6.3 发送触发函数(每个 SEND 能力生成一个) +### 3.2 发送触发 ```c int dr_send_sensor(const void *data, size_t len); int dr_send_status(const void *data, size_t len); ``` -返回值:0 成功 / -1 未初始化 / -2 无端口匹配该 send_type +返回值:0 成功 / -1 未初始化 / -2 无匹配端口 -### 6.4 接收回调(每个 RECV 能力生成一个声明) +### 3.3 接收回调 ```c void dr_on_recv_rtcm(uint32_t port_id, const void *data, size_t len); void dr_on_recv_command(uint32_t port_id, const void *data, size_t len); ``` -库提供弱符号默认空实现。 - -### 6.5 能力元数据 - -```c -int dr_send_cap_count(void); -const char *dr_send_cap_name(int cap_id); -const char *dr_send_cap_desc(int cap_id); - -int dr_recv_cap_count(void); -const char *dr_recv_cap_name(int cap_id); -const char *dr_recv_cap_desc(int cap_id); -``` - --- -## 7. 核心流程 +## 4. 核心流程 -### 7.1 发送流程 +### 4.1 发送 ```plantuml @startuml -actor "应用" as App -participant "dr_send_sensor()" as Send -participant "端口表 ports[]" as Table -participant "handlers[]" as HList -participant "外设" as Dev +actor App +participant dr_send_sensor as Send +participant ports[] as Table +participant handlers[] as H -App -> Send : send_sensor("temp:25.3", 9) - -group 初始化检查 - Send -> Send : initialized? - alt 否 → return -1 - end -end - -group 遍历端口表 - loop for p in 0..MAX_PORTS-1 - Send -> Table : ports[p].in_use && ports[p].send_type == SENSOR ? - alt 匹配 - loop for h in 0..count-1 - Send -> HList : handlers[h](p, data, len) - HList -> Dev : 实际外设写入 - end +App -> Send : send_sensor("temp", 9) +loop for p in 0..MAX_PORTS-1 + Send -> Table : in_use && send_type == SENSOR ? + alt 匹配 + loop for h in handlers + Send -> H : handlers[h](p, data, len) end end end - -alt 至少调了一次 handler +alt 至少调了一次 Send --> App : return 0 -else 无匹配端口 +else 无匹配 Send --> App : return -2 end @enduml ``` -### 7.2 接收流程 +### 4.2 接收(dispatch) ```plantuml @startuml -actor "外设" as Dev -participant "用户分发代码" as Dispatch -participant "on_recv_rtcm()" as Callback +actor 外设ISR as Dev +participant dr_port_dispatch as Dispatch +participant recv_table[] as Table +participant dr_on_recv_rtcm as Cb -Dev -> Dispatch : 收到数据,识别为 rtcm -Dispatch -> Callback : on_recv_rtcm(port_id, data, len) -Callback --> Dispatch : void -@enduml -``` - -### 7.3 端口生命周期 - -```plantuml -@startuml -state "空闲" as Idle -state "已创建\nsend_type=-1\ncount=0" as Created -state "已配置" as Config - -[*] --> Idle : reset / 启动 -Idle --> Created : port_create() -Created --> Config : set_send_type / add_handler -Config --> Config : 切换 send_type\n增删 handler -Config --> Created : set_send_type(-1) / clear_handlers -Created --> Idle : port_delete() -Config --> Idle : port_delete() -Idle --> Created : 复用 ID +Dev -> Dispatch : dispatch(port, data, len) +Dispatch -> Table : 查 recv_table[port.recv_type] +Dispatch -> Cb : recv_table[rid](port, data, len) +Cb --> Dev : void @enduml ``` --- -## 8. 路由场景 +## 5. Dispatch 实现 -### 场景 1:sensor 广播到 UART + TCP + CAN +`dr_port_dispatch` 是零分支的查表路由: ```c -dr_port_t p1 = dr_port_create(); -dr_port_set_send_type(p1, DR_SEND_sensor); -dr_port_add_handler(p1, uart_send); -dr_port_add_handler(p1, tcp_send); - -dr_port_t p2 = dr_port_create(); -dr_port_set_send_type(p2, DR_SEND_sensor); -dr_port_add_handler(p2, can_send); - -dr_send_sensor("temp:25.3", 9); -// → uart_send(p1, "temp:25.3", 9) -// → tcp_send(p1, "temp:25.3", 9) -// → can_send(p2, "temp:25.3", 9) -``` - -### 场景 2:仅接收 - -```c -void dr_on_recv_rtcm(uint32_t port_id, const void *data, size_t len) { - apply_rtcm_correction(data, len); +void dr_port_dispatch(dr_port_t port, const void *data, size_t len) +{ + if (!initialized) return; + if (port < 0 || port >= DR_MAX_PORTS || !ports[port].in_use) return; + int rid = ports[port].recv_type; + if (rid < 0 || rid >= DR_RECV_CAP_COUNT) return; + recv_table[rid]((uint32_t)port, data, len); // 直接函数指针调用 } -// 外设收到后:dr_on_recv_rtcm(port, data, len); ``` -### 场景 3:运行时移除 handler - -```c -dr_port_remove_handler(p1, tcp_send); -// 下次 send_sensor 不再触发 tcp -``` +`recv_table[]` 是 `const` 数组,编译期由 X-macro 生成。`DR_RECV_rtcm` 的枚举值等于数组下标,查表时间为 O(1),无分支预测开销,适合 ISR。 --- -## 9. 弱符号机制 - -```plantuml -@startuml -package "libDataRouter.a" { - component "弱符号\nvoid on_recv_rtcm(...){}" as Weak -} -package "用户代码" { - component "强符号\nvoid on_recv_rtcm(...){...}" as Strong -} -package "最终链接产物" { - component "使用用户版本" as Result -} -Strong --> Result -Weak ..> Result : 后备 -@enduml -``` - ---- - -## 10. 错误码 +## 6. 错误码 | 返回值 | 含义 | |:------:|------| -| 0 | 成功,至少有一个 handler 被调用 | -| -1 | 库未初始化(未调 init) | -| -2 | 无端口匹配该 send_type(或匹配端口均无 handler) | +| 0 | 成功 | +| -1 | 库未初始化 | +| -2 | 无端口匹配该 send_type | --- -## 11. 配置扩展 - -```c -#define DR_SEND_CAPABILITIES \ - DR_SEND(sensor, "Sensor data") \ - DR_SEND(debug, "Debug log") /* 新增 */ - -#define DR_RECV_CAPABILITIES \ - DR_RECV(rtcm, "RTCM data") \ - DR_RECV(ota, "OTA firmware") /* 新增 */ -``` - -```bash -cmake -DCMAKE_C_FLAGS="-DDR_MAX_PORTS=128 -DDR_MAX_HANDLERS_PER_PORT=8" -``` - ---- - -## 12. 约束 +## 7. 约束 | 约束 | 说明 | |------|------| diff --git a/README.md b/README.md index 960516d..594bcc2 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,22 @@ # DataRouter -轻量级 C 语言数据路由库。**发送能力和接收能力分开声明**,端口绑定 `send_type` 和外设 handler 列表。调用 `dr_send_(data, len)` 时自动广播到所有匹配端口。 +轻量级 C 语言数据路由库。发送能力和接收能力分开声明,端口绑定 `send_type` + `recv_type` + handler 列表。发送时自动广播到所有匹配端口,接收时 `dr_port_dispatch()` 自动路由到对应回调。 ## 概念 | 术语 | 说明 | |------|------| -| **发送能力 (Send Capability)** | 编译期声明的可发送报文类型,生成 `dr_send_(data, len)` | -| **接收能力 (Recv Capability)** | 编译期声明的可接收报文类型,生成 `dr_on_recv_(port, data, len)` | -| **端口 (Port)** | 运行期创建的连接点,包含 `send_type` + handler 列表 | +| **发送能力** | 编译期声明的可发送报文类型,生成 `dr_send_(data, len)` | +| **接收能力** | 编译期声明的可接收报文类型,生成 `dr_on_recv_(port, data, len)` | +| **端口** | 运行期连接点,包含 `send_type` + `recv_type` + handler 列表 | | **Handler** | 用户实现的物理外设发送函数,一个端口可注册多个(默认上限 4) | +| **Dispatch** | `dr_port_dispatch(port, data, len)` 根据端口的 `recv_type` 自动路由到回调 | ## 快速开始 ### 1. 声明能力 -创建自己的 `dr_config.h`(可参考 `dr_config_template.h`): +创建自己的 `datarouter_config.h`(可参考 `datarouter_config_template.h`): ```c #define DR_SEND_CAPABILITIES \ @@ -27,24 +28,15 @@ DR_RECV(command, "Remote control command") ``` -> 库自带默认配置。将你的 `dr_config.h` 放到项目目录, -> 编译时添加 `-I /path/to/your/config` 即可覆盖。无需修改库源码。 -> -> ```cmake -> include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR}) -> add_subdirectory(path/to/DataRouter) -> target_link_libraries(my_app DataRouter) -> ``` - -### 2. 实现外设发送 handler +### 2. 实现 handler 和回调 ```c -static void uart_send(uint32_t port_id, const void *data, size_t len) { +static void uart_write(uint32_t port_id, const void *data, size_t len) { HAL_UART_Transmit(&huart1, data, len, 1000); } -static void tcp_send(uint32_t port_id, const void *data, size_t len) { - send(tcp_socket, data, len, 0); +void dr_on_recv_rtcm(uint32_t port_id, const void *data, size_t len) { + apply_rtcm_correction(data, len); } ``` @@ -53,25 +45,17 @@ static void tcp_send(uint32_t port_id, const void *data, size_t len) { ```c dr_init(); -/* —— 传感器上报:sensor 报文走 UART + TCP —— */ -dr_port_t tx = dr_port_create(); -dr_port_set_send_type(tx, DR_SEND_sensor); -dr_port_add_handler(tx, uart_send); -dr_port_add_handler(tx, tcp_send); +dr_port_t uart = dr_port_create(); +dr_port_set_send_type(uart, DR_SEND_sensor); +dr_port_set_recv_type(uart, DR_RECV_rtcm); +dr_port_add_handler(uart, uart_write); +dr_port_add_handler(uart, tcp_write); -/* 不指定端口,库自动广播到所有 send_type=sensor 的端口 */ -dr_send_sensor("temperature:25.3", 16); // → UART + TCP +/* 发送:自动广播到所有 send_type=sensor 的端口 */ +dr_send_sensor("temperature:25.3", 16); -/* —— 接收:实现回调,外设数据到达后自行调用 —— */ -dr_on_recv_rtcm(rx_port, data, len); -``` - -### 4. 实现接收回调 - -```c -void dr_on_recv_rtcm(uint32_t port_id, const void *data, size_t len) { - apply_rtcm_correction(data, len); -} +/* 接收:外设中断中一行 dispatch,自动调 dr_on_recv_rtcm() */ +dr_port_dispatch(uart, data, len); ``` ## API 一览 @@ -79,45 +63,45 @@ void dr_on_recv_rtcm(uint32_t port_id, const void *data, size_t len) { ### 初始化 ```c -void dr_init(void); // 幂等初始化 -void dr_reset(void); // 重置全部状态 +void dr_init(void); +void dr_reset(void); ``` ### 端口管理 ```c -dr_port_t dr_port_create(void); // 创建端口 -void dr_port_delete(dr_port_t port); // 删除 +dr_port_t dr_port_create(void); +void dr_port_delete(dr_port_t port); -void dr_port_set_send_type(port, int cap_id); // 设置发送类型 -int dr_port_get_send_type(port); // -1 = 未设置 +void dr_port_set_send_type(port, int cap_id); +int dr_port_get_send_type(port); +void dr_port_set_recv_type(port, int cap_id); +int dr_port_get_recv_type(port); -int dr_port_add_handler(port, handler); // 注册外设 handler -int dr_port_remove_handler(port, handler); // 移除 -void dr_port_clear_handlers(port); // 清除全部 -int dr_port_handler_count(port); // 当前数量 +int dr_port_add_handler(port, handler); +int dr_port_remove_handler(port, handler); +void dr_port_clear_handlers(port); +int dr_port_handler_count(port); + +void dr_port_dispatch(port, data, len); /* 自动路由接收 */ ``` -### 发送触发(每个 SEND 能力生成一个) +### 发送触发 ```c int dr_send_sensor(const void *data, size_t len); // 0 成功 int dr_send_status(const void *data, size_t len); // -1 未初始化 - // -2 无匹配端口 + // -2 无匹配端口 ``` -不指定端口,库遍历所有端口,`send_type` 匹配的逐一调用其全部 handler。 - -### 接收回调(每个 RECV 能力生成一个声明) +### 接收回调 ```c void dr_on_recv_rtcm(uint32_t port_id, const void *data, size_t len); void dr_on_recv_command(uint32_t port_id, const void *data, size_t len); ``` -库提供弱符号默认空实现,用户定义同名函数覆盖。 - -### 能力元数据(两组独立) +### 能力元数据 ```c int dr_send_cap_count(void); @@ -132,114 +116,23 @@ const char *dr_recv_cap_desc(int cap_id); ## 路由模型 ``` -dr_send_sensor("temperature:25.3", 16) +发送:dr_send_sensor("temp:25.3", 9) │ - ├─→ port 0 (send_type=sensor) handlers[0]=uart_send → UART - │ handlers[1]=tcp_send → TCP - └─→ port 1 (send_type=sensor) handlers[0]=can_send → CAN + ├─→ port A (send_type=sensor) handlers = [uart_write, tcp_write] + └─→ port B (send_type=sensor) handlers = [can_write] + +接收:dr_port_dispatch(uart1, data, len) + │ + └─→ uart1.recv_type = DR_RECV_rtcm + └─→ dr_on_recv_rtcm(uart1, data, len) ← 查表调用 ``` -- 调用 `send_(data, len)` 时,库遍历所有端口,自动匹配 `send_type` -- 每个端口可注册多个 handler(一对多广播) -- 同一 handler 可注册到多个端口(多路复用) -- 运行时通过 `add_handler` / `remove_handler` / `port_delete` 动态改变路由 -- 无任何端口匹配该 `send_type` 时返回 -2 - -## 编译 - -```bash -cmake -S . -B build -cmake --build build -``` - -可选配置: - -```bash -cmake -DCMAKE_C_FLAGS="-DDR_MAX_PORTS=128 -DDR_MAX_HANDLERS_PER_PORT=8" -``` - -## 嵌入式 / Cortex-M 构建 - -库支持 `arm-none-eabi-gcc` 交叉编译,提供 **7 个 CMake preset** 覆盖常见 Cortex-M 目标: - -```bash -# 列出所有 preset -cmake --list-presets - -# Cortex-M3(软浮点) -cmake --preset cm3 -cmake --build --preset cm3 - -# Cortex-M4F(硬件浮点,单精度) -cmake --preset cm4-hard -cmake --build --preset cm4-hard - -# Cortex-M7F(硬件浮点,双精度) -cmake --preset cm7-hard-dp -cmake --build --preset cm7-hard-dp -``` - -> 使用 preset 编译需要 `arm-none-eabi-gcc` 在 PATH 中。 -> 各 preset 均启用 `DR_SOURCE_ONLY` 模式——只提供源文件路径, -> 你需要将 `datarouter.c` 加入自己的 target 以继承项目编译标志: - -```cmake -# 你的 CMakeLists.txt -set(DR_SOURCE_ONLY ON) -FetchContent_MakeAvailable(datarouter) -target_sources(my_app PRIVATE ${datarouter_SOURCE_DIR}/datarouter.c) -target_include_directories(my_app PRIVATE ${datarouter_SOURCE_DIR}) -``` - -### 编译兼容性矩阵 - -启用 `DR_COMPILE_CHECK` 可自动检测 `arm-none-eabi-gcc` 并对 CM3/CM4/CM7 -各变体执行编译验证: - -```bash -cmake -B build-check -DDR_COMPILE_CHECK=ON -cmake --build build-check # 生成 check_cm3, check_cm4-hard, … 等 target -# 或单独检查某个变体: -make check_cm7-hard-dp -``` - -### 内置 CMake Preset 列表 - -| Preset | 目标 | FPU | ABI | -|--------|------|-----|-----| -| `cm3` | Cortex-M3 | 无 | soft | -| `cm4-soft` | Cortex-M4 | 无 | soft | -| `cm4-hard` | Cortex-M4F | FPv4-SP-D16 | hard | -| `cm7-soft` | Cortex-M7 | 无 | soft | -| `cm7-hard-sp` | Cortex-M7F | FPv5-SP-D16 | hard | -| `cm7-hard-dp` | Cortex-M7F | FPv5-D16 | hard | - ## 测试 ```bash cmake --build build ctest --test-dir build -# 或直接运行 ./build/test_datarouter ``` -当前 22 个测试用例,覆盖端口管理、send_type 与 handler 设置、自动广播路由、接收回调、能力元数据。 - -## 文件结构 - -``` -dr_config.h 发送 + 接收能力配置(用户可覆盖) -datarouter.h 公共 API + X-macro 展开 -datarouter.c 实现(端口表 + send 遍历) -dr_config_template.h 外部配置模板 -example.c 使用示例 -tests/test_datarouter.c 单元测试(22 用例) -DESIGN.md 详细设计文档(含 PlantUML 图) -TEST_CASES.md 测试用例文档 -``` - -## 依赖 - -- C11 编译器 -- CMake ≥ 3.10 -- 无第三方库依赖 +当前 24 个测试用例,覆盖端口管理、send_type/recv_type、handler、自动广播、dispatch、能力元数据。 diff --git a/TEST_CASES.md b/TEST_CASES.md index 95e185e..1e7c763 100644 --- a/TEST_CASES.md +++ b/TEST_CASES.md @@ -26,63 +26,65 @@ ctest --test-dir build | # | 测试名 | 描述 | 步骤 | 预期 | |---|--------|------|------|------| -| 1 | `error_uninit` | 未 init 时发送 | `send_sensor("d", 1)` | 返回 -1 | +| 1 | `error_uninit` | 未 init 时发送 | `dr_send_sensor("d", 1)` | 返回 -1 | ### 2. 端口管理(4 个) | # | 测试名 | 描述 | 步骤 | 预期 | |---|--------|------|------|------| -| 2 | `port_create_basic` | 基本创建 | `port_create()` | 返回 ≥ 0 | -| 3 | `port_create_max` | 满容量 64 | 循环创建到满再尝试 | 64 个成功,第 65 个 INVALID | +| 2 | `port_create_basic` | 基本创建 | `dr_port_create()` | 返回 ≥ 0 | +| 3 | `port_create_max` | 满容量 64 | 循环到满 | 第 65 个 INVALID | | 4 | `port_delete_invalid` | 删除无效 ID | `delete(-1)` + `delete(999)` | 不崩溃 | -| 5 | `port_default_state` | 默认状态 | 新建端口,查 send_type / handler count | `send_type=-1`, `count=0` | +| 5 | `port_default_state` | 默认状态 | 查 send_type / recv_type / count | 均为 -1 / 0 | -### 3. send_type 与 handler(3 个) +### 3. send_type / recv_type / handler(4 个) | # | 测试名 | 描述 | 步骤 | 预期 | |---|--------|------|------|------| -| 6 | `send_type_set_get` | 设置并清除 send_type | `set_send_type(sensor)` → `get` → `set(-1)` → `get` | sensor → -1 | -| 7 | `handler_add_remove` | 添加后移除 handler | add(h) count=1 → remove(h) count=0 | count 正确 | -| 8 | `handler_clear` | 清除全部 handler | add → clear | count == 0 | +| 6 | `send_type_set_clear` | 设置并清除 send_type | set(sensor) → get → set(-1) → get | sensor → -1 | +| 7 | `recv_type_set_clear` | 设置并清除 recv_type | set(rtcm) → get → set(-1) → get | rtcm → -1 | +| 8 | `types_independent` | 收发类型独立设置 | 同时 set_send(sensor) + set_recv(command) | 各自返回正确值 | +| 9 | `handler_add_remove` | 添加后移除 handler | add(h) → count=1 → remove(h) → count=0 | 正确 | -### 4. 发送 — 自动广播(11 个) +### 4. 发送 — 自动广播(7 个) | # | 测试名 | 描述 | 步骤 | 预期 | |---|--------|------|------|------| -| 9 | `send_no_match_port` | 无匹配端口 | 不创建任何端口,直接 `send_sensor` | 返回 -2 | -| 10 | `send_one_port_one_handler` | 单端口单 handler | 创建端口中 send_type + add(h) → send | 返回 0;handler 1 次 | -| 11 | `send_one_port_multi_handler` | 单端口双 handler | add(h) + add(h2) → send | handler 2 次 | -| 12 | `send_multi_ports_same_type` | 两端口同类型 | p1(sensor,h) + p2(sensor,h) → send | handler 2 次,各端口一次 | -| 13 | `send_multi_port_multi_handler` | 多端口多 handler | p1(h,h2) + p2(h3) → send | handler 3 次 | -| 14 | `send_type_filter` | 类型过滤 | p1(sensor,h) + p2(status,h) → `send_sensor` | 只触发 p1 | -| 15 | `send_after_remove_handler` | 移除 handler 后 | add(h) → send count=1 → remove(h) → send | 第二次返回 -2 | -| 16 | `send_after_delete_port` | 删除端口后 | 创建→设好→删除→send | 返回 -2 | -| 17 | `send_zero_length` | 零长度数据 | send("x", 0) | 返回 0;handler 被调 | -| 18 | `send_null_data` | NULL 数据 | send(NULL, 0) | 返回 0;handler 被调 | -| 19 | `send_multi_cap` | 多能力独立 | p1(sensor) + p2(status) → 分别 send_sensor + send_status | 各触发匹配的端口 | +| 10 | `send_no_match_port` | 无匹配端口 | 不创建端口直接 send | 返回 -2 | +| 11 | `send_one_port_one_handler` | 单端口单 handler | send_type + add → send | 0,handler 1 次 | +| 12 | `send_multi_ports` | 两端口同类型 | p1(sensor) + p2(sensor) → send | handler 2 次 | +| 13 | `send_type_filter` | 类型过滤 | p1(sensor) + p2(status) → send_sensor | 只触发 p1 | +| 14 | `send_after_delete` | 删除端口后 send | 创建→设好→删除→send | 返回 -2 | +| 15 | `send_zero_length` | 零长度数据 | send("x", 0) | 0,handler 被调 | +| 16 | `send_null_data` | NULL 数据 | send(NULL, 0) | 0,handler 被调 | -### 5. 接收回调(1 个) +### 5. 接收回调 + dispatch(6 个) | # | 测试名 | 描述 | 步骤 | 预期 | |---|--------|------|------|------| -| 20 | `recv_callback` | 用户实现接收回调 | `on_recv_rtcm(0, "d", 1)` | 回调被触发 | +| 17 | `recv_callback_direct` | 直接调用回调 | `dr_on_recv_rtcm()` + `dr_on_recv_command()` | 各自触发 | +| 18 | `dispatch_valid` | dispatch 自动路由 rtcm | set_recv(rtcm) → dispatch(port, data) | dr_on_recv_rtcm 触发 | +| 19 | `dispatch_command` | dispatch 自动路由 command | set_recv(command) → dispatch(port, data) | dr_on_recv_command 触发 | +| 20 | `dispatch_unset` | recv_type 未设置 | recv_type=-1 → dispatch | 无回调触发 | +| 21 | `dispatch_invalid_port` | 无效端口 dispatch | dispatch(999/-1, ...) | 无回调触发 | +| 22 | `dispatch_deleted_port` | 已删端口 dispatch | 创建→设置→删除→dispatch | 无回调触发 | ### 6. 能力元数据(2 个) | # | 测试名 | 描述 | 步骤 | 预期 | |---|--------|------|------|------| -| 21 | `send_cap_meta` | 发送能力元数据 | count / name / desc / 边界 | SEND 两组正确 | -| 22 | `recv_cap_meta` | 接收能力元数据 | count / name / desc / 边界 | RECV 两组正确 | +| 23 | `send_cap_meta` | 发送能力元数据 | count / name / 边界 | 正确 | +| 24 | `recv_cap_meta` | 接收能力元数据 | count / name / 边界 | 正确 | ## 覆盖矩阵 | 功能模块 | 用例数 | 主要验证点 | |----------|:------:|------------| | 未初始化 | 1 | send → -1 | -| 端口管理 | 4 | 创建/满容量/删除异常/默认状态 | -| send_type & handler | 3 | 设置/清除/添加/移除/清空 | -| 发送自动广播 | 11 | 无匹配/单播/多 handler 单端口/多端口同类型/多端口多 handler/类型过滤/移除后/删除后/零长/NULL/多能力独立 | -| 接收回调 | 1 | 用户实现覆盖弱符号 | -| 能力元数据 | 2 | SEND 与 RECV 两组独立查询 | +| 端口管理 | 4 | 创建/满容量/删除/默认状态 | +| 收发类型 & handler | 4 | send_type/recv_type 独立设置、handler 增删 | +| 发送自动广播 | 7 | 单播/多播/过滤/删除后/零长/NULL | +| 接收 dispatch | 6 | 直接回调/有效路由/无效路由/未设置/无效端口/已删除 | +| 能力元数据 | 2 | 两组独立查询 | -**总计:22 个测试用例** +**总计:24 个测试用例** diff --git a/build/DataRouterConfig.cmake b/build/DataRouterConfig.cmake index a68423a..8de9700 100644 --- a/build/DataRouterConfig.cmake +++ b/build/DataRouterConfig.cmake @@ -3,7 +3,7 @@ ####### Any changes to this file will be overwritten by the next CMake run #### ####### The input file was DataRouterConfig.cmake.in ######## -get_filename_component(PACKAGE_PREFIX_DIR "${CMAKE_CURRENT_LIST_DIR}/../" ABSOLUTE) +get_filename_component(PACKAGE_PREFIX_DIR "${CMAKE_CURRENT_LIST_DIR}/../../../" ABSOLUTE) macro(set_and_check _var _file) set(${_var} "${_file}") @@ -24,12 +24,23 @@ endmacro() #################################################################################### -set_and_check(DataRouter_INCLUDE_DIR "${PACKAGE_PREFIX_DIR}/include") -set_and_check(DataRouter_LIBRARY_DIR "${PACKAGE_PREFIX_DIR}/lib") +get_filename_component(DataRouter_INCLUDE_DIR + "${CMAKE_CURRENT_LIST_DIR}/../../../include" ABSOLUTE) +get_filename_component(DataRouter_LIBRARY_DIR + "${CMAKE_CURRENT_LIST_DIR}/../../../lib" ABSOLUTE) include("${CMAKE_CURRENT_LIST_DIR}/DataRouterTargets.cmake" OPTIONAL) -if(NOT TARGET DataRouter::DataRouter) +# 无论 targets 文件是否加载,都覆盖 include 路径为正确值 +# (targets 文件中的 _IMPORT_PREFIX 在某些平台/工具链下可能计算异常) +if(TARGET DataRouter) + set_target_properties(DataRouter PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${DataRouter_INCLUDE_DIR}" + ) + if(NOT TARGET DataRouter::DataRouter) + add_library(DataRouter::DataRouter ALIAS DataRouter) + endif() +else() add_library(DataRouter::DataRouter STATIC IMPORTED) set_target_properties(DataRouter::DataRouter PROPERTIES IMPORTED_LOCATION "${DataRouter_LIBRARY_DIR}/${CMAKE_STATIC_LIBRARY_PREFIX}DataRouter${CMAKE_STATIC_LIBRARY_SUFFIX}" diff --git a/build/example b/build/example index c2ee5879e338199d07e0f2d248598d5c5ea9deef..330e15afcbed316e94da3f19d1cdc9e6fb042555 100755 GIT binary patch delta 4820 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 delta 4284 zcmaJ^dvH|M9X{vg9kOOO4|X>p*_&(WeX_^otVlBDvc{|A{HG zZp_hbi(ALA+(@oiv$aL2g%N5O)kFQYP?toguTo#E+3}W8eG%$W!GBSuS5#Mv)8$w* zjm0JzLNnM{%8E17oVX;zjTvk_-3zuhF3EaYuiVeDFR_V-}D^oVgGy=lzisr0ci zkAFvB8QuI(RA`#V-x#>hw1D$r`ejOH;C^4%m?d;63MF>M9Ksgy1y6;J-%^xQ-VwFX zXl0Lg1XM?&DxJ^`r)GQ%!xuas97bff9yzFngq}xumv@ARZX_^P;T_p>EHsJ&L2szX z8#=DKAmO)|aE~_-^6g4*{GX!uf`0>fCPpPy?xiX22)NJzEq<)WI~Iw8nK zyWxt#Sj04LOe)H8bz6h06xGudc<(*V3U3!V(Rwtv`n)EoL^`Uk) zsRX}|63jA>p;cykW&*@h(b}odVL1%$RQroN0)>5;i~I>RBA&ZX-a)zaAlT9&z3`k;VXGwsrz0@{SU=FM`8YoXlcS2M^+#X9_(Tl{VH9i46d%0*>GE+&FEJw~f@(#mHPZSX(p@9@hl zi{wJgWZs>#lou7#rkvIQc6esg(s887!msvgMakog?F7~WUja4%{{l<`{^1)%*#f){ zd<9rBt|-TVPXYf4{3CD@_~2FK!M<$<)&REy8-V++DatnB;Dn;Q2^CaNC4b>0||g7-y;F=x4@IY?EfMj=m19JB=iIGfUg2;fCqpL zz!Bg!V4A*Wj6%57BAopccY zFM%-!@}Q~fa|G+8t_$uR`ZlVcVL}x)J^-{tGlTBTIlv4 z&^w@8p{<+5x(0NG`E$ zALT&5)B2P^ulNCdE%Y^a%#Xvm0}k#VNbn-`y6XdfEjS*Fcb;zQbr`ANQD_=hZ&5U> z4s1VNa@cr3B@_klP8ck5aw8onvhl5SzNnecqG#eBz}|k{x!x1+)EtOfy9!kmb*_g;h%9PbCiq!`@6>5_ zpBlye^m>Uo@PzQGh!{x_d@gc2#38I5gouAx`0o@%c;v4O{-ofwa|!_JgLJ1HR!Ly+m9lp6RPJ(FZ*;rk+-;9q(uYs%0W?F>Tqrs|J{V6x8R38J-l z+^OpFY>k+r6}%BUyj$#K;m-Q?`~miw*uw=v$rb^TS9887(1+S;(;hBBr{H-+VYlGh z(lvLcJp<7FiQro`e>*O_uCsaw>Pkcw-b@F}%%wX-*pOLw|FMYLBlvH`CD9%Q(B1-{ ztFAEr4VlW%%vRrESjVQ8^;&0BeUsLq%jLCHv*Z>%Q1LuXR^$#iD{pctUG@+SNwEXH z%Qi?j!mixy%rc7a^)UKGdG$lXGO)aAF_%^wsLfrJ7~Z|(S@e#3B|k@3+*N#nT+3~= z+G9*PovTNN@lX`Cd5RLIzUAp{PkBQ4gSwwCc^0IrH82~kh0lUPYFtrn3g7$t@r|jr zIy&gF@mM;yq6*(9rs^tgqBYg)6E>(R(6Z=AwKMTTnQje5(bej*gx4x{GlN_;w*Lcg CeBbf_ diff --git a/build/test_datarouter b/build/test_datarouter index 2a205d004201655e65dc3ee902fba27d5c088c21..c2633908e154413fa6ee645916bc35dbb6777acc 100755 GIT binary patch literal 31320 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 DR_MAX_PORTS || !ports[port].in_use) return; + ports[port].recv_type = cap_id; +} + +int dr_port_get_recv_type(dr_port_t port) +{ + if (!initialized) return -1; + if (port < 0 || port >= DR_MAX_PORTS || !ports[port].in_use) return -1; + return ports[port].recv_type; +} + +void dr_port_dispatch(dr_port_t port, const void *data, size_t len) +{ + if (!initialized) return; + if (port < 0 || port >= DR_MAX_PORTS || !ports[port].in_use) return; + int rid = ports[port].recv_type; + if (rid < 0 || rid >= DR_RECV_CAP_COUNT) return; + recv_table[rid]((uint32_t)port, data, len); +} + int dr_port_add_handler(dr_port_t port, dr_send_handler_t handler) { if (!initialized) return -1; diff --git a/datarouter.h b/datarouter.h index ff89a33..fa867ec 100644 --- a/datarouter.h +++ b/datarouter.h @@ -6,14 +6,17 @@ * * - **发送能力**:编译期声明的可发送报文类型,生成 dr_send_(data, len) * - **接收能力**:编译期声明的可接收报文类型,生成 dr_on_recv_(port, data, len) - * - **端口**:运行期创建的连接点,包含 send_type + handler 列表 + * - **端口**:运行期连接点,包含 send_type + recv_type + handler 列表 * - * ## 路由 + * ## 发送路由 * - * 调用 dr_send_(data, len) 时,库遍历所有端口, - * 找到 send_type 匹配的端口,逐一调用其全部 handler。无需指定从哪个端口发送。 + * dr_send_(data, len) 遍历所有端口,找到 send_type 匹配的端口, + * 逐一调用其全部 handler,无需指定从哪个端口发送。 * - * 接收侧由用户自行调用 dr_on_recv_(port, data, len) 处理外设收到的数据。 + * ## 接收路由 + * + * 外设(UART / SPI / CAN)收到数据后,调用 dr_port_dispatch(port, data, len), + * 库根据端口的 recv_type 自动查表调用对应的 dr_on_recv_(),无需手动 if/else。 */ #ifndef DR_H @@ -89,12 +92,26 @@ void dr_port_delete(dr_port_t port); void dr_port_set_send_type(dr_port_t port, int cap_id); int dr_port_get_send_type(dr_port_t port); +void dr_port_set_recv_type(dr_port_t port, int cap_id); +int dr_port_get_recv_type(dr_port_t port); int dr_port_add_handler(dr_port_t port, dr_send_handler_t handler); int dr_port_remove_handler(dr_port_t port, dr_send_handler_t handler); void dr_port_clear_handlers(dr_port_t port); int dr_port_handler_count(dr_port_t port); +/** + * @brief 按端口绑定的 recv_type 自动路由到对应的 dr_on_recv_() 回调 + * + * 外设中断中收到数据后调用此函数,无需自己写 if/else 判断报文类型。 + * 库通过 X-macro 生成函数指针表,直接查表调用,零分支预测开销。 + * + * @param[in] port 接收端口 ID + * @param[in] data 数据缓冲区 + * @param[in] len 数据长度 + */ +void dr_port_dispatch(dr_port_t port, const void *data, size_t len); + /* ========== 能力元数据查询 ========== */ int dr_send_cap_count(void); diff --git a/example.c b/example.c index 6bca696..bcf1ce1 100644 --- a/example.c +++ b/example.c @@ -1,73 +1,76 @@ /** * @file example.c * - * 调用 dr_send_sensor(data, len) 无需指定端口, - * 库自动广播到所有 send_type=sensor 的端口。 + * 展示: + * 1. send_type + 多 handler 发送广播 + * 2. recv_type + dr_port_dispatch 自动路由接收 + * 3. 外设中断中只需一行 dr_port_dispatch() */ #include "datarouter.h" #include -static void uart_send(uint32_t port_id, const void *data, size_t len) +/* ---- 发送 handler ---- */ + +static void uart_write(uint32_t port_id, const void *data, size_t len) { printf("[UART port %u] %.*s\n", port_id, (int)len, (const char *)data); } -static void tcp_send(uint32_t port_id, const void *data, size_t len) +static void tcp_write(uint32_t port_id, const void *data, size_t len) { printf("[TCP port %u] %.*s\n", port_id, (int)len, (const char *)data); } -static void can_send(uint32_t port_id, const void *data, size_t len) +/* ---- 接收回调 ---- */ + +void dr_on_recv_rtcm(uint32_t port_id, const void *data, size_t len) { - printf("[CAN port %u] %.*s\n", port_id, (int)len, (const char *)data); + printf("[RX:rtcm port %u] %.*s\n", port_id, (int)len, (const char *)data); } void dr_on_recv_command(uint32_t port_id, const void *data, size_t len) { - printf("[RX:cmd port %u] %.*s\n", port_id, (int)len, (const char *)data); + printf("[RX:cmd port %u] %.*s\n", port_id, (int)len, (const char *)data); } +/* ---- main ---- */ + int main(void) { dr_init(); - printf("Capabilities:\n"); - for (int i = 0; i < dr_send_cap_count(); i++) - printf(" SEND [%d] %s - %s\n", i, - dr_send_cap_name(i), dr_send_cap_desc(i)); - /* - * 创建两个端口,都设 send_type=sensor,各绑不同外设。 - * 调用 dr_send_sensor() 时两个端口同时触发。 + * 创建端口,配置发送和接收能力。 + * 一个端口可以同时有 send_type 和 recv_type。 */ - dr_port_t p1 = dr_port_create(); - dr_port_set_send_type(p1, DR_SEND_sensor); - dr_port_add_handler(p1, uart_send); - dr_port_add_handler(p1, tcp_send); + dr_port_t uart1 = dr_port_create(); + dr_port_set_send_type(uart1, DR_SEND_sensor); + dr_port_set_recv_type(uart1, DR_RECV_rtcm); + dr_port_add_handler(uart1, uart_write); + dr_port_add_handler(uart1, tcp_write); - dr_port_t p2 = dr_port_create(); - dr_port_set_send_type(p2, DR_SEND_sensor); - dr_port_add_handler(p2, can_send); + dr_port_t uart2 = dr_port_create(); + dr_port_set_send_type(uart2, DR_SEND_status); + dr_port_set_recv_type(uart2, DR_RECV_command); + dr_port_add_handler(uart2, uart_write); - /* 不指定端口,自动广播到所有 sensor 端口 */ - printf("\n--- send_sensor (auto-route to ALL sensor ports) ---\n"); + /* ---- 发送 ---- */ + printf("--- send sensor (auto-route to uart1) ---\n"); dr_send_sensor("temperature:25.3", 16); - /* 运行时移除一个 handler,下次发送不再触发它 */ - printf("\n--- remove TCP handler from p1, resend ---\n"); - dr_port_remove_handler(p1, tcp_send); - dr_send_sensor("temperature:26.1", 16); + printf("\n--- send status (auto-route to uart2) ---\n"); + dr_send_status("status:OK", 9); - /* 接收侧:用户自行调用回调 */ - printf("\n--- recv command ---\n"); - dr_on_recv_command(0, "reboot", 6); + /* ---- 接收(模拟外设中断) ---- */ + printf("\n--- UART1 received data → dispatch to rtcm ---\n"); + dr_port_dispatch(uart1, "rtcm_data_v3.2", 14); - /* 删除一个端口,触发时不再走它 */ - printf("\n--- delete p2 (CAN), resend ---\n"); - dr_port_delete(p2); - dr_send_sensor("temperature:27.0", 16); + printf("\n--- UART2 received data → dispatch to command ---\n"); + dr_port_dispatch(uart2, "reboot", 6); - dr_port_delete(p1); + /* 清理 */ + dr_port_delete(uart1); + dr_port_delete(uart2); return 0; } diff --git a/tests/test_datarouter.c b/tests/test_datarouter.c index facb0c2..55ca9d5 100644 --- a/tests/test_datarouter.c +++ b/tests/test_datarouter.c @@ -52,16 +52,20 @@ cleanup: \ } while (0) /* ================================================================ - * 测试用 handler + * 测试用 handler / 回调 * ================================================================ */ static int _h_cnt; -static int _h_port[16]; -static int _h_pos; -static void _h(uint32_t p, const void *d, size_t l) { (void)d; (void)l; _h_cnt++; if (_h_pos<16) _h_port[_h_pos++]=p; } -static void _h2(uint32_t p, const void *d, size_t l) { (void)d; (void)l; _h_cnt++; if (_h_pos<16) _h_port[_h_pos++]=p; } -static void _h3(uint32_t p, const void *d, size_t l) { (void)d; (void)l; _h_cnt++; if (_h_pos<16) _h_port[_h_pos++]=p; } -static void reset_h(void) { _h_cnt = 0; _h_pos = 0; } +static void _h(uint32_t p, const void *d, size_t l) { (void)p; (void)d; (void)l; _h_cnt++; } +static void reset_h(void) { _h_cnt = 0; } + +static int _rx_rtcm; +static int _rx_cmd; +void dr_on_recv_rtcm(uint32_t p, const void *d, size_t l) + { (void)p; (void)d; (void)l; _rx_rtcm++; } +void dr_on_recv_command(uint32_t p, const void *d, size_t l) + { (void)p; (void)d; (void)l; _rx_cmd++; } +static void reset_rx(void) { _rx_rtcm = _rx_cmd = 0; } /* ================================================================ * 1. 未初始化 @@ -97,14 +101,15 @@ TEST_END() TEST_START(port_default_state) dr_port_t p = FIXTURE_CREATE(&_fix); CHECK(dr_port_get_send_type(p) == -1, NULL); + CHECK(dr_port_get_recv_type(p) == -1, NULL); CHECK(dr_port_handler_count(p) == 0, NULL); TEST_END() /* ================================================================ - * 3. send_type 与 handler + * 3. send_type / recv_type 与 handler * ================================================================ */ -TEST_START(send_type_set_get) +TEST_START(send_type_set_clear) dr_port_t p = FIXTURE_CREATE(&_fix); dr_port_set_send_type(p, DR_SEND_sensor); CHECK(dr_port_get_send_type(p) == DR_SEND_sensor, NULL); @@ -112,6 +117,22 @@ TEST_START(send_type_set_get) CHECK(dr_port_get_send_type(p) == -1, NULL); TEST_END() +TEST_START(recv_type_set_clear) + dr_port_t p = FIXTURE_CREATE(&_fix); + dr_port_set_recv_type(p, DR_RECV_rtcm); + CHECK(dr_port_get_recv_type(p) == DR_RECV_rtcm, NULL); + dr_port_set_recv_type(p, -1); + CHECK(dr_port_get_recv_type(p) == -1, NULL); +TEST_END() + +TEST_START(types_independent) + dr_port_t p = FIXTURE_CREATE(&_fix); + dr_port_set_send_type(p, DR_SEND_sensor); + dr_port_set_recv_type(p, DR_RECV_command); + CHECK(dr_port_get_send_type(p) == DR_SEND_sensor, NULL); + CHECK(dr_port_get_recv_type(p) == DR_RECV_command, NULL); +TEST_END() + TEST_START(handler_add_remove) dr_port_t p = FIXTURE_CREATE(&_fix); CHECK(dr_port_add_handler(p, _h) == 0, NULL); @@ -120,19 +141,11 @@ TEST_START(handler_add_remove) CHECK(dr_port_handler_count(p) == 0, NULL); TEST_END() -TEST_START(handler_clear) - dr_port_t p = FIXTURE_CREATE(&_fix); - dr_port_add_handler(p, _h); - dr_port_clear_handlers(p); - CHECK(dr_port_handler_count(p) == 0, NULL); -TEST_END() - /* ================================================================ * 4. 发送 — 自动广播 * ================================================================ */ TEST_START(send_no_match_port) - /* 没有端口配置该 send_type */ CHECK(dr_send_sensor("d", 1) == -2, "expected -2"); TEST_END() @@ -143,21 +156,9 @@ TEST_START(send_one_port_one_handler) reset_h(); CHECK(dr_send_sensor("d", 1) == 0, NULL); CHECK(_h_cnt == 1, "cnt=%d", _h_cnt); - CHECK(_h_port[0] == (int)p, "port=%d", _h_port[0]); TEST_END() -TEST_START(send_one_port_multi_handler) - dr_port_t p = FIXTURE_CREATE(&_fix); - dr_port_set_send_type(p, DR_SEND_sensor); - dr_port_add_handler(p, _h); - dr_port_add_handler(p, _h2); - reset_h(); - CHECK(dr_send_sensor("d", 1) == 0, NULL); - CHECK(_h_cnt == 2, "cnt=%d", _h_cnt); -TEST_END() - -TEST_START(send_multi_ports_same_type) - /* 两个端口同类型,各一个 handler */ +TEST_START(send_multi_ports) dr_port_t p1 = FIXTURE_CREATE(&_fix); dr_port_t p2 = FIXTURE_CREATE(&_fix); dr_port_set_send_type(p1, DR_SEND_sensor); @@ -167,26 +168,9 @@ TEST_START(send_multi_ports_same_type) reset_h(); CHECK(dr_send_sensor("d", 1) == 0, NULL); CHECK(_h_cnt == 2, "cnt=%d", _h_cnt); - /* p1 和 p2 各触发一次 */ - CHECK((_h_port[0]==p1 && _h_port[1]==p2) || - (_h_port[0]==p2 && _h_port[1]==p1), "ports %d,%d", _h_port[0], _h_port[1]); -TEST_END() - -TEST_START(send_multi_port_multi_handler) - dr_port_t p1 = FIXTURE_CREATE(&_fix); - dr_port_t p2 = FIXTURE_CREATE(&_fix); - dr_port_set_send_type(p1, DR_SEND_sensor); - dr_port_set_send_type(p2, DR_SEND_sensor); - dr_port_add_handler(p1, _h); - dr_port_add_handler(p1, _h2); /* p1 两个 */ - dr_port_add_handler(p2, _h3); /* p2 一个 */ - reset_h(); - CHECK(dr_send_sensor("d", 1) == 0, NULL); - CHECK(_h_cnt == 3, "cnt=%d", _h_cnt); TEST_END() TEST_START(send_type_filter) - /* 两个端口不同类型,只触发匹配的 */ dr_port_t p1 = FIXTURE_CREATE(&_fix); dr_port_t p2 = FIXTURE_CREATE(&_fix); dr_port_set_send_type(p1, DR_SEND_sensor); @@ -196,19 +180,9 @@ TEST_START(send_type_filter) reset_h(); CHECK(dr_send_sensor("d", 1) == 0, NULL); CHECK(_h_cnt == 1, "cnt=%d", _h_cnt); - CHECK(_h_port[0] == (int)p1, "only p1 should fire"); TEST_END() -TEST_START(send_after_remove_handler) - dr_port_t p = FIXTURE_CREATE(&_fix); - dr_port_set_send_type(p, DR_SEND_sensor); - dr_port_add_handler(p, _h); - reset_h(); dr_send_sensor("d", 1); CHECK(_h_cnt == 1, NULL); - dr_port_remove_handler(p, _h); - CHECK(dr_send_sensor("d", 1) == -2, "expected -2 after remove"); -TEST_END() - -TEST_START(send_after_delete_port) +TEST_START(send_after_delete) dr_port_t p = FIXTURE_CREATE(&_fix); dr_port_set_send_type(p, DR_SEND_sensor); dr_port_add_handler(p, _h); @@ -235,33 +209,65 @@ TEST_START(send_null_data) CHECK(_h_cnt == 1, NULL); TEST_END() -TEST_START(send_multi_cap) - /* 发送 status 不影响 sensor 端口 */ - dr_port_t p1 = FIXTURE_CREATE(&_fix); - dr_port_t p2 = FIXTURE_CREATE(&_fix); - dr_port_set_send_type(p1, DR_SEND_sensor); - dr_port_set_send_type(p2, DR_SEND_status); - dr_port_add_handler(p1, _h); - dr_port_add_handler(p2, _h); - reset_h(); - CHECK(dr_send_sensor("d", 1) == 0, NULL); - CHECK(_h_cnt == 1, "sensor: cnt=%d", _h_cnt); - CHECK(dr_send_status("d", 1) == 0, NULL); - CHECK(_h_cnt == 2, "status: cnt=%d", _h_cnt); -TEST_END() - /* ================================================================ - * 5. 接收回调 + * 5. 接收回调 + dispatch * ================================================================ */ -static int _rx_r; -void dr_on_recv_rtcm(uint32_t p, const void *d, size_t l) - { (void)p; (void)d; (void)l; _rx_r++; } - -TEST_START(recv_callback) - _rx_r = 0; +TEST_START(recv_callback_direct) + /* 直接调用回调(旧方式) */ + reset_rx(); dr_on_recv_rtcm(0, "d", 1); - CHECK(_rx_r == 1, "cnt=%d", _rx_r); + CHECK(_rx_rtcm == 1, "rtcm=%d", _rx_rtcm); + dr_on_recv_command(1, "d", 1); + CHECK(_rx_cmd == 1, "cmd=%d", _rx_cmd); +TEST_END() + +TEST_START(dispatch_valid) + /* dr_port_dispatch 自动路由到对应回调 */ + dr_port_t p = FIXTURE_CREATE(&_fix); + dr_port_set_recv_type(p, DR_RECV_rtcm); + + reset_rx(); + dr_port_dispatch(p, "data", 4); + CHECK(_rx_rtcm == 1, "rtcm should fire: %d", _rx_rtcm); + CHECK(_rx_cmd == 0, "cmd should not fire: %d", _rx_cmd); +TEST_END() + +TEST_START(dispatch_command) + dr_port_t p = FIXTURE_CREATE(&_fix); + dr_port_set_recv_type(p, DR_RECV_command); + + reset_rx(); + dr_port_dispatch(p, "data", 4); + CHECK(_rx_cmd == 1, "cmd should fire: %d", _rx_cmd); + CHECK(_rx_rtcm == 0, "rtcm should not fire: %d", _rx_rtcm); +TEST_END() + +TEST_START(dispatch_unset) + /* recv_type 为 -1 时 dispatch 不应调用任何回调 */ + dr_port_t p = FIXTURE_CREATE(&_fix); + reset_rx(); + dr_port_dispatch(p, "data", 4); + CHECK(_rx_rtcm == 0, NULL); + CHECK(_rx_cmd == 0, NULL); +TEST_END() + +TEST_START(dispatch_invalid_port) + reset_rx(); + dr_port_dispatch(999, "data", 4); + dr_port_dispatch(-1, "data", 4); + CHECK(_rx_rtcm == 0, NULL); + CHECK(_rx_cmd == 0, NULL); +TEST_END() + +TEST_START(dispatch_deleted_port) + dr_port_t p = FIXTURE_CREATE(&_fix); + dr_port_set_recv_type(p, DR_RECV_rtcm); + dr_port_delete(p); + reset_rx(); + dr_port_dispatch(p, "data", 4); + CHECK(_rx_rtcm == 0, "deleted should not fire"); + _fix.count--; TEST_END() /* ================================================================ @@ -271,24 +277,20 @@ TEST_END() TEST_START(send_cap_meta) CHECK(dr_send_cap_count() == 2, NULL); CHECK(strcmp(dr_send_cap_name(0), "sensor") == 0, NULL); - CHECK(strcmp(dr_send_cap_desc(0), "Sensor data report") == 0, NULL); CHECK(dr_send_cap_name(-1) == NULL, NULL); - CHECK(dr_send_cap_name(99) == NULL, NULL); TEST_END() TEST_START(recv_cap_meta) CHECK(dr_recv_cap_count() == 2, NULL); CHECK(strcmp(dr_recv_cap_name(0), "rtcm") == 0, NULL); - CHECK(strcmp(dr_recv_cap_desc(0), "RTCM correction data") == 0, NULL); CHECK(dr_recv_cap_name(-1) == NULL, NULL); - CHECK(dr_recv_cap_desc(99) == NULL, NULL); TEST_END() /* ================================================================ * 主函数 * ================================================================ */ -#define RUN(name) do { reset_h(); _rx_r = 0; test_##name(); } while (0) +#define RUN(name) do { reset_h(); reset_rx(); test_##name(); } while (0) int main(void) { @@ -307,28 +309,30 @@ int main(void) printf("\n"); dr_reset(); dr_init(); - printf("[send_type & handler]\n"); - RUN(send_type_set_get); + printf("[send_type / recv_type / handler]\n"); + RUN(send_type_set_clear); + RUN(recv_type_set_clear); + RUN(types_independent); RUN(handler_add_remove); - RUN(handler_clear); printf("\n"); printf("[send — auto broadcast]\n"); RUN(send_no_match_port); RUN(send_one_port_one_handler); - RUN(send_one_port_multi_handler); - RUN(send_multi_ports_same_type); - RUN(send_multi_port_multi_handler); + RUN(send_multi_ports); RUN(send_type_filter); - RUN(send_after_remove_handler); - RUN(send_after_delete_port); + RUN(send_after_delete); RUN(send_zero_length); RUN(send_null_data); - RUN(send_multi_cap); printf("\n"); - printf("[receive callback]\n"); - RUN(recv_callback); + printf("[receive — dispatch]\n"); + RUN(recv_callback_direct); + RUN(dispatch_valid); + RUN(dispatch_command); + RUN(dispatch_unset); + RUN(dispatch_invalid_port); + RUN(dispatch_deleted_port); printf("\n"); printf("[capability metadata]\n"); -- 2.54.0