From f56ede73167ba77ed626a001705bdb276817bfee Mon Sep 17 00:00:00 2001 From: ypc <15051963820@163.com> Date: Tue, 7 Jul 2026 17:30:10 +0800 Subject: [PATCH] feat(libdatacenter): complete data center module with 15-file split MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 纯 C 接口(60+ API),对齐 RTU 原工程 v1+v2 全部功能 - 5 种信号类型:out/in/yk/ao/param + SBO 控制流程 - module_id 回路阻断 + 脏标记延迟回调 + 值校验(19 种类型) - 异步执行模型:yk/ao/param_exec_async + 超时 + 取消 - 内置 tinyxml2 解析 param.xml/self_param.xml - 命令调试面板(libcmd CMD_REGISTER_C) - O(1) XXH3_128bits 哈希查找(复用 libmy_xxhash) - x86 编译通过,产物 libdatacenter.a --- .../public/libdatacenter/API-libdatacenter.md | 332 +++++++++++ release/inc/myDatacenter.h | 558 ++++++++++++++++++ release/src/public/libdatacenter/makefile | 32 + src/public/libdatacenter/inc/dc_internal.h | 190 ++++++ src/public/libdatacenter/inc/myDatacenter.h | 558 ++++++++++++++++++ src/public/libdatacenter/src/dc_api_wrap.cpp | 79 +++ src/public/libdatacenter/src/dc_async.cpp | 349 +++++++++++ src/public/libdatacenter/src/dc_core.cpp | 241 ++++++++ src/public/libdatacenter/src/dc_event.cpp | 153 +++++ src/public/libdatacenter/src/dc_param_cfg.cpp | 423 +++++++++++++ src/public/libdatacenter/src/dc_signal_ao.cpp | 247 ++++++++ .../libdatacenter/src/dc_signal_check.cpp | 105 ++++ src/public/libdatacenter/src/dc_signal_in.cpp | 79 +++ .../libdatacenter/src/dc_signal_out.cpp | 120 ++++ .../libdatacenter/src/dc_signal_param.cpp | 290 +++++++++ .../libdatacenter/src/dc_signal_query.cpp | 336 +++++++++++ .../libdatacenter/src/dc_signal_show.cpp | 205 +++++++ .../libdatacenter/src/dc_signal_util.cpp | 272 +++++++++ src/public/libdatacenter/src/dc_signal_yk.cpp | 192 ++++++ 19 files changed, 4761 insertions(+) create mode 100644 docs/public/libdatacenter/API-libdatacenter.md create mode 100644 release/inc/myDatacenter.h create mode 100644 release/src/public/libdatacenter/makefile create mode 100644 src/public/libdatacenter/inc/dc_internal.h create mode 100644 src/public/libdatacenter/inc/myDatacenter.h create mode 100644 src/public/libdatacenter/src/dc_api_wrap.cpp create mode 100644 src/public/libdatacenter/src/dc_async.cpp create mode 100644 src/public/libdatacenter/src/dc_core.cpp create mode 100644 src/public/libdatacenter/src/dc_event.cpp create mode 100644 src/public/libdatacenter/src/dc_param_cfg.cpp create mode 100644 src/public/libdatacenter/src/dc_signal_ao.cpp create mode 100644 src/public/libdatacenter/src/dc_signal_check.cpp create mode 100644 src/public/libdatacenter/src/dc_signal_in.cpp create mode 100644 src/public/libdatacenter/src/dc_signal_out.cpp create mode 100644 src/public/libdatacenter/src/dc_signal_param.cpp create mode 100644 src/public/libdatacenter/src/dc_signal_query.cpp create mode 100644 src/public/libdatacenter/src/dc_signal_show.cpp create mode 100644 src/public/libdatacenter/src/dc_signal_util.cpp create mode 100644 src/public/libdatacenter/src/dc_signal_yk.cpp diff --git a/docs/public/libdatacenter/API-libdatacenter.md b/docs/public/libdatacenter/API-libdatacenter.md new file mode 100644 index 0000000..8208721 --- /dev/null +++ b/docs/public/libdatacenter/API-libdatacenter.md @@ -0,0 +1,332 @@ +# API-libdatacenter — 数据中心模块 + +> 版本: v3.0 +> 日期: 2026-07-07 +> 状态: 已实现(对齐 RTU 原工程 v1+v2 全部功能) + +--- + +## 1. 模块概述 + +libdatacenter 是 RTU 通讯装置的核心数据中间层,负责: + +| 功能 | 说明 | +|------|------| +| 信号注册与管理 | 5 种信号类型(out/in/yk/ao/param)统一存储和 XXH3 哈希索引 | +| 回路阻断 | 通过 `module_id` 实现"修改信号的模块不收到自己的回调" | +| 延迟变化检测 | out 信号采用脏标记 + 100ms 批量检测,避免频繁回调 | +| SBO 控制流程 | 支持直控(Direct)和选控(Select-Before-Operate)两种模式 | +| 参数元数据 | min/max/step/unit 值校验 | +| 配置文件持久化 | 通过 libxml C 接口读写 param.xml/self_param.xml | +| 事件队列 | SOE 事件、扰动数据、故障录波三类队列(支持多消费者) | +| 异步执行 | yk/ao/param 异步控制 + 超时检测 + 取消 | +| 命令调试 | 通过 libcmd 注册 `datacenter` 命令,交互式查看信号 | + +### 架构依赖 + +``` +libmy_xxhash → libxml → libcmd + ↘ ↙ + libdatacenter +``` + +--- + +## 2. 数据结构 + +### 2.1 控制类型 +```c +typedef enum +{ + DC_CTRL_NONE = 0, /**< 无控制 */ + DC_CTRL_DIRECT = 1, /**< 直控(直接执行) */ + DC_CTRL_SBO = 2 /**< 选控(Select-Before-Operate) */ +} dc_ctrl_type_t; +``` + +### 2.2 控制步骤 +```c +typedef enum +{ + DC_STEP_READY = 0, /**< 就绪 */ + DC_STEP_SELECT = 1, /**< 选择 */ + DC_STEP_DIRECT = 2, /**< 执行 */ + DC_STEP_CANCEL = 3 /**< 取消 */ +} dc_ctrl_step_t; +``` + +### 2.3 SBO 状态转换 +``` +READY ──→ SELECT(锁住信号,暂存值到 ctrl.p_data) + │ │ + │ ├──→ DIRECT(返校比对 → 写入 → ctrl.step = READY) + │ │ + │ └──→ CANCEL(回退,ctrl.step = READY) + │ + └──→ DIRECT(直控:直接写入) +``` + +### 2.4 控制上下文 +```c +typedef struct +{ + uint8_t step; /**< 当前步骤 */ + uint8_t type; /**< 控制类型 */ + uint8_t data_type; /**< 数据类型 */ + uint8_t reserved; /**< 保留 */ + void *p_data; /**< SELECT时暂存选择值,DIRECT时返校比对 */ +} dc_ctrl_t; +``` + +### 2.5 参数元数据 +```c +typedef struct +{ + float min; /**< 最小值 */ + float max; /**< 最大值 */ + float step; /**< 步长 */ + char unit[32]; /**< 单位 */ +} dc_param_meta_t; +``` + +### 2.6 错误码 +| 值 | 名称 | 说明 | +|----|------|------| +| 0 | DC_OK | 成功 | +| -1 | DC_ERR_PARAM | 参数错误 | +| -2 | DC_ERR_NOTFOUND | 信号不存在 | +| -3 | DC_ERR_TYPE | 数据类型不匹配 | +| -4 | DC_ERR_STEP | SBO 步骤错误 | +| -5 | DC_ERR_VAL | 值校验失败 | +| -6 | DC_ERR_TIMEOUT | 异步超时 | +| -7 | DC_ERR_BUSY | 忙 | +| -8 | DC_ERR_MEM | 内存不足 | +| -9 | DC_ERR_FULL | 队列已满 | +| -10 | DC_ERR_LOCKED | 已锁定 | +| -11 | DC_ERR_EXISTS | 重复注册 | +| -12 | DC_ERR_CANCEL | 已取消 | + +--- + +## 3. 接口函数 + +### 3.1 模块生命周期 + +| 函数 | 说明 | +|------|------| +| `dc_init(cfg_dir)` | 初始化,自动加载 param.xml | +| `dc_cleanup()` | 清理所有资源 | +| `dc_run_100ms()` | 每100ms:out变化检测 + 事件弹出 + 异步超时 | +| `dc_run_1000ms()` | 每1000ms:参数变更检测 → 写 self_param.xml | + +### 3.2 Out 信号(遥测/遥信) + +| 函数 | 说明 | +|------|------| +| `dc_signal_out(saddr, desc, data_type, p_data, module_id)` | 注册输出信号 | +| `dc_signal_out_with_callback(..., cb, module_id)` | 注册 + 延迟变化回调 | +| `dc_signal_out_link_with_callback(saddr, pp_data, cb, module_id)` | 链接已有信号 | +| `dc_set_out_signal_val(saddr, set_data, module_id)` | 设值(脏标记 + 延迟回调) | + +### 3.3 In 信号(输入信号,链接 out) + +| 函数 | 说明 | +|------|------| +| `dc_signal_in(saddr, desc, link_saddr, pp_data, module_id)` | 注册并关联 out | +| `dc_signal_in_with_callback(..., cb, module_id)` | 注册 + 回调(挂在 out 上) | + +### 3.4 YK 信号(遥控) + +| 函数 | 说明 | +|------|------| +| `dc_signal_yk(...)` | 注册遥控信号 | +| `dc_signal_yk_link_with_callback(...)` | 链接已有 | +| `dc_signal_yk_set_status(saddr, step, ctrl, p_data, module_id)` | 同步控制 | +| `dc_yk_exec_async(saddr, step, ctrl, p_data, module_id, result_cb, arg, timeout, req_id)` | 异步控制 | +| `dc_yk_set_result(req_id, result, err)` | 异步结果通知 | +| `dc_yk_cancel_async(req_id)` | 取消异步 | +| `dc_yk_register_exec_cb(saddr, exec_cb, ctx)` | 注册执行回调 | + +### 3.5 AO 信号(模拟输出) + +| 函数 | 说明 | +|------|------| +| `dc_signal_ao(...)` | 注册 AO 信号 | +| `dc_signal_ao_link_with_callback(...)` | 链接已有 | +| `dc_signal_ao_set_val(saddr, step, ctrl, p_data, module_id)` | 同步控制(SBO + 值校验) | +| `dc_signal_ao_set_val_without_check(...)` | 无校验设值 | +| `dc_ao_exec_async(...)` | 异步控制 | +| `dc_ao_set_result(...)` | 异步结果通知 | +| `dc_ao_cancel_async(...)` | 取消异步 | +| `dc_ao_register_exec_cb(...)` | 注册执行回调 | + +### 3.6 Param 信号(定值,多区) + +| 函数 | 说明 | +|------|------| +| `dc_signal_param(saddr, desc, data_type, ctrl_type, pp_data, num_zones, cb, module_id)` | 注册参数 | +| `dc_signal_param_link_with_callback(...)` | 链接已有 | +| `dc_signal_param_set_val(saddr, step, ctrl, zone, p_data, module_id)` | 同步设值 | +| `dc_signal_param_set_val_without_check(...)` | 无校验设值 | +| `dc_param_exec_async(...)` | 异步控制 | +| `dc_param_set_result(...)` | 异步结果通知 | +| `dc_param_cancel_async(...)` | 取消异步 | +| `dc_param_register_exec_cb(...)` | 注册执行回调 | + +### 3.7 信号查询 + +| 函数 | 说明 | +|------|------| +| `dc_get_out_signal_info(saddr, desc, desc_len, data_type, pp_data)` | 查询 out | +| `dc_get_in_signal_info(...)` | 查询 in | +| `dc_get_ao_signal_info(saddr, desc, desc_len, data_type, p_meta, ctrl_type, pp_data)` | 查询 AO(含元数据) | +| `dc_get_param_signal_info(saddr, desc, desc_len, data_type, p_meta, ctrl_type, p_data_array, p_count)` | 查询 Param(多区) | +| `dc_get_yk_signal_info(...)` | 查询 YK | +| `dc_get_signal_val(p_data, data_type, buf, buf_len)` | 值 → 字符串 | +| `dc_get_signal_count(type_str)` | 按类型统计 | +| `dc_get_signal_info_by_id(type_str, id, ...)` | 按 ID 查询 | + +### 3.8 事件队列 + +| 函数 | 说明 | +|------|------| +| `dc_event_queue_push(p_soe)` | SOE 入队 | +| `dc_event_register_queue_pop(cb)` | 注册 SOE 消费回调 | +| `dc_disturb_dd_queue_push(p_dd)` | 扰动数据入队 | +| `dc_disturb_dd_register_queue_pop(cb)` | 注册扰动消费回调 | +| `dc_fault_queue_push(p_fault)` | 故障数据入队 | +| `dc_fault_register_queue_pop(cb)` | 注册故障消费回调 | + +### 3.9 类型工具 + +| 函数 | 说明 | +|------|------| +| `dc_get_type_name(data_type)` | ID → 名称(35 → "uint32_t") | +| `dc_get_type_id_by_name(name)` | 名称 → ID("float" → 38) | +| `dc_get_type_len(data_type)` | 获取字节长度 | +| `dc_create_data(data_type)` / `dc_destroy_data(p_data, data_type)` | 类型感知的分配/释放 | +| `dc_copy_val(dst, src, data_type)` / `dc_compare_val(p1, p2, data_type)` | 类型感知的复制/比较 | +| `dc_set_val_from_str(p_data, data_type, str)` | 字符串 → 值 | + +### 3.10 配置与异步管理 + +| 函数 | 说明 | +|------|------| +| `dc_param_cfg_changed()` | 查询是否有未保存变更 | +| `dc_async_pending_count()` | 异步请求 pending 数 | +| `dc_async_set_default_timeout(ms)` | 全局默认超时 | +| `dc_async_set_timeout_yk/ao/param(ms)` | 各类型默认超时 | +| `dc_save_out_signals_xml(path)` | 导出 out 信号到 XML | + +--- + +## 4. 使用示例 + +```c +#include "myDatacenter.h" +#include + +static void on_out_changed(const char *saddr, uint8_t data_type, + const void *p_new, const void *p_old) +{ + printf("out %s: %d → %d\n", saddr, *(int *)p_old, *(int *)p_new); +} + +static void on_yk_changed(const char *saddr, dc_ctrl_step_t step, + uint8_t data_type, uint8_t zone, + const void *p_data) +{ + printf("yk %s step=%d val=%d\n", saddr, step, *(uint8_t *)p_data); +} + +int main(void) +{ + /* 初始化,自动加载 param.xml */ + dc_init("/etc/rtu/config/"); + + /* 注册 out 信号 */ + int yc_val = 0; + dc_signal_out("RPC1:YC:1:val", "母线电压", DATA_TYPE_S32, + &yc_val, "protocol_104"); + + /* 另一个模块链接 out 信号并注册回调 */ + void *p_linked = NULL; + dc_signal_out_link_with_callback("RPC1:YC:1:val", &p_linked, + on_out_changed, "web_server"); + + /* 注册 yk 信号(直控模式) */ + uint8_t yk_val = 0; + dc_signal_yk("RPC1:YK:1:val", "开关合闸", DATA_TYPE_U8, + DC_CTRL_DIRECT, &yk_val, on_yk_changed, "protocol_104"); + + /* 修改 out 信号值(回路阻断:protocol_104 自身的回调不会被触发) */ + yc_val = 220; + dc_set_out_signal_val("RPC1:YC:1:val", &yc_val, "protocol_104"); + + /* 100ms 周期:on_out_changed 被调用 */ + dc_run_100ms(); + + /* YK 直控执行(同步回调:on_yk_changed 立即触发) */ + dc_ctrl_t ctrl = {0}; + ctrl.type = DC_CTRL_DIRECT; + ctrl.data_type = DATA_TYPE_U8; + uint8_t new_val = 1; + dc_signal_yk_set_status("RPC1:YK:1:val", DC_STEP_DIRECT, + &ctrl, &new_val, "web_server"); + + dc_cleanup(); + return 0; +} +``` + +--- + +## 5. 依赖关系 + +| 依赖 | 模块 | 用途 | +|------|------|------| +| ⇩ | `myBase.h` | 基础类型定义(DATA_TYPE_* 宏) | +| ⇩ | `myLog.h` | 日志输出 | +| ⇩ | `libmy_xxhash` | XXH3_128bits 哈希查找(O(1)) | +| ⇩ | `libxml` | 纯 C 接口 XML 解析(param.xml/self_param.xml) | +| ⇩ | `libcmd` | 命令注册(`datacenter` 调试面板) | + +--- + +## 6. 源文件组成(15 文件,对齐 RTU 原工程) + +| 文件 | 职责 | +|------|------| +| `inc/myDatacenter.h` | 纯 C 对外接口(60+ API) | +| `src/dc_internal.h` | C++ 内部声明(哈希/信号表/异步队列) | +| `src/dc_core.cpp` | 全局变量 + XXH128 哈希 + dc_init/run | +| `src/dc_signal_out.cpp` | Out 信号注册/链接/设值 | +| `src/dc_signal_in.cpp` | In 信号注册(关联 out) | +| `src/dc_signal_yk.cpp` | YK 注册 + 同步/异步控制 | +| `src/dc_signal_ao.cpp` | AO 注册 + 同步/异步控制 + SBO | +| `src/dc_signal_param.cpp` | Param 多区定值 + 同步/异步控制 | +| `src/dc_signal_query.cpp` | 全部 get_xxx_info 函数 | +| `src/dc_signal_util.cpp` | 19 种类型工具函数 | +| `src/dc_signal_check.cpp` | SBO 校验 + 值校验 + YK 校验 | +| `src/dc_signal_show.cpp` | 命令注册 + XML 导出 | +| `src/dc_param_cfg.cpp` | param.xml 解析 + self_param.xml 保存 | +| `src/dc_event.cpp` | SOE/扰动/故障事件队列 | +| `src/dc_async.cpp` | 异步请求队列(入队/超时/取消/清理) | +| `src/dc_api_wrap.cpp` | C API 包装(get_type_name 等) | + +--- + +## 7. 新旧工程差异 + +| 维度 | RTU v1 | RTU v2 | 新工程 | +|------|--------|--------|--------| +| 接口语言 | C++ (std::string) | 纯 C (const char*) | 纯 C + Allman 风格 | +| 哈希 | XXH128 multimap | 同 v1 | unordered_map + XXH128 hasher | +| 参数文件 | tinyxml2 嵌入 | 同 v1 | libxml C 接口 | +| 异步执行 | 无 | _exec_async 系列 | _exec_async 系列 | +| 回路阻断 | ✓ | ✓ | ✓ | +| SBO 流程 | ✓ | ✓ | ✓ | +| 多区定值 | ✓ | ✓ | ✓ | +| 错误码 | -1/0 | enum dc_error_t | enum dc_error_t(12种) | +| 命令调试 | cmd_dc | 无 | CMD_REGISTER_C(libcmd) | +| 文件数 | 13 .cpp | 8 .cpp | 15 .cpp(按功能拆分) | diff --git a/release/inc/myDatacenter.h b/release/inc/myDatacenter.h new file mode 100644 index 0000000..6064e77 --- /dev/null +++ b/release/inc/myDatacenter.h @@ -0,0 +1,558 @@ +/** + * @file myDatacenter.h + * @brief 数据中心模块 — 纯 C 对外接口 + * @details 信号注册/控制/查询 + 事件队列 + 异步执行 + 参数配置。 + * 内部通过 libmy_xxhash 做 O(1) 哈希查找, + * 参数配置通过 libxml C 接口解耦。 + * 对齐 RTU 原工程 libdatacenter v1 + v2 全部功能。 + */ + +#ifndef _MY_DATACENTER_H_ +#define _MY_DATACENTER_H_ + +#include +#include +#include "xxhash.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* ===== 地址/描述/模块/错误信息最大长度 ===== */ +#define DC_SADDR_MAX_LEN 128 +#define DC_DESC_MAX_LEN 256 +#define DC_UNIT_MAX_LEN 32 +#define DC_MODULE_MAX_LEN 64 +#define DC_ERR_MSG_MAX_LEN 256 + +/* ===== 容量限制 ===== */ +#define DC_MAX_CALLBACKS 16 +#define DC_MAX_LINK_SADDRS 32 +#define DC_MAX_DATA_ZONES 8 +#define DC_MAX_PENDING 64 + +/** datacenter 自身模块标识 */ +#define MODULE_DATACENTER "datacenter" + +/* ===== 控制类型枚举 ===== */ +typedef enum +{ + DC_CTRL_NONE = 0, /**< 无控制 */ + DC_CTRL_DIRECT = 1, /**< 直控(直接执行,无需选择-确认) */ + DC_CTRL_SBO = 2 /**< 选控(Select-Before-Operate 两阶段) */ +} dc_ctrl_type_t; + +/* ===== 控制步骤枚举 ===== */ +typedef enum +{ + DC_STEP_READY = 0, /**< 就绪/空闲 */ + DC_STEP_SELECT = 1, /**< 选择(SBO 第一步:锁住信号并暂存选择值) */ + DC_STEP_DIRECT = 2, /**< 执行(SBO 第二步确认写入,直控直接执行) */ + DC_STEP_CANCEL = 3 /**< 取消(回退到 READY) */ +} dc_ctrl_step_t; + +/* ===== 错误码枚举 ===== */ +typedef enum +{ + DC_OK = 0, /**< 成功 */ + DC_ERR_PARAM = -1, /**< 参数错误(NULL/越界) */ + DC_ERR_NOTFOUND = -2, /**< 信号不存在 */ + DC_ERR_TYPE = -3, /**< 数据类型不匹配 */ + DC_ERR_STEP = -4, /**< SBO 步骤错误 */ + DC_ERR_VAL = -5, /**< 值校验失败(越界/未变化) */ + DC_ERR_TIMEOUT = -6, /**< 异步请求超时 */ + DC_ERR_BUSY = -7, /**< 忙 */ + DC_ERR_MEM = -8, /**< 内存不足 */ + DC_ERR_FULL = -9, /**< 请求队列已满 */ + DC_ERR_LOCKED = -10, /**< 已锁定 */ + DC_ERR_EXISTS = -11, /**< 信号已存在(重复注册) */ + DC_ERR_CANCEL = -12 /**< 异步请求已取消 */ +} dc_error_t; + +/* ===== 控制上下文 ===== */ +typedef struct +{ + uint8_t step; /**< 当前控制步骤(dc_ctrl_step_t) */ + uint8_t type; /**< 控制类型(dc_ctrl_type_t) */ + uint8_t data_type; /**< 数据类型(DATA_TYPE_*) */ + uint8_t reserved; /**< 保留 */ + void *p_data; /**< SELECT 时暂存选择值,DIRECT 时用于返校比对 */ +} dc_ctrl_t; + +/* ===== 信号参数元数据(min/max/step/unit) ===== */ +typedef struct +{ + float min; /**< 最小值 */ + float max; /**< 最大值 */ + float step; /**< 步长 */ + char unit[DC_UNIT_MAX_LEN]; /**< 单位(如 "kV", "A", "Hz") */ +} dc_param_meta_t; + +/* ===== 回调函数类型 ===== */ + +/** + * @brief out 信号值变更回调(延迟触发,100ms 周期批量检测后调用) + * @param saddr 信号地址 + * @param data_type 数据类型 + * @param p_new 新值指针 + * @param p_old 旧值指针 + */ +typedef void (*dc_out_change_cb_t)(const char *saddr, uint8_t data_type, + const void *p_new, const void *p_old); + +/** + * @brief yk/ao/param 信号控制变更回调(同步触发,控制场景即时响应) + * @param saddr 信号地址 + * @param step 控制步骤 + * @param data_type 数据类型 + * @param zone 定值区号(param 信号使用,其他为 0) + * @param p_data 数据指针 + */ +typedef void (*dc_signal_change_cb_t)(const char *saddr, dc_ctrl_step_t step, + uint8_t data_type, uint8_t zone, + const void *p_data); + +/** @brief 事件队列消费回调 */ +typedef void (*dc_queue_pop_cb_t)(void *p_data); + +/** + * @brief 异步请求完成回调(通知发起方模块A) + * @param req_id 请求 ID + * @param result 结果码(DC_OK / DC_ERR_*) + * @param err 错误信息(成功时为空) + * @param arg 用户参数 + */ +typedef void (*dc_async_result_cb_t)(uint32_t req_id, int result, + const char *err, void *arg); + +/** + * @brief 异步执行回调(datacenter 调用模块B执行实际操作) + * @param req_id 请求 ID + * @param saddr 信号地址 + * @param step 控制步骤 + * @param data_type 数据类型 + * @param zone 定值区号 + * @param p_data 操作数据 + * @param ctx 模块上下文 + * @return 0 已接受(稍后通过 dc_set_result 返回结果),-1 拒绝 + */ +typedef int (*dc_async_exec_cb_t)(uint32_t req_id, const char *saddr, + dc_ctrl_step_t step, uint8_t data_type, + uint8_t zone, const void *p_data, void *ctx); + +/* ===== 内部数据结构(对调用方透明,通过 API 访问) ===== */ + +/** 回调条目 */ +typedef struct +{ + char module_id[DC_MODULE_MAX_LEN]; + dc_out_change_cb_t out_cb; + dc_signal_change_cb_t ctrl_cb; +} dc_cb_entry_t; + +/** 信号节点 */ +typedef struct +{ + uint32_t id; + XXH128_hash_t hash; + char saddr[DC_SADDR_MAX_LEN]; + char desc[DC_DESC_MAX_LEN]; + uint8_t data_type; + uint8_t ctrl_type; + + void **vec_p_data; + int vec_p_data_count; + void *p_last_data; + + char link_saddr[DC_SADDR_MAX_LEN]; + char **link_saddrs; + int link_count; + + dc_param_meta_t param; + + dc_cb_entry_t cb_list[DC_MAX_CALLBACKS]; + int cb_count; + + char last_caller_module[DC_MODULE_MAX_LEN]; + + dc_async_exec_cb_t exec_cb; + void *module_ctx; +} dc_signal_t; + +/* ================================================================== + * 模块生命周期 + * ================================================================== */ + +/** + * @brief 初始化数据中心 + * @param cfg_dir 配置文件目录(用于查找 param.xml) + * @return DC_OK 成功,<0 失败 + */ +int dc_init(const char *cfg_dir); + +/** @brief 清理数据中心,释放所有信号和队列资源 */ +void dc_cleanup(void); + +/** + * @brief 每 100ms 周期调用 + * @details out 信号变化检测 + 批量触发回调 + 事件队列弹出 + 异步超时检测 + */ +void dc_run_100ms(void); + +/** + * @brief 每 1000ms 周期调用 + * @details 检测参数配置变更标志,有变更则写 self_param.xml + */ +void dc_run_1000ms(void); + +/* ================================================================== + * Out 信号注册(遥测/遥信输出) + * ================================================================== */ + +/** + * @brief 注册输出信号 + * @param saddr 信号地址(唯一标识) + * @param desc 信号描述 + * @param data_type 数据类型(DATA_TYPE_*) + * @param p_data 数据指针 + * @param module_id 模块标识(用于回路阻断) + * @return DC_OK 成功,DC_ERR_EXISTS 重复注册 + */ +int dc_signal_out(const char *saddr, const char *desc, uint8_t data_type, + void *p_data, const char *module_id); + +/** + * @brief 注册输出信号并绑定变化回调 + * @param cb 值变更回调(延迟触发,100ms 批量检测后调用) + */ +int dc_signal_out_with_callback(const char *saddr, const char *desc, + uint8_t data_type, void *p_data, + dc_out_change_cb_t cb, const char *module_id); + +/** + * @brief 链接已有 out 信号,获取数据指针并注册回调 + * @param p_data 输出:out 信号的数据指针 + */ +int dc_signal_out_link_with_callback(const char *saddr, void **p_data, + dc_out_change_cb_t cb, const char *module_id); + +/** + * @brief 设置输出信号值(标记脏,100ms 后触发变更检测和延迟回调) + * @param module_id 调用方模块标识(自身不会被回调——回路阻断) + */ +int dc_set_out_signal_val(const char *saddr, const void *set_data, + const char *module_id); + +/* ================================================================== + * In 信号注册(输入信号,链接到 out 信号的数据指针) + * ================================================================== */ + +/** + * @brief 注册输入信号并链接到输出信号 + * @param link_saddr 要链接的 out 信号地址 + * @param p_data 输出:被链接 out 信号的数据指针 + */ +int dc_signal_in(const char *saddr, const char *desc, + const char *link_saddr, void **p_data, const char *module_id); + +/** + * @brief 注册输入信号并绑定回调(回调挂在被链接的 out 信号上) + */ +int dc_signal_in_with_callback(const char *saddr, const char *desc, + const char *link_saddr, void **p_data, + dc_out_change_cb_t cb, const char *module_id); + +/* ================================================================== + * YK 信号(遥控) + * ================================================================== */ + +/** + * @brief 注册遥控信号 + * @param ctrl_type 控制类型(DC_CTRL_DIRECT 直控 / DC_CTRL_SBO 选控) + * @param cb 控制变更回调(同步触发) + */ +int dc_signal_yk(const char *saddr, const char *desc, + uint8_t data_type, uint8_t ctrl_type, void *p_data, + dc_signal_change_cb_t cb, const char *module_id); + +/** @brief 链接已有 YK 信号,获取数据指针并注册回调 */ +int dc_signal_yk_link_with_callback(const char *saddr, void **p_data, + dc_signal_change_cb_t cb, const char *module_id); + +/** + * @brief YK 同步设置(处理 SBO 流程:SELECT→DIRECT/CANCEL) + * @param step 控制步骤 + * @param ctrl 控制上下文(进入时携带 type/data_type/p_data,执行完毕后回写当前 step) + * @param p_data 操作数据 + */ +int dc_signal_yk_set_status(const char *saddr, dc_ctrl_step_t step, + dc_ctrl_t *ctrl, const void *p_data, + const char *module_id); + +/** + * @brief YK 异步远程执行 + * @param result_cb 结果回调 + * @param timeout_ms 超时时间(毫秒),0 使用默认值 + * @param request_id 输出:请求 ID + */ +int dc_yk_exec_async(const char *saddr, dc_ctrl_step_t step, + const dc_ctrl_t *ctrl, const void *p_data, + const char *module_id, + dc_async_result_cb_t result_cb, void *user_arg, + uint32_t timeout_ms, uint32_t *request_id); + +/** @brief 设置 YK 异步执行结果(模块B回调) */ +int dc_yk_set_result(uint32_t request_id, int result_code, const char *err_msg); + +/** @brief 取消 YK 异步请求 */ +int dc_yk_cancel_async(uint32_t request_id); + +/** @brief 注册 YK 异步执行回调(模块B初始化时调用) */ +int dc_yk_register_exec_cb(const char *saddr, dc_async_exec_cb_t exec_cb, + void *module_ctx); + +/* ================================================================== + * AO 信号(模拟输出) + * ================================================================== */ + +/** + * @brief 注册 AO 信号 + * @param ctrl_type 控制类型 + * @param cb 控制变更回调(同步触发) + */ +int dc_signal_ao(const char *saddr, const char *desc, + uint8_t data_type, uint8_t ctrl_type, void *p_data, + dc_signal_change_cb_t cb, const char *module_id); + +/** @brief 链接已有 AO 信号 */ +int dc_signal_ao_link_with_callback(const char *saddr, void **p_data, + dc_signal_change_cb_t cb, const char *module_id); + +/** + * @brief AO 同步设置(SBO 流程 + 值校验) + * @details DIRECT 执行后 ctrl->step 自动复位到 READY + */ +int dc_signal_ao_set_val(const char *saddr, dc_ctrl_step_t step, + dc_ctrl_t *ctrl, const void *p_data, + const char *module_id); + +/** @brief AO 无校验设置(跳过 SBO 流程和值校验,用于初始化恢复) */ +int dc_signal_ao_set_val_without_check(const char *saddr, uint8_t data_type, + const void *p_data, const char *module_id); + +/** @brief AO 异步执行 */ +int dc_ao_exec_async(const char *saddr, dc_ctrl_step_t step, + const dc_ctrl_t *ctrl, const void *p_data, + const char *module_id, + dc_async_result_cb_t result_cb, void *user_arg, + uint32_t timeout_ms, uint32_t *request_id); + +/** @brief 设置 AO 异步执行结果 */ +int dc_ao_set_result(uint32_t request_id, int result_code, const char *err_msg); + +/** @brief 取消 AO 异步请求 */ +int dc_ao_cancel_async(uint32_t request_id); + +/** @brief 注册 AO 异步执行回调 */ +int dc_ao_register_exec_cb(const char *saddr, dc_async_exec_cb_t exec_cb, + void *module_ctx); + +/* ================================================================== + * Param 信号(参数/定值,多定值区) + * ================================================================== */ + +/** + * @brief 注册参数信号 + * @param p_data 数据指针数组(每个 zone 一个指针) + * @param num_zones 定值区数量 + */ +int dc_signal_param(const char *saddr, const char *desc, + uint8_t data_type, uint8_t ctrl_type, + void **p_data, int num_zones, + dc_signal_change_cb_t cb, const char *module_id); + +/** @brief 链接已有参数信号 */ +int dc_signal_param_link_with_callback(const char *saddr, void **p_data, + int num_zones, + dc_signal_change_cb_t cb, + const char *module_id); + +/** + * @brief Param 同步设置(SBO + zone 值校验) + * @param setting_zone 定值区号(0-based) + */ +int dc_signal_param_set_val(const char *saddr, dc_ctrl_step_t step, + dc_ctrl_t *ctrl, uint8_t setting_zone, + const void *p_data, const char *module_id); + +/** @brief Param 无校验设置 */ +int dc_signal_param_set_val_without_check(const char *saddr, uint8_t data_type, + uint8_t setting_zone, + const void *p_data, + const char *module_id); + +/** @brief Param 异步执行 */ +int dc_param_exec_async(const char *saddr, dc_ctrl_step_t step, + const dc_ctrl_t *ctrl, uint8_t setting_zone, + const void *p_data, const char *module_id, + dc_async_result_cb_t result_cb, void *user_arg, + uint32_t timeout_ms, uint32_t *request_id); + +/** @brief 设置 Param 异步执行结果 */ +int dc_param_set_result(uint32_t request_id, int result_code, + const char *err_msg); + +/** @brief 取消 Param 异步请求 */ +int dc_param_cancel_async(uint32_t request_id); + +/** @brief 注册 Param 异步执行回调 */ +int dc_param_register_exec_cb(const char *saddr, dc_async_exec_cb_t exec_cb, + void *module_ctx); + +/* ================================================================== + * 信号查询 + * ================================================================== */ + +/** @brief 查询 out 信号信息 */ +int dc_get_out_signal_info(const char *saddr, char *desc, int desc_len, + uint8_t *data_type, void **p_data); + +/** @brief 查询 in 信号信息 */ +int dc_get_in_signal_info(const char *saddr, char *desc, int desc_len, + uint8_t *data_type, void **p_data); + +/** @brief 查询 AO 信号信息(含参数元数据) */ +int dc_get_ao_signal_info(const char *saddr, char *desc, int desc_len, + uint8_t *data_type, dc_param_meta_t *p_meta, + uint8_t *ctrl_type, void **p_data); + +/** + * @brief 查询 Param 信号信息(含多区数据指针列表) + * @param p_data_array 输出:数据指针数组(调用方提供缓冲区) + * @param p_count 输出:定值区数量 + */ +int dc_get_param_signal_info(const char *saddr, char *desc, int desc_len, + uint8_t *data_type, dc_param_meta_t *p_meta, + uint8_t *ctrl_type, + void **p_data_array, int *p_count); + +/** @brief 查询 YK 信号信息 */ +int dc_get_yk_signal_info(const char *saddr, char *desc, int desc_len, + uint8_t *data_type, uint8_t *ctrl_type, void **p_data); + +/** @brief 获取信号值的字符串表示 */ +int dc_get_signal_val(const void *p_data, uint8_t data_type, + char *buf, int buf_len); + +/** + * @brief 按类型统计信号数量 + * @param type_str 类型字符串:"out"/"in"/"yk"/"ao"/"param" + * @return 信号数量 + */ +int dc_get_signal_count(const char *type_str); + +/** + * @brief 按类型和 ID 查询信号基本信息(供前端遍历) + * @param type_str "out"/"in"/"yk"/"ao"/"param" + * @param id 信号 ID + * @param saddr/saddr_len 输出:信号地址 + * @param desc/desc_len 输出:描述 + * @param data_type_str/type_str_len 输出:数据类型名称 + * @param ctrl_type 输出:控制类型 + * @param link_str/link_len 输出:链接信号的逗号分隔列表 + */ +int dc_get_signal_info_by_id(const char *type_str, uint32_t id, + char *saddr, int saddr_len, + char *desc, int desc_len, + char *data_type_str, int type_str_len, + uint8_t *ctrl_type, + char *link_str, int link_len); + +/* ================================================================== + * 事件/扰动/故障队列 + * ================================================================== */ + +/** @brief SOE 事件入队 */ +int dc_event_queue_push(void *p_soe); + +/** @brief 注册 SOE 事件消费回调(支持多消费者) */ +int dc_event_register_queue_pop(dc_queue_pop_cb_t cb); + +/** @brief 扰动数据入队 */ +int dc_disturb_dd_queue_push(void *p_dd); + +/** @brief 注册扰动数据消费回调 */ +int dc_disturb_dd_register_queue_pop(dc_queue_pop_cb_t cb); + +/** @brief 故障数据入队 */ +int dc_fault_queue_push(void *p_fault); + +/** @brief 注册故障数据消费回调 */ +int dc_fault_register_queue_pop(dc_queue_pop_cb_t cb); + +/* ================================================================== + * 类型工具函数 + * ================================================================== */ + +/** @brief 数据类型 ID → 名称(如 35 → "uint32_t") */ +const char *dc_get_type_name(uint8_t data_type); + +/** @brief 名称 → 数据类型 ID(如 "float" → 38) */ +uint8_t dc_get_type_id_by_name(const char *name); + +/** @brief 获取数据类型的字节长度 */ +uint8_t dc_get_type_len(uint8_t data_type); + +/** @brief 根据类型分配并清零内存 */ +void *dc_create_data(uint8_t data_type); + +/** @brief 根据类型释放内存 */ +void dc_destroy_data(void *p_data, uint8_t data_type); + +/** @brief 类型感知的值复制 */ +int dc_copy_val(void *dst, const void *src, uint8_t data_type); + +/** @brief 类型感知的值比较(0 = 相等,-1 = 不等) */ +int dc_compare_val(const void *p1, const void *p2, uint8_t data_type); + +/** @brief 字符串 → 值(如 "3.14" → 3.14f) */ +int dc_set_val_from_str(void *p_data, uint8_t data_type, const char *str); + +/* ================================================================== + * 参数配置与异步管理 + * ================================================================== */ + +/** @brief 查询参数配置是否有未保存的变更(0=无,1=有) */ +int dc_param_cfg_changed(void); + +/** @brief 获取当前异步请求 pending 数量 */ +int dc_async_pending_count(void); + +/** @brief 设置全局默认异步超时时间(毫秒) */ +void dc_async_set_default_timeout(uint32_t timeout_ms); + +/** @brief 设置 YK 遥控默认异步超时(0=使用全局默认) */ +void dc_async_set_timeout_yk(uint32_t timeout_ms); + +/** @brief 设置 AO 调节默认异步超时(0=使用全局默认) */ +void dc_async_set_timeout_ao(uint32_t timeout_ms); + +/** @brief 设置 Param 定值默认异步超时(0=使用全局默认) */ +void dc_async_set_timeout_param(uint32_t timeout_ms); + +/* ================================================================== + * 导出 + * ================================================================== */ + +/** + * @brief 保存所有已注册 out 信号到 XML + * @param path 目标文件路径 + * @return DC_OK 成功,<0 失败 + */ +int dc_save_out_signals_xml(const char *path); + +#ifdef __cplusplus +} +#endif +#endif /* _MY_DATACENTER_H_ */ diff --git a/release/src/public/libdatacenter/makefile b/release/src/public/libdatacenter/makefile new file mode 100644 index 0000000..f8dbf17 --- /dev/null +++ b/release/src/public/libdatacenter/makefile @@ -0,0 +1,32 @@ +include ./../../../linux.mk +L := $(notdir $(realpath $(CURDIR)/..)) +M := libdatacenter +O := $(LIB_REL)/$(M).a +S := $(SRC_ROOT_DIR)/$(L)/libdatacenter/src +I := -I$(SRC_ROOT_DIR)/$(L)/libdatacenter/inc -I$(SRC_ROOT_DIR)/public/libmy_xxhash/inc -I$(SRC_ROOT_DIR)/public/libxml/inc -I$(SRC_ROOT_DIR)/public/libcmd/inc +B := $(CURDIR)/$(M)/obj +SRCS := $(wildcard $(S)/*.cpp) +OBJS := $(patsubst $(S)/%.cpp, $(B)/%.o, $(SRCS)) +F := $(CXX_FLAGS) $(I) + +.PHONY: all +all: + @mkdir -p $(ROOT_DIR)/release/inc + @cp -f $(S:src=inc)/*.h $(ROOT_DIR)/release/inc/ 2>/dev/null; true + @$(MAKE) $(O) + +$(O): $(OBJS) + @mkdir -p $(dir $@) + $(AR) rcs $@ $^ + @echo "[$(M)] built" + +$(B)/%.o: $(S)/%.cpp + @mkdir -p $(dir $@) + $(CXX) $(F) -c $< -o $@ + +.PHONY: clean +clean: + rm -rf $(B) $(O) + +.PHONY: rebuild +rebuild: clean all diff --git a/src/public/libdatacenter/inc/dc_internal.h b/src/public/libdatacenter/inc/dc_internal.h new file mode 100644 index 0000000..560910f --- /dev/null +++ b/src/public/libdatacenter/inc/dc_internal.h @@ -0,0 +1,190 @@ +/** + * @file dc_internal.h + * @brief 数据中心内部共享声明(C++,仅模块内可见) + * @note 对齐 RTU 原工程 dc_signal_internal.h 的架构 + */ +#ifndef DC_INTERNAL_H +#define DC_INTERNAL_H + +#include "myDatacenter.h" +#include "myBase.h" +#include "myLog.h" +#include "xxhash.h" + +#include +#include +#include +#include +#include +#include +#include + +/* ================================================================ + * XXH128 哈希函数对象(与 RTU 原工程完全一致) + * ================================================================ */ +struct DCXXH128Hash +{ + size_t operator()(const XXH128_hash_t &h) const noexcept + { + const uint64_t mix1 = h.low64 ^ (h.high64 << 17); + const uint64_t mix2 = mix1 + (h.high64 >> 13); + const uint64_t mix3 = mix2 ^ (h.low64 << 23); + + return static_cast(mix3 ^ (mix3 >> 32)); + } +}; + +struct DCXXH128Equal +{ + bool operator()(const XXH128_hash_t &a, const XXH128_hash_t &b) const noexcept + { + return (a.high64 == b.high64) && (a.low64 == b.low64); + } +}; + +typedef std::unordered_multimap dc_hash_map_t; + +/* ================================================================ + * 信号存储容器 + * ================================================================ */ +typedef struct +{ + std::mutex mtx; + uint32_t signal_id; + dc_hash_map_t map_signals; + std::vector id_index; +} dc_signal_map_t; + +/* ================================================================ + * 异步请求结构体 + * ================================================================ */ +typedef struct dc_async_request +{ + uint32_t request_id; + char saddr[DC_SADDR_MAX_LEN]; + dc_ctrl_step_t step; + uint8_t data_type; + uint8_t setting_zone; + uint8_t ctrl_type; + uint8_t signal_type; /* 0=yk, 1=ao, 2=param */ + + void *p_select_data; + void *p_direct_data; + + uint64_t start_time_ms; + uint32_t timeout_ms; + + char caller_module[DC_MODULE_MAX_LEN]; + dc_async_result_cb_t result_cb; + void *user_arg; + + dc_async_exec_cb_t exec_cb; + void *module_ctx; + + int result_code; + char err_msg[DC_ERR_MSG_MAX_LEN]; + + struct dc_async_request *next; +} dc_async_request_t; + +/* ================================================================ + * 异步请求队列 + * ================================================================ */ +typedef struct +{ + std::mutex mtx; + uint32_t next_id; + dc_async_request_t *head; + int count; + int max_pending; + uint32_t default_timeout_ms; + uint32_t default_timeout_yk_ms; + uint32_t default_timeout_ao_ms; + uint32_t default_timeout_param_ms; + std::unordered_map req_map; +} dc_async_queue_t; + +/* ================================================================ + * 全局变量声明 + * ================================================================ */ +extern dc_signal_map_t g_dc_out; +extern dc_signal_map_t g_dc_in; +extern dc_signal_map_t g_dc_yk; +extern dc_signal_map_t g_dc_ao; +extern dc_signal_map_t g_dc_param; + +extern std::vector g_dirty_out_signals; +extern std::mutex g_dirty_out_mutex; + +extern dc_async_queue_t g_async_queue; + +extern bool g_param_cfg_changed; +extern std::mutex g_param_cfg_mutex; + +extern char g_cfg_dir[256]; +extern bool g_initialized; + +extern std::unordered_map g_param_metadata; + +/* ================================================================ + * 跨文件函数声明 + * ================================================================ */ + +/* dc_core.cpp */ +int dc_core_add(dc_signal_t &signal, dc_signal_map_t &smap); +dc_signal_t *dc_core_find(const char *saddr, dc_signal_map_t &smap); +dc_signal_t *dc_core_find_out(const char *saddr); +dc_signal_t *dc_core_find_in(const char *saddr); +dc_signal_t *dc_core_find_by_id(uint32_t id, dc_signal_map_t &smap); +void dc_core_mark_dirty(dc_signal_t *p_signal); + +/* dc_signal_util.cpp */ +int dc_util_val_compare(uint8_t data_type, const void *p1, const void *p2); +void dc_util_val_copy(void *dst, const void *src, uint8_t data_type); +void *dc_util_create_data(uint8_t data_type); +void dc_util_destroy_data(void *p_data, uint8_t data_type); +std::string dc_util_val_to_string(const void *p_data, uint8_t data_type); +int dc_util_val_from_string(void *p_data, uint8_t data_type, const std::string &str); +std::string dc_util_type_name(uint8_t data_type); +uint8_t dc_util_type_id_by_name(const std::string &name); +uint8_t dc_util_type_len(uint8_t data_type); + +/* dc_signal_check.cpp */ +int dc_check_ctrl_val_valid(const dc_ctrl_t *ctrl, const void *p_data); +int dc_check_ctrl_valid(const dc_signal_t *p_signal, dc_ctrl_step_t step, + dc_ctrl_t *ctrl, const void *p_data); +int dc_check_val_valid(const dc_signal_t *p_signal, uint8_t setting_zone, + const void *p_data); +int dc_check_yk_val_valid(dc_signal_t *p_signal, void *p_data); + +/* dc_event.cpp */ +int dc_disturb_dd_queue_push(void *p_dd); +int dc_disturb_dd_register_queue_pop(dc_queue_pop_cb_t cb); +void dc_disturb_dd_queue_pop(void); +int dc_event_queue_push(void *p_soe); +int dc_event_register_queue_pop(dc_queue_pop_cb_t cb); +void dc_event_queue_pop(void); +int dc_fault_queue_push(void *p_fault); +int dc_fault_register_queue_pop(dc_queue_pop_cb_t cb); +void dc_fault_queue_pop(void); + +/* dc_async.cpp */ +uint32_t dc_async_enqueue(dc_signal_map_t &smap, const char *saddr, + dc_ctrl_step_t step, const dc_ctrl_t *ctrl, + const void *p_data, uint8_t setting_zone, + uint8_t signal_type, const char *caller_module, + dc_async_result_cb_t result_cb, void *user_arg, + uint32_t timeout_ms); +int dc_async_set_result(uint32_t request_id, int result_code, const char *err_msg); +int dc_async_cancel(uint32_t request_id); +void dc_async_timeout_check(void); +void dc_async_cleanup(void); + +/* dc_param_cfg.cpp */ +int dc_param_cfg_parse(const char *path); +void dc_param_metadata_store(const char *saddr, const dc_param_meta_t ¶m); +bool dc_param_metadata_lookup(const char *saddr, dc_param_meta_t &out_param); +void dc_param_cfg_check(const char *path); + +#endif /* DC_INTERNAL_H */ diff --git a/src/public/libdatacenter/inc/myDatacenter.h b/src/public/libdatacenter/inc/myDatacenter.h new file mode 100644 index 0000000..6064e77 --- /dev/null +++ b/src/public/libdatacenter/inc/myDatacenter.h @@ -0,0 +1,558 @@ +/** + * @file myDatacenter.h + * @brief 数据中心模块 — 纯 C 对外接口 + * @details 信号注册/控制/查询 + 事件队列 + 异步执行 + 参数配置。 + * 内部通过 libmy_xxhash 做 O(1) 哈希查找, + * 参数配置通过 libxml C 接口解耦。 + * 对齐 RTU 原工程 libdatacenter v1 + v2 全部功能。 + */ + +#ifndef _MY_DATACENTER_H_ +#define _MY_DATACENTER_H_ + +#include +#include +#include "xxhash.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* ===== 地址/描述/模块/错误信息最大长度 ===== */ +#define DC_SADDR_MAX_LEN 128 +#define DC_DESC_MAX_LEN 256 +#define DC_UNIT_MAX_LEN 32 +#define DC_MODULE_MAX_LEN 64 +#define DC_ERR_MSG_MAX_LEN 256 + +/* ===== 容量限制 ===== */ +#define DC_MAX_CALLBACKS 16 +#define DC_MAX_LINK_SADDRS 32 +#define DC_MAX_DATA_ZONES 8 +#define DC_MAX_PENDING 64 + +/** datacenter 自身模块标识 */ +#define MODULE_DATACENTER "datacenter" + +/* ===== 控制类型枚举 ===== */ +typedef enum +{ + DC_CTRL_NONE = 0, /**< 无控制 */ + DC_CTRL_DIRECT = 1, /**< 直控(直接执行,无需选择-确认) */ + DC_CTRL_SBO = 2 /**< 选控(Select-Before-Operate 两阶段) */ +} dc_ctrl_type_t; + +/* ===== 控制步骤枚举 ===== */ +typedef enum +{ + DC_STEP_READY = 0, /**< 就绪/空闲 */ + DC_STEP_SELECT = 1, /**< 选择(SBO 第一步:锁住信号并暂存选择值) */ + DC_STEP_DIRECT = 2, /**< 执行(SBO 第二步确认写入,直控直接执行) */ + DC_STEP_CANCEL = 3 /**< 取消(回退到 READY) */ +} dc_ctrl_step_t; + +/* ===== 错误码枚举 ===== */ +typedef enum +{ + DC_OK = 0, /**< 成功 */ + DC_ERR_PARAM = -1, /**< 参数错误(NULL/越界) */ + DC_ERR_NOTFOUND = -2, /**< 信号不存在 */ + DC_ERR_TYPE = -3, /**< 数据类型不匹配 */ + DC_ERR_STEP = -4, /**< SBO 步骤错误 */ + DC_ERR_VAL = -5, /**< 值校验失败(越界/未变化) */ + DC_ERR_TIMEOUT = -6, /**< 异步请求超时 */ + DC_ERR_BUSY = -7, /**< 忙 */ + DC_ERR_MEM = -8, /**< 内存不足 */ + DC_ERR_FULL = -9, /**< 请求队列已满 */ + DC_ERR_LOCKED = -10, /**< 已锁定 */ + DC_ERR_EXISTS = -11, /**< 信号已存在(重复注册) */ + DC_ERR_CANCEL = -12 /**< 异步请求已取消 */ +} dc_error_t; + +/* ===== 控制上下文 ===== */ +typedef struct +{ + uint8_t step; /**< 当前控制步骤(dc_ctrl_step_t) */ + uint8_t type; /**< 控制类型(dc_ctrl_type_t) */ + uint8_t data_type; /**< 数据类型(DATA_TYPE_*) */ + uint8_t reserved; /**< 保留 */ + void *p_data; /**< SELECT 时暂存选择值,DIRECT 时用于返校比对 */ +} dc_ctrl_t; + +/* ===== 信号参数元数据(min/max/step/unit) ===== */ +typedef struct +{ + float min; /**< 最小值 */ + float max; /**< 最大值 */ + float step; /**< 步长 */ + char unit[DC_UNIT_MAX_LEN]; /**< 单位(如 "kV", "A", "Hz") */ +} dc_param_meta_t; + +/* ===== 回调函数类型 ===== */ + +/** + * @brief out 信号值变更回调(延迟触发,100ms 周期批量检测后调用) + * @param saddr 信号地址 + * @param data_type 数据类型 + * @param p_new 新值指针 + * @param p_old 旧值指针 + */ +typedef void (*dc_out_change_cb_t)(const char *saddr, uint8_t data_type, + const void *p_new, const void *p_old); + +/** + * @brief yk/ao/param 信号控制变更回调(同步触发,控制场景即时响应) + * @param saddr 信号地址 + * @param step 控制步骤 + * @param data_type 数据类型 + * @param zone 定值区号(param 信号使用,其他为 0) + * @param p_data 数据指针 + */ +typedef void (*dc_signal_change_cb_t)(const char *saddr, dc_ctrl_step_t step, + uint8_t data_type, uint8_t zone, + const void *p_data); + +/** @brief 事件队列消费回调 */ +typedef void (*dc_queue_pop_cb_t)(void *p_data); + +/** + * @brief 异步请求完成回调(通知发起方模块A) + * @param req_id 请求 ID + * @param result 结果码(DC_OK / DC_ERR_*) + * @param err 错误信息(成功时为空) + * @param arg 用户参数 + */ +typedef void (*dc_async_result_cb_t)(uint32_t req_id, int result, + const char *err, void *arg); + +/** + * @brief 异步执行回调(datacenter 调用模块B执行实际操作) + * @param req_id 请求 ID + * @param saddr 信号地址 + * @param step 控制步骤 + * @param data_type 数据类型 + * @param zone 定值区号 + * @param p_data 操作数据 + * @param ctx 模块上下文 + * @return 0 已接受(稍后通过 dc_set_result 返回结果),-1 拒绝 + */ +typedef int (*dc_async_exec_cb_t)(uint32_t req_id, const char *saddr, + dc_ctrl_step_t step, uint8_t data_type, + uint8_t zone, const void *p_data, void *ctx); + +/* ===== 内部数据结构(对调用方透明,通过 API 访问) ===== */ + +/** 回调条目 */ +typedef struct +{ + char module_id[DC_MODULE_MAX_LEN]; + dc_out_change_cb_t out_cb; + dc_signal_change_cb_t ctrl_cb; +} dc_cb_entry_t; + +/** 信号节点 */ +typedef struct +{ + uint32_t id; + XXH128_hash_t hash; + char saddr[DC_SADDR_MAX_LEN]; + char desc[DC_DESC_MAX_LEN]; + uint8_t data_type; + uint8_t ctrl_type; + + void **vec_p_data; + int vec_p_data_count; + void *p_last_data; + + char link_saddr[DC_SADDR_MAX_LEN]; + char **link_saddrs; + int link_count; + + dc_param_meta_t param; + + dc_cb_entry_t cb_list[DC_MAX_CALLBACKS]; + int cb_count; + + char last_caller_module[DC_MODULE_MAX_LEN]; + + dc_async_exec_cb_t exec_cb; + void *module_ctx; +} dc_signal_t; + +/* ================================================================== + * 模块生命周期 + * ================================================================== */ + +/** + * @brief 初始化数据中心 + * @param cfg_dir 配置文件目录(用于查找 param.xml) + * @return DC_OK 成功,<0 失败 + */ +int dc_init(const char *cfg_dir); + +/** @brief 清理数据中心,释放所有信号和队列资源 */ +void dc_cleanup(void); + +/** + * @brief 每 100ms 周期调用 + * @details out 信号变化检测 + 批量触发回调 + 事件队列弹出 + 异步超时检测 + */ +void dc_run_100ms(void); + +/** + * @brief 每 1000ms 周期调用 + * @details 检测参数配置变更标志,有变更则写 self_param.xml + */ +void dc_run_1000ms(void); + +/* ================================================================== + * Out 信号注册(遥测/遥信输出) + * ================================================================== */ + +/** + * @brief 注册输出信号 + * @param saddr 信号地址(唯一标识) + * @param desc 信号描述 + * @param data_type 数据类型(DATA_TYPE_*) + * @param p_data 数据指针 + * @param module_id 模块标识(用于回路阻断) + * @return DC_OK 成功,DC_ERR_EXISTS 重复注册 + */ +int dc_signal_out(const char *saddr, const char *desc, uint8_t data_type, + void *p_data, const char *module_id); + +/** + * @brief 注册输出信号并绑定变化回调 + * @param cb 值变更回调(延迟触发,100ms 批量检测后调用) + */ +int dc_signal_out_with_callback(const char *saddr, const char *desc, + uint8_t data_type, void *p_data, + dc_out_change_cb_t cb, const char *module_id); + +/** + * @brief 链接已有 out 信号,获取数据指针并注册回调 + * @param p_data 输出:out 信号的数据指针 + */ +int dc_signal_out_link_with_callback(const char *saddr, void **p_data, + dc_out_change_cb_t cb, const char *module_id); + +/** + * @brief 设置输出信号值(标记脏,100ms 后触发变更检测和延迟回调) + * @param module_id 调用方模块标识(自身不会被回调——回路阻断) + */ +int dc_set_out_signal_val(const char *saddr, const void *set_data, + const char *module_id); + +/* ================================================================== + * In 信号注册(输入信号,链接到 out 信号的数据指针) + * ================================================================== */ + +/** + * @brief 注册输入信号并链接到输出信号 + * @param link_saddr 要链接的 out 信号地址 + * @param p_data 输出:被链接 out 信号的数据指针 + */ +int dc_signal_in(const char *saddr, const char *desc, + const char *link_saddr, void **p_data, const char *module_id); + +/** + * @brief 注册输入信号并绑定回调(回调挂在被链接的 out 信号上) + */ +int dc_signal_in_with_callback(const char *saddr, const char *desc, + const char *link_saddr, void **p_data, + dc_out_change_cb_t cb, const char *module_id); + +/* ================================================================== + * YK 信号(遥控) + * ================================================================== */ + +/** + * @brief 注册遥控信号 + * @param ctrl_type 控制类型(DC_CTRL_DIRECT 直控 / DC_CTRL_SBO 选控) + * @param cb 控制变更回调(同步触发) + */ +int dc_signal_yk(const char *saddr, const char *desc, + uint8_t data_type, uint8_t ctrl_type, void *p_data, + dc_signal_change_cb_t cb, const char *module_id); + +/** @brief 链接已有 YK 信号,获取数据指针并注册回调 */ +int dc_signal_yk_link_with_callback(const char *saddr, void **p_data, + dc_signal_change_cb_t cb, const char *module_id); + +/** + * @brief YK 同步设置(处理 SBO 流程:SELECT→DIRECT/CANCEL) + * @param step 控制步骤 + * @param ctrl 控制上下文(进入时携带 type/data_type/p_data,执行完毕后回写当前 step) + * @param p_data 操作数据 + */ +int dc_signal_yk_set_status(const char *saddr, dc_ctrl_step_t step, + dc_ctrl_t *ctrl, const void *p_data, + const char *module_id); + +/** + * @brief YK 异步远程执行 + * @param result_cb 结果回调 + * @param timeout_ms 超时时间(毫秒),0 使用默认值 + * @param request_id 输出:请求 ID + */ +int dc_yk_exec_async(const char *saddr, dc_ctrl_step_t step, + const dc_ctrl_t *ctrl, const void *p_data, + const char *module_id, + dc_async_result_cb_t result_cb, void *user_arg, + uint32_t timeout_ms, uint32_t *request_id); + +/** @brief 设置 YK 异步执行结果(模块B回调) */ +int dc_yk_set_result(uint32_t request_id, int result_code, const char *err_msg); + +/** @brief 取消 YK 异步请求 */ +int dc_yk_cancel_async(uint32_t request_id); + +/** @brief 注册 YK 异步执行回调(模块B初始化时调用) */ +int dc_yk_register_exec_cb(const char *saddr, dc_async_exec_cb_t exec_cb, + void *module_ctx); + +/* ================================================================== + * AO 信号(模拟输出) + * ================================================================== */ + +/** + * @brief 注册 AO 信号 + * @param ctrl_type 控制类型 + * @param cb 控制变更回调(同步触发) + */ +int dc_signal_ao(const char *saddr, const char *desc, + uint8_t data_type, uint8_t ctrl_type, void *p_data, + dc_signal_change_cb_t cb, const char *module_id); + +/** @brief 链接已有 AO 信号 */ +int dc_signal_ao_link_with_callback(const char *saddr, void **p_data, + dc_signal_change_cb_t cb, const char *module_id); + +/** + * @brief AO 同步设置(SBO 流程 + 值校验) + * @details DIRECT 执行后 ctrl->step 自动复位到 READY + */ +int dc_signal_ao_set_val(const char *saddr, dc_ctrl_step_t step, + dc_ctrl_t *ctrl, const void *p_data, + const char *module_id); + +/** @brief AO 无校验设置(跳过 SBO 流程和值校验,用于初始化恢复) */ +int dc_signal_ao_set_val_without_check(const char *saddr, uint8_t data_type, + const void *p_data, const char *module_id); + +/** @brief AO 异步执行 */ +int dc_ao_exec_async(const char *saddr, dc_ctrl_step_t step, + const dc_ctrl_t *ctrl, const void *p_data, + const char *module_id, + dc_async_result_cb_t result_cb, void *user_arg, + uint32_t timeout_ms, uint32_t *request_id); + +/** @brief 设置 AO 异步执行结果 */ +int dc_ao_set_result(uint32_t request_id, int result_code, const char *err_msg); + +/** @brief 取消 AO 异步请求 */ +int dc_ao_cancel_async(uint32_t request_id); + +/** @brief 注册 AO 异步执行回调 */ +int dc_ao_register_exec_cb(const char *saddr, dc_async_exec_cb_t exec_cb, + void *module_ctx); + +/* ================================================================== + * Param 信号(参数/定值,多定值区) + * ================================================================== */ + +/** + * @brief 注册参数信号 + * @param p_data 数据指针数组(每个 zone 一个指针) + * @param num_zones 定值区数量 + */ +int dc_signal_param(const char *saddr, const char *desc, + uint8_t data_type, uint8_t ctrl_type, + void **p_data, int num_zones, + dc_signal_change_cb_t cb, const char *module_id); + +/** @brief 链接已有参数信号 */ +int dc_signal_param_link_with_callback(const char *saddr, void **p_data, + int num_zones, + dc_signal_change_cb_t cb, + const char *module_id); + +/** + * @brief Param 同步设置(SBO + zone 值校验) + * @param setting_zone 定值区号(0-based) + */ +int dc_signal_param_set_val(const char *saddr, dc_ctrl_step_t step, + dc_ctrl_t *ctrl, uint8_t setting_zone, + const void *p_data, const char *module_id); + +/** @brief Param 无校验设置 */ +int dc_signal_param_set_val_without_check(const char *saddr, uint8_t data_type, + uint8_t setting_zone, + const void *p_data, + const char *module_id); + +/** @brief Param 异步执行 */ +int dc_param_exec_async(const char *saddr, dc_ctrl_step_t step, + const dc_ctrl_t *ctrl, uint8_t setting_zone, + const void *p_data, const char *module_id, + dc_async_result_cb_t result_cb, void *user_arg, + uint32_t timeout_ms, uint32_t *request_id); + +/** @brief 设置 Param 异步执行结果 */ +int dc_param_set_result(uint32_t request_id, int result_code, + const char *err_msg); + +/** @brief 取消 Param 异步请求 */ +int dc_param_cancel_async(uint32_t request_id); + +/** @brief 注册 Param 异步执行回调 */ +int dc_param_register_exec_cb(const char *saddr, dc_async_exec_cb_t exec_cb, + void *module_ctx); + +/* ================================================================== + * 信号查询 + * ================================================================== */ + +/** @brief 查询 out 信号信息 */ +int dc_get_out_signal_info(const char *saddr, char *desc, int desc_len, + uint8_t *data_type, void **p_data); + +/** @brief 查询 in 信号信息 */ +int dc_get_in_signal_info(const char *saddr, char *desc, int desc_len, + uint8_t *data_type, void **p_data); + +/** @brief 查询 AO 信号信息(含参数元数据) */ +int dc_get_ao_signal_info(const char *saddr, char *desc, int desc_len, + uint8_t *data_type, dc_param_meta_t *p_meta, + uint8_t *ctrl_type, void **p_data); + +/** + * @brief 查询 Param 信号信息(含多区数据指针列表) + * @param p_data_array 输出:数据指针数组(调用方提供缓冲区) + * @param p_count 输出:定值区数量 + */ +int dc_get_param_signal_info(const char *saddr, char *desc, int desc_len, + uint8_t *data_type, dc_param_meta_t *p_meta, + uint8_t *ctrl_type, + void **p_data_array, int *p_count); + +/** @brief 查询 YK 信号信息 */ +int dc_get_yk_signal_info(const char *saddr, char *desc, int desc_len, + uint8_t *data_type, uint8_t *ctrl_type, void **p_data); + +/** @brief 获取信号值的字符串表示 */ +int dc_get_signal_val(const void *p_data, uint8_t data_type, + char *buf, int buf_len); + +/** + * @brief 按类型统计信号数量 + * @param type_str 类型字符串:"out"/"in"/"yk"/"ao"/"param" + * @return 信号数量 + */ +int dc_get_signal_count(const char *type_str); + +/** + * @brief 按类型和 ID 查询信号基本信息(供前端遍历) + * @param type_str "out"/"in"/"yk"/"ao"/"param" + * @param id 信号 ID + * @param saddr/saddr_len 输出:信号地址 + * @param desc/desc_len 输出:描述 + * @param data_type_str/type_str_len 输出:数据类型名称 + * @param ctrl_type 输出:控制类型 + * @param link_str/link_len 输出:链接信号的逗号分隔列表 + */ +int dc_get_signal_info_by_id(const char *type_str, uint32_t id, + char *saddr, int saddr_len, + char *desc, int desc_len, + char *data_type_str, int type_str_len, + uint8_t *ctrl_type, + char *link_str, int link_len); + +/* ================================================================== + * 事件/扰动/故障队列 + * ================================================================== */ + +/** @brief SOE 事件入队 */ +int dc_event_queue_push(void *p_soe); + +/** @brief 注册 SOE 事件消费回调(支持多消费者) */ +int dc_event_register_queue_pop(dc_queue_pop_cb_t cb); + +/** @brief 扰动数据入队 */ +int dc_disturb_dd_queue_push(void *p_dd); + +/** @brief 注册扰动数据消费回调 */ +int dc_disturb_dd_register_queue_pop(dc_queue_pop_cb_t cb); + +/** @brief 故障数据入队 */ +int dc_fault_queue_push(void *p_fault); + +/** @brief 注册故障数据消费回调 */ +int dc_fault_register_queue_pop(dc_queue_pop_cb_t cb); + +/* ================================================================== + * 类型工具函数 + * ================================================================== */ + +/** @brief 数据类型 ID → 名称(如 35 → "uint32_t") */ +const char *dc_get_type_name(uint8_t data_type); + +/** @brief 名称 → 数据类型 ID(如 "float" → 38) */ +uint8_t dc_get_type_id_by_name(const char *name); + +/** @brief 获取数据类型的字节长度 */ +uint8_t dc_get_type_len(uint8_t data_type); + +/** @brief 根据类型分配并清零内存 */ +void *dc_create_data(uint8_t data_type); + +/** @brief 根据类型释放内存 */ +void dc_destroy_data(void *p_data, uint8_t data_type); + +/** @brief 类型感知的值复制 */ +int dc_copy_val(void *dst, const void *src, uint8_t data_type); + +/** @brief 类型感知的值比较(0 = 相等,-1 = 不等) */ +int dc_compare_val(const void *p1, const void *p2, uint8_t data_type); + +/** @brief 字符串 → 值(如 "3.14" → 3.14f) */ +int dc_set_val_from_str(void *p_data, uint8_t data_type, const char *str); + +/* ================================================================== + * 参数配置与异步管理 + * ================================================================== */ + +/** @brief 查询参数配置是否有未保存的变更(0=无,1=有) */ +int dc_param_cfg_changed(void); + +/** @brief 获取当前异步请求 pending 数量 */ +int dc_async_pending_count(void); + +/** @brief 设置全局默认异步超时时间(毫秒) */ +void dc_async_set_default_timeout(uint32_t timeout_ms); + +/** @brief 设置 YK 遥控默认异步超时(0=使用全局默认) */ +void dc_async_set_timeout_yk(uint32_t timeout_ms); + +/** @brief 设置 AO 调节默认异步超时(0=使用全局默认) */ +void dc_async_set_timeout_ao(uint32_t timeout_ms); + +/** @brief 设置 Param 定值默认异步超时(0=使用全局默认) */ +void dc_async_set_timeout_param(uint32_t timeout_ms); + +/* ================================================================== + * 导出 + * ================================================================== */ + +/** + * @brief 保存所有已注册 out 信号到 XML + * @param path 目标文件路径 + * @return DC_OK 成功,<0 失败 + */ +int dc_save_out_signals_xml(const char *path); + +#ifdef __cplusplus +} +#endif +#endif /* _MY_DATACENTER_H_ */ diff --git a/src/public/libdatacenter/src/dc_api_wrap.cpp b/src/public/libdatacenter/src/dc_api_wrap.cpp new file mode 100644 index 0000000..5966f59 --- /dev/null +++ b/src/public/libdatacenter/src/dc_api_wrap.cpp @@ -0,0 +1,79 @@ +/** @file dc_api_wrap.cpp — C API 包装函数(类型工具) */ +#include "dc_internal.h" + +/** @brief 获取数据类型名称字符串(extern "C",返回 static 缓冲区指针) */ +extern "C" const char *dc_get_type_name(uint8_t dt) +{ + static std::string s; + + s = dc_util_type_name(dt); + + return s.c_str(); +} + +/** @brief 通过名称获取数据类型 ID */ +extern "C" uint8_t dc_get_type_id_by_name(const char *name) +{ + return dc_util_type_id_by_name(name ? name : ""); +} + +/** @brief 获取数据类型的字节长度 */ +extern "C" uint8_t dc_get_type_len(uint8_t dt) +{ + return dc_util_type_len(dt); +} + +/** @brief 根据数据类型创建零初始化内存块 */ +extern "C" void *dc_create_data(uint8_t dt) +{ + return dc_util_create_data(dt); +} + +/** @brief 释放由 dc_create_data 分配的内存 */ +extern "C" void dc_destroy_data(void *p, uint8_t dt) +{ + dc_util_destroy_data(p, dt); +} + +/** @brief 将源数据按类型拷贝到目标 */ +extern "C" int dc_copy_val(void *dst, const void *src, uint8_t dt) +{ + if (!dst || !src) + { + return DC_ERR_PARAM; + } + + dc_util_val_copy(dst, src, dt); + + return DC_OK; +} + +/** @brief 按类型比较两个值,返回 0 相等、-1 不等或参数错误 */ +extern "C" int dc_compare_val(const void *p1, const void *p2, uint8_t dt) +{ + if (!p1 || !p2) + { + return -1; + } + + return dc_util_val_compare(dt, p1, p2); +} + +/** @brief 从字符串解析值并按类型写入内存 */ +extern "C" int dc_set_val_from_str(void *p, uint8_t dt, const char *s) +{ + if (!p || !s) + { + return DC_ERR_PARAM; + } + + return dc_util_val_from_string(p, dt, s); +} + +/** @brief 检查参数配置文件是否已变更 */ +extern "C" int dc_param_cfg_changed(void) +{ + std::lock_guard lk(g_param_cfg_mutex); + + return g_param_cfg_changed ? 1 : 0; +} diff --git a/src/public/libdatacenter/src/dc_async.cpp b/src/public/libdatacenter/src/dc_async.cpp new file mode 100644 index 0000000..973b112 --- /dev/null +++ b/src/public/libdatacenter/src/dc_async.cpp @@ -0,0 +1,349 @@ +/** @file dc_async.cpp — 异步请求队列管理 */ +#include "dc_internal.h" +#include + +/** + * @brief 获取单调时钟毫秒数 + * @return 自系统启动以来的毫秒数 + */ +static uint64_t dc_get_monotonic_ms(void) +{ + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + + return (uint64_t)ts.tv_sec * 1000 + (uint64_t)ts.tv_nsec / 1000000; +} + +/** + * @brief 将异步请求加入队列 + * @param smap 信号表 + * @param saddr 信号地址 + * @param step 控制步骤 + * @param ctrl 控制上下文 + * @param p_data 数据指针 + * @param setting_zone 定值区号 + * @param signal_type 信号类型(0=yk, 1=ao, 2=param) + * @param caller_module 调用方模块名 + * @param result_cb 结果回调 + * @param user_arg 用户参数 + * @param timeout_ms 超时毫秒数(0=使用默认值) + * @return request_id(>0),失败返回 0 + */ +uint32_t dc_async_enqueue(dc_signal_map_t &smap, const char *saddr, + dc_ctrl_step_t step, const dc_ctrl_t *ctrl, + const void *p_data, uint8_t setting_zone, + uint8_t signal_type, const char *caller_module, + dc_async_result_cb_t result_cb, void *user_arg, + uint32_t timeout_ms) +{ + dc_async_exec_cb_t sig_exec_cb = NULL; + void *sig_ctx = NULL; + dc_signal_t *ps = dc_core_find(saddr, smap); + + if (ps) + { + sig_exec_cb = ps->exec_cb; + sig_ctx = ps->module_ctx; + } + + std::lock_guard lk(g_async_queue.mtx); + + if (g_async_queue.count >= g_async_queue.max_pending) + { + LOG_E("async queue full\n"); + + return 0; + } + + if (!timeout_ms) + { + uint32_t *pt = NULL; + + switch (signal_type) + { + case 0: + pt = &g_async_queue.default_timeout_yk_ms; + break; + case 1: + pt = &g_async_queue.default_timeout_ao_ms; + break; + case 2: + pt = &g_async_queue.default_timeout_param_ms; + break; + } + + timeout_ms = (pt && *pt) ? *pt : g_async_queue.default_timeout_ms; + } + + dc_async_request_t *req = new dc_async_request_t(); + + memset(req, 0, sizeof(*req)); + + if (++g_async_queue.next_id == 0) + { + ++g_async_queue.next_id; + } + + req->request_id = g_async_queue.next_id; + + if (saddr) + { + strncpy(req->saddr, saddr, DC_SADDR_MAX_LEN - 1); + } + + req->step = step; + req->setting_zone = setting_zone; + req->signal_type = signal_type; + + if (ctrl) + { + req->data_type = ctrl->data_type; + req->ctrl_type = ctrl->type; + } + + if (ctrl && ctrl->p_data && p_data) + { + req->p_select_data = dc_util_create_data(ctrl->data_type); + + if (req->p_select_data) + { + dc_util_val_copy(req->p_select_data, ctrl->p_data, ctrl->data_type); + } + + req->p_direct_data = dc_util_create_data(ctrl->data_type); + + if (req->p_direct_data) + { + dc_util_val_copy(req->p_direct_data, p_data, ctrl->data_type); + } + } + + req->start_time_ms = dc_get_monotonic_ms(); + req->timeout_ms = timeout_ms; + + if (caller_module) + { + strncpy(req->caller_module, caller_module, DC_MODULE_MAX_LEN - 1); + } + + req->result_cb = result_cb; + req->user_arg = user_arg; + req->exec_cb = sig_exec_cb; + req->module_ctx = sig_ctx; + + req->next = g_async_queue.head; + g_async_queue.head = req; + g_async_queue.count++; + g_async_queue.req_map[req->request_id] = req; + + return req->request_id; +} + +/** + * @brief 设置异步请求的执行结果 + * @param request_id 请求 ID + * @param result_code 结果码(0=成功,负值=错误) + * @param err_msg 错误信息 + * @return DC_OK 成功,DC_ERR_NOTFOUND/DC_ERR_TIMEOUT 失败 + */ +int dc_async_set_result(uint32_t request_id, int result_code, const char *err_msg) +{ + dc_async_result_cb_t cb = NULL; + void *arg = NULL; + int existed = 0; + + { + std::lock_guard lk(g_async_queue.mtx); + auto it = g_async_queue.req_map.find(request_id); + + if (it == g_async_queue.req_map.end()) + { + return DC_ERR_NOTFOUND; + } + + dc_async_request_t *req = it->second; + + if (req->result_code == DC_ERR_TIMEOUT) + { + LOG_I("req %u timeout, ignore\n", request_id); + + return DC_ERR_TIMEOUT; + } + + req->result_code = result_code; + + if (err_msg) + { + strncpy(req->err_msg, err_msg, DC_ERR_MSG_MAX_LEN - 1); + } + + cb = req->result_cb; + arg = req->user_arg; + existed = 1; + } + + if (existed && cb) + { + cb(request_id, result_code, err_msg, arg); + } + + return DC_OK; +} + +/** + * @brief 取消未完成的异步请求 + * @param request_id 请求 ID + * @return DC_OK 成功,DC_ERR_NOTFOUND/DC_ERR_STEP 失败 + */ +int dc_async_cancel(uint32_t request_id) +{ + dc_async_result_cb_t cb = NULL; + void *arg = NULL; + + { + std::lock_guard lk(g_async_queue.mtx); + auto it = g_async_queue.req_map.find(request_id); + + if (it == g_async_queue.req_map.end()) + { + return DC_ERR_NOTFOUND; + } + + dc_async_request_t *req = it->second; + + if (req->result_code != 0) + { + return DC_ERR_STEP; + } + + req->result_code = DC_ERR_CANCEL; + snprintf(req->err_msg, sizeof(req->err_msg), "cancelled"); + cb = req->result_cb; + arg = req->user_arg; + } + + if (cb) + { + cb(request_id, DC_ERR_CANCEL, "cancelled", arg); + } + + return DC_OK; +} + +/** + * @brief 超时检测:遍历队列,标记超时请求并触发回调 + */ +void dc_async_timeout_check(void) +{ + uint64_t now = dc_get_monotonic_ms(); + + std::lock_guard lk(g_async_queue.mtx); + + for (dc_async_request_t *req = g_async_queue.head; req; req = req->next) + { + if (req->result_code == 0 && (now - req->start_time_ms) >= req->timeout_ms) + { + req->result_code = DC_ERR_TIMEOUT; + snprintf(req->err_msg, sizeof(req->err_msg), "timeout %ums", req->timeout_ms); + + if (req->result_cb) + { + req->result_cb(req->request_id, DC_ERR_TIMEOUT, req->err_msg, req->user_arg); + } + } + } +} + +/** + * @brief 清理已完成/超时/取消的请求,释放内存 + */ +void dc_async_cleanup(void) +{ + std::lock_guard lk(g_async_queue.mtx); + + dc_async_request_t **pp = &g_async_queue.head; + + while (*pp) + { + dc_async_request_t *req = *pp; + + if (req->result_code != 0) + { + *pp = req->next; + g_async_queue.req_map.erase(req->request_id); + + if (req->p_select_data) + { + dc_util_destroy_data(req->p_select_data, req->data_type); + } + + if (req->p_direct_data) + { + dc_util_destroy_data(req->p_direct_data, req->data_type); + } + + delete req; + g_async_queue.count--; + } + else + { + pp = &req->next; + } + } +} + +/** + * @brief 获取当前 pending 请求数量 + * @return pending 请求数 + */ +int dc_async_pending_count(void) +{ + std::lock_guard lk(g_async_queue.mtx); + + return g_async_queue.count; +} + +/** + * @brief 设置全局默认超时时间 + * @param ms 超时毫秒数 + */ +void dc_async_set_default_timeout(uint32_t ms) +{ + std::lock_guard lk(g_async_queue.mtx); + + g_async_queue.default_timeout_ms = ms; +} + +/** + * @brief 设置遥控(yk)默认超时时间 + * @param ms 超时毫秒数 + */ +void dc_async_set_timeout_yk(uint32_t ms) +{ + std::lock_guard lk(g_async_queue.mtx); + + g_async_queue.default_timeout_yk_ms = ms; +} + +/** + * @brief 设置 AO 默认超时时间 + * @param ms 超时毫秒数 + */ +void dc_async_set_timeout_ao(uint32_t ms) +{ + std::lock_guard lk(g_async_queue.mtx); + + g_async_queue.default_timeout_ao_ms = ms; +} + +/** + * @brief 设置参数(param)默认超时时间 + * @param ms 超时毫秒数 + */ +void dc_async_set_timeout_param(uint32_t ms) +{ + std::lock_guard lk(g_async_queue.mtx); + + g_async_queue.default_timeout_param_ms = ms; +} diff --git a/src/public/libdatacenter/src/dc_core.cpp b/src/public/libdatacenter/src/dc_core.cpp new file mode 100644 index 0000000..4ac521b --- /dev/null +++ b/src/public/libdatacenter/src/dc_core.cpp @@ -0,0 +1,241 @@ +/** @file dc_core.cpp — 全局变量 + 哈希操作 + 生命周期 */ +#include "dc_internal.h" + +dc_signal_map_t g_dc_out = {}; +dc_signal_map_t g_dc_in = {}; +dc_signal_map_t g_dc_yk = {}; +dc_signal_map_t g_dc_ao = {}; +dc_signal_map_t g_dc_param = {}; + +std::vector g_dirty_out_signals; +std::mutex g_dirty_out_mutex; + +dc_async_queue_t g_async_queue = {}; + +bool g_param_cfg_changed = false; +std::mutex g_param_cfg_mutex; + +char g_cfg_dir[256] = {0}; +bool g_initialized = false; + +std::unordered_map g_param_metadata; + +/** @brief 添加脏信号到列表,由 100ms 周期检测变更 */ +void dc_core_mark_dirty(dc_signal_t *p_signal) +{ + if (!p_signal) + { + return; + } + + std::lock_guard lk(g_dirty_out_mutex); + g_dirty_out_signals.push_back(p_signal); +} + +/** @brief 计算 saddr 的 XXH128 哈希 */ +static XXH128_hash_t dc_core_hash(const char *saddr) +{ + return XXH3_128bits(saddr, strlen(saddr)); +} + +/** @brief 添加信号到 map(已存在则更新) */ +int dc_core_add(dc_signal_t &signal, dc_signal_map_t &smap) +{ + std::lock_guard lk(smap.mtx); + auto range = smap.map_signals.equal_range(signal.hash); + + for (auto it = range.first; it != range.second; ++it) + { + if (0 == strcmp(it->second.saddr, signal.saddr)) + { + uint32_t oid = it->second.id; + + it->second = signal; + it->second.id = oid; + + return DC_OK; + } + } + + auto it = smap.map_signals.emplace(signal.hash, signal); + + it->second.id = smap.signal_id++; + + if (smap.id_index.size() <= it->second.id) + { + smap.id_index.resize(it->second.id + 1, NULL); + } + + smap.id_index[it->second.id] = &it->second; + + return DC_OK; +} + +/** @brief 按 saddr 在指定 map 中查找信号 */ +dc_signal_t *dc_core_find(const char *saddr, dc_signal_map_t &smap) +{ + if (!saddr) + { + return NULL; + } + + XXH128_hash_t h = dc_core_hash(saddr); + std::lock_guard lk(smap.mtx); + auto range = smap.map_signals.equal_range(h); + + for (auto it = range.first; it != range.second; ++it) + { + if (0 == strcmp(it->second.saddr, saddr)) + { + return &it->second; + } + } + + return NULL; +} + +/** @brief 快捷查找 out 信号 */ +dc_signal_t *dc_core_find_out(const char *saddr) +{ + return dc_core_find(saddr, g_dc_out); +} + +/** @brief 快捷查找 in 信号 */ +dc_signal_t *dc_core_find_in(const char *saddr) +{ + return dc_core_find(saddr, g_dc_in); +} + +/** @brief 按 ID 查找信号(O(1)) */ +dc_signal_t *dc_core_find_by_id(uint32_t id, dc_signal_map_t &smap) +{ + std::lock_guard lk(smap.mtx); + + if (id >= smap.id_index.size()) + { + return NULL; + } + + return smap.id_index[id]; +} + +/* ========== 生命周期 ========== */ + +/** @brief 初始化 datacenter,设置全局变量并尝试加载 param.xml */ +int dc_init(const char *cfg_dir) +{ + if (g_initialized) + { + LOG_I("datacenter already init\n"); + + return DC_OK; + } + + if (cfg_dir) + { + strncpy(g_cfg_dir, cfg_dir, sizeof(g_cfg_dir) - 1); + } + + g_async_queue.max_pending = DC_MAX_PENDING; + g_async_queue.default_timeout_ms = 30000; + + g_initialized = true; + + /* 尝试加载参数配置文件 */ + if (cfg_dir && cfg_dir[0]) + { + char param_path[512]; + + snprintf(param_path, sizeof(param_path), "%s/param.xml", cfg_dir); + if (0 != dc_param_cfg_parse(param_path)) + { + LOG_I("no param.xml at %s, skipping config load\n", param_path); + } + } + + LOG_I("datacenter init ok, cfg=%s\n", g_cfg_dir); + + return DC_OK; +} + +/** @brief 清理 datacenter */ +void dc_cleanup(void) +{ + if (!g_initialized) + { + return; + } + + g_dirty_out_signals.clear(); + g_initialized = false; + LOG_I("datacenter cleaned up\n"); +} + +/** @brief 100ms 变化检测:去重 → 值比对 → 回调 → 回路阻断 */ +static void dc_change_out_check(void) +{ + std::vector dirty; + + { + std::lock_guard lk(g_dirty_out_mutex); + dirty.swap(g_dirty_out_signals); + } + + if (dirty.empty()) + { + return; + } + + std::sort(dirty.begin(), dirty.end()); + auto last = std::unique(dirty.begin(), dirty.end()); + + for (auto it = dirty.begin(); it != last; ++it) + { + dc_signal_t *ps = *it; + + if (!ps->vec_p_data || !ps->vec_p_data[0] || !ps->p_last_data) + { + continue; + } + + if (0 == dc_util_val_compare(ps->data_type, ps->vec_p_data[0], ps->p_last_data)) + { + continue; + } + + for (int i = 0; i < ps->cb_count; i++) + { + if (!ps->cb_list[i].out_cb) + { + continue; + } + + if (0 == strcmp(ps->cb_list[i].module_id, ps->last_caller_module)) + { + continue; + } + + ps->cb_list[i].out_cb(ps->saddr, ps->data_type, ps->vec_p_data[0], ps->p_last_data); + } + + dc_util_val_copy(ps->p_last_data, ps->vec_p_data[0], ps->data_type); + } +} + +/** @brief 100ms 周期:变化检测 + 事件出队 + 异步超时检查 */ +void dc_run_100ms(void) +{ + dc_change_out_check(); + dc_disturb_dd_queue_pop(); + dc_event_queue_pop(); + dc_fault_queue_pop(); + dc_async_timeout_check(); + dc_async_cleanup(); +} + +/** @brief 1000ms 周期:参数配置文件变更检测 */ +void dc_run_1000ms(void) +{ + dc_param_cfg_check(g_cfg_dir); +} + diff --git a/src/public/libdatacenter/src/dc_event.cpp b/src/public/libdatacenter/src/dc_event.cpp new file mode 100644 index 0000000..5fbe72f --- /dev/null +++ b/src/public/libdatacenter/src/dc_event.cpp @@ -0,0 +1,153 @@ +/** @file dc_event.cpp — 事件队列(SOE/扰动/故障) */ +#include "dc_internal.h" +#include + +static std::mutex g_soe_mutex; +static std::queue g_soe_queue; +static std::vector g_soe_cbs; + +static std::mutex g_dist_mutex; +static std::queue g_dist_queue; +static std::vector g_dist_cbs; + +static std::mutex g_fault_mutex; +static std::queue g_fault_queue; +static std::vector g_fault_cbs; + +/** @brief 事件队列弹出内部实现:swap 交换后遍历回调 */ +static void dc_event_pop_inner(std::mutex &mtx, std::queue &q, + std::vector &cbs) +{ + std::queue lq; + std::vector lcbs; + + { + std::lock_guard lk(mtx); + lq.swap(q); + lcbs = cbs; + } + + while (!lq.empty()) + { + void *p = lq.front(); + lq.pop(); + for (auto &cb : lcbs) + { + cb(p); + } + } +} + +/* ================================================================ + * SOE 事件队列 + * ================================================================ */ + +/** @brief 向 SOE 事件队列推送数据 */ +int dc_event_queue_push(void *p) +{ + if (!p) + { + return DC_ERR_PARAM; + } + + std::lock_guard lk(g_soe_mutex); + g_soe_queue.push(p); + + return DC_OK; +} + +/** @brief 注册 SOE 事件队列弹出回调 */ +int dc_event_register_queue_pop(dc_queue_pop_cb_t cb) +{ + if (!cb) + { + return DC_ERR_PARAM; + } + + std::lock_guard lk(g_soe_mutex); + g_soe_cbs.push_back(cb); + + return DC_OK; +} + +/** @brief 触发 SOE 事件队列弹出 */ +void dc_event_queue_pop(void) +{ + dc_event_pop_inner(g_soe_mutex, g_soe_queue, g_soe_cbs); +} + +/* ================================================================ + * 扰动录波事件队列 + * ================================================================ */ + +/** @brief 向扰动事件队列推送数据 */ +int dc_disturb_dd_queue_push(void *p) +{ + if (!p) + { + return DC_ERR_PARAM; + } + + std::lock_guard lk(g_dist_mutex); + g_dist_queue.push(p); + + return DC_OK; +} + +/** @brief 注册扰动事件队列弹出回调 */ +int dc_disturb_dd_register_queue_pop(dc_queue_pop_cb_t cb) +{ + if (!cb) + { + return DC_ERR_PARAM; + } + + std::lock_guard lk(g_dist_mutex); + g_dist_cbs.push_back(cb); + + return DC_OK; +} + +/** @brief 触发扰动事件队列弹出 */ +void dc_disturb_dd_queue_pop(void) +{ + dc_event_pop_inner(g_dist_mutex, g_dist_queue, g_dist_cbs); +} + +/* ================================================================ + * 故障事件队列 + * ================================================================ */ + +/** @brief 向故障事件队列推送数据 */ +int dc_fault_queue_push(void *p) +{ + if (!p) + { + return DC_ERR_PARAM; + } + + std::lock_guard lk(g_fault_mutex); + g_fault_queue.push(p); + + return DC_OK; +} + +/** @brief 注册故障事件队列弹出回调 */ +int dc_fault_register_queue_pop(dc_queue_pop_cb_t cb) +{ + if (!cb) + { + return DC_ERR_PARAM; + } + + std::lock_guard lk(g_fault_mutex); + g_fault_cbs.push_back(cb); + + return DC_OK; +} + +/** @brief 触发故障事件队列弹出 */ +void dc_fault_queue_pop(void) +{ + dc_event_pop_inner(g_fault_mutex, g_fault_queue, g_fault_cbs); +} diff --git a/src/public/libdatacenter/src/dc_param_cfg.cpp b/src/public/libdatacenter/src/dc_param_cfg.cpp new file mode 100644 index 0000000..fefe38e --- /dev/null +++ b/src/public/libdatacenter/src/dc_param_cfg.cpp @@ -0,0 +1,423 @@ +/** @file dc_param_cfg.cpp — 参数配置解析与保存(直接调用 tinyxml2 C++ API) */ +#include "dc_internal.h" +#include "tinyxml2.h" +#include + +/** + * @brief 存储信号参数元数据到全局表 + * @param saddr 信号地址 + * @param param 参数元数据(min/max/step/unit) + */ +void dc_param_metadata_store(const char *saddr, const dc_param_meta_t ¶m) +{ + g_param_metadata[saddr] = param; +} + +/** + * @brief 查找信号参数元数据 + * @param saddr 信号地址 + * @param out_param 输出参数元数据 + * @return true 找到,false 未找到 + */ +bool dc_param_metadata_lookup(const char *saddr, dc_param_meta_t &out_param) +{ + auto it = g_param_metadata.find(saddr); + + if (it != g_param_metadata.end()) + { + out_param = it->second; + + return true; + } + + return false; +} + +/** + * @brief 解析 param.xml 中的 Ao/Param 元数据(min/max/step/unit) + * @param path param.xml 文件路径 + * @return 0 成功,-1 失败 + */ +static int dc_param_parse_metadata(const char *path) +{ + tinyxml2::XMLDocument doc; + + if (doc.LoadFile(path) != tinyxml2::XML_SUCCESS) + { + LOG_E("load %s failed\n", path); + + return -1; + } + + tinyxml2::XMLElement *root = doc.RootElement(); + + if (!root) + { + return -1; + } + + const char *sections[] = {"Ao", "Param"}; + + for (int si = 0; si < 2; si++) + { + tinyxml2::XMLElement *elem = root->FirstChildElement(sections[si]); + + for (tinyxml2::XMLElement *sig = (elem ? elem->FirstChildElement("Signal") : NULL); + sig; + sig = sig->NextSiblingElement("Signal")) + { + const char *saddr = sig->Attribute("saddr"); + + if (!saddr) + { + continue; + } + + dc_param_meta_t m; + + m.min = sig->FloatAttribute("min", 0.0f); + m.max = sig->FloatAttribute("max", 0.0f); + m.step = sig->FloatAttribute("step", 0.0f); + const char *u = sig->Attribute("unit"); + + strncpy(m.unit, u ? u : "", DC_UNIT_MAX_LEN - 1); + g_param_metadata[saddr] = m; + } + } + + return 0; +} + +/** + * @brief 加载 self_param.xml 已保存的值 + * @param path self_param.xml 文件路径 + * @return saddr→value 映射表 + */ +static std::unordered_map dc_self_param_load(const char *path) +{ + std::unordered_map vals; + tinyxml2::XMLDocument doc; + + if (doc.LoadFile(path) != tinyxml2::XML_SUCCESS) + { + return vals; + } + + tinyxml2::XMLElement *root = doc.RootElement(); + + if (!root) + { + return vals; + } + + const char *sections[] = {"Ao", "Param"}; + + for (int si = 0; si < 2; si++) + { + tinyxml2::XMLElement *elem = root->FirstChildElement(sections[si]); + + for (tinyxml2::XMLElement *sig = (elem ? elem->FirstChildElement("Signal") : NULL); + sig; + sig = sig->NextSiblingElement("Signal")) + { + const char *sa = sig->Attribute("saddr"); + + if (!sa) + { + continue; + } + + if (si == 0) + { + const char *v = sig->Attribute("value"); + + if (v) + { + vals[sa] = v; + } + } + else + { + for (tinyxml2::XMLElement *item = sig->FirstChildElement("Item"); + item; + item = item->NextSiblingElement("Item")) + { + int idx = item->IntAttribute("index", -1); + const char *v = item->Attribute("value"); + + if (idx >= 1 && v) + { + vals[std::string(sa) + "_" + std::to_string(idx)] = v; + } + } + } + } + } + + return vals; +} + +/** + * @brief 解析 param.xml + self_param.xml,注册 Ao 和 Param 信号 + * @param path param.xml 文件路径 + * @return 0 成功,-1 失败 + */ +int dc_param_cfg_parse(const char *path) +{ + if (0 != dc_param_parse_metadata(path)) + { + LOG_E("parse metadata failed\n"); + + return -1; + } + + std::string self_path = path; + size_t pos = self_path.rfind("param.xml"); + + if (pos != std::string::npos) + { + self_path.replace(pos, 9, "self_param.xml"); + } + + auto self_vals = dc_self_param_load(self_path.c_str()); + + LOG_I("loaded %zu saved values from self_param.xml\n", self_vals.size()); + + /* 注册 Ao 和 Param 信号 */ + tinyxml2::XMLDocument doc; + + if (doc.LoadFile(path) != tinyxml2::XML_SUCCESS) + { + return -1; + } + + tinyxml2::XMLElement *root = doc.RootElement(); + + if (!root) + { + return -1; + } + + /* Ao 段 */ + tinyxml2::XMLElement *ao_elem = root->FirstChildElement("Ao"); + + if (ao_elem) + { + for (tinyxml2::XMLElement *sig = ao_elem->FirstChildElement("Signal"); + sig; + sig = sig->NextSiblingElement("Signal")) + { + const char *sa = sig->Attribute("saddr"); + const char *de = sig->Attribute("desc"); + const char *ty = sig->Attribute("type"); + + if (!sa || !de || !ty) + { + continue; + } + + uint8_t dt = dc_util_type_id_by_name(ty); + + if (!dt) + { + continue; + } + + auto it = self_vals.find(sa); + std::string rv = (it != self_vals.end()) ? it->second : ""; + + if (rv.empty()) + { + const char *va = sig->Attribute("value"); + + if (va) + { + rv = va; + } + } + + void *pd = dc_util_create_data(dt); + + if (pd && !rv.empty()) + { + dc_util_val_from_string(pd, dt, rv); + } + + if (pd) + { + dc_signal_ao(sa, de, dt, DC_CTRL_SBO, pd, NULL, MODULE_DATACENTER); + } + } + } + + /* Param 段 */ + tinyxml2::XMLElement *param_elem = root->FirstChildElement("Param"); + + if (param_elem) + { + for (tinyxml2::XMLElement *sig = param_elem->FirstChildElement("Signal"); + sig; + sig = sig->NextSiblingElement("Signal")) + { + const char *sa = sig->Attribute("saddr"); + const char *de = sig->Attribute("desc"); + const char *ty = sig->Attribute("type"); + + if (!sa || !de || !ty) + { + continue; + } + + uint8_t dt = dc_util_type_id_by_name(ty); + + if (!dt) + { + continue; + } + + int num = sig->IntAttribute("num", -1); + + if (num < 1) + { + continue; + } + + std::vector vec; + + for (tinyxml2::XMLElement *item = sig->FirstChildElement("Item"); + item; + item = item->NextSiblingElement("Item")) + { + int idx = item->IntAttribute("index", -1); + std::string key = std::string(sa) + "_" + std::to_string(idx); + auto it = self_vals.find(key); + std::string rv = (it != self_vals.end()) ? it->second : ""; + const char *va = item->Attribute("value"); + + if (rv.empty() && va) + { + rv = va; + } + + void *pd = dc_util_create_data(dt); + + if (pd && !rv.empty()) + { + dc_util_val_from_string(pd, dt, rv); + } + + if (pd) + { + vec.push_back(pd); + } + } + + if (!vec.empty()) + { + dc_signal_param(sa, de, dt, DC_CTRL_SBO, vec.data(), num, NULL, + MODULE_DATACENTER); + } + } + } + + g_param_cfg_changed = true; + + return 0; +} + +/** + * @brief 检测参数配置变更标志,有变更则写 self_param.xml + * @param path 参数配置文件目录(用于构造 self_param.xml 路径) + */ +void dc_param_cfg_check(const char *path) +{ + if (!g_param_cfg_changed || !path || !path[0]) + { + return; + } + + tinyxml2::XMLDocument doc; + + doc.InsertEndChild(doc.NewDeclaration()); + + tinyxml2::XMLElement *root = doc.NewElement("Root"); + + doc.InsertEndChild(root); + + /* Ao 段 */ + tinyxml2::XMLElement *ao_elem = doc.NewElement("Ao"); + + for (uint32_t i = 0; i < g_dc_ao.signal_id; i++) + { + dc_signal_t *ps = dc_core_find_by_id(i, g_dc_ao); + + if (!ps || !ps->vec_p_data || !ps->vec_p_data[0]) + { + continue; + } + + std::string v = dc_util_val_to_string(ps->vec_p_data[0], ps->data_type); + tinyxml2::XMLElement *sig = doc.NewElement("Signal"); + + sig->SetAttribute("saddr", ps->saddr); + sig->SetAttribute("value", v.c_str()); + ao_elem->InsertEndChild(sig); + } + + root->InsertEndChild(ao_elem); + + /* Param 段 */ + tinyxml2::XMLElement *param_elem = doc.NewElement("Param"); + + for (uint32_t i = 0; i < g_dc_param.signal_id; i++) + { + dc_signal_t *ps = dc_core_find_by_id(i, g_dc_param); + + if (!ps || !ps->vec_p_data) + { + continue; + } + + tinyxml2::XMLElement *sig = doc.NewElement("Signal"); + + sig->SetAttribute("saddr", ps->saddr); + + for (int z = 0; z < ps->vec_p_data_count; z++) + { + if (!ps->vec_p_data[z]) + { + continue; + } + + std::string v = dc_util_val_to_string(ps->vec_p_data[z], ps->data_type); + tinyxml2::XMLElement *item = doc.NewElement("Item"); + + item->SetAttribute("index", z + 1); + item->SetAttribute("value", v.c_str()); + sig->InsertEndChild(item); + } + + param_elem->InsertEndChild(sig); + } + + root->InsertEndChild(param_elem); + + std::string self_path = path; + size_t pos = self_path.rfind("param.xml"); + + if (pos != std::string::npos) + { + self_path.replace(pos, 9, "self_param.xml"); + } + + if (doc.SaveFile(self_path.c_str()) == tinyxml2::XML_SUCCESS) + { + LOG_I("param saved to %s\n", self_path.c_str()); + } + else + { + LOG_E("failed to save param to %s\n", self_path.c_str()); + } + + g_param_cfg_changed = false; +} diff --git a/src/public/libdatacenter/src/dc_signal_ao.cpp b/src/public/libdatacenter/src/dc_signal_ao.cpp new file mode 100644 index 0000000..fad4368 --- /dev/null +++ b/src/public/libdatacenter/src/dc_signal_ao.cpp @@ -0,0 +1,247 @@ +/** @file dc_signal_ao.cpp — AO 信号注册/控制/异步执行 */ +#include "dc_internal.h" + +/** + * @brief 创建 AO 信号:若已存在则复制旧值并替换指针;新注册则重新构建信号结构 + */ +extern "C" int dc_signal_ao(const char *saddr, const char *desc, uint8_t data_type, uint8_t ctrl_type, void *p_data, dc_signal_change_cb_t cb, const char *module_id) +{ + if (!g_initialized || !saddr || !p_data || !module_id) + { + return DC_ERR_PARAM; + } + + dc_signal_t *existing = dc_core_find(saddr, g_dc_ao); + + if (existing) + { + dc_util_val_copy(p_data, existing->vec_p_data[0], existing->data_type); + } + + dc_signal_t sig = {}; + sig.hash = XXH3_128bits(saddr, strlen(saddr)); + strncpy(sig.saddr, saddr, DC_SADDR_MAX_LEN - 1); + + if (desc) + { + strncpy(sig.desc, desc, DC_DESC_MAX_LEN - 1); + } + + sig.data_type = data_type; + sig.ctrl_type = ctrl_type; + sig.vec_p_data = (void **)malloc(sizeof(void *)); + sig.vec_p_data[0] = p_data; + sig.vec_p_data_count = 1; + + if (existing) + { + for (int i = 0; i < existing->cb_count && sig.cb_count < DC_MAX_CALLBACKS; i++) + { + sig.cb_list[sig.cb_count] = existing->cb_list[i]; + sig.cb_count++; + } + } + + if (cb && sig.cb_count < DC_MAX_CALLBACKS) + { + strncpy(sig.cb_list[sig.cb_count].module_id, module_id, DC_MODULE_MAX_LEN - 1); + sig.cb_list[sig.cb_count].ctrl_cb = cb; + sig.cb_count++; + } + + g_param_cfg_changed = true; + + return dc_core_add(sig, g_dc_ao); +} + +/** + * @brief 链接已有 AO 信号:将 *p_data 指向信号的 vec_p_data[0],可选添加 cb + */ +extern "C" int dc_signal_ao_link_with_callback(const char *saddr, void **p_data, dc_signal_change_cb_t cb, const char *module_id) +{ + if (!g_initialized || !saddr || !p_data || !module_id) + { + return DC_ERR_PARAM; + } + + dc_signal_t *ps = dc_core_find(saddr, g_dc_ao); + + if (!ps) + { + return DC_ERR_NOTFOUND; + } + + if (!ps->vec_p_data || !ps->vec_p_data[0]) + { + return DC_ERR_PARAM; + } + + *p_data = ps->vec_p_data[0]; + + if (cb && ps->cb_count < DC_MAX_CALLBACKS) + { + strncpy(ps->cb_list[ps->cb_count].module_id, module_id, DC_MODULE_MAX_LEN - 1); + ps->cb_list[ps->cb_count].ctrl_cb = cb; + ps->cb_count++; + } + + return DC_OK; +} + +/** + * @brief 同步 AO 设值:ctrl 校验 → step 处理(DIRECT 后 ctrl.step = READY)→ 遍历回调(跳过发起方 module_id)→ 标记 changed + */ +extern "C" int dc_signal_ao_set_val(const char *saddr, dc_ctrl_step_t step, dc_ctrl_t *ctrl, const void *p_data, const char *module_id) +{ + if (!g_initialized || !saddr || !ctrl || !p_data || !module_id) + { + return DC_ERR_PARAM; + } + + dc_signal_t *ps = dc_core_find(saddr, g_dc_ao); + + if (!ps) + { + return DC_ERR_NOTFOUND; + } + + int ret = dc_check_ctrl_valid(ps, step, ctrl, p_data); + + if (ret != DC_OK) + { + return ret; + } + + if (step == DC_STEP_SELECT) + { + dc_util_val_copy(ctrl->p_data, p_data, ps->data_type); + ctrl->step = DC_STEP_SELECT; + } + else if (step == DC_STEP_DIRECT) + { + dc_util_val_copy(ps->vec_p_data[0], p_data, ps->data_type); + ctrl->step = DC_STEP_READY; + } + else if (step == DC_STEP_CANCEL) + { + ctrl->step = DC_STEP_READY; + } + + for (int i = 0; i < ps->cb_count; i++) + { + if (!ps->cb_list[i].ctrl_cb) + { + continue; + } + + if (0 == strcmp(ps->cb_list[i].module_id, module_id)) + { + continue; + } + + ps->cb_list[i].ctrl_cb(ps->saddr, step, ps->data_type, 0, ps->vec_p_data[0]); + } + + g_param_cfg_changed = true; + + return DC_OK; +} + +/** + * @brief 跳过 ctrl 校验直接写入 AO 值:直接写数据 → 遍历回调(跳过发起方 module_id)→ 标记 changed + */ +extern "C" int dc_signal_ao_set_val_without_check(const char *saddr, uint8_t data_type, const void *p_data, const char *module_id) +{ + if (!g_initialized || !saddr || !p_data || !module_id) + { + return DC_ERR_PARAM; + } + + dc_signal_t *ps = dc_core_find(saddr, g_dc_ao); + + if (!ps) + { + return DC_ERR_NOTFOUND; + } + + dc_util_val_copy(ps->vec_p_data[0], p_data, data_type); + + for (int i = 0; i < ps->cb_count; i++) + { + if (!ps->cb_list[i].ctrl_cb) + { + continue; + } + + if (0 == strcmp(ps->cb_list[i].module_id, module_id)) + { + continue; + } + + ps->cb_list[i].ctrl_cb(ps->saddr, DC_STEP_DIRECT, ps->data_type, 0, ps->vec_p_data[0]); + } + + g_param_cfg_changed = true; + + return DC_OK; +} + +/** + * @brief 发起 AO 异步执行请求 + */ +extern "C" int dc_ao_exec_async(const char *saddr, dc_ctrl_step_t step, const dc_ctrl_t *ctrl, const void *p_data, const char *module_id, dc_async_result_cb_t result_cb, void *user_arg, uint32_t timeout_ms, uint32_t *request_id) +{ + if (!g_initialized || !saddr || !ctrl || !p_data || !module_id) + { + return DC_ERR_PARAM; + } + + uint32_t req_id = dc_async_enqueue(g_dc_ao, saddr, step, ctrl, p_data, 0, 1, module_id, result_cb, user_arg, timeout_ms); + + if (request_id) + { + *request_id = req_id; + } + + return (req_id == 0) ? DC_ERR_FULL : DC_OK; +} + +/** + * @brief 模块 B 返回 AO 异步执行结果 + */ +extern "C" int dc_ao_set_result(uint32_t request_id, int result_code, const char *err_msg) +{ + return dc_async_set_result(request_id, result_code, err_msg); +} + +/** + * @brief 取消未完成的 AO 异步请求 + */ +extern "C" int dc_ao_cancel_async(uint32_t request_id) +{ + return dc_async_cancel(request_id); +} + +/** + * @brief 注册 AO 异步执行回调(模块 B 调用) + */ +extern "C" int dc_ao_register_exec_cb(const char *saddr, dc_async_exec_cb_t exec_cb, void *module_ctx) +{ + if (!g_initialized || !saddr) + { + return DC_ERR_PARAM; + } + + dc_signal_t *ps = dc_core_find(saddr, g_dc_ao); + + if (!ps) + { + return DC_ERR_NOTFOUND; + } + + ps->exec_cb = exec_cb; + ps->module_ctx = module_ctx; + + return DC_OK; +} + diff --git a/src/public/libdatacenter/src/dc_signal_check.cpp b/src/public/libdatacenter/src/dc_signal_check.cpp new file mode 100644 index 0000000..56ca399 --- /dev/null +++ b/src/public/libdatacenter/src/dc_signal_check.cpp @@ -0,0 +1,105 @@ +/** @file dc_signal_check.cpp — 控制校验 + 值校验 */ +#include "dc_internal.h" + +/** @brief 校验直控值是否与选控暂存值一致 */ +int dc_check_ctrl_val_valid(const dc_ctrl_t *ctrl, const void *p_data) +{ + uint8_t len = dc_util_type_len(ctrl->data_type); + + if (!len) + { + return DC_ERR_TYPE; + } + + if (0 != memcmp(ctrl->p_data, p_data, len)) + { + LOG_E("direct val != selected val\n"); + return DC_ERR_VAL; + } + + return DC_OK; +} + +/** @brief SBO 选控-直控-撤销完整流程校验 */ +int dc_check_ctrl_valid(const dc_signal_t *ps, dc_ctrl_step_t step, + dc_ctrl_t *ctrl, const void *p_data) +{ + if (ps->data_type != ctrl->data_type) + { + LOG_E("type mismatch %d!=%d\n", ps->data_type, ctrl->data_type); + return DC_ERR_TYPE; + } + + if (ctrl->type == DC_CTRL_DIRECT) + { + if (step == DC_STEP_DIRECT) + { + return DC_OK; + } + return DC_ERR_STEP; + } + + if (ctrl->type == DC_CTRL_SBO) + { + if (ctrl->step == DC_STEP_READY && step == DC_STEP_SELECT) + { + return DC_OK; + } + + if (ctrl->step == DC_STEP_SELECT) + { + if (step == DC_STEP_DIRECT) + { + return dc_check_ctrl_val_valid(ctrl, p_data); + } + + if (step == DC_STEP_CANCEL) + { + return DC_OK; + } + } + } + + LOG_E("invalid ctrl type=%d step=%d\n", ctrl->type, step); + + return DC_ERR_STEP; +} + +/** @brief 校验新值与当前值是否不同(变更检测) */ +int dc_check_val_valid(const dc_signal_t *ps, uint8_t zone, const void *p_data) +{ + if (zone >= (uint8_t)ps->vec_p_data_count) + { + LOG_E("zone %d out of range\n", zone); + return DC_ERR_PARAM; + } + + const void *cur = ps->vec_p_data[zone]; + + if (0 == dc_util_val_compare(ps->data_type, cur, p_data)) + { + LOG_E("val not changed\n"); + return DC_ERR_VAL; + } + + return DC_OK; +} + +/** @brief 校验遥控值有效性(仅允许 0/1) */ +int dc_check_yk_val_valid(dc_signal_t *ps, void *p_data) +{ + if (ps->data_type == DATA_TYPE_B || ps->data_type == DATA_TYPE_U8) + { + uint8_t v = *(uint8_t *)p_data; + + if (v != 0 && v != 1) + { + LOG_E("invalid yk val %d\n", v); + return DC_ERR_VAL; + } + + return DC_OK; + } + + return DC_ERR_TYPE; +} diff --git a/src/public/libdatacenter/src/dc_signal_in.cpp b/src/public/libdatacenter/src/dc_signal_in.cpp new file mode 100644 index 0000000..a93828a --- /dev/null +++ b/src/public/libdatacenter/src/dc_signal_in.cpp @@ -0,0 +1,79 @@ +/** @file dc_signal_in.cpp — In 信号注册 */ +#include "dc_internal.h" + +/** @brief In 信号注册内部实现:关联 out 信号、建立链接、追加回调 */ +static int dc_in_register_impl(const char *saddr, const char *desc, const char *link_saddr, void **p_data, dc_out_change_cb_t cb, const char *module_id) +{ + if (!g_initialized || !saddr || !link_saddr || !p_data || !module_id) + { + return DC_ERR_PARAM; + } + + if (!link_saddr[0]) + { + return DC_ERR_PARAM; + } + + if (dc_core_find_in(saddr)) + { + LOG_E("in %s exists\n", saddr); + + return DC_ERR_EXISTS; + } + + dc_signal_t *ps_out = dc_core_find_out(link_saddr); + + if (!ps_out) + { + LOG_E("link out %s not found\n", link_saddr); + + return DC_ERR_NOTFOUND; + } + + *p_data = ps_out->vec_p_data[0]; + + dc_signal_t sig = {}; + + sig.hash = XXH3_128bits(saddr, strlen(saddr)); + strncpy(sig.saddr, saddr, DC_SADDR_MAX_LEN - 1); + + if (desc) + { + strncpy(sig.desc, desc, DC_DESC_MAX_LEN - 1); + } + + sig.data_type = ps_out->data_type; + strncpy(sig.link_saddr, link_saddr, DC_SADDR_MAX_LEN - 1); + sig.vec_p_data = (void **)malloc(sizeof(void *)); + sig.vec_p_data[0] = *p_data; + sig.vec_p_data_count = 1; + + /* out 记录 in 的链接 */ + char **nl = (char **)realloc(ps_out->link_saddrs, (ps_out->link_count + 1) * sizeof(char *)); + + nl[ps_out->link_count] = strdup(saddr); + ps_out->link_saddrs = nl; + ps_out->link_count++; + + if (cb && ps_out->cb_count < DC_MAX_CALLBACKS) + { + strncpy(ps_out->cb_list[ps_out->cb_count].module_id, module_id, DC_MODULE_MAX_LEN - 1); + ps_out->cb_list[ps_out->cb_count].out_cb = cb; + ps_out->cb_count++; + } + + return dc_core_add(sig, g_dc_in); +} + +/** @brief 注册 in 信号(链接到 out,无回调) */ +int dc_signal_in(const char *saddr, const char *desc, const char *link_saddr, void **p_data, const char *module_id) +{ + return dc_in_register_impl(saddr, desc, link_saddr, p_data, NULL, module_id); +} + +/** @brief 注册 in 信号(链接到 out,带变更回调) */ +int dc_signal_in_with_callback(const char *saddr, const char *desc, const char *link_saddr, void **p_data, dc_out_change_cb_t cb, const char *module_id) +{ + return dc_in_register_impl(saddr, desc, link_saddr, p_data, cb, module_id); +} + diff --git a/src/public/libdatacenter/src/dc_signal_out.cpp b/src/public/libdatacenter/src/dc_signal_out.cpp new file mode 100644 index 0000000..aaa2abf --- /dev/null +++ b/src/public/libdatacenter/src/dc_signal_out.cpp @@ -0,0 +1,120 @@ +/** @file dc_signal_out.cpp — Out 信号注册/链接/设值 */ +#include "dc_internal.h" + +/** @brief Out 信号注册内部实现:创建信号、分配内存、注册回调 */ +static int dc_out_register_impl(const char *saddr, const char *desc, uint8_t data_type, void *p_data, dc_out_change_cb_t cb, const char *module_id) +{ + if (!g_initialized || !saddr || !p_data || !module_id) + { + return DC_ERR_PARAM; + } + + if (dc_core_find_out(saddr)) + { + LOG_E("out %s exists\n", saddr); + + return DC_ERR_EXISTS; + } + + dc_signal_t sig = {}; + + sig.hash = XXH3_128bits(saddr, strlen(saddr)); + strncpy(sig.saddr, saddr, DC_SADDR_MAX_LEN - 1); + + if (desc) + { + strncpy(sig.desc, desc, DC_DESC_MAX_LEN - 1); + } + + sig.data_type = data_type; + sig.vec_p_data = (void **)malloc(sizeof(void *)); + sig.vec_p_data[0] = p_data; + sig.vec_p_data_count = 1; + + sig.p_last_data = dc_util_create_data(data_type); + + if (!sig.p_last_data) + { + free(sig.vec_p_data); + + return DC_ERR_MEM; + } + + dc_util_val_copy(sig.p_last_data, p_data, data_type); + + if (cb) + { + strncpy(sig.cb_list[0].module_id, module_id, DC_MODULE_MAX_LEN - 1); + sig.cb_list[0].out_cb = cb; + sig.cb_count = 1; + } + + return dc_core_add(sig, g_dc_out); +} + +/** @brief 注册 out 信号(无回调) */ +int dc_signal_out(const char *saddr, const char *desc, uint8_t data_type, void *p_data, const char *module_id) +{ + return dc_out_register_impl(saddr, desc, data_type, p_data, NULL, module_id); +} + +/** @brief 注册 out 信号(带变更回调) */ +int dc_signal_out_with_callback(const char *saddr, const char *desc, uint8_t data_type, void *p_data, dc_out_change_cb_t cb, const char *module_id) +{ + return dc_out_register_impl(saddr, desc, data_type, p_data, cb, module_id); +} + +/** @brief 链接已有 out 信号并追加回调 */ +int dc_signal_out_link_with_callback(const char *saddr, void **p_data, dc_out_change_cb_t cb, const char *module_id) +{ + if (!g_initialized || !saddr || !p_data || !module_id) + { + return DC_ERR_PARAM; + } + + dc_signal_t *ps = dc_core_find_out(saddr); + + if (!ps) + { + return DC_ERR_NOTFOUND; + } + + if (!ps->vec_p_data || !ps->vec_p_data[0]) + { + return DC_ERR_PARAM; + } + + *p_data = ps->vec_p_data[0]; + + if (cb && ps->cb_count < DC_MAX_CALLBACKS) + { + strncpy(ps->cb_list[ps->cb_count].module_id, module_id, DC_MODULE_MAX_LEN - 1); + ps->cb_list[ps->cb_count].out_cb = cb; + ps->cb_count++; + } + + return DC_OK; +} + +/** @brief 设置 out 信号值(标记脏 + 回路阻断) */ +int dc_set_out_signal_val(const char *saddr, const void *set_data, const char *module_id) +{ + if (!g_initialized || !saddr || !set_data || !module_id) + { + return DC_ERR_PARAM; + } + + dc_signal_t *ps = dc_core_find_out(saddr); + + if (!ps) + { + return DC_ERR_NOTFOUND; + } + + strncpy(ps->last_caller_module, module_id, DC_MODULE_MAX_LEN - 1); + dc_core_mark_dirty(ps); + dc_util_val_copy(ps->vec_p_data[0], set_data, ps->data_type); + + return DC_OK; +} + diff --git a/src/public/libdatacenter/src/dc_signal_param.cpp b/src/public/libdatacenter/src/dc_signal_param.cpp new file mode 100644 index 0000000..aa5253f --- /dev/null +++ b/src/public/libdatacenter/src/dc_signal_param.cpp @@ -0,0 +1,290 @@ +/** @file dc_signal_param.cpp — Param 信号注册/控制/异步执行 */ +#include "dc_internal.h" + +/** + * @brief 创建 Param 定值信号:若已存在则复制各 zone 旧值并替换指针;新注册则执行 metadata_lookup 并标记 changed + */ +extern "C" int dc_signal_param(const char *saddr, const char *desc, uint8_t data_type, uint8_t ctrl_type, void **p_data, int num_zones, dc_signal_change_cb_t cb, const char *module_id) +{ + if (!g_initialized || !saddr || !p_data || !module_id) + { + return DC_ERR_PARAM; + } + + if (num_zones <= 0 || num_zones > DC_MAX_DATA_ZONES) + { + return DC_ERR_PARAM; + } + + dc_signal_t *existing = dc_core_find(saddr, g_dc_param); + + if (existing && existing->data_type != data_type) + { + LOG_E("param %s data_type mismatch %d!=%d\n", saddr, existing->data_type, data_type); + + return DC_ERR_TYPE; + } + + if (existing) + { + for (int i = 0; i < num_zones && i < existing->vec_p_data_count; i++) + { + dc_util_val_copy(p_data[i], existing->vec_p_data[i], existing->data_type); + } + } + + dc_signal_t sig = {}; + sig.hash = XXH3_128bits(saddr, strlen(saddr)); + strncpy(sig.saddr, saddr, DC_SADDR_MAX_LEN - 1); + + if (desc) + { + strncpy(sig.desc, desc, DC_DESC_MAX_LEN - 1); + } + + sig.data_type = data_type; + sig.ctrl_type = ctrl_type; + sig.vec_p_data = (void **)malloc(sizeof(void *) * num_zones); + + for (int i = 0; i < num_zones; i++) + { + sig.vec_p_data[i] = p_data[i]; + } + + sig.vec_p_data_count = num_zones; + + if (!existing) + { + dc_param_meta_t meta; + + if (dc_param_metadata_lookup(saddr, meta)) + { + sig.param = meta; + } + } + else + { + sig.param = existing->param; + + for (int i = 0; i < existing->cb_count && sig.cb_count < DC_MAX_CALLBACKS; i++) + { + sig.cb_list[sig.cb_count] = existing->cb_list[i]; + sig.cb_count++; + } + } + + if (cb && sig.cb_count < DC_MAX_CALLBACKS) + { + strncpy(sig.cb_list[sig.cb_count].module_id, module_id, DC_MODULE_MAX_LEN - 1); + sig.cb_list[sig.cb_count].ctrl_cb = cb; + sig.cb_count++; + } + + g_param_cfg_changed = true; + + return dc_core_add(sig, g_dc_param); +} + +/** + * @brief 链接已有 Param 信号:将各 zone 的 p_data 指向信号的 vec_p_data,可选添加 cb + */ +extern "C" int dc_signal_param_link_with_callback(const char *saddr, void **p_data, int num_zones, dc_signal_change_cb_t cb, const char *module_id) +{ + if (!g_initialized || !saddr || !p_data || !module_id) + { + return DC_ERR_PARAM; + } + + dc_signal_t *ps = dc_core_find(saddr, g_dc_param); + + if (!ps) + { + return DC_ERR_NOTFOUND; + } + + if (num_zones > ps->vec_p_data_count) + { + return DC_ERR_PARAM; + } + + for (int i = 0; i < num_zones; i++) + { + p_data[i] = ps->vec_p_data[i]; + } + + if (cb && ps->cb_count < DC_MAX_CALLBACKS) + { + strncpy(ps->cb_list[ps->cb_count].module_id, module_id, DC_MODULE_MAX_LEN - 1); + ps->cb_list[ps->cb_count].ctrl_cb = cb; + ps->cb_count++; + } + + return DC_OK; +} + +/** + * @brief 同步 Param 设值:ctrl 校验 + val 校验 → step 处理 → 遍历回调(跳过发起方 module_id)→ 标记 changed + */ +extern "C" int dc_signal_param_set_val(const char *saddr, dc_ctrl_step_t step, dc_ctrl_t *ctrl, uint8_t setting_zone, const void *p_data, const char *module_id) +{ + if (!g_initialized || !saddr || !ctrl || !p_data || !module_id) + { + return DC_ERR_PARAM; + } + + dc_signal_t *ps = dc_core_find(saddr, g_dc_param); + + if (!ps) + { + return DC_ERR_NOTFOUND; + } + + int ret = dc_check_ctrl_valid(ps, step, ctrl, p_data); + + if (ret != DC_OK) + { + return ret; + } + + ret = dc_check_val_valid(ps, setting_zone, p_data); + + if (ret != DC_OK) + { + return ret; + } + + if (step == DC_STEP_SELECT) + { + dc_util_val_copy(ctrl->p_data, p_data, ps->data_type); + } + else if (step == DC_STEP_DIRECT) + { + dc_util_val_copy(ps->vec_p_data[setting_zone], p_data, ps->data_type); + } + /* DC_STEP_CANCEL: no data operation */ + + ctrl->step = step; + + for (int i = 0; i < ps->cb_count; i++) + { + if (!ps->cb_list[i].ctrl_cb) + { + continue; + } + + if (0 == strcmp(ps->cb_list[i].module_id, module_id)) + { + continue; + } + + ps->cb_list[i].ctrl_cb(ps->saddr, step, ps->data_type, setting_zone, ps->vec_p_data[setting_zone]); + } + + g_param_cfg_changed = true; + + return DC_OK; +} + +/** + * @brief 跳过 ctrl 校验直接写入 Param 值:校验 zone → 写数据 → 遍历回调(跳过发起方 module_id)→ 标记 changed + */ +extern "C" int dc_signal_param_set_val_without_check(const char *saddr, uint8_t data_type, uint8_t setting_zone, const void *p_data, const char *module_id) +{ + if (!g_initialized || !saddr || !p_data || !module_id) + { + return DC_ERR_PARAM; + } + + dc_signal_t *ps = dc_core_find(saddr, g_dc_param); + + if (!ps) + { + return DC_ERR_NOTFOUND; + } + + if (setting_zone >= (uint8_t)ps->vec_p_data_count) + { + return DC_ERR_PARAM; + } + + dc_util_val_copy(ps->vec_p_data[setting_zone], p_data, data_type); + + for (int i = 0; i < ps->cb_count; i++) + { + if (!ps->cb_list[i].ctrl_cb) + { + continue; + } + + if (0 == strcmp(ps->cb_list[i].module_id, module_id)) + { + continue; + } + + ps->cb_list[i].ctrl_cb(ps->saddr, DC_STEP_DIRECT, ps->data_type, setting_zone, ps->vec_p_data[setting_zone]); + } + + g_param_cfg_changed = true; + + return DC_OK; +} + +/** + * @brief 发起 Param 异步执行请求 + */ +extern "C" int dc_param_exec_async(const char *saddr, dc_ctrl_step_t step, const dc_ctrl_t *ctrl, uint8_t setting_zone, const void *p_data, const char *module_id, dc_async_result_cb_t result_cb, void *user_arg, uint32_t timeout_ms, uint32_t *request_id) +{ + if (!g_initialized || !saddr || !ctrl || !p_data || !module_id) + { + return DC_ERR_PARAM; + } + + uint32_t req_id = dc_async_enqueue(g_dc_param, saddr, step, ctrl, p_data, setting_zone, 2, module_id, result_cb, user_arg, timeout_ms); + + if (request_id) + { + *request_id = req_id; + } + + return (req_id == 0) ? DC_ERR_FULL : DC_OK; +} + +/** + * @brief 模块 B 返回 Param 异步执行结果 + */ +extern "C" int dc_param_set_result(uint32_t request_id, int result_code, const char *err_msg) +{ + return dc_async_set_result(request_id, result_code, err_msg); +} + +/** + * @brief 取消未完成的 Param 异步请求 + */ +extern "C" int dc_param_cancel_async(uint32_t request_id) +{ + return dc_async_cancel(request_id); +} + +/** + * @brief 注册 Param 异步执行回调(模块 B 调用) + */ +extern "C" int dc_param_register_exec_cb(const char *saddr, dc_async_exec_cb_t exec_cb, void *module_ctx) +{ + if (!g_initialized || !saddr) + { + return DC_ERR_PARAM; + } + + dc_signal_t *ps = dc_core_find(saddr, g_dc_param); + + if (!ps) + { + return DC_ERR_NOTFOUND; + } + + ps->exec_cb = exec_cb; + ps->module_ctx = module_ctx; + + return DC_OK; +} + diff --git a/src/public/libdatacenter/src/dc_signal_query.cpp b/src/public/libdatacenter/src/dc_signal_query.cpp new file mode 100644 index 0000000..049926a --- /dev/null +++ b/src/public/libdatacenter/src/dc_signal_query.cpp @@ -0,0 +1,336 @@ +/** @file dc_signal_query.cpp — 信号查询 */ +#include "dc_internal.h" + +/** @brief 通用信号信息查询内部函数 */ +static int dc_query_info(dc_signal_map_t *pmap, const char *saddr, + char *desc, int desc_len, uint8_t *data_type, void **p_data) +{ + if (!g_initialized || !saddr) + { + return DC_ERR_PARAM; + } + + dc_signal_t *ps = dc_core_find(saddr, *pmap); + + if (!ps) + { + return DC_ERR_NOTFOUND; + } + + if (desc && desc_len > 0) + { + strncpy(desc, ps->desc, desc_len - 1); + desc[desc_len - 1] = '\0'; + } + + if (data_type) + { + *data_type = ps->data_type; + } + + if (p_data && ps->vec_p_data && ps->vec_p_data_count > 0) + { + *p_data = ps->vec_p_data[0]; + } + + return DC_OK; +} + +/** @brief 查询 out 信号信息 */ +int dc_get_out_signal_info(const char *saddr, char *desc, int desc_len, + uint8_t *data_type, void **p_data) +{ + return dc_query_info(&g_dc_out, saddr, desc, desc_len, data_type, p_data); +} + +/** @brief 查询 in 信号信息 */ +int dc_get_in_signal_info(const char *saddr, char *desc, int desc_len, + uint8_t *data_type, void **p_data) +{ + return dc_query_info(&g_dc_in, saddr, desc, desc_len, data_type, p_data); +} + +/** @brief 查询 AO 信号信息(含参数元数据和控型) */ +int dc_get_ao_signal_info(const char *saddr, char *desc, int desc_len, + uint8_t *data_type, dc_param_meta_t *p_meta, uint8_t *ctrl_type, + void **p_data) +{ + if (!g_initialized || !saddr) + { + return DC_ERR_PARAM; + } + + dc_signal_t *ps = dc_core_find(saddr, g_dc_ao); + + if (!ps) + { + return DC_ERR_NOTFOUND; + } + + if (desc && desc_len > 0) + { + strncpy(desc, ps->desc, desc_len - 1); + desc[desc_len - 1] = '\0'; + } + + if (data_type) + { + *data_type = ps->data_type; + } + + if (p_meta) + { + *p_meta = ps->param; + } + + if (ctrl_type) + { + *ctrl_type = ps->ctrl_type; + } + + if (p_data && ps->vec_p_data && ps->vec_p_data_count > 0) + { + *p_data = ps->vec_p_data[0]; + } + + return DC_OK; +} + +/** @brief 查询定值信号信息(含多定值区数据指针数组) */ +int dc_get_param_signal_info(const char *saddr, char *desc, int desc_len, + uint8_t *data_type, dc_param_meta_t *p_meta, uint8_t *ctrl_type, + void **p_data_array, int *p_count) +{ + if (!g_initialized || !saddr) + { + return DC_ERR_PARAM; + } + + dc_signal_t *ps = dc_core_find(saddr, g_dc_param); + + if (!ps) + { + return DC_ERR_NOTFOUND; + } + + if (desc && desc_len > 0) + { + strncpy(desc, ps->desc, desc_len - 1); + desc[desc_len - 1] = '\0'; + } + + if (data_type) + { + *data_type = ps->data_type; + } + + if (p_meta) + { + *p_meta = ps->param; + } + + if (ctrl_type) + { + *ctrl_type = ps->ctrl_type; + } + + if (p_data_array && p_count) + { + *p_count = ps->vec_p_data_count; + + for (int i = 0; i < ps->vec_p_data_count; i++) + { + p_data_array[i] = ps->vec_p_data[i]; + } + } + + return DC_OK; +} + +/** @brief 查询遥控信号信息 */ +int dc_get_yk_signal_info(const char *saddr, char *desc, int desc_len, + uint8_t *data_type, uint8_t *ctrl_type, void **p_data) +{ + if (!g_initialized || !saddr) + { + return DC_ERR_PARAM; + } + + dc_signal_t *ps = dc_core_find(saddr, g_dc_yk); + + if (!ps) + { + return DC_ERR_NOTFOUND; + } + + if (desc && desc_len > 0) + { + strncpy(desc, ps->desc, desc_len - 1); + desc[desc_len - 1] = '\0'; + } + + if (data_type) + { + *data_type = ps->data_type; + } + + if (ctrl_type) + { + *ctrl_type = ps->ctrl_type; + } + + if (p_data && ps->vec_p_data && ps->vec_p_data_count > 0) + { + *p_data = ps->vec_p_data[0]; + } + + return DC_OK; +} + +/** @brief 将信号值按类型转为字符串写入 buf */ +int dc_get_signal_val(const void *p_data, uint8_t data_type, + char *buf, int buf_len) +{ + if (!p_data || !buf || buf_len <= 0) + { + return DC_ERR_PARAM; + } + + std::string s = dc_util_val_to_string(p_data, data_type); + + if (s.empty()) + { + return DC_ERR_TYPE; + } + + strncpy(buf, s.c_str(), buf_len - 1); + buf[buf_len - 1] = '\0'; + + return DC_OK; +} + +/** @brief 按信号类型字符串获取已注册信号数量 */ +int dc_get_signal_count(const char *type_str) +{ + dc_signal_map_t *pmap = NULL; + + if (type_str) + { + if (0 == strcmp(type_str, "out")) + { + pmap = &g_dc_out; + } + else if (0 == strcmp(type_str, "in")) + { + pmap = &g_dc_in; + } + else if (0 == strcmp(type_str, "yk")) + { + pmap = &g_dc_yk; + } + else if (0 == strcmp(type_str, "ao")) + { + pmap = &g_dc_ao; + } + else if (0 == strcmp(type_str, "param")) + { + pmap = &g_dc_param; + } + } + + if (!pmap) + { + return 0; + } + + return (int)pmap->signal_id; +} + +/** @brief 按信号类型和 ID 获取信号详细信息 */ +int dc_get_signal_info_by_id(const char *type_str, uint32_t id, + char *saddr, int saddr_len, char *desc, int desc_len, + char *data_type_str, int type_str_len, uint8_t *ctrl_type, + char *link_str, int link_len) +{ + if (!type_str) + { + return DC_ERR_PARAM; + } + + dc_signal_map_t *pmap = NULL; + + if (0 == strcmp(type_str, "out")) + { + pmap = &g_dc_out; + } + else if (0 == strcmp(type_str, "in")) + { + pmap = &g_dc_in; + } + else if (0 == strcmp(type_str, "yk")) + { + pmap = &g_dc_yk; + } + else if (0 == strcmp(type_str, "ao")) + { + pmap = &g_dc_ao; + } + else if (0 == strcmp(type_str, "param")) + { + pmap = &g_dc_param; + } + + if (!pmap) + { + return DC_ERR_PARAM; + } + + dc_signal_t *ps = dc_core_find_by_id(id, *pmap); + + if (!ps) + { + return DC_ERR_NOTFOUND; + } + + if (saddr && saddr_len > 0) + { + strncpy(saddr, ps->saddr, saddr_len - 1); + saddr[saddr_len - 1] = '\0'; + } + + if (desc && desc_len > 0) + { + strncpy(desc, ps->desc, desc_len - 1); + desc[desc_len - 1] = '\0'; + } + + if (data_type_str && type_str_len > 0) + { + std::string n = dc_util_type_name(ps->data_type); + + strncpy(data_type_str, n.c_str(), type_str_len - 1); + data_type_str[type_str_len - 1] = '\0'; + } + + if (ctrl_type) + { + *ctrl_type = ps->ctrl_type; + } + + if (link_str && link_len > 0) + { + link_str[0] = '\0'; + + for (int i = 0; i < ps->link_count; i++) + { + if (i > 0) + { + strncat(link_str, ",", link_len - strlen(link_str) - 1); + } + strncat(link_str, ps->link_saddrs[i], + link_len - strlen(link_str) - 1); + } + } + + return DC_OK; +} diff --git a/src/public/libdatacenter/src/dc_signal_show.cpp b/src/public/libdatacenter/src/dc_signal_show.cpp new file mode 100644 index 0000000..0c427d6 --- /dev/null +++ b/src/public/libdatacenter/src/dc_signal_show.cpp @@ -0,0 +1,205 @@ +/** @file dc_signal_show.cpp — 信号展示/导出(tinyxml2 C++ API + libcmd C 接口) */ +#include "dc_internal.h" +#include "myCmd.h" +#include "tinyxml2.h" +#include +#include + +extern "C" { + +/** + * @brief 格式化打印一张信号表的所有信号 + * @param smap 信号表引用 + */ +static void dc_show_signals(dc_signal_map_t &smap) +{ + const int COL_ID = 8; + const int COL_SADDR = 36; + const int COL_DESC = 48; + const int COL_TYPE = 10; + const int COL_VAL = 24; + const int COL_LINK = 48; + + auto pad_right = [](const std::string &s, int w) -> std::string + { + int pad = w - (int)s.length(); + + if (pad > 0) + { + return s + std::string((size_t)pad, ' '); + } + + return s + ' '; + }; + + std::cout << "\t" + << pad_right("id", COL_ID) + << pad_right("saddr", COL_SADDR) + << pad_right("desc", COL_DESC) + << pad_right("type", COL_TYPE) + << pad_right("val", COL_VAL) + << pad_right("link_saddrs", COL_LINK) + << std::endl; + + int total = COL_ID + COL_SADDR + COL_DESC + COL_TYPE + COL_VAL + COL_LINK; + + std::cout << "\t" << std::string((size_t)total, '-') << std::endl; + + for (uint32_t i = 0; i < smap.signal_id; i++) + { + dc_signal_t *ps = dc_core_find_by_id(i, smap); + + if (!ps) + { + continue; + } + + std::string link_str; + + for (int j = 0; j < ps->link_count; j++) + { + if (j > 0) + { + link_str += ", "; + } + + link_str += ps->link_saddrs[j]; + } + + std::string val_str = dc_util_val_to_string(ps->vec_p_data[0], + ps->data_type); + + std::cout << "\t" + << pad_right(std::to_string(ps->id), COL_ID) + << pad_right(ps->saddr, COL_SADDR) + << pad_right(ps->desc, COL_DESC) + << pad_right(dc_util_type_name(ps->data_type), COL_TYPE) + << pad_right(val_str, COL_VAL) + << pad_right(link_str, COL_LINK) + << std::endl; + } +} + +/** + * @brief datacenter 命令处理:out/in/yk/ao/param/all 子命令 + */ +static void cmd_dc_func(int argc, char *argv[]) +{ + const char *help = "datacenter out|in|yk|ao|param|all\n"; + + if (argc < 2) + { + printf("%s", help); + + return; + } + + if (0 == strcmp(argv[1], "out")) + { + printf("out signals:\n"); + dc_show_signals(g_dc_out); + } + else if (0 == strcmp(argv[1], "in")) + { + printf("in signals:\n"); + dc_show_signals(g_dc_in); + } + else if (0 == strcmp(argv[1], "yk")) + { + printf("yk signals:\n"); + dc_show_signals(g_dc_yk); + } + else if (0 == strcmp(argv[1], "ao")) + { + printf("ao signals:\n"); + dc_show_signals(g_dc_ao); + } + else if (0 == strcmp(argv[1], "param")) + { + printf("param signals:\n"); + dc_show_signals(g_dc_param); + } + else if (0 == strcmp(argv[1], "all")) + { + printf("out signals:\n"); + dc_show_signals(g_dc_out); + printf("in signals:\n"); + dc_show_signals(g_dc_in); + printf("yk signals:\n"); + dc_show_signals(g_dc_yk); + printf("ao signals:\n"); + dc_show_signals(g_dc_ao); + printf("param signals:\n"); + dc_show_signals(g_dc_param); + } +} + +/** + * @brief datacenter 命令的 Tab 补全回调 + */ +static void cmd_dc_complete_func(const char *buf, char ***completions, int *ncomp) +{ + static const char *subs[] = {"out", "in", "yk", "ao", "param", "all"}; + + cmd_sub_complete(buf, completions, ncomp, subs, + (int)(sizeof(subs) / sizeof(subs[0]))); +} + +CMD_REGISTER_C("datacenter", cmd_dc_func, "datacenter信号查看", cmd_dc_complete_func); + +/** + * @brief 保存所有已注册 out 信号到 XML(PLC 配置导出) + * @param path 目标文件路径 + * @return DC_OK 成功,<0 失败 + */ +int dc_save_out_signals_xml(const char *path) +{ + if (!g_initialized || !path) + { + return DC_ERR_PARAM; + } + + tinyxml2::XMLDocument doc; + + doc.InsertEndChild(doc.NewDeclaration()); + + tinyxml2::XMLElement *root = doc.NewElement("Root"); + + doc.InsertEndChild(root); + + tinyxml2::XMLElement *out_elem = doc.NewElement("Out"); + + out_elem->SetAttribute("count", (int)g_dc_out.signal_id); + + for (uint32_t i = 0; i < g_dc_out.signal_id; i++) + { + dc_signal_t *ps = dc_core_find_by_id(i, g_dc_out); + + if (!ps) + { + continue; + } + + tinyxml2::XMLElement *sig = doc.NewElement("Signal"); + + sig->SetAttribute("no", (int)i); + sig->SetAttribute("saddr", ps->saddr); + sig->SetAttribute("desc", ps->desc); + out_elem->InsertEndChild(sig); + } + + root->InsertEndChild(out_elem); + + if (doc.SaveFile(path) != tinyxml2::XML_SUCCESS) + { + LOG_E("failed to save out signals to %s\n", path); + + return DC_ERR_PARAM; + } + + LOG_I("out signals saved to %s, count=%u\n", path, g_dc_out.signal_id); + + return DC_OK; +} + +} /* extern "C" */ diff --git a/src/public/libdatacenter/src/dc_signal_util.cpp b/src/public/libdatacenter/src/dc_signal_util.cpp new file mode 100644 index 0000000..4e4e267 --- /dev/null +++ b/src/public/libdatacenter/src/dc_signal_util.cpp @@ -0,0 +1,272 @@ +/** @file dc_signal_util.cpp — 类型工具函数 */ +#include "dc_internal.h" + +struct { uint8_t id; const char *name; uint8_t len; } static const kTypeTab[] = { + {DATA_TYPE_B, "bool", 1}, + {DATA_TYPE_S8, "int8_t", 1}, + {DATA_TYPE_U8, "uint8_t", 1}, + {DATA_TYPE_S16, "int16_t", 2}, + {DATA_TYPE_U16, "uint16_t", 2}, + {DATA_TYPE_S32, "int32_t", 4}, + {DATA_TYPE_U32, "uint32_t", 4}, + {DATA_TYPE_L64, "int64_t", 8}, + {DATA_TYPE_UL64,"uint64_t", 8}, + {DATA_TYPE_F32, "float", 4}, + {DATA_TYPE_D64, "double", 8}, + {DATA_TYPE_IP, "ip", 4}, + {DATA_TYPE_MAC, "mac", 8}, + {DATA_TYPE_C8, "char8", 8}, + {DATA_TYPE_C32, "char32", 32}, + {DATA_TYPE_C64, "char64", 64}, + {DATA_TYPE_C128,"char128", 128}, + {DATA_TYPE_C1, "char1", 1}, + {DATA_TYPE_STR, "str", 128}, +}; + +static const int kTypeCount = sizeof(kTypeTab) / sizeof(kTypeTab[0]); + +/** @brief 获取数据类型名称字符串 */ +std::string dc_util_type_name(uint8_t dt) +{ + for (int i = 0; i < kTypeCount; i++) + { + if (kTypeTab[i].id == dt) + { + return kTypeTab[i].name; + } + } + + return "unknown"; +} + +/** @brief 通过名称字符串获取数据类型 ID */ +uint8_t dc_util_type_id_by_name(const std::string &n) +{ + for (int i = 0; i < kTypeCount; i++) + { + if (n == kTypeTab[i].name) + { + return kTypeTab[i].id; + } + } + + return 0; +} + +/** @brief 获取数据类型的字节长度 */ +uint8_t dc_util_type_len(uint8_t dt) +{ + for (int i = 0; i < kTypeCount; i++) + { + if (kTypeTab[i].id == dt) + { + return kTypeTab[i].len; + } + } + + return 0; +} + +/** @brief 根据数据类型创建零初始化内存块 */ +void *dc_util_create_data(uint8_t dt) +{ + uint8_t len = dc_util_type_len(dt); + + if (!len) + { + return NULL; + } + + return calloc(1, len); +} + +/** @brief 释放由 dc_util_create_data 分配的内存 */ +void dc_util_destroy_data(void *p, uint8_t dt) +{ + (void)dt; + free(p); +} + +/** @brief 将源数据按类型拷贝到目标 */ +void dc_util_val_copy(void *dst, const void *src, uint8_t dt) +{ + uint8_t len = dc_util_type_len(dt); + + if (len && dst && src) + { + memcpy(dst, src, len); + } +} + +/** @brief 按类型比较两个值是否相等,返回 0 相等,-1 不等或错误 */ +int dc_util_val_compare(uint8_t dt, const void *p1, const void *p2) +{ + uint8_t len = dc_util_type_len(dt); + + if (!len || !p1 || !p2) + { + return -1; + } + + return memcmp(p1, p2, len) == 0 ? 0 : -1; +} + +/** @brief 将值按类型转换为字符串表示 */ +std::string dc_util_val_to_string(const void *p, uint8_t dt) +{ + if (!p) + { + return ""; + } + + char buf[256] = {0}; + + switch (dt) + { + case DATA_TYPE_B: + snprintf(buf, sizeof(buf), "%s", *(uint8_t *)p ? "true" : "false"); + break; + case DATA_TYPE_S8: + snprintf(buf, sizeof(buf), "%d", *(int8_t *)p); + break; + case DATA_TYPE_U8: + snprintf(buf, sizeof(buf), "%u", *(uint8_t *)p); + break; + case DATA_TYPE_S16: + snprintf(buf, sizeof(buf), "%d", *(int16_t *)p); + break; + case DATA_TYPE_U16: + snprintf(buf, sizeof(buf), "%u", *(uint16_t *)p); + break; + case DATA_TYPE_S32: + snprintf(buf, sizeof(buf), "%d", *(int32_t *)p); + break; + case DATA_TYPE_U32: + snprintf(buf, sizeof(buf), "%u", *(uint32_t *)p); + break; + case DATA_TYPE_L64: + snprintf(buf, sizeof(buf), "%lld", (long long)*(int64_t *)p); + break; + case DATA_TYPE_UL64: + snprintf(buf, sizeof(buf), "%llu", (unsigned long long)*(uint64_t *)p); + break; + case DATA_TYPE_F32: + snprintf(buf, sizeof(buf), "%.6g", (double)*(float *)p); + break; + case DATA_TYPE_D64: + snprintf(buf, sizeof(buf), "%.12g", *(double *)p); + break; + case DATA_TYPE_IP: + snprintf(buf, sizeof(buf), "%d.%d.%d.%d", + ((uint8_t *)p)[0], ((uint8_t *)p)[1], + ((uint8_t *)p)[2], ((uint8_t *)p)[3]); + break; + case DATA_TYPE_MAC: + snprintf(buf, sizeof(buf), "%02X:%02X:%02X:%02X:%02X:%02X", + ((uint8_t *)p)[0], ((uint8_t *)p)[1], + ((uint8_t *)p)[2], ((uint8_t *)p)[3], + ((uint8_t *)p)[4], ((uint8_t *)p)[5]); + break; + case DATA_TYPE_C8: + case DATA_TYPE_C32: + case DATA_TYPE_C64: + case DATA_TYPE_C128: + case DATA_TYPE_STR: + snprintf(buf, sizeof(buf), "%s", (char *)p); + break; + case DATA_TYPE_C1: + snprintf(buf, sizeof(buf), "%c", *(char *)p); + break; + default: + return ""; + } + + return std::string(buf); +} + +/** @brief 从字符串解析值并按类型写入内存 */ +int dc_util_val_from_string(void *p, uint8_t dt, const std::string &s) +{ + if (!p || s.empty()) + { + return -1; + } + + switch (dt) + { + case DATA_TYPE_B: + *(uint8_t *)p = (s == "true" || s == "1") ? 1 : 0; + break; + case DATA_TYPE_S8: + *(int8_t *)p = (int8_t)atoi(s.c_str()); + break; + case DATA_TYPE_U8: + *(uint8_t *)p = (uint8_t)atoi(s.c_str()); + break; + case DATA_TYPE_S16: + *(int16_t *)p = (int16_t)atoi(s.c_str()); + break; + case DATA_TYPE_U16: + *(uint16_t *)p = (uint16_t)atoi(s.c_str()); + break; + case DATA_TYPE_S32: + *(int32_t *)p = (int32_t)atoi(s.c_str()); + break; + case DATA_TYPE_U32: + *(uint32_t *)p = (uint32_t)strtoul(s.c_str(), NULL, 10); + break; + case DATA_TYPE_L64: + *(int64_t *)p = (int64_t)strtoll(s.c_str(), NULL, 10); + break; + case DATA_TYPE_UL64: + *(uint64_t *)p = (uint64_t)strtoull(s.c_str(), NULL, 10); + break; + case DATA_TYPE_F32: + *(float *)p = (float)atof(s.c_str()); + break; + case DATA_TYPE_D64: + *(double *)p = atof(s.c_str()); + break; + case DATA_TYPE_IP: + { + int a, b, c, d; + + if (4 == sscanf(s.c_str(), "%d.%d.%d.%d", &a, &b, &c, &d)) + { + ((uint8_t *)p)[0] = (uint8_t)a; + ((uint8_t *)p)[1] = (uint8_t)b; + ((uint8_t *)p)[2] = (uint8_t)c; + ((uint8_t *)p)[3] = (uint8_t)d; + } + break; + } + case DATA_TYPE_MAC: + { + int m[6]; + + if (6 == sscanf(s.c_str(), "%02X:%02X:%02X:%02X:%02X:%02X", + &m[0], &m[1], &m[2], &m[3], &m[4], &m[5])) + { + for (int i = 0; i < 6; i++) + { + ((uint8_t *)p)[i] = (uint8_t)m[i]; + } + } + break; + } + case DATA_TYPE_C8: + case DATA_TYPE_C32: + case DATA_TYPE_C64: + case DATA_TYPE_C128: + case DATA_TYPE_STR: + strncpy((char *)p, s.c_str(), dc_util_type_len(dt) - 1); + break; + case DATA_TYPE_C1: + *(char *)p = s[0]; + break; + default: + return -1; + } + + return 0; +} diff --git a/src/public/libdatacenter/src/dc_signal_yk.cpp b/src/public/libdatacenter/src/dc_signal_yk.cpp new file mode 100644 index 0000000..4f5b45a --- /dev/null +++ b/src/public/libdatacenter/src/dc_signal_yk.cpp @@ -0,0 +1,192 @@ +/** @file dc_signal_yk.cpp — YK 信号注册/控制/异步执行 */ +#include "dc_internal.h" + +/** + * @brief 创建 YK 遥控信号:计算 XXH128 hash,注册到 g_dc_yk,将 cb 加入 ctrl_cb 列表 + */ +extern "C" int dc_signal_yk(const char *saddr, const char *desc, uint8_t data_type, uint8_t ctrl_type, void *p_data, dc_signal_change_cb_t cb, const char *module_id) +{ + if (!g_initialized || !saddr || !p_data || !module_id) + { + return DC_ERR_PARAM; + } + + if (dc_core_find(saddr, g_dc_yk)) + { + LOG_E("yk %s exists\n", saddr); + + return DC_ERR_EXISTS; + } + + dc_signal_t sig = {}; + sig.hash = XXH3_128bits(saddr, strlen(saddr)); + strncpy(sig.saddr, saddr, DC_SADDR_MAX_LEN - 1); + + if (desc) + { + strncpy(sig.desc, desc, DC_DESC_MAX_LEN - 1); + } + + sig.data_type = data_type; + sig.ctrl_type = ctrl_type; + sig.vec_p_data = (void **)malloc(sizeof(void *)); + sig.vec_p_data[0] = p_data; + sig.vec_p_data_count = 1; + + if (cb) + { + strncpy(sig.cb_list[0].module_id, module_id, DC_MODULE_MAX_LEN - 1); + sig.cb_list[0].ctrl_cb = cb; + sig.cb_count = 1; + } + + return dc_core_add(sig, g_dc_yk); +} + +/** + * @brief 链接已有 YK 信号:将 *p_data 指向信号的 vec_p_data[0],可选添加 cb + */ +extern "C" int dc_signal_yk_link_with_callback(const char *saddr, void **p_data, dc_signal_change_cb_t cb, const char *module_id) +{ + if (!g_initialized || !saddr || !p_data || !module_id) + { + return DC_ERR_PARAM; + } + + dc_signal_t *ps = dc_core_find(saddr, g_dc_yk); + + if (!ps) + { + return DC_ERR_NOTFOUND; + } + + if (!ps->vec_p_data || !ps->vec_p_data[0]) + { + return DC_ERR_PARAM; + } + + *p_data = ps->vec_p_data[0]; + + if (cb && ps->cb_count < DC_MAX_CALLBACKS) + { + strncpy(ps->cb_list[ps->cb_count].module_id, module_id, DC_MODULE_MAX_LEN - 1); + ps->cb_list[ps->cb_count].ctrl_cb = cb; + ps->cb_count++; + } + + return DC_OK; +} + +/** + * @brief 同步遥控执行:ctrl 校验 → step 处理(SELECT 写暂存 / DIRECT 写数据)→ 遍历回调(跳过发起方 module_id) + */ +extern "C" int dc_signal_yk_set_status(const char *saddr, dc_ctrl_step_t step, dc_ctrl_t *ctrl, const void *p_data, const char *module_id) +{ + if (!g_initialized || !saddr || !ctrl || !p_data || !module_id) + { + return DC_ERR_PARAM; + } + + dc_signal_t *ps = dc_core_find(saddr, g_dc_yk); + + if (!ps) + { + return DC_ERR_NOTFOUND; + } + + int ret = dc_check_ctrl_valid(ps, step, ctrl, p_data); + + if (ret != DC_OK) + { + return ret; + } + + if (step == DC_STEP_SELECT) + { + dc_util_val_copy(ctrl->p_data, p_data, ps->data_type); + } + else if (step == DC_STEP_DIRECT) + { + dc_util_val_copy(ps->vec_p_data[0], p_data, ps->data_type); + } + /* DC_STEP_CANCEL: no data operation */ + + ctrl->step = step; + + for (int i = 0; i < ps->cb_count; i++) + { + if (!ps->cb_list[i].ctrl_cb) + { + continue; + } + + if (0 == strcmp(ps->cb_list[i].module_id, module_id)) + { + continue; + } + + ps->cb_list[i].ctrl_cb(ps->saddr, step, ps->data_type, 0, ps->vec_p_data[0]); + } + + return DC_OK; +} + +/** + * @brief 发起遥控异步执行请求 + */ +extern "C" int dc_yk_exec_async(const char *saddr, dc_ctrl_step_t step, const dc_ctrl_t *ctrl, const void *p_data, const char *module_id, dc_async_result_cb_t result_cb, void *user_arg, uint32_t timeout_ms, uint32_t *request_id) +{ + if (!g_initialized || !saddr || !ctrl || !p_data || !module_id) + { + return DC_ERR_PARAM; + } + + uint32_t req_id = dc_async_enqueue(g_dc_yk, saddr, step, ctrl, p_data, 0, 0, module_id, result_cb, user_arg, timeout_ms); + + if (request_id) + { + *request_id = req_id; + } + + return (req_id == 0) ? DC_ERR_FULL : DC_OK; +} + +/** + * @brief 模块 B 返回遥控异步执行结果 + */ +extern "C" int dc_yk_set_result(uint32_t request_id, int result_code, const char *err_msg) +{ + return dc_async_set_result(request_id, result_code, err_msg); +} + +/** + * @brief 取消未完成的遥控异步请求 + */ +extern "C" int dc_yk_cancel_async(uint32_t request_id) +{ + return dc_async_cancel(request_id); +} + +/** + * @brief 注册遥控异步执行回调(模块 B 调用) + */ +extern "C" int dc_yk_register_exec_cb(const char *saddr, dc_async_exec_cb_t exec_cb, void *module_ctx) +{ + if (!g_initialized || !saddr) + { + return DC_ERR_PARAM; + } + + dc_signal_t *ps = dc_core_find(saddr, g_dc_yk); + + if (!ps) + { + return DC_ERR_NOTFOUND; + } + + ps->exec_cb = exec_cb; + ps->module_ctx = module_ctx; + + return DC_OK; +} +